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, samba.xattr_tdb, samba.posix_eadb
29 from samba.samba3 import param as s3param
30 from samba.dcerpc import security, xattr, idmap
31 from samba.ndr import ndr_pack, ndr_unpack
32 from samba.samba3 import smbd
33 from samba.auth import admin_session
36 # don't include volumes
37 SMB_FILE_ATTRIBUTE_FLAGS = smb.FILE_ATTRIBUTE_SYSTEM | \
38 smb.FILE_ATTRIBUTE_DIRECTORY | \
39 smb.FILE_ATTRIBUTE_ARCHIVE | \
40 smb.FILE_ATTRIBUTE_HIDDEN
43 SECURITY_SECINFO_FLAGS = security.SECINFO_OWNER | \
44 security.SECINFO_GROUP | \
45 security.SECINFO_DACL | \
49 # SEC_FLAG_SYSTEM_SECURITY is required otherwise get Access Denied
50 SECURITY_SEC_FLAGS = security.SEC_FLAG_SYSTEM_SECURITY | \
51 security.SEC_FLAG_MAXIMUM_ALLOWED
54 class XattrBackendError(Exception):
55 """A generic xattr backend error."""
58 def checkset_backend(lp, backend, eadbfile):
59 '''return the path to the eadb, or None'''
61 xattr_tdb = lp.get("xattr_tdb:file")
62 if xattr_tdb is not None:
63 return (samba.xattr_tdb, lp.get("xattr_tdb:file"))
64 posix_eadb = lp.get("posix:eadb")
65 if posix_eadb is not None:
66 return (samba.posix_eadb, lp.get("posix:eadb"))
68 elif backend == "native":
70 elif backend == "eadb":
71 if eadbfile is not None:
72 return (samba.posix_eadb, eadbfile)
74 return (samba.posix_eadb, os.path.abspath(os.path.join(lp.get("private dir"), "eadb.tdb")))
75 elif backend == "tdb":
76 if eadbfile is not None:
77 return (samba.xattr_tdb, eadbfile)
79 return (samba.xattr_tdb, os.path.abspath(os.path.join(lp.get("state dir"), "xattr.tdb")))
81 raise XattrBackendError("Invalid xattr backend choice %s"%backend)
83 def getdosinfo(lp, file):
85 attribute = samba.xattr_native.wrap_getxattr(file,
86 xattr.XATTR_DOSATTRIB_NAME_S3)
90 return ndr_unpack(xattr.DOSATTRIB, attribute)
92 def getntacl(lp, file, backend=None, eadbfile=None, direct_db_access=True, service=None):
94 (backend_obj, dbname) = checkset_backend(lp, backend, eadbfile)
95 if dbname is not None:
97 attribute = backend_obj.wrap_getxattr(dbname, file,
98 xattr.XATTR_NTACL_NAME)
100 # FIXME: Don't catch all exceptions, just those related to opening
102 print("Fail to open %s" % dbname)
103 attribute = samba.xattr_native.wrap_getxattr(file,
104 xattr.XATTR_NTACL_NAME)
106 attribute = samba.xattr_native.wrap_getxattr(file,
107 xattr.XATTR_NTACL_NAME)
108 ntacl = ndr_unpack(xattr.NTACL, attribute)
109 if ntacl.version == 1:
111 elif ntacl.version == 2:
113 elif ntacl.version == 3:
115 elif ntacl.version == 4:
118 return smbd.get_nt_acl(file, SECURITY_SECINFO_FLAGS, service=service)
121 def setntacl(lp, file, sddl, domsid,
122 backend=None, eadbfile=None,
123 use_ntvfs=True, skip_invalid_chown=False,
124 passdb=None, service=None, session_info=None):
126 A wrapper for smbd set_nt_acl api.
129 lp (LoadParam): load param from conf
130 file (str): a path to file or dir
131 sddl (str): ntacl sddl string
132 service (str): name of share service, e.g.: sysvol
133 session_info (auth_session_info): session info for authentication
136 Get `session_info` with `samba.auth.user_session`, do not use the
143 assert(isinstance(domsid, str) or isinstance(domsid, security.dom_sid))
144 if isinstance(domsid, str):
145 sid = security.dom_sid(domsid)
146 elif isinstance(domsid, security.dom_sid):
150 assert(isinstance(sddl, str) or isinstance(sddl, security.descriptor))
151 if isinstance(sddl, str):
152 sd = security.descriptor.from_sddl(sddl, sid)
153 elif isinstance(sddl, security.descriptor):
155 sddl = sd.as_sddl(sid)
157 if not use_ntvfs and skip_invalid_chown:
158 # Check if the owner can be resolved as a UID
159 (owner_id, owner_type) = passdb.sid_to_id(sd.owner_sid)
160 if ((owner_type != idmap.ID_TYPE_UID) and (owner_type != idmap.ID_TYPE_BOTH)):
161 # Check if this particular owner SID was domain admins,
162 # because we special-case this as mapping to
163 # 'administrator' instead.
164 if sd.owner_sid == security.dom_sid("%s-%d" % (domsid, security.DOMAIN_RID_ADMINS)):
165 administrator = security.dom_sid("%s-%d" % (domsid, security.DOMAIN_RID_ADMINISTRATOR))
166 (admin_id, admin_type) = passdb.sid_to_id(administrator)
168 # Confirm we have a UID for administrator
169 if ((admin_type == idmap.ID_TYPE_UID) or (admin_type == idmap.ID_TYPE_BOTH)):
171 # Set it, changing the owner to 'administrator' rather than domain admins
173 sd2.owner_sid = administrator
176 file, SECURITY_SECINFO_FLAGS, sd2,
177 service=service, session_info=session_info)
179 # and then set an NTVFS ACL (which does not set the posix ACL) to pretend the owner really was set
182 raise XattrBackendError("Unable to find UID for domain administrator %s, got id %d of type %d" % (administrator, admin_id, admin_type))
184 # For all other owning users, reset the owner to root
185 # and then set the ACL without changing the owner
187 # This won't work in test environments, as it tries a real (rather than xattr-based fake) chown
192 security.SECINFO_GROUP |
193 security.SECINFO_DACL |
194 security.SECINFO_SACL,
195 sd, service=service, session_info=session_info)
198 (backend_obj, dbname) = checkset_backend(lp, backend, eadbfile)
199 ntacl = xattr.NTACL()
202 if dbname is not None:
204 backend_obj.wrap_setxattr(dbname,
205 file, xattr.XATTR_NTACL_NAME, ndr_pack(ntacl))
207 # FIXME: Don't catch all exceptions, just those related to opening
209 print("Fail to open %s" % dbname)
210 samba.xattr_native.wrap_setxattr(file, xattr.XATTR_NTACL_NAME,
213 samba.xattr_native.wrap_setxattr(file, xattr.XATTR_NTACL_NAME,
217 file, SECURITY_SECINFO_FLAGS, sd,
218 service=service, session_info=session_info)
221 def ldapmask2filemask(ldm):
222 """Takes the access mask of a DS ACE and transform them in a File ACE mask.
224 RIGHT_DS_CREATE_CHILD = 0x00000001
225 RIGHT_DS_DELETE_CHILD = 0x00000002
226 RIGHT_DS_LIST_CONTENTS = 0x00000004
227 ACTRL_DS_SELF = 0x00000008
228 RIGHT_DS_READ_PROPERTY = 0x00000010
229 RIGHT_DS_WRITE_PROPERTY = 0x00000020
230 RIGHT_DS_DELETE_TREE = 0x00000040
231 RIGHT_DS_LIST_OBJECT = 0x00000080
232 RIGHT_DS_CONTROL_ACCESS = 0x00000100
233 FILE_READ_DATA = 0x0001
234 FILE_LIST_DIRECTORY = 0x0001
235 FILE_WRITE_DATA = 0x0002
236 FILE_ADD_FILE = 0x0002
237 FILE_APPEND_DATA = 0x0004
238 FILE_ADD_SUBDIRECTORY = 0x0004
239 FILE_CREATE_PIPE_INSTANCE = 0x0004
240 FILE_READ_EA = 0x0008
241 FILE_WRITE_EA = 0x0010
242 FILE_EXECUTE = 0x0020
243 FILE_TRAVERSE = 0x0020
244 FILE_DELETE_CHILD = 0x0040
245 FILE_READ_ATTRIBUTES = 0x0080
246 FILE_WRITE_ATTRIBUTES = 0x0100
248 READ_CONTROL = 0x00020000
249 WRITE_DAC = 0x00040000
250 WRITE_OWNER = 0x00080000
251 SYNCHRONIZE = 0x00100000
252 STANDARD_RIGHTS_ALL = 0x001F0000
254 filemask = ldm & STANDARD_RIGHTS_ALL
256 if (ldm & RIGHT_DS_READ_PROPERTY) and (ldm & RIGHT_DS_LIST_CONTENTS):
257 filemask = filemask | (SYNCHRONIZE | FILE_LIST_DIRECTORY |
258 FILE_READ_ATTRIBUTES | FILE_READ_EA |
259 FILE_READ_DATA | FILE_EXECUTE)
261 if ldm & RIGHT_DS_WRITE_PROPERTY:
262 filemask = filemask | (SYNCHRONIZE | FILE_WRITE_DATA |
263 FILE_APPEND_DATA | FILE_WRITE_EA |
264 FILE_WRITE_ATTRIBUTES | FILE_ADD_FILE |
265 FILE_ADD_SUBDIRECTORY)
267 if ldm & RIGHT_DS_CREATE_CHILD:
268 filemask = filemask | (FILE_ADD_SUBDIRECTORY | FILE_ADD_FILE)
270 if ldm & RIGHT_DS_DELETE_CHILD:
271 filemask = filemask | FILE_DELETE_CHILD
276 def dsacl2fsacl(dssddl, sid, as_sddl=True):
279 This function takes an the SDDL representation of a DS
280 ACL and return the SDDL representation of this ACL adapted
281 for files. It's used for Policy object provision
283 ref = security.descriptor.from_sddl(dssddl, sid)
284 fdescr = security.descriptor()
285 fdescr.owner_sid = ref.owner_sid
286 fdescr.group_sid = ref.group_sid
287 fdescr.type = ref.type
288 fdescr.revision = ref.revision
290 for i in range(0, len(aces)):
292 if not ace.type & security.SEC_ACE_TYPE_ACCESS_ALLOWED_OBJECT and str(ace.trustee) != security.SID_BUILTIN_PREW2K:
293 # if fdescr.type & security.SEC_DESC_DACL_AUTO_INHERITED:
294 ace.flags = ace.flags | security.SEC_ACE_FLAG_OBJECT_INHERIT | security.SEC_ACE_FLAG_CONTAINER_INHERIT
295 if str(ace.trustee) == security.SID_CREATOR_OWNER:
296 # For Creator/Owner the IO flag is set as this ACE has only a sense for child objects
297 ace.flags = ace.flags | security.SEC_ACE_FLAG_INHERIT_ONLY
298 ace.access_mask = ldapmask2filemask(ace.access_mask)
304 return fdescr.as_sddl(sid)
309 A wrapper class for SMB connection
311 smb_path: path with separator "\\" other than "/"
314 def __init__(self, smb_conn, dom_sid):
315 self.smb_conn = smb_conn
316 self.dom_sid = dom_sid
318 def get_acl(self, smb_path, as_sddl=False):
319 assert '/' not in smb_path
321 ntacl_sd = self.smb_conn.get_acl(
322 smb_path, SECURITY_SECINFO_FLAGS, SECURITY_SEC_FLAGS)
324 return ntacl_sd.as_sddl(self.dom_sid) if as_sddl else ntacl_sd
326 def list(self, smb_path=''):
328 List file and dir base names in smb_path without recursive.
330 assert '/' not in smb_path
331 return self.smb_conn.list(smb_path, attribs=SMB_FILE_ATTRIBUTE_FLAGS)
333 def is_dir(self, attrib):
335 Check whether the attrib value is a directory.
337 attrib is from list method.
339 return bool(attrib & smb.FILE_ATTRIBUTE_DIRECTORY)
341 def join(self, root, name):
345 return root + '\\' + name if root else name
347 def loadfile(self, smb_path):
348 assert '/' not in smb_path
349 return self.smb_conn.loadfile(smb_path)
351 def create_tree(self, tree, smb_path=''):
353 Create files as defined in tree
355 for name, content in tree.items():
356 fullname = self.join(smb_path, name)
357 if isinstance(content, dict): # a dir
358 if not self.smb_conn.chkpath(fullname):
359 self.smb_conn.mkdir(fullname)
360 self.create_tree(content, smb_path=fullname)
362 self.smb_conn.savefile(fullname, content)
364 def get_tree(self, smb_path=''):
366 Get the tree structure via smb conn
368 self.smb_conn.list example:
375 'short_name': 'dir1',
381 'short_name': 'file0.txt',
387 for item in self.list(smb_path):
389 fullname = self.join(smb_path, name)
390 if self.is_dir(item['attrib']):
391 tree[name] = self.get_tree(smb_path=fullname)
393 tree[name] = self.loadfile(fullname)
396 def get_ntacls(self, smb_path=''):
398 Get ntacl for each file and dir via smb conn
401 for item in self.list(smb_path):
403 fullname = self.join(smb_path, name)
404 if self.is_dir(item['attrib']):
405 ntacls.update(self.get_ntacls(smb_path=fullname))
407 ntacl_sd = self.get_acl(fullname)
408 ntacls[fullname] = ntacl_sd.as_sddl(self.dom_sid)
411 def delete_tree(self):
412 for item in self.list():
414 if self.is_dir(item['attrib']):
415 self.smb_conn.deltree(name)
417 self.smb_conn.unlink(name)
422 def __init__(self, service, smb_conf_path, dom_sid):
423 self.service = service
424 self.dom_sid = dom_sid
426 # this is important to help smbd find services.
427 self.lp = s3param.get_context()
428 self.lp.load(smb_conf_path)
430 self.use_ntvfs = "smb" in self.lp.get("server services")
432 def getntacl(self, path, as_sddl=False, direct_db_access=None):
433 if direct_db_access is None:
434 direct_db_access = self.use_ntvfs
438 direct_db_access=direct_db_access,
439 service=self.service)
441 return ntacl_sd.as_sddl(self.dom_sid) if as_sddl else ntacl_sd
443 def setntacl(self, path, ntacl_sd):
444 # ntacl_sd can be obj or str
445 return setntacl(self.lp, path, ntacl_sd, self.dom_sid)
448 def _create_ntacl_file(dst, ntacl_sddl_str):
449 with open(dst + '.NTACL', 'w') as f:
450 f.write(ntacl_sddl_str)
453 def _read_ntacl_file(src):
454 with open(src + '.NTACL', 'r') as f:
458 def backup_online(smb_conn, dest_tarfile_path, dom_sid):
460 Backup all files and dirs with ntacl for the serive behind smb_conn.
462 1. Create a temp dir as container dir
463 2. Backup all files with dir structure into container dir
464 3. Generate file.NTACL files for each file and dir in contianer dir
465 4. Create a tar file from container dir(without top level folder)
466 5. Delete contianer dir
469 if isinstance(dom_sid, str):
470 dom_sid = security.dom_sid(dom_sid)
472 smb_helper = SMBHelper(smb_conn, dom_sid)
474 remotedir = '' # root dir
476 localdir = tempfile.mkdtemp()
485 for e in smb_helper.list(smb_path=r_dir):
486 r_name = smb_helper.join(r_dir, e['name'])
487 l_name = os.path.join(l_dir, e['name'])
489 if smb_helper.is_dir(e['attrib']):
490 r_dirs.append(r_name)
491 l_dirs.append(l_name)
494 data = smb_helper.loadfile(r_name)
495 with open(l_name, 'wb') as f:
498 # get ntacl for this entry and save alongside
499 ntacl_sddl_str = smb_helper.get_acl(r_name, as_sddl=True)
500 _create_ntacl_file(l_name, ntacl_sddl_str)
502 with tarfile.open(name=dest_tarfile_path, mode='w:gz') as tar:
503 for name in os.listdir(localdir):
504 path = os.path.join(localdir, name)
505 tar.add(path, arcname=name)
507 shutil.rmtree(localdir)
510 def backup_offline(src_service_path, dest_tarfile_path, samdb_conn, smb_conf_path):
512 Backup files and ntacls to a tarfile for a service
514 service = src_service_path.rstrip('/').rsplit('/', 1)[-1]
515 tempdir = tempfile.mkdtemp()
517 dom_sid_str = samdb_conn.get_domain_sid()
518 dom_sid = security.dom_sid(dom_sid_str)
520 ntacls_helper = NtaclsHelper(service, smb_conf_path, dom_sid)
522 for dirpath, dirnames, filenames in os.walk(src_service_path):
523 # each dir only cares about its direct children
524 rel_dirpath = os.path.relpath(dirpath, start=src_service_path)
525 dst_dirpath = os.path.join(tempdir, rel_dirpath)
527 # create sub dirs and NTACL file
528 for dirname in dirnames:
529 src = os.path.join(dirpath, dirname)
530 dst = os.path.join(dst_dirpath, dirname)
531 # mkdir with metadata
532 smbd.mkdir(dst, service)
533 ntacl_sddl_str = ntacls_helper.getntacl(src, as_sddl=True)
534 _create_ntacl_file(dst, ntacl_sddl_str)
536 # create files and NTACL file, then copy data
537 for filename in filenames:
538 src = os.path.join(dirpath, filename)
539 dst = os.path.join(dst_dirpath, filename)
540 # create an empty file with metadata
541 smbd.create_file(dst, service)
542 ntacl_sddl_str = ntacls_helper.getntacl(src, as_sddl=True)
543 _create_ntacl_file(dst, ntacl_sddl_str)
546 with open(src, 'rb') as src_file:
547 data = src_file.read()
548 with open(dst, 'wb') as dst_file:
551 # add all files in tempdir to tarfile without a top folder
552 with tarfile.open(name=dest_tarfile_path, mode='w:gz') as tar:
553 for name in os.listdir(tempdir):
554 path = os.path.join(tempdir, name)
555 tar.add(path, arcname=name)
557 shutil.rmtree(tempdir)
560 def backup_restore(src_tarfile_path, dst_service_path, samdb_conn, smb_conf_path):
562 Restore files and ntacls from a tarfile to a service
564 service = dst_service_path.rstrip('/').rsplit('/', 1)[-1]
565 tempdir = tempfile.mkdtemp() # src files
567 dom_sid_str = samdb_conn.get_domain_sid()
568 dom_sid = security.dom_sid(dom_sid_str)
570 ntacls_helper = NtaclsHelper(service, smb_conf_path, dom_sid)
572 with tarfile.open(src_tarfile_path) as f:
573 f.extractall(path=tempdir)
574 # e.g.: /tmp/tmpRNystY/{dir1,dir1.NTACL,...file1,file1.NTACL}
576 for dirpath, dirnames, filenames in os.walk(tempdir):
577 rel_dirpath = os.path.relpath(dirpath, start=tempdir)
578 dst_dirpath = os.path.normpath(
579 os.path.join(dst_service_path, rel_dirpath))
581 for dirname in dirnames:
582 if not dirname.endswith('.NTACL'):
583 src = os.path.join(dirpath, dirname)
584 dst = os.path.join(dst_dirpath, dirname)
585 if not os.path.isdir(dst):
586 # dst must be absolute path for smbd API
587 smbd.mkdir(dst, service)
588 ntacl_sddl_str = _read_ntacl_file(src)
589 ntacls_helper.setntacl(dst, ntacl_sddl_str)
591 for filename in filenames:
592 if not filename.endswith('.NTACL'):
593 src = os.path.join(dirpath, filename)
594 dst = os.path.join(dst_dirpath, filename)
595 if not os.path.isfile(dst):
596 # dst must be absolute path for smbd API
597 smbd.create_file(dst, service)
598 ntacl_sddl_str = _read_ntacl_file(src)
599 ntacls_helper.setntacl(dst, ntacl_sddl_str)
602 with open(src, 'rb') as src_file:
603 data = src_file.read()
604 with open(dst, 'wb') as dst_file:
607 shutil.rmtree(tempdir)