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
.samba3
import libsmb_samba_internal
as libsmb
36 from samba
.logger
import get_samba_logger
37 from samba
import NTSTATUSError
38 from samba
.auth_util
import system_session_unix
40 # don't include volumes
41 SMB_FILE_ATTRIBUTE_FLAGS
= libsmb
.FILE_ATTRIBUTE_SYSTEM | \
42 libsmb
.FILE_ATTRIBUTE_DIRECTORY | \
43 libsmb
.FILE_ATTRIBUTE_ARCHIVE | \
44 libsmb
.FILE_ATTRIBUTE_HIDDEN
47 SECURITY_SECINFO_FLAGS
= security
.SECINFO_OWNER | \
48 security
.SECINFO_GROUP | \
49 security
.SECINFO_DACL | \
53 # SEC_FLAG_SYSTEM_SECURITY is required otherwise get Access Denied
54 SECURITY_SEC_FLAGS
= security
.SEC_FLAG_SYSTEM_SECURITY | \
55 security
.SEC_STD_READ_CONTROL
58 class XattrBackendError(Exception):
59 """A generic xattr backend error."""
62 def checkset_backend(lp
, backend
, eadbfile
):
63 '''return the path to the eadb, or None'''
65 xattr_tdb
= lp
.get("xattr_tdb:file")
66 if xattr_tdb
is not None:
67 return (samba
.xattr_tdb
, lp
.get("xattr_tdb:file"))
68 posix_eadb
= lp
.get("posix:eadb")
69 if posix_eadb
is not None:
70 return (samba
.posix_eadb
, lp
.get("posix:eadb"))
72 elif backend
== "native":
74 elif backend
== "eadb":
75 if eadbfile
is not None:
76 return (samba
.posix_eadb
, eadbfile
)
78 return (samba
.posix_eadb
, os
.path
.abspath(os
.path
.join(lp
.get("private dir"), "eadb.tdb")))
79 elif backend
== "tdb":
80 if eadbfile
is not None:
81 return (samba
.xattr_tdb
, eadbfile
)
83 state_dir
= lp
.get("state directory")
84 db_path
= os
.path
.abspath(os
.path
.join(state_dir
, "xattr.tdb"))
85 return (samba
.xattr_tdb
, db_path
)
87 raise XattrBackendError("Invalid xattr backend choice %s" % backend
)
90 def getdosinfo(lp
, file):
92 attribute
= samba
.xattr_native
.wrap_getxattr(file,
93 xattr
.XATTR_DOSATTRIB_NAME_S3
)
97 return ndr_unpack(xattr
.DOSATTRIB
, attribute
)
105 direct_db_access
=True,
108 (backend_obj
, dbname
) = checkset_backend(lp
, backend
, eadbfile
)
109 if dbname
is not None:
111 attribute
= backend_obj
.wrap_getxattr(dbname
, file,
112 xattr
.XATTR_NTACL_NAME
)
114 # FIXME: Don't catch all exceptions, just those related to opening
116 print("Fail to open %s" % dbname
)
117 attribute
= samba
.xattr_native
.wrap_getxattr(file,
118 xattr
.XATTR_NTACL_NAME
)
120 attribute
= samba
.xattr_native
.wrap_getxattr(file,
121 xattr
.XATTR_NTACL_NAME
)
122 ntacl
= ndr_unpack(xattr
.NTACL
, attribute
)
123 if ntacl
.version
== 1:
125 elif ntacl
.version
== 2:
127 elif ntacl
.version
== 3:
129 elif ntacl
.version
== 4:
132 return smbd
.get_nt_acl(file,
133 SECURITY_SECINFO_FLAGS
,
138 def setntacl(lp
, file, sddl
, domsid
, session_info
,
139 backend
=None, eadbfile
=None,
140 use_ntvfs
=True, skip_invalid_chown
=False,
141 passdb
=None, service
=None):
143 A wrapper for smbd set_nt_acl api.
146 lp (LoadParam): load param from conf
147 file (str): a path to file or dir
148 sddl (str): ntacl sddl string
149 service (str): name of share service, e.g.: sysvol
150 session_info (auth_session_info): session info for authentication
153 Get `session_info` with `samba.auth.user_session`, do not use the
160 assert(isinstance(domsid
, str) or isinstance(domsid
, security
.dom_sid
))
161 if isinstance(domsid
, str):
162 sid
= security
.dom_sid(domsid
)
163 elif isinstance(domsid
, security
.dom_sid
):
167 assert(isinstance(sddl
, str) or isinstance(sddl
, security
.descriptor
))
168 if isinstance(sddl
, str):
169 sd
= security
.descriptor
.from_sddl(sddl
, sid
)
170 elif isinstance(sddl
, security
.descriptor
):
172 sddl
= sd
.as_sddl(sid
)
174 if not use_ntvfs
and skip_invalid_chown
:
175 # Check if the owner can be resolved as a UID
176 (owner_id
, owner_type
) = passdb
.sid_to_id(sd
.owner_sid
)
177 if ((owner_type
!= idmap
.ID_TYPE_UID
) and (owner_type
!= idmap
.ID_TYPE_BOTH
)):
178 # Check if this particular owner SID was domain admins,
179 # because we special-case this as mapping to
180 # 'administrator' instead.
181 if sd
.owner_sid
== security
.dom_sid("%s-%d" % (domsid
, security
.DOMAIN_RID_ADMINS
)):
182 administrator
= security
.dom_sid("%s-%d" % (domsid
, security
.DOMAIN_RID_ADMINISTRATOR
))
183 (admin_id
, admin_type
) = passdb
.sid_to_id(administrator
)
185 # Confirm we have a UID for administrator
186 if ((admin_type
== idmap
.ID_TYPE_UID
) or (admin_type
== idmap
.ID_TYPE_BOTH
)):
188 # Set it, changing the owner to 'administrator' rather than domain admins
190 sd2
.owner_sid
= administrator
193 file, SECURITY_SECINFO_FLAGS
, sd2
,
197 # and then set an NTVFS ACL (which does not set the posix ACL) to pretend the owner really was set
200 raise XattrBackendError("Unable to find UID for domain administrator %s, got id %d of type %d" % (administrator
, admin_id
, admin_type
))
202 # For all other owning users, reset the owner to root
203 # and then set the ACL without changing the owner
205 # This won't work in test environments, as it tries a real (rather than xattr-based fake) chown
210 security
.SECINFO_GROUP |
211 security
.SECINFO_DACL |
212 security
.SECINFO_SACL
,
218 (backend_obj
, dbname
) = checkset_backend(lp
, backend
, eadbfile
)
219 ntacl
= xattr
.NTACL()
222 if dbname
is not None:
224 backend_obj
.wrap_setxattr(dbname
,
225 file, xattr
.XATTR_NTACL_NAME
, ndr_pack(ntacl
))
227 # FIXME: Don't catch all exceptions, just those related to opening
229 print("Fail to open %s" % dbname
)
230 samba
.xattr_native
.wrap_setxattr(file, xattr
.XATTR_NTACL_NAME
,
233 samba
.xattr_native
.wrap_setxattr(file, xattr
.XATTR_NTACL_NAME
,
237 file, SECURITY_SECINFO_FLAGS
, sd
,
238 service
=service
, session_info
=session_info
)
241 def ldapmask2filemask(ldm
):
242 """Takes the access mask of a DS ACE and transform them in a File ACE mask.
244 RIGHT_DS_CREATE_CHILD
= 0x00000001
245 RIGHT_DS_DELETE_CHILD
= 0x00000002
246 RIGHT_DS_LIST_CONTENTS
= 0x00000004
247 ACTRL_DS_SELF
= 0x00000008
248 RIGHT_DS_READ_PROPERTY
= 0x00000010
249 RIGHT_DS_WRITE_PROPERTY
= 0x00000020
250 RIGHT_DS_DELETE_TREE
= 0x00000040
251 RIGHT_DS_LIST_OBJECT
= 0x00000080
252 RIGHT_DS_CONTROL_ACCESS
= 0x00000100
253 FILE_READ_DATA
= 0x0001
254 FILE_LIST_DIRECTORY
= 0x0001
255 FILE_WRITE_DATA
= 0x0002
256 FILE_ADD_FILE
= 0x0002
257 FILE_APPEND_DATA
= 0x0004
258 FILE_ADD_SUBDIRECTORY
= 0x0004
259 FILE_CREATE_PIPE_INSTANCE
= 0x0004
260 FILE_READ_EA
= 0x0008
261 FILE_WRITE_EA
= 0x0010
262 FILE_EXECUTE
= 0x0020
263 FILE_TRAVERSE
= 0x0020
264 FILE_DELETE_CHILD
= 0x0040
265 FILE_READ_ATTRIBUTES
= 0x0080
266 FILE_WRITE_ATTRIBUTES
= 0x0100
268 READ_CONTROL
= 0x00020000
269 WRITE_DAC
= 0x00040000
270 WRITE_OWNER
= 0x00080000
271 SYNCHRONIZE
= 0x00100000
272 STANDARD_RIGHTS_ALL
= 0x001F0000
274 filemask
= ldm
& STANDARD_RIGHTS_ALL
276 if (ldm
& RIGHT_DS_READ_PROPERTY
) and (ldm
& RIGHT_DS_LIST_CONTENTS
):
277 filemask
= filemask |
(SYNCHRONIZE | FILE_LIST_DIRECTORY |
278 FILE_READ_ATTRIBUTES | FILE_READ_EA |
279 FILE_READ_DATA | FILE_EXECUTE
)
281 if ldm
& RIGHT_DS_WRITE_PROPERTY
:
282 filemask
= filemask |
(SYNCHRONIZE | FILE_WRITE_DATA |
283 FILE_APPEND_DATA | FILE_WRITE_EA |
284 FILE_WRITE_ATTRIBUTES | FILE_ADD_FILE |
285 FILE_ADD_SUBDIRECTORY
)
287 if ldm
& RIGHT_DS_CREATE_CHILD
:
288 filemask
= filemask |
(FILE_ADD_SUBDIRECTORY | FILE_ADD_FILE
)
290 if ldm
& RIGHT_DS_DELETE_CHILD
:
291 filemask
= filemask | FILE_DELETE_CHILD
296 def dsacl2fsacl(dssddl
, sid
, as_sddl
=True):
299 This function takes an the SDDL representation of a DS
300 ACL and return the SDDL representation of this ACL adapted
301 for files. It's used for Policy object provision
303 ref
= security
.descriptor
.from_sddl(dssddl
, sid
)
304 fdescr
= security
.descriptor()
305 fdescr
.owner_sid
= ref
.owner_sid
306 fdescr
.group_sid
= ref
.group_sid
307 fdescr
.type = ref
.type
308 fdescr
.revision
= ref
.revision
310 for i
in range(0, len(aces
)):
312 if not ace
.type & security
.SEC_ACE_TYPE_ACCESS_ALLOWED_OBJECT
and str(ace
.trustee
) != security
.SID_BUILTIN_PREW2K
:
313 # if fdescr.type & security.SEC_DESC_DACL_AUTO_INHERITED:
314 ace
.flags
= ace
.flags | security
.SEC_ACE_FLAG_OBJECT_INHERIT | security
.SEC_ACE_FLAG_CONTAINER_INHERIT
315 if str(ace
.trustee
) == security
.SID_CREATOR_OWNER
:
316 # For Creator/Owner the IO flag is set as this ACE has only a sense for child objects
317 ace
.flags
= ace
.flags | security
.SEC_ACE_FLAG_INHERIT_ONLY
318 ace
.access_mask
= ldapmask2filemask(ace
.access_mask
)
324 return fdescr
.as_sddl(sid
)
329 A wrapper class for SMB connection
331 smb_path: path with separator "\\" other than "/"
334 def __init__(self
, smb_conn
, dom_sid
):
335 self
.smb_conn
= smb_conn
336 self
.dom_sid
= dom_sid
338 def get_acl(self
, smb_path
, as_sddl
=False):
339 assert '/' not in smb_path
341 ntacl_sd
= self
.smb_conn
.get_acl(
342 smb_path
, SECURITY_SECINFO_FLAGS
, SECURITY_SEC_FLAGS
)
344 return ntacl_sd
.as_sddl(self
.dom_sid
) if as_sddl
else ntacl_sd
346 def list(self
, smb_path
=''):
348 List file and dir base names in smb_path without recursive.
350 assert '/' not in smb_path
351 return self
.smb_conn
.list(smb_path
, attribs
=SMB_FILE_ATTRIBUTE_FLAGS
)
353 def is_dir(self
, attrib
):
355 Check whether the attrib value is a directory.
357 attrib is from list method.
359 return bool(attrib
& libsmb
.FILE_ATTRIBUTE_DIRECTORY
)
361 def join(self
, root
, name
):
365 return root
+ '\\' + name
if root
else name
367 def loadfile(self
, smb_path
):
368 assert '/' not in smb_path
369 return self
.smb_conn
.loadfile(smb_path
)
371 def create_tree(self
, tree
, smb_path
=''):
373 Create files as defined in tree
375 for name
, content
in tree
.items():
376 fullname
= self
.join(smb_path
, name
)
377 if isinstance(content
, dict): # a dir
378 if not self
.smb_conn
.chkpath(fullname
):
379 self
.smb_conn
.mkdir(fullname
)
380 self
.create_tree(content
, smb_path
=fullname
)
382 self
.smb_conn
.savefile(fullname
, content
)
384 def get_tree(self
, smb_path
=''):
386 Get the tree structure via smb conn
388 self.smb_conn.list example:
395 'short_name': 'dir1',
401 'short_name': 'file0.txt',
407 for item
in self
.list(smb_path
):
409 fullname
= self
.join(smb_path
, name
)
410 if self
.is_dir(item
['attrib']):
411 tree
[name
] = self
.get_tree(smb_path
=fullname
)
413 tree
[name
] = self
.loadfile(fullname
)
416 def get_ntacls(self
, smb_path
=''):
418 Get ntacl for each file and dir via smb conn
421 for item
in self
.list(smb_path
):
423 fullname
= self
.join(smb_path
, name
)
424 if self
.is_dir(item
['attrib']):
425 ntacls
.update(self
.get_ntacls(smb_path
=fullname
))
427 ntacl_sd
= self
.get_acl(fullname
)
428 ntacls
[fullname
] = ntacl_sd
.as_sddl(self
.dom_sid
)
431 def delete_tree(self
):
432 for item
in self
.list():
434 if self
.is_dir(item
['attrib']):
435 self
.smb_conn
.deltree(name
)
437 self
.smb_conn
.unlink(name
)
442 def __init__(self
, service
, smb_conf_path
, dom_sid
):
443 self
.service
= service
444 self
.dom_sid
= dom_sid
446 # this is important to help smbd find services.
447 self
.lp
= s3param
.get_context()
448 self
.lp
.load(smb_conf_path
)
450 self
.use_ntvfs
= "smb" in self
.lp
.get("server services")
452 def getntacl(self
, path
, session_info
, as_sddl
=False, direct_db_access
=None):
453 if direct_db_access
is None:
454 direct_db_access
= self
.use_ntvfs
457 self
.lp
, path
, session_info
,
458 direct_db_access
=direct_db_access
,
459 service
=self
.service
)
461 return ntacl_sd
.as_sddl(self
.dom_sid
) if as_sddl
else ntacl_sd
463 def setntacl(self
, path
, ntacl_sd
, session_info
):
464 # ntacl_sd can be obj or str
465 return setntacl(self
.lp
, path
, ntacl_sd
, self
.dom_sid
, session_info
,
466 use_ntvfs
=self
.use_ntvfs
)
469 def _create_ntacl_file(dst
, ntacl_sddl_str
):
470 with
open(dst
+ '.NTACL', 'w') as f
:
471 f
.write(ntacl_sddl_str
)
474 def _read_ntacl_file(src
):
475 ntacl_file
= src
+ '.NTACL'
477 if not os
.path
.exists(ntacl_file
):
480 with
open(ntacl_file
, 'r') as f
:
484 def backup_online(smb_conn
, dest_tarfile_path
, dom_sid
):
486 Backup all files and dirs with ntacl for the serive behind smb_conn.
488 1. Create a temp dir as container dir
489 2. Backup all files with dir structure into container dir
490 3. Generate file.NTACL files for each file and dir in contianer dir
491 4. Create a tar file from container dir(without top level folder)
492 5. Delete contianer dir
495 logger
= get_samba_logger()
497 if isinstance(dom_sid
, str):
498 dom_sid
= security
.dom_sid(dom_sid
)
500 smb_helper
= SMBHelper(smb_conn
, dom_sid
)
502 remotedir
= '' # root dir
504 localdir
= tempfile
.mkdtemp()
513 for e
in smb_helper
.list(smb_path
=r_dir
):
514 r_name
= smb_helper
.join(r_dir
, e
['name'])
515 l_name
= os
.path
.join(l_dir
, e
['name'])
517 if smb_helper
.is_dir(e
['attrib']):
518 r_dirs
.append(r_name
)
519 l_dirs
.append(l_name
)
522 data
= smb_helper
.loadfile(r_name
)
523 with
open(l_name
, 'wb') as f
:
526 # get ntacl for this entry and save alongside
528 ntacl_sddl_str
= smb_helper
.get_acl(r_name
, as_sddl
=True)
529 _create_ntacl_file(l_name
, ntacl_sddl_str
)
530 except NTSTATUSError
as e
:
531 logger
.error('Failed to get the ntacl for %s: %s' % \
533 logger
.warning('The permissions for %s may not be' % r_name
+
534 ' restored correctly')
536 with tarfile
.open(name
=dest_tarfile_path
, mode
='w:gz') as tar
:
537 for name
in os
.listdir(localdir
):
538 path
= os
.path
.join(localdir
, name
)
539 tar
.add(path
, arcname
=name
)
541 shutil
.rmtree(localdir
)
544 def backup_offline(src_service_path
, dest_tarfile_path
, samdb_conn
, smb_conf_path
):
546 Backup files and ntacls to a tarfile for a service
548 service
= src_service_path
.rstrip('/').rsplit('/', 1)[-1]
549 tempdir
= tempfile
.mkdtemp()
550 session_info
= system_session_unix()
552 dom_sid_str
= samdb_conn
.get_domain_sid()
553 dom_sid
= security
.dom_sid(dom_sid_str
)
555 ntacls_helper
= NtaclsHelper(service
, smb_conf_path
, dom_sid
)
557 for dirpath
, dirnames
, filenames
in os
.walk(src_service_path
):
558 # each dir only cares about its direct children
559 rel_dirpath
= os
.path
.relpath(dirpath
, start
=src_service_path
)
560 dst_dirpath
= os
.path
.join(tempdir
, rel_dirpath
)
562 # create sub dirs and NTACL file
563 for dirname
in dirnames
:
564 src
= os
.path
.join(dirpath
, dirname
)
565 dst
= os
.path
.join(dst_dirpath
, dirname
)
566 # mkdir with metadata
567 smbd
.mkdir(dst
, session_info
, service
)
568 ntacl_sddl_str
= ntacls_helper
.getntacl(src
, session_info
, as_sddl
=True)
569 _create_ntacl_file(dst
, ntacl_sddl_str
)
571 # create files and NTACL file, then copy data
572 for filename
in filenames
:
573 src
= os
.path
.join(dirpath
, filename
)
574 dst
= os
.path
.join(dst_dirpath
, filename
)
575 # create an empty file with metadata
576 smbd
.create_file(dst
, session_info
, service
)
577 ntacl_sddl_str
= ntacls_helper
.getntacl(src
, session_info
, as_sddl
=True)
578 _create_ntacl_file(dst
, ntacl_sddl_str
)
581 with
open(src
, 'rb') as src_file
:
582 data
= src_file
.read()
583 with
open(dst
, 'wb') as dst_file
:
586 # add all files in tempdir to tarfile without a top folder
587 with tarfile
.open(name
=dest_tarfile_path
, mode
='w:gz') as tar
:
588 for name
in os
.listdir(tempdir
):
589 path
= os
.path
.join(tempdir
, name
)
590 tar
.add(path
, arcname
=name
)
592 shutil
.rmtree(tempdir
)
595 def backup_restore(src_tarfile_path
, dst_service_path
, samdb_conn
, smb_conf_path
):
597 Restore files and ntacls from a tarfile to a service
599 logger
= get_samba_logger()
600 service
= dst_service_path
.rstrip('/').rsplit('/', 1)[-1]
601 tempdir
= tempfile
.mkdtemp() # src files
603 dom_sid_str
= samdb_conn
.get_domain_sid()
604 dom_sid
= security
.dom_sid(dom_sid_str
)
606 ntacls_helper
= NtaclsHelper(service
, smb_conf_path
, dom_sid
)
607 session_info
= system_session_unix()
609 with tarfile
.open(src_tarfile_path
) as f
:
610 f
.extractall(path
=tempdir
)
611 # e.g.: /tmp/tmpRNystY/{dir1,dir1.NTACL,...file1,file1.NTACL}
613 for dirpath
, dirnames
, filenames
in os
.walk(tempdir
):
614 rel_dirpath
= os
.path
.relpath(dirpath
, start
=tempdir
)
615 dst_dirpath
= os
.path
.normpath(
616 os
.path
.join(dst_service_path
, rel_dirpath
))
618 for dirname
in dirnames
:
619 if not dirname
.endswith('.NTACL'):
620 src
= os
.path
.join(dirpath
, dirname
)
621 dst
= os
.path
.join(dst_dirpath
, dirname
)
622 if not os
.path
.isdir(dst
):
623 # dst must be absolute path for smbd API
624 smbd
.mkdir(dst
, session_info
, service
)
626 ntacl_sddl_str
= _read_ntacl_file(src
)
628 ntacls_helper
.setntacl(dst
, ntacl_sddl_str
, session_info
)
631 'Failed to restore ntacl for directory %s.' % dst
632 + ' Please check the permissions are correct')
634 for filename
in filenames
:
635 if not filename
.endswith('.NTACL'):
636 src
= os
.path
.join(dirpath
, filename
)
637 dst
= os
.path
.join(dst_dirpath
, filename
)
638 if not os
.path
.isfile(dst
):
639 # dst must be absolute path for smbd API
640 smbd
.create_file(dst
, session_info
, service
)
642 ntacl_sddl_str
= _read_ntacl_file(src
)
644 ntacls_helper
.setntacl(dst
, ntacl_sddl_str
, session_info
)
646 logger
.warning('Failed to restore ntacl for file %s.' % dst
647 + ' Please check the permissions are correct')
650 with
open(src
, 'rb') as src_file
:
651 data
= src_file
.read()
652 with
open(dst
, 'wb') as dst_file
:
655 shutil
.rmtree(tempdir
)