ctdb-tcp: Make error handling for outbound connection consistent
[Samba.git] / python / samba / ntacls.py
blob0dcf958f7273f3534992e0134596f6bbc1dbf6a6
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
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 | \
50 security.SECINFO_SACL
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'''
64 if backend is 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"))
71 return (None, None)
72 elif backend == "native":
73 return (None, None)
74 elif backend == "eadb":
75 if eadbfile is not None:
76 return (samba.posix_eadb, eadbfile)
77 else:
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)
82 else:
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)
86 else:
87 raise XattrBackendError("Invalid xattr backend choice %s" % backend)
90 def getdosinfo(lp, file):
91 try:
92 attribute = samba.xattr_native.wrap_getxattr(file,
93 xattr.XATTR_DOSATTRIB_NAME_S3)
94 except Exception:
95 return
97 return ndr_unpack(xattr.DOSATTRIB, attribute)
100 def getntacl(lp,
101 file,
102 session_info,
103 backend=None,
104 eadbfile=None,
105 direct_db_access=True,
106 service=None):
107 if direct_db_access:
108 (backend_obj, dbname) = checkset_backend(lp, backend, eadbfile)
109 if dbname is not None:
110 try:
111 attribute = backend_obj.wrap_getxattr(dbname, file,
112 xattr.XATTR_NTACL_NAME)
113 except Exception:
114 # FIXME: Don't catch all exceptions, just those related to opening
115 # xattrdb
116 print("Fail to open %s" % dbname)
117 attribute = samba.xattr_native.wrap_getxattr(file,
118 xattr.XATTR_NTACL_NAME)
119 else:
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:
124 return ntacl.info
125 elif ntacl.version == 2:
126 return ntacl.info.sd
127 elif ntacl.version == 3:
128 return ntacl.info.sd
129 elif ntacl.version == 4:
130 return ntacl.info.sd
131 else:
132 return smbd.get_nt_acl(file,
133 SECURITY_SECINFO_FLAGS,
134 session_info,
135 service=service)
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.
145 Args:
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
152 Note:
153 Get `session_info` with `samba.auth.user_session`, do not use the
154 `admin_session` api.
156 Returns:
157 None
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):
164 sid = domsid
165 domsid = str(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):
171 sd = sddl
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
189 sd2 = sd
190 sd2.owner_sid = administrator
192 smbd.set_nt_acl(
193 file, SECURITY_SECINFO_FLAGS, sd2,
194 session_info,
195 service=service)
197 # and then set an NTVFS ACL (which does not set the posix ACL) to pretend the owner really was set
198 use_ntvfs = True
199 else:
200 raise XattrBackendError("Unable to find UID for domain administrator %s, got id %d of type %d" % (administrator, admin_id, admin_type))
201 else:
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
207 os.chown(file, 0, 0)
208 smbd.set_nt_acl(
209 file,
210 security.SECINFO_GROUP |
211 security.SECINFO_DACL |
212 security.SECINFO_SACL,
214 session_info,
215 service=service)
217 if use_ntvfs:
218 (backend_obj, dbname) = checkset_backend(lp, backend, eadbfile)
219 ntacl = xattr.NTACL()
220 ntacl.version = 1
221 ntacl.info = sd
222 if dbname is not None:
223 try:
224 backend_obj.wrap_setxattr(dbname,
225 file, xattr.XATTR_NTACL_NAME, ndr_pack(ntacl))
226 except Exception:
227 # FIXME: Don't catch all exceptions, just those related to opening
228 # xattrdb
229 print("Fail to open %s" % dbname)
230 samba.xattr_native.wrap_setxattr(file, xattr.XATTR_NTACL_NAME,
231 ndr_pack(ntacl))
232 else:
233 samba.xattr_native.wrap_setxattr(file, xattr.XATTR_NTACL_NAME,
234 ndr_pack(ntacl))
235 else:
236 smbd.set_nt_acl(
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
267 DELETE = 0x00010000
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
293 return filemask
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
309 aces = ref.dacl.aces
310 for i in range(0, len(aces)):
311 ace = aces[i]
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)
319 fdescr.dacl_add(ace)
321 if not as_sddl:
322 return fdescr
324 return fdescr.as_sddl(sid)
327 class SMBHelper:
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):
363 Join path with '\\'
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)
381 else: # a file
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:
392 'attrib': 16,
393 'mtime': 1528848309,
394 'name': 'dir1',
395 'short_name': 'dir1',
396 'size': 0L
397 }, {
398 'attrib': 32,
399 'mtime': 1528848309,
400 'name': 'file0.txt',
401 'short_name': 'file0.txt',
402 'size': 10L
406 tree = {}
407 for item in self.list(smb_path):
408 name = item['name']
409 fullname = self.join(smb_path, name)
410 if self.is_dir(item['attrib']):
411 tree[name] = self.get_tree(smb_path=fullname)
412 else:
413 tree[name] = self.loadfile(fullname)
414 return tree
416 def get_ntacls(self, smb_path=''):
418 Get ntacl for each file and dir via smb conn
420 ntacls = {}
421 for item in self.list(smb_path):
422 name = item['name']
423 fullname = self.join(smb_path, name)
424 if self.is_dir(item['attrib']):
425 ntacls.update(self.get_ntacls(smb_path=fullname))
426 else:
427 ntacl_sd = self.get_acl(fullname)
428 ntacls[fullname] = ntacl_sd.as_sddl(self.dom_sid)
429 return ntacls
431 def delete_tree(self):
432 for item in self.list():
433 name = item['name']
434 if self.is_dir(item['attrib']):
435 self.smb_conn.deltree(name)
436 else:
437 self.smb_conn.unlink(name)
440 class NtaclsHelper:
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
456 ntacl_sd = getntacl(
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):
478 return None
480 with open(ntacl_file, 'r') as f:
481 return f.read()
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()
506 r_dirs = [remotedir]
507 l_dirs = [localdir]
509 while r_dirs:
510 r_dir = r_dirs.pop()
511 l_dir = l_dirs.pop()
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)
520 os.mkdir(l_name)
521 else:
522 data = smb_helper.loadfile(r_name)
523 with open(l_name, 'wb') as f:
524 f.write(data)
526 # get ntacl for this entry and save alongside
527 try:
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' % \
532 (r_name, e.args[1]))
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)
580 # now put data in
581 with open(src, 'rb') as src_file:
582 data = src_file.read()
583 with open(dst, 'wb') as dst_file:
584 dst_file.write(data)
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)
627 if ntacl_sddl_str:
628 ntacls_helper.setntacl(dst, ntacl_sddl_str, session_info)
629 else:
630 logger.warning(
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)
643 if ntacl_sddl_str:
644 ntacls_helper.setntacl(dst, ntacl_sddl_str, session_info)
645 else:
646 logger.warning('Failed to restore ntacl for file %s.' % dst
647 + ' Please check the permissions are correct')
649 # now put data in
650 with open(src, 'rb') as src_file:
651 data = src_file.read()
652 with open(dst, 'wb') as dst_file:
653 dst_file.write(data)
655 shutil.rmtree(tempdir)