lib: Use gpfs.h from third_party on Linux
[Samba.git] / python / samba / ntacls.py
blob5bf646caf9f1704bcb0ab7ff069ee1097e6e4b3b
1 # Unix SMB/CIFS implementation.
2 # Copyright (C) Matthieu Patou <mat@matws.net> 2009-2010
5 # This program is free software; you can redistribute it and/or modify
6 # it under the terms of the GNU General Public License as published by
7 # the Free Software Foundation; either version 3 of the License, or
8 # (at your option) any later version.
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # GNU General Public License for more details.
15 # You should have received a copy of the GNU General Public License
16 # along with this program. If not, see <http://www.gnu.org/licenses/>.
19 from __future__ import print_function
20 """NT Acls."""
23 import os
24 import tarfile
25 import tempfile
26 import shutil
28 import samba.xattr_native
29 import samba.xattr_tdb
30 import samba.posix_eadb
31 from samba.samba3 import param as s3param
32 from samba.dcerpc import security, xattr, idmap
33 from samba.ndr import ndr_pack, ndr_unpack
34 from samba.samba3 import smbd
35 from samba.samba3 import libsmb_samba_internal as libsmb
37 # don't include volumes
38 SMB_FILE_ATTRIBUTE_FLAGS = libsmb.FILE_ATTRIBUTE_SYSTEM | \
39 libsmb.FILE_ATTRIBUTE_DIRECTORY | \
40 libsmb.FILE_ATTRIBUTE_ARCHIVE | \
41 libsmb.FILE_ATTRIBUTE_HIDDEN
44 SECURITY_SECINFO_FLAGS = security.SECINFO_OWNER | \
45 security.SECINFO_GROUP | \
46 security.SECINFO_DACL | \
47 security.SECINFO_SACL
50 # SEC_FLAG_SYSTEM_SECURITY is required otherwise get Access Denied
51 SECURITY_SEC_FLAGS = security.SEC_FLAG_SYSTEM_SECURITY | \
52 security.SEC_FLAG_MAXIMUM_ALLOWED
55 class XattrBackendError(Exception):
56 """A generic xattr backend error."""
59 def checkset_backend(lp, backend, eadbfile):
60 '''return the path to the eadb, or None'''
61 if backend is None:
62 xattr_tdb = lp.get("xattr_tdb:file")
63 if xattr_tdb is not None:
64 return (samba.xattr_tdb, lp.get("xattr_tdb:file"))
65 posix_eadb = lp.get("posix:eadb")
66 if posix_eadb is not None:
67 return (samba.posix_eadb, lp.get("posix:eadb"))
68 return (None, None)
69 elif backend == "native":
70 return (None, None)
71 elif backend == "eadb":
72 if eadbfile is not None:
73 return (samba.posix_eadb, eadbfile)
74 else:
75 return (samba.posix_eadb, os.path.abspath(os.path.join(lp.get("private dir"), "eadb.tdb")))
76 elif backend == "tdb":
77 if eadbfile is not None:
78 return (samba.xattr_tdb, eadbfile)
79 else:
80 return (samba.xattr_tdb, os.path.abspath(os.path.join(lp.get("state dir"), "xattr.tdb")))
81 else:
82 raise XattrBackendError("Invalid xattr backend choice %s" % backend)
85 def getdosinfo(lp, file):
86 try:
87 attribute = samba.xattr_native.wrap_getxattr(file,
88 xattr.XATTR_DOSATTRIB_NAME_S3)
89 except Exception:
90 return
92 return ndr_unpack(xattr.DOSATTRIB, attribute)
95 def getntacl(lp,
96 file,
97 backend=None,
98 eadbfile=None,
99 direct_db_access=True,
100 service=None,
101 session_info=None):
102 if direct_db_access:
103 (backend_obj, dbname) = checkset_backend(lp, backend, eadbfile)
104 if dbname is not None:
105 try:
106 attribute = backend_obj.wrap_getxattr(dbname, file,
107 xattr.XATTR_NTACL_NAME)
108 except Exception:
109 # FIXME: Don't catch all exceptions, just those related to opening
110 # xattrdb
111 print("Fail to open %s" % dbname)
112 attribute = samba.xattr_native.wrap_getxattr(file,
113 xattr.XATTR_NTACL_NAME)
114 else:
115 attribute = samba.xattr_native.wrap_getxattr(file,
116 xattr.XATTR_NTACL_NAME)
117 ntacl = ndr_unpack(xattr.NTACL, attribute)
118 if ntacl.version == 1:
119 return ntacl.info
120 elif ntacl.version == 2:
121 return ntacl.info.sd
122 elif ntacl.version == 3:
123 return ntacl.info.sd
124 elif ntacl.version == 4:
125 return ntacl.info.sd
126 else:
127 return smbd.get_nt_acl(file,
128 SECURITY_SECINFO_FLAGS,
129 service=service,
130 session_info=session_info)
133 def setntacl(lp, file, sddl, domsid,
134 backend=None, eadbfile=None,
135 use_ntvfs=True, skip_invalid_chown=False,
136 passdb=None, service=None, session_info=None):
138 A wrapper for smbd set_nt_acl api.
140 Args:
141 lp (LoadParam): load param from conf
142 file (str): a path to file or dir
143 sddl (str): ntacl sddl string
144 service (str): name of share service, e.g.: sysvol
145 session_info (auth_session_info): session info for authentication
147 Note:
148 Get `session_info` with `samba.auth.user_session`, do not use the
149 `admin_session` api.
151 Returns:
152 None
155 assert(isinstance(domsid, str) or isinstance(domsid, security.dom_sid))
156 if isinstance(domsid, str):
157 sid = security.dom_sid(domsid)
158 elif isinstance(domsid, security.dom_sid):
159 sid = domsid
160 domsid = str(sid)
162 assert(isinstance(sddl, str) or isinstance(sddl, security.descriptor))
163 if isinstance(sddl, str):
164 sd = security.descriptor.from_sddl(sddl, sid)
165 elif isinstance(sddl, security.descriptor):
166 sd = sddl
167 sddl = sd.as_sddl(sid)
169 if not use_ntvfs and skip_invalid_chown:
170 # Check if the owner can be resolved as a UID
171 (owner_id, owner_type) = passdb.sid_to_id(sd.owner_sid)
172 if ((owner_type != idmap.ID_TYPE_UID) and (owner_type != idmap.ID_TYPE_BOTH)):
173 # Check if this particular owner SID was domain admins,
174 # because we special-case this as mapping to
175 # 'administrator' instead.
176 if sd.owner_sid == security.dom_sid("%s-%d" % (domsid, security.DOMAIN_RID_ADMINS)):
177 administrator = security.dom_sid("%s-%d" % (domsid, security.DOMAIN_RID_ADMINISTRATOR))
178 (admin_id, admin_type) = passdb.sid_to_id(administrator)
180 # Confirm we have a UID for administrator
181 if ((admin_type == idmap.ID_TYPE_UID) or (admin_type == idmap.ID_TYPE_BOTH)):
183 # Set it, changing the owner to 'administrator' rather than domain admins
184 sd2 = sd
185 sd2.owner_sid = administrator
187 smbd.set_nt_acl(
188 file, SECURITY_SECINFO_FLAGS, sd2,
189 service=service, session_info=session_info)
191 # and then set an NTVFS ACL (which does not set the posix ACL) to pretend the owner really was set
192 use_ntvfs = True
193 else:
194 raise XattrBackendError("Unable to find UID for domain administrator %s, got id %d of type %d" % (administrator, admin_id, admin_type))
195 else:
196 # For all other owning users, reset the owner to root
197 # and then set the ACL without changing the owner
199 # This won't work in test environments, as it tries a real (rather than xattr-based fake) chown
201 os.chown(file, 0, 0)
202 smbd.set_nt_acl(
203 file,
204 security.SECINFO_GROUP |
205 security.SECINFO_DACL |
206 security.SECINFO_SACL,
207 sd, service=service, session_info=session_info)
209 if use_ntvfs:
210 (backend_obj, dbname) = checkset_backend(lp, backend, eadbfile)
211 ntacl = xattr.NTACL()
212 ntacl.version = 1
213 ntacl.info = sd
214 if dbname is not None:
215 try:
216 backend_obj.wrap_setxattr(dbname,
217 file, xattr.XATTR_NTACL_NAME, ndr_pack(ntacl))
218 except Exception:
219 # FIXME: Don't catch all exceptions, just those related to opening
220 # xattrdb
221 print("Fail to open %s" % dbname)
222 samba.xattr_native.wrap_setxattr(file, xattr.XATTR_NTACL_NAME,
223 ndr_pack(ntacl))
224 else:
225 samba.xattr_native.wrap_setxattr(file, xattr.XATTR_NTACL_NAME,
226 ndr_pack(ntacl))
227 else:
228 smbd.set_nt_acl(
229 file, SECURITY_SECINFO_FLAGS, sd,
230 service=service, session_info=session_info)
233 def ldapmask2filemask(ldm):
234 """Takes the access mask of a DS ACE and transform them in a File ACE mask.
236 RIGHT_DS_CREATE_CHILD = 0x00000001
237 RIGHT_DS_DELETE_CHILD = 0x00000002
238 RIGHT_DS_LIST_CONTENTS = 0x00000004
239 ACTRL_DS_SELF = 0x00000008
240 RIGHT_DS_READ_PROPERTY = 0x00000010
241 RIGHT_DS_WRITE_PROPERTY = 0x00000020
242 RIGHT_DS_DELETE_TREE = 0x00000040
243 RIGHT_DS_LIST_OBJECT = 0x00000080
244 RIGHT_DS_CONTROL_ACCESS = 0x00000100
245 FILE_READ_DATA = 0x0001
246 FILE_LIST_DIRECTORY = 0x0001
247 FILE_WRITE_DATA = 0x0002
248 FILE_ADD_FILE = 0x0002
249 FILE_APPEND_DATA = 0x0004
250 FILE_ADD_SUBDIRECTORY = 0x0004
251 FILE_CREATE_PIPE_INSTANCE = 0x0004
252 FILE_READ_EA = 0x0008
253 FILE_WRITE_EA = 0x0010
254 FILE_EXECUTE = 0x0020
255 FILE_TRAVERSE = 0x0020
256 FILE_DELETE_CHILD = 0x0040
257 FILE_READ_ATTRIBUTES = 0x0080
258 FILE_WRITE_ATTRIBUTES = 0x0100
259 DELETE = 0x00010000
260 READ_CONTROL = 0x00020000
261 WRITE_DAC = 0x00040000
262 WRITE_OWNER = 0x00080000
263 SYNCHRONIZE = 0x00100000
264 STANDARD_RIGHTS_ALL = 0x001F0000
266 filemask = ldm & STANDARD_RIGHTS_ALL
268 if (ldm & RIGHT_DS_READ_PROPERTY) and (ldm & RIGHT_DS_LIST_CONTENTS):
269 filemask = filemask | (SYNCHRONIZE | FILE_LIST_DIRECTORY |
270 FILE_READ_ATTRIBUTES | FILE_READ_EA |
271 FILE_READ_DATA | FILE_EXECUTE)
273 if ldm & RIGHT_DS_WRITE_PROPERTY:
274 filemask = filemask | (SYNCHRONIZE | FILE_WRITE_DATA |
275 FILE_APPEND_DATA | FILE_WRITE_EA |
276 FILE_WRITE_ATTRIBUTES | FILE_ADD_FILE |
277 FILE_ADD_SUBDIRECTORY)
279 if ldm & RIGHT_DS_CREATE_CHILD:
280 filemask = filemask | (FILE_ADD_SUBDIRECTORY | FILE_ADD_FILE)
282 if ldm & RIGHT_DS_DELETE_CHILD:
283 filemask = filemask | FILE_DELETE_CHILD
285 return filemask
288 def dsacl2fsacl(dssddl, sid, as_sddl=True):
291 This function takes an the SDDL representation of a DS
292 ACL and return the SDDL representation of this ACL adapted
293 for files. It's used for Policy object provision
295 ref = security.descriptor.from_sddl(dssddl, sid)
296 fdescr = security.descriptor()
297 fdescr.owner_sid = ref.owner_sid
298 fdescr.group_sid = ref.group_sid
299 fdescr.type = ref.type
300 fdescr.revision = ref.revision
301 aces = ref.dacl.aces
302 for i in range(0, len(aces)):
303 ace = aces[i]
304 if not ace.type & security.SEC_ACE_TYPE_ACCESS_ALLOWED_OBJECT and str(ace.trustee) != security.SID_BUILTIN_PREW2K:
305 # if fdescr.type & security.SEC_DESC_DACL_AUTO_INHERITED:
306 ace.flags = ace.flags | security.SEC_ACE_FLAG_OBJECT_INHERIT | security.SEC_ACE_FLAG_CONTAINER_INHERIT
307 if str(ace.trustee) == security.SID_CREATOR_OWNER:
308 # For Creator/Owner the IO flag is set as this ACE has only a sense for child objects
309 ace.flags = ace.flags | security.SEC_ACE_FLAG_INHERIT_ONLY
310 ace.access_mask = ldapmask2filemask(ace.access_mask)
311 fdescr.dacl_add(ace)
313 if not as_sddl:
314 return fdescr
316 return fdescr.as_sddl(sid)
319 class SMBHelper:
321 A wrapper class for SMB connection
323 smb_path: path with separator "\\" other than "/"
326 def __init__(self, smb_conn, dom_sid):
327 self.smb_conn = smb_conn
328 self.dom_sid = dom_sid
330 def get_acl(self, smb_path, as_sddl=False):
331 assert '/' not in smb_path
333 ntacl_sd = self.smb_conn.get_acl(
334 smb_path, SECURITY_SECINFO_FLAGS, SECURITY_SEC_FLAGS)
336 return ntacl_sd.as_sddl(self.dom_sid) if as_sddl else ntacl_sd
338 def list(self, smb_path=''):
340 List file and dir base names in smb_path without recursive.
342 assert '/' not in smb_path
343 return self.smb_conn.list(smb_path, attribs=SMB_FILE_ATTRIBUTE_FLAGS)
345 def is_dir(self, attrib):
347 Check whether the attrib value is a directory.
349 attrib is from list method.
351 return bool(attrib & libsmb.FILE_ATTRIBUTE_DIRECTORY)
353 def join(self, root, name):
355 Join path with '\\'
357 return root + '\\' + name if root else name
359 def loadfile(self, smb_path):
360 assert '/' not in smb_path
361 return self.smb_conn.loadfile(smb_path)
363 def create_tree(self, tree, smb_path=''):
365 Create files as defined in tree
367 for name, content in tree.items():
368 fullname = self.join(smb_path, name)
369 if isinstance(content, dict): # a dir
370 if not self.smb_conn.chkpath(fullname):
371 self.smb_conn.mkdir(fullname)
372 self.create_tree(content, smb_path=fullname)
373 else: # a file
374 self.smb_conn.savefile(fullname, content)
376 def get_tree(self, smb_path=''):
378 Get the tree structure via smb conn
380 self.smb_conn.list example:
384 'attrib': 16,
385 'mtime': 1528848309,
386 'name': 'dir1',
387 'short_name': 'dir1',
388 'size': 0L
389 }, {
390 'attrib': 32,
391 'mtime': 1528848309,
392 'name': 'file0.txt',
393 'short_name': 'file0.txt',
394 'size': 10L
398 tree = {}
399 for item in self.list(smb_path):
400 name = item['name']
401 fullname = self.join(smb_path, name)
402 if self.is_dir(item['attrib']):
403 tree[name] = self.get_tree(smb_path=fullname)
404 else:
405 tree[name] = self.loadfile(fullname)
406 return tree
408 def get_ntacls(self, smb_path=''):
410 Get ntacl for each file and dir via smb conn
412 ntacls = {}
413 for item in self.list(smb_path):
414 name = item['name']
415 fullname = self.join(smb_path, name)
416 if self.is_dir(item['attrib']):
417 ntacls.update(self.get_ntacls(smb_path=fullname))
418 else:
419 ntacl_sd = self.get_acl(fullname)
420 ntacls[fullname] = ntacl_sd.as_sddl(self.dom_sid)
421 return ntacls
423 def delete_tree(self):
424 for item in self.list():
425 name = item['name']
426 if self.is_dir(item['attrib']):
427 self.smb_conn.deltree(name)
428 else:
429 self.smb_conn.unlink(name)
432 class NtaclsHelper:
434 def __init__(self, service, smb_conf_path, dom_sid):
435 self.service = service
436 self.dom_sid = dom_sid
438 # this is important to help smbd find services.
439 self.lp = s3param.get_context()
440 self.lp.load(smb_conf_path)
442 self.use_ntvfs = "smb" in self.lp.get("server services")
444 def getntacl(self, path, as_sddl=False, direct_db_access=None):
445 if direct_db_access is None:
446 direct_db_access = self.use_ntvfs
448 ntacl_sd = getntacl(
449 self.lp, path,
450 direct_db_access=direct_db_access,
451 service=self.service)
453 return ntacl_sd.as_sddl(self.dom_sid) if as_sddl else ntacl_sd
455 def setntacl(self, path, ntacl_sd):
456 # ntacl_sd can be obj or str
457 return setntacl(self.lp, path, ntacl_sd, self.dom_sid,
458 use_ntvfs=self.use_ntvfs)
461 def _create_ntacl_file(dst, ntacl_sddl_str):
462 with open(dst + '.NTACL', 'w') as f:
463 f.write(ntacl_sddl_str)
466 def _read_ntacl_file(src):
467 with open(src + '.NTACL', 'r') as f:
468 return f.read()
471 def backup_online(smb_conn, dest_tarfile_path, dom_sid):
473 Backup all files and dirs with ntacl for the serive behind smb_conn.
475 1. Create a temp dir as container dir
476 2. Backup all files with dir structure into container dir
477 3. Generate file.NTACL files for each file and dir in contianer dir
478 4. Create a tar file from container dir(without top level folder)
479 5. Delete contianer dir
482 if isinstance(dom_sid, str):
483 dom_sid = security.dom_sid(dom_sid)
485 smb_helper = SMBHelper(smb_conn, dom_sid)
487 remotedir = '' # root dir
489 localdir = tempfile.mkdtemp()
491 r_dirs = [remotedir]
492 l_dirs = [localdir]
494 while r_dirs:
495 r_dir = r_dirs.pop()
496 l_dir = l_dirs.pop()
498 for e in smb_helper.list(smb_path=r_dir):
499 r_name = smb_helper.join(r_dir, e['name'])
500 l_name = os.path.join(l_dir, e['name'])
502 if smb_helper.is_dir(e['attrib']):
503 r_dirs.append(r_name)
504 l_dirs.append(l_name)
505 os.mkdir(l_name)
506 else:
507 data = smb_helper.loadfile(r_name)
508 with open(l_name, 'wb') as f:
509 f.write(data)
511 # get ntacl for this entry and save alongside
512 ntacl_sddl_str = smb_helper.get_acl(r_name, as_sddl=True)
513 _create_ntacl_file(l_name, ntacl_sddl_str)
515 with tarfile.open(name=dest_tarfile_path, mode='w:gz') as tar:
516 for name in os.listdir(localdir):
517 path = os.path.join(localdir, name)
518 tar.add(path, arcname=name)
520 shutil.rmtree(localdir)
523 def backup_offline(src_service_path, dest_tarfile_path, samdb_conn, smb_conf_path):
525 Backup files and ntacls to a tarfile for a service
527 service = src_service_path.rstrip('/').rsplit('/', 1)[-1]
528 tempdir = tempfile.mkdtemp()
530 dom_sid_str = samdb_conn.get_domain_sid()
531 dom_sid = security.dom_sid(dom_sid_str)
533 ntacls_helper = NtaclsHelper(service, smb_conf_path, dom_sid)
535 for dirpath, dirnames, filenames in os.walk(src_service_path):
536 # each dir only cares about its direct children
537 rel_dirpath = os.path.relpath(dirpath, start=src_service_path)
538 dst_dirpath = os.path.join(tempdir, rel_dirpath)
540 # create sub dirs and NTACL file
541 for dirname in dirnames:
542 src = os.path.join(dirpath, dirname)
543 dst = os.path.join(dst_dirpath, dirname)
544 # mkdir with metadata
545 smbd.mkdir(dst, service)
546 ntacl_sddl_str = ntacls_helper.getntacl(src, as_sddl=True)
547 _create_ntacl_file(dst, ntacl_sddl_str)
549 # create files and NTACL file, then copy data
550 for filename in filenames:
551 src = os.path.join(dirpath, filename)
552 dst = os.path.join(dst_dirpath, filename)
553 # create an empty file with metadata
554 smbd.create_file(dst, service)
555 ntacl_sddl_str = ntacls_helper.getntacl(src, as_sddl=True)
556 _create_ntacl_file(dst, ntacl_sddl_str)
558 # now put data in
559 with open(src, 'rb') as src_file:
560 data = src_file.read()
561 with open(dst, 'wb') as dst_file:
562 dst_file.write(data)
564 # add all files in tempdir to tarfile without a top folder
565 with tarfile.open(name=dest_tarfile_path, mode='w:gz') as tar:
566 for name in os.listdir(tempdir):
567 path = os.path.join(tempdir, name)
568 tar.add(path, arcname=name)
570 shutil.rmtree(tempdir)
573 def backup_restore(src_tarfile_path, dst_service_path, samdb_conn, smb_conf_path):
575 Restore files and ntacls from a tarfile to a service
577 service = dst_service_path.rstrip('/').rsplit('/', 1)[-1]
578 tempdir = tempfile.mkdtemp() # src files
580 dom_sid_str = samdb_conn.get_domain_sid()
581 dom_sid = security.dom_sid(dom_sid_str)
583 ntacls_helper = NtaclsHelper(service, smb_conf_path, dom_sid)
585 with tarfile.open(src_tarfile_path) as f:
586 f.extractall(path=tempdir)
587 # e.g.: /tmp/tmpRNystY/{dir1,dir1.NTACL,...file1,file1.NTACL}
589 for dirpath, dirnames, filenames in os.walk(tempdir):
590 rel_dirpath = os.path.relpath(dirpath, start=tempdir)
591 dst_dirpath = os.path.normpath(
592 os.path.join(dst_service_path, rel_dirpath))
594 for dirname in dirnames:
595 if not dirname.endswith('.NTACL'):
596 src = os.path.join(dirpath, dirname)
597 dst = os.path.join(dst_dirpath, dirname)
598 if not os.path.isdir(dst):
599 # dst must be absolute path for smbd API
600 smbd.mkdir(dst, service)
601 ntacl_sddl_str = _read_ntacl_file(src)
602 ntacls_helper.setntacl(dst, ntacl_sddl_str)
604 for filename in filenames:
605 if not filename.endswith('.NTACL'):
606 src = os.path.join(dirpath, filename)
607 dst = os.path.join(dst_dirpath, filename)
608 if not os.path.isfile(dst):
609 # dst must be absolute path for smbd API
610 smbd.create_file(dst, service)
611 ntacl_sddl_str = _read_ntacl_file(src)
612 ntacls_helper.setntacl(dst, ntacl_sddl_str)
614 # now put data in
615 with open(src, 'rb') as src_file:
616 data = src_file.read()
617 with open(dst, 'wb') as dst_file:
618 dst_file.write(data)
620 shutil.rmtree(tempdir)