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
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.auth import admin_session
38 # don't include volumes
39 SMB_FILE_ATTRIBUTE_FLAGS = smb.FILE_ATTRIBUTE_SYSTEM | \
40 smb.FILE_ATTRIBUTE_DIRECTORY | \
41 smb.FILE_ATTRIBUTE_ARCHIVE | \
42 smb.FILE_ATTRIBUTE_HIDDEN
45 SECURITY_SECINFO_FLAGS = security.SECINFO_OWNER | \
46 security.SECINFO_GROUP | \
47 security.SECINFO_DACL | \
51 # SEC_FLAG_SYSTEM_SECURITY is required otherwise get Access Denied
52 SECURITY_SEC_FLAGS = security.SEC_FLAG_SYSTEM_SECURITY | \
53 security.SEC_FLAG_MAXIMUM_ALLOWED
56 class XattrBackendError(Exception):
57 """A generic xattr backend error."""
60 def checkset_backend(lp, backend, eadbfile):
61 '''return the path to the eadb, or None'''
63 xattr_tdb = lp.get("xattr_tdb:file")
64 if xattr_tdb is not None:
65 return (samba.xattr_tdb, lp.get("xattr_tdb:file"))
66 posix_eadb = lp.get("posix:eadb")
67 if posix_eadb is not None:
68 return (samba.posix_eadb, lp.get("posix:eadb"))
70 elif backend == "native":
72 elif backend == "eadb":
73 if eadbfile is not None:
74 return (samba.posix_eadb, eadbfile)
76 return (samba.posix_eadb, os.path.abspath(os.path.join(lp.get("private dir"), "eadb.tdb")))
77 elif backend == "tdb":
78 if eadbfile is not None:
79 return (samba.xattr_tdb, eadbfile)
81 return (samba.xattr_tdb, os.path.abspath(os.path.join(lp.get("state dir"), "xattr.tdb")))
83 raise XattrBackendError("Invalid xattr backend choice %s" %backend)
86 def getdosinfo(lp, file):
88 attribute = samba.xattr_native.wrap_getxattr(file,
89 xattr.XATTR_DOSATTRIB_NAME_S3)
93 return ndr_unpack(xattr.DOSATTRIB, attribute)
96 def getntacl(lp, file, backend=None, eadbfile=None, direct_db_access=True, service=None):
98 (backend_obj, dbname) = checkset_backend(lp, backend, eadbfile)
99 if dbname is not None:
101 attribute = backend_obj.wrap_getxattr(dbname, file,
102 xattr.XATTR_NTACL_NAME)
104 # FIXME: Don't catch all exceptions, just those related to opening
106 print("Fail to open %s" % dbname)
107 attribute = samba.xattr_native.wrap_getxattr(file,
108 xattr.XATTR_NTACL_NAME)
110 attribute = samba.xattr_native.wrap_getxattr(file,
111 xattr.XATTR_NTACL_NAME)
112 ntacl = ndr_unpack(xattr.NTACL, attribute)
113 if ntacl.version == 1:
115 elif ntacl.version == 2:
117 elif ntacl.version == 3:
119 elif ntacl.version == 4:
122 return smbd.get_nt_acl(file, SECURITY_SECINFO_FLAGS, service=service)
125 def setntacl(lp, file, sddl, domsid,
126 backend=None, eadbfile=None,
127 use_ntvfs=True, skip_invalid_chown=False,
128 passdb=None, service=None, session_info=None):
130 A wrapper for smbd set_nt_acl api.
133 lp (LoadParam): load param from conf
134 file (str): a path to file or dir
135 sddl (str): ntacl sddl string
136 service (str): name of share service, e.g.: sysvol
137 session_info (auth_session_info): session info for authentication
140 Get `session_info` with `samba.auth.user_session`, do not use the
147 assert(isinstance(domsid, str) or isinstance(domsid, security.dom_sid))
148 if isinstance(domsid, str):
149 sid = security.dom_sid(domsid)
150 elif isinstance(domsid, security.dom_sid):
154 assert(isinstance(sddl, str) or isinstance(sddl, security.descriptor))
155 if isinstance(sddl, str):
156 sd = security.descriptor.from_sddl(sddl, sid)
157 elif isinstance(sddl, security.descriptor):
159 sddl = sd.as_sddl(sid)
161 if not use_ntvfs and skip_invalid_chown:
162 # Check if the owner can be resolved as a UID
163 (owner_id, owner_type) = passdb.sid_to_id(sd.owner_sid)
164 if ((owner_type != idmap.ID_TYPE_UID) and (owner_type != idmap.ID_TYPE_BOTH)):
165 # Check if this particular owner SID was domain admins,
166 # because we special-case this as mapping to
167 # 'administrator' instead.
168 if sd.owner_sid == security.dom_sid("%s-%d" % (domsid, security.DOMAIN_RID_ADMINS)):
169 administrator = security.dom_sid("%s-%d" % (domsid, security.DOMAIN_RID_ADMINISTRATOR))
170 (admin_id, admin_type) = passdb.sid_to_id(administrator)
172 # Confirm we have a UID for administrator
173 if ((admin_type == idmap.ID_TYPE_UID) or (admin_type == idmap.ID_TYPE_BOTH)):
175 # Set it, changing the owner to 'administrator' rather than domain admins
177 sd2.owner_sid = administrator
180 file, SECURITY_SECINFO_FLAGS, sd2,
181 service=service, session_info=session_info)
183 # and then set an NTVFS ACL (which does not set the posix ACL) to pretend the owner really was set
186 raise XattrBackendError("Unable to find UID for domain administrator %s, got id %d of type %d" % (administrator, admin_id, admin_type))
188 # For all other owning users, reset the owner to root
189 # and then set the ACL without changing the owner
191 # This won't work in test environments, as it tries a real (rather than xattr-based fake) chown
196 security.SECINFO_GROUP |
197 security.SECINFO_DACL |
198 security.SECINFO_SACL,
199 sd, service=service, session_info=session_info)
202 (backend_obj, dbname) = checkset_backend(lp, backend, eadbfile)
203 ntacl = xattr.NTACL()
206 if dbname is not None:
208 backend_obj.wrap_setxattr(dbname,
209 file, xattr.XATTR_NTACL_NAME, ndr_pack(ntacl))
211 # FIXME: Don't catch all exceptions, just those related to opening
213 print("Fail to open %s" % dbname)
214 samba.xattr_native.wrap_setxattr(file, xattr.XATTR_NTACL_NAME,
217 samba.xattr_native.wrap_setxattr(file, xattr.XATTR_NTACL_NAME,
221 file, SECURITY_SECINFO_FLAGS, sd,
222 service=service, session_info=session_info)
225 def ldapmask2filemask(ldm):
226 """Takes the access mask of a DS ACE and transform them in a File ACE mask.
228 RIGHT_DS_CREATE_CHILD = 0x00000001
229 RIGHT_DS_DELETE_CHILD = 0x00000002
230 RIGHT_DS_LIST_CONTENTS = 0x00000004
231 ACTRL_DS_SELF = 0x00000008
232 RIGHT_DS_READ_PROPERTY = 0x00000010
233 RIGHT_DS_WRITE_PROPERTY = 0x00000020
234 RIGHT_DS_DELETE_TREE = 0x00000040
235 RIGHT_DS_LIST_OBJECT = 0x00000080
236 RIGHT_DS_CONTROL_ACCESS = 0x00000100
237 FILE_READ_DATA = 0x0001
238 FILE_LIST_DIRECTORY = 0x0001
239 FILE_WRITE_DATA = 0x0002
240 FILE_ADD_FILE = 0x0002
241 FILE_APPEND_DATA = 0x0004
242 FILE_ADD_SUBDIRECTORY = 0x0004
243 FILE_CREATE_PIPE_INSTANCE = 0x0004
244 FILE_READ_EA = 0x0008
245 FILE_WRITE_EA = 0x0010
246 FILE_EXECUTE = 0x0020
247 FILE_TRAVERSE = 0x0020
248 FILE_DELETE_CHILD = 0x0040
249 FILE_READ_ATTRIBUTES = 0x0080
250 FILE_WRITE_ATTRIBUTES = 0x0100
252 READ_CONTROL = 0x00020000
253 WRITE_DAC = 0x00040000
254 WRITE_OWNER = 0x00080000
255 SYNCHRONIZE = 0x00100000
256 STANDARD_RIGHTS_ALL = 0x001F0000
258 filemask = ldm & STANDARD_RIGHTS_ALL
260 if (ldm & RIGHT_DS_READ_PROPERTY) and (ldm & RIGHT_DS_LIST_CONTENTS):
261 filemask = filemask | (SYNCHRONIZE | FILE_LIST_DIRECTORY |
262 FILE_READ_ATTRIBUTES | FILE_READ_EA |
263 FILE_READ_DATA | FILE_EXECUTE)
265 if ldm & RIGHT_DS_WRITE_PROPERTY:
266 filemask = filemask | (SYNCHRONIZE | FILE_WRITE_DATA |
267 FILE_APPEND_DATA | FILE_WRITE_EA |
268 FILE_WRITE_ATTRIBUTES | FILE_ADD_FILE |
269 FILE_ADD_SUBDIRECTORY)
271 if ldm & RIGHT_DS_CREATE_CHILD:
272 filemask = filemask | (FILE_ADD_SUBDIRECTORY | FILE_ADD_FILE)
274 if ldm & RIGHT_DS_DELETE_CHILD:
275 filemask = filemask | FILE_DELETE_CHILD
280 def dsacl2fsacl(dssddl, sid, as_sddl=True):
283 This function takes an the SDDL representation of a DS
284 ACL and return the SDDL representation of this ACL adapted
285 for files. It's used for Policy object provision
287 ref = security.descriptor.from_sddl(dssddl, sid)
288 fdescr = security.descriptor()
289 fdescr.owner_sid = ref.owner_sid
290 fdescr.group_sid = ref.group_sid
291 fdescr.type = ref.type
292 fdescr.revision = ref.revision
294 for i in range(0, len(aces)):
296 if not ace.type & security.SEC_ACE_TYPE_ACCESS_ALLOWED_OBJECT and str(ace.trustee) != security.SID_BUILTIN_PREW2K:
297 # if fdescr.type & security.SEC_DESC_DACL_AUTO_INHERITED:
298 ace.flags = ace.flags | security.SEC_ACE_FLAG_OBJECT_INHERIT | security.SEC_ACE_FLAG_CONTAINER_INHERIT
299 if str(ace.trustee) == security.SID_CREATOR_OWNER:
300 # For Creator/Owner the IO flag is set as this ACE has only a sense for child objects
301 ace.flags = ace.flags | security.SEC_ACE_FLAG_INHERIT_ONLY
302 ace.access_mask = ldapmask2filemask(ace.access_mask)
308 return fdescr.as_sddl(sid)
313 A wrapper class for SMB connection
315 smb_path: path with separator "\\" other than "/"
318 def __init__(self, smb_conn, dom_sid):
319 self.smb_conn = smb_conn
320 self.dom_sid = dom_sid
322 def get_acl(self, smb_path, as_sddl=False):
323 assert '/' not in smb_path
325 ntacl_sd = self.smb_conn.get_acl(
326 smb_path, SECURITY_SECINFO_FLAGS, SECURITY_SEC_FLAGS)
328 return ntacl_sd.as_sddl(self.dom_sid) if as_sddl else ntacl_sd
330 def list(self, smb_path=''):
332 List file and dir base names in smb_path without recursive.
334 assert '/' not in smb_path
335 return self.smb_conn.list(smb_path, attribs=SMB_FILE_ATTRIBUTE_FLAGS)
337 def is_dir(self, attrib):
339 Check whether the attrib value is a directory.
341 attrib is from list method.
343 return bool(attrib & smb.FILE_ATTRIBUTE_DIRECTORY)
345 def join(self, root, name):
349 return root + '\\' + name if root else name
351 def loadfile(self, smb_path):
352 assert '/' not in smb_path
353 return self.smb_conn.loadfile(smb_path)
355 def create_tree(self, tree, smb_path=''):
357 Create files as defined in tree
359 for name, content in tree.items():
360 fullname = self.join(smb_path, name)
361 if isinstance(content, dict): # a dir
362 if not self.smb_conn.chkpath(fullname):
363 self.smb_conn.mkdir(fullname)
364 self.create_tree(content, smb_path=fullname)
366 self.smb_conn.savefile(fullname, content)
368 def get_tree(self, smb_path=''):
370 Get the tree structure via smb conn
372 self.smb_conn.list example:
379 'short_name': 'dir1',
385 'short_name': 'file0.txt',
391 for item in self.list(smb_path):
393 fullname = self.join(smb_path, name)
394 if self.is_dir(item['attrib']):
395 tree[name] = self.get_tree(smb_path=fullname)
397 tree[name] = self.loadfile(fullname)
400 def get_ntacls(self, smb_path=''):
402 Get ntacl for each file and dir via smb conn
405 for item in self.list(smb_path):
407 fullname = self.join(smb_path, name)
408 if self.is_dir(item['attrib']):
409 ntacls.update(self.get_ntacls(smb_path=fullname))
411 ntacl_sd = self.get_acl(fullname)
412 ntacls[fullname] = ntacl_sd.as_sddl(self.dom_sid)
415 def delete_tree(self):
416 for item in self.list():
418 if self.is_dir(item['attrib']):
419 self.smb_conn.deltree(name)
421 self.smb_conn.unlink(name)
426 def __init__(self, service, smb_conf_path, dom_sid):
427 self.service = service
428 self.dom_sid = dom_sid
430 # this is important to help smbd find services.
431 self.lp = s3param.get_context()
432 self.lp.load(smb_conf_path)
434 self.use_ntvfs = "smb" in self.lp.get("server services")
436 def getntacl(self, path, as_sddl=False, direct_db_access=None):
437 if direct_db_access is None:
438 direct_db_access = self.use_ntvfs
442 direct_db_access=direct_db_access,
443 service=self.service)
445 return ntacl_sd.as_sddl(self.dom_sid) if as_sddl else ntacl_sd
447 def setntacl(self, path, ntacl_sd):
448 # ntacl_sd can be obj or str
449 return setntacl(self.lp, path, ntacl_sd, self.dom_sid)
452 def _create_ntacl_file(dst, ntacl_sddl_str):
453 with open(dst + '.NTACL', 'w') as f:
454 f.write(ntacl_sddl_str)
457 def _read_ntacl_file(src):
458 with open(src + '.NTACL', 'r') as f:
462 def backup_online(smb_conn, dest_tarfile_path, dom_sid):
464 Backup all files and dirs with ntacl for the serive behind smb_conn.
466 1. Create a temp dir as container dir
467 2. Backup all files with dir structure into container dir
468 3. Generate file.NTACL files for each file and dir in contianer dir
469 4. Create a tar file from container dir(without top level folder)
470 5. Delete contianer dir
473 if isinstance(dom_sid, str):
474 dom_sid = security.dom_sid(dom_sid)
476 smb_helper = SMBHelper(smb_conn, dom_sid)
478 remotedir = '' # root dir
480 localdir = tempfile.mkdtemp()
489 for e in smb_helper.list(smb_path=r_dir):
490 r_name = smb_helper.join(r_dir, e['name'])
491 l_name = os.path.join(l_dir, e['name'])
493 if smb_helper.is_dir(e['attrib']):
494 r_dirs.append(r_name)
495 l_dirs.append(l_name)
498 data = smb_helper.loadfile(r_name)
499 with open(l_name, 'wb') as f:
502 # get ntacl for this entry and save alongside
503 ntacl_sddl_str = smb_helper.get_acl(r_name, as_sddl=True)
504 _create_ntacl_file(l_name, ntacl_sddl_str)
506 with tarfile.open(name=dest_tarfile_path, mode='w:gz') as tar:
507 for name in os.listdir(localdir):
508 path = os.path.join(localdir, name)
509 tar.add(path, arcname=name)
511 shutil.rmtree(localdir)
514 def backup_offline(src_service_path, dest_tarfile_path, samdb_conn, smb_conf_path):
516 Backup files and ntacls to a tarfile for a service
518 service = src_service_path.rstrip('/').rsplit('/', 1)[-1]
519 tempdir = tempfile.mkdtemp()
521 dom_sid_str = samdb_conn.get_domain_sid()
522 dom_sid = security.dom_sid(dom_sid_str)
524 ntacls_helper = NtaclsHelper(service, smb_conf_path, dom_sid)
526 for dirpath, dirnames, filenames in os.walk(src_service_path):
527 # each dir only cares about its direct children
528 rel_dirpath = os.path.relpath(dirpath, start=src_service_path)
529 dst_dirpath = os.path.join(tempdir, rel_dirpath)
531 # create sub dirs and NTACL file
532 for dirname in dirnames:
533 src = os.path.join(dirpath, dirname)
534 dst = os.path.join(dst_dirpath, dirname)
535 # mkdir with metadata
536 smbd.mkdir(dst, service)
537 ntacl_sddl_str = ntacls_helper.getntacl(src, as_sddl=True)
538 _create_ntacl_file(dst, ntacl_sddl_str)
540 # create files and NTACL file, then copy data
541 for filename in filenames:
542 src = os.path.join(dirpath, filename)
543 dst = os.path.join(dst_dirpath, filename)
544 # create an empty file with metadata
545 smbd.create_file(dst, service)
546 ntacl_sddl_str = ntacls_helper.getntacl(src, as_sddl=True)
547 _create_ntacl_file(dst, ntacl_sddl_str)
550 with open(src, 'rb') as src_file:
551 data = src_file.read()
552 with open(dst, 'wb') as dst_file:
555 # add all files in tempdir to tarfile without a top folder
556 with tarfile.open(name=dest_tarfile_path, mode='w:gz') as tar:
557 for name in os.listdir(tempdir):
558 path = os.path.join(tempdir, name)
559 tar.add(path, arcname=name)
561 shutil.rmtree(tempdir)
564 def backup_restore(src_tarfile_path, dst_service_path, samdb_conn, smb_conf_path):
566 Restore files and ntacls from a tarfile to a service
568 service = dst_service_path.rstrip('/').rsplit('/', 1)[-1]
569 tempdir = tempfile.mkdtemp() # src files
571 dom_sid_str = samdb_conn.get_domain_sid()
572 dom_sid = security.dom_sid(dom_sid_str)
574 ntacls_helper = NtaclsHelper(service, smb_conf_path, dom_sid)
576 with tarfile.open(src_tarfile_path) as f:
577 f.extractall(path=tempdir)
578 # e.g.: /tmp/tmpRNystY/{dir1,dir1.NTACL,...file1,file1.NTACL}
580 for dirpath, dirnames, filenames in os.walk(tempdir):
581 rel_dirpath = os.path.relpath(dirpath, start=tempdir)
582 dst_dirpath = os.path.normpath(
583 os.path.join(dst_service_path, rel_dirpath))
585 for dirname in dirnames:
586 if not dirname.endswith('.NTACL'):
587 src = os.path.join(dirpath, dirname)
588 dst = os.path.join(dst_dirpath, dirname)
589 if not os.path.isdir(dst):
590 # dst must be absolute path for smbd API
591 smbd.mkdir(dst, service)
592 ntacl_sddl_str = _read_ntacl_file(src)
593 ntacls_helper.setntacl(dst, ntacl_sddl_str)
595 for filename in filenames:
596 if not filename.endswith('.NTACL'):
597 src = os.path.join(dirpath, filename)
598 dst = os.path.join(dst_dirpath, filename)
599 if not os.path.isfile(dst):
600 # dst must be absolute path for smbd API
601 smbd.create_file(dst, service)
602 ntacl_sddl_str = _read_ntacl_file(src)
603 ntacls_helper.setntacl(dst, ntacl_sddl_str)
606 with open(src, 'rb') as src_file:
607 data = src_file.read()
608 with open(dst, 'wb') as dst_file:
611 shutil.rmtree(tempdir)