gpo: Ensure all files are retrieved in fetch
[ambi/samba-autobuild/.git] / python / samba / netcmd / gpo.py
1 # implement samba_tool gpo commands
2 #
3 # Copyright Andrew Tridgell 2010
4 # Copyright Amitay Isaacs 2011-2012 <amitay@gmail.com>
5 #
6 # based on C implementation by Guenther Deschner and Wilco Baan Hofman
7 #
8 # This program is free software; you can redistribute it and/or modify
9 # it under the terms of the GNU General Public License as published by
10 # the Free Software Foundation; either version 3 of the License, or
11 # (at your option) any later version.
12 #
13 # This program is distributed in the hope that it will be useful,
14 # but WITHOUT ANY WARRANTY; without even the implied warranty of
15 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16 # GNU General Public License for more details.
17 #
18 # You should have received a copy of the GNU General Public License
19 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
20 #
21
22 import os
23 import samba.getopt as options
24 import ldb
25
26 from samba.auth import system_session
27 from samba.netcmd import (
28     Command,
29     CommandError,
30     Option,
31     SuperCommand,
32     )
33 from samba.samdb import SamDB
34 from samba import dsdb
35 from samba.dcerpc import security
36 from samba.ndr import ndr_unpack
37 import samba.security
38 import samba.auth
39 from samba.auth import AUTH_SESSION_INFO_DEFAULT_GROUPS, AUTH_SESSION_INFO_AUTHENTICATED, AUTH_SESSION_INFO_SIMPLE_PRIVILEGES
40 from samba.netcmd.common import netcmd_finddc
41 from samba import policy
42 from samba import smb
43 import uuid
44 from samba.ntacls import dsacl2fsacl
45 from samba.dcerpc import nbt
46 from samba.net import Net
47
48
49 def samdb_connect(ctx):
50     '''make a ldap connection to the server'''
51     try:
52         ctx.samdb = SamDB(url=ctx.url,
53                           session_info=system_session(),
54                           credentials=ctx.creds, lp=ctx.lp)
55     except Exception as e:
56         raise CommandError("LDAP connection to %s failed " % ctx.url, e)
57
58
59 def attr_default(msg, attrname, default):
60     '''get an attribute from a ldap msg with a default'''
61     if attrname in msg:
62         return msg[attrname][0]
63     return default
64
65
66 def gpo_flags_string(value):
67     '''return gpo flags string'''
68     flags = policy.get_gpo_flags(value)
69     if not flags:
70         ret = 'NONE'
71     else:
72         ret = ' '.join(flags)
73     return ret
74
75
76 def gplink_options_string(value):
77     '''return gplink options string'''
78     options = policy.get_gplink_options(value)
79     if not options:
80         ret = 'NONE'
81     else:
82         ret = ' '.join(options)
83     return ret
84
85
86 def parse_gplink(gplink):
87     '''parse a gPLink into an array of dn and options'''
88     ret = []
89     a = gplink.split(']')
90     for g in a:
91         if not g:
92             continue
93         d = g.split(';')
94         if len(d) != 2 or not d[0].startswith("[LDAP://"):
95             raise RuntimeError("Badly formed gPLink '%s'" % g)
96         ret.append({ 'dn' : d[0][8:], 'options' : int(d[1])})
97     return ret
98
99
100 def encode_gplink(gplist):
101     '''Encode an array of dn and options into gPLink string'''
102     ret = ''
103     for g in gplist:
104         ret += "[LDAP://%s;%d]" % (g['dn'], g['options'])
105     return ret
106
107
108 def dc_url(lp, creds, url=None, dc=None):
109     '''If URL is not specified, return URL for writable DC.
110     If dc is provided, use that to construct ldap URL'''
111
112     if url is None:
113         if dc is None:
114             try:
115                 dc = netcmd_finddc(lp, creds)
116             except Exception as e:
117                 raise RuntimeError("Could not find a DC for domain", e)
118         url = 'ldap://' + dc
119     return url
120
121
122 def get_gpo_dn(samdb, gpo):
123     '''Construct the DN for gpo'''
124
125     dn = samdb.get_default_basedn()
126     dn.add_child(ldb.Dn(samdb, "CN=Policies,CN=System"))
127     dn.add_child(ldb.Dn(samdb, "CN=%s" % gpo))
128     return dn
129
130
131 def get_gpo_info(samdb, gpo=None, displayname=None, dn=None,
132                  sd_flags=security.SECINFO_OWNER|security.SECINFO_GROUP|security.SECINFO_DACL|security.SECINFO_SACL):
133     '''Get GPO information using gpo, displayname or dn'''
134
135     policies_dn = samdb.get_default_basedn()
136     policies_dn.add_child(ldb.Dn(samdb, "CN=Policies,CN=System"))
137
138     base_dn = policies_dn
139     search_expr = "(objectClass=groupPolicyContainer)"
140     search_scope = ldb.SCOPE_ONELEVEL
141
142     if gpo is not None:
143         search_expr = "(&(objectClass=groupPolicyContainer)(name=%s))" % ldb.binary_encode(gpo)
144
145     if displayname is not None:
146         search_expr = "(&(objectClass=groupPolicyContainer)(displayname=%s))" % ldb.binary_encode(displayname)
147
148     if dn is not None:
149         base_dn = dn
150         search_scope = ldb.SCOPE_BASE
151
152     try:
153         msg = samdb.search(base=base_dn, scope=search_scope,
154                             expression=search_expr,
155                             attrs=['nTSecurityDescriptor',
156                                     'versionNumber',
157                                     'flags',
158                                     'name',
159                                     'displayName',
160                                     'gPCFileSysPath'],
161                             controls=['sd_flags:1:%d' % sd_flags])
162     except Exception as e:
163         if gpo is not None:
164             mesg = "Cannot get information for GPO %s" % gpo
165         else:
166             mesg = "Cannot get information for GPOs"
167         raise CommandError(mesg, e)
168
169     return msg
170
171
172 def get_gpo_containers(samdb, gpo):
173     '''lists dn of containers for a GPO'''
174
175     search_expr = "(&(objectClass=*)(gPLink=*%s*))" % gpo
176     try:
177         msg = samdb.search(expression=search_expr, attrs=['gPLink'])
178     except Exception as e:
179         raise CommandError("Could not find container(s) with GPO %s" % gpo, e)
180
181     return msg
182
183
184 def del_gpo_link(samdb, container_dn, gpo):
185     '''delete GPO link for the container'''
186     # Check if valid Container DN and get existing GPlinks
187     try:
188         msg = samdb.search(base=container_dn, scope=ldb.SCOPE_BASE,
189                             expression="(objectClass=*)",
190                             attrs=['gPLink'])[0]
191     except Exception as e:
192         raise CommandError("Container '%s' does not exist" % container_dn, e)
193
194     found = False
195     gpo_dn = str(get_gpo_dn(samdb, gpo))
196     if 'gPLink' in msg:
197         gplist = parse_gplink(msg['gPLink'][0])
198         for g in gplist:
199             if g['dn'].lower() == gpo_dn.lower():
200                 gplist.remove(g)
201                 found = True
202                 break
203     else:
204         raise CommandError("No GPO(s) linked to this container")
205
206     if not found:
207         raise CommandError("GPO '%s' not linked to this container" % gpo)
208
209     m = ldb.Message()
210     m.dn = container_dn
211     if gplist:
212         gplink_str = encode_gplink(gplist)
213         m['r0'] = ldb.MessageElement(gplink_str, ldb.FLAG_MOD_REPLACE, 'gPLink')
214     else:
215         m['d0'] = ldb.MessageElement(msg['gPLink'][0], ldb.FLAG_MOD_DELETE, 'gPLink')
216     try:
217         samdb.modify(m)
218     except Exception as e:
219         raise CommandError("Error removing GPO from container", e)
220
221
222 def parse_unc(unc):
223     '''Parse UNC string into a hostname, a service, and a filepath'''
224     if unc.startswith('\\\\') and unc.startswith('//'):
225         raise ValueError("UNC doesn't start with \\\\ or //")
226     tmp = unc[2:].split('/', 2)
227     if len(tmp) == 3:
228         return tmp
229     tmp = unc[2:].split('\\', 2)
230     if len(tmp) == 3:
231         return tmp
232     raise ValueError("Invalid UNC string: %s" % unc)
233
234 attr_flags = smb.FILE_ATTRIBUTE_SYSTEM | \
235              smb.FILE_ATTRIBUTE_DIRECTORY | \
236              smb.FILE_ATTRIBUTE_ARCHIVE | \
237              smb.FILE_ATTRIBUTE_HIDDEN
238
239 def copy_directory_remote_to_local(conn, remotedir, localdir):
240     if not os.path.isdir(localdir):
241         os.mkdir(localdir)
242     r_dirs = [ remotedir ]
243     l_dirs = [ localdir ]
244     while r_dirs:
245         r_dir = r_dirs.pop()
246         l_dir = l_dirs.pop()
247
248         dirlist = conn.list(r_dir, attribs=attr_flags)
249         for e in dirlist:
250             r_name = r_dir + '\\' + e['name']
251             l_name = os.path.join(l_dir, e['name'])
252
253             if e['attrib'] & smb.FILE_ATTRIBUTE_DIRECTORY:
254                 r_dirs.append(r_name)
255                 l_dirs.append(l_name)
256                 os.mkdir(l_name)
257             else:
258                 data = conn.loadfile(r_name)
259                 open(l_name, 'w').write(data)
260
261
262 def copy_directory_local_to_remote(conn, localdir, remotedir):
263     if not conn.chkpath(remotedir):
264         conn.mkdir(remotedir)
265     l_dirs = [ localdir ]
266     r_dirs = [ remotedir ]
267     while l_dirs:
268         l_dir = l_dirs.pop()
269         r_dir = r_dirs.pop()
270
271         dirlist = os.listdir(l_dir)
272         for e in dirlist:
273             l_name = os.path.join(l_dir, e)
274             r_name = r_dir + '\\' + e
275
276             if os.path.isdir(l_name):
277                 l_dirs.append(l_name)
278                 r_dirs.append(r_name)
279                 conn.mkdir(r_name)
280             else:
281                 data = open(l_name, 'r').read()
282                 conn.savefile(r_name, data)
283
284
285 def create_directory_hier(conn, remotedir):
286     elems = remotedir.replace('/', '\\').split('\\')
287     path = ""
288     for e in elems:
289         path = path + '\\' + e
290         if not conn.chkpath(path):
291             conn.mkdir(path)
292
293
294 class cmd_listall(Command):
295     """List all GPOs."""
296
297     synopsis = "%prog [options]"
298
299     takes_optiongroups = {
300         "sambaopts": options.SambaOptions,
301         "versionopts": options.VersionOptions,
302         "credopts": options.CredentialsOptions,
303     }
304
305     takes_options = [
306         Option("-H", "--URL", help="LDB URL for database or target server", type=str,
307                metavar="URL", dest="H")
308         ]
309
310     def run(self, H=None, sambaopts=None, credopts=None, versionopts=None):
311
312         self.lp = sambaopts.get_loadparm()
313         self.creds = credopts.get_credentials(self.lp, fallback_machine=True)
314
315         self.url = dc_url(self.lp, self.creds, H)
316
317         samdb_connect(self)
318
319         msg = get_gpo_info(self.samdb, None)
320
321         for m in msg:
322             self.outf.write("GPO          : %s\n" % m['name'][0])
323             self.outf.write("display name : %s\n" % m['displayName'][0])
324             self.outf.write("path         : %s\n" % m['gPCFileSysPath'][0])
325             self.outf.write("dn           : %s\n" % m.dn)
326             self.outf.write("version      : %s\n" % attr_default(m, 'versionNumber', '0'))
327             self.outf.write("flags        : %s\n" % gpo_flags_string(int(attr_default(m, 'flags', 0))))
328             self.outf.write("\n")
329
330
331 class cmd_list(Command):
332     """List GPOs for an account."""
333
334     synopsis = "%prog <username> [options]"
335
336     takes_args = ['username']
337     takes_optiongroups = {
338         "sambaopts": options.SambaOptions,
339         "versionopts": options.VersionOptions,
340         "credopts": options.CredentialsOptions,
341     }
342
343     takes_options = [
344         Option("-H", "--URL", help="LDB URL for database or target server",
345             type=str, metavar="URL", dest="H")
346         ]
347
348     def run(self, username, H=None, sambaopts=None, credopts=None, versionopts=None):
349
350         self.lp = sambaopts.get_loadparm()
351         self.creds = credopts.get_credentials(self.lp, fallback_machine=True)
352
353         self.url = dc_url(self.lp, self.creds, H)
354
355         samdb_connect(self)
356
357         try:
358             msg = self.samdb.search(expression='(&(|(samAccountName=%s)(samAccountName=%s$))(objectClass=User))' %
359                                                 (ldb.binary_encode(username),ldb.binary_encode(username)))
360             user_dn = msg[0].dn
361         except Exception:
362             raise CommandError("Failed to find account %s" % username)
363
364         # check if its a computer account
365         try:
366             msg = self.samdb.search(base=user_dn, scope=ldb.SCOPE_BASE, attrs=['objectClass'])[0]
367             is_computer = 'computer' in msg['objectClass']
368         except Exception:
369             raise CommandError("Failed to find objectClass for user %s" % username)
370
371         session_info_flags = ( AUTH_SESSION_INFO_DEFAULT_GROUPS |
372                                AUTH_SESSION_INFO_AUTHENTICATED )
373
374         # When connecting to a remote server, don't look up the local privilege DB
375         if self.url is not None and self.url.startswith('ldap'):
376             session_info_flags |= AUTH_SESSION_INFO_SIMPLE_PRIVILEGES
377
378         session = samba.auth.user_session(self.samdb, lp_ctx=self.lp, dn=user_dn,
379                                           session_info_flags=session_info_flags)
380
381         token = session.security_token
382
383         gpos = []
384
385         inherit = True
386         dn = ldb.Dn(self.samdb, str(user_dn)).parent()
387         while True:
388             msg = self.samdb.search(base=dn, scope=ldb.SCOPE_BASE, attrs=['gPLink', 'gPOptions'])[0]
389             if 'gPLink' in msg:
390                 glist = parse_gplink(msg['gPLink'][0])
391                 for g in glist:
392                     if not inherit and not (g['options'] & dsdb.GPLINK_OPT_ENFORCE):
393                         continue
394                     if g['options'] & dsdb.GPLINK_OPT_DISABLE:
395                         continue
396
397                     try:
398                         sd_flags=security.SECINFO_OWNER|security.SECINFO_GROUP|security.SECINFO_DACL
399                         gmsg = self.samdb.search(base=g['dn'], scope=ldb.SCOPE_BASE,
400                                                  attrs=['name', 'displayName', 'flags',
401                                                         'nTSecurityDescriptor'],
402                                                  controls=['sd_flags:1:%d' % sd_flags])
403                         secdesc_ndr = gmsg[0]['nTSecurityDescriptor'][0]
404                         secdesc = ndr_unpack(security.descriptor, secdesc_ndr)
405                     except Exception:
406                         self.outf.write("Failed to fetch gpo object with nTSecurityDescriptor %s\n" %
407                             g['dn'])
408                         continue
409
410                     try:
411                         samba.security.access_check(secdesc, token,
412                                                     security.SEC_STD_READ_CONTROL |
413                                                     security.SEC_ADS_LIST |
414                                                     security.SEC_ADS_READ_PROP)
415                     except RuntimeError:
416                         self.outf.write("Failed access check on %s\n" % msg.dn)
417                         continue
418
419                     # check the flags on the GPO
420                     flags = int(attr_default(gmsg[0], 'flags', 0))
421                     if is_computer and (flags & dsdb.GPO_FLAG_MACHINE_DISABLE):
422                         continue
423                     if not is_computer and (flags & dsdb.GPO_FLAG_USER_DISABLE):
424                         continue
425                     gpos.append((gmsg[0]['displayName'][0], gmsg[0]['name'][0]))
426
427             # check if this blocks inheritance
428             gpoptions = int(attr_default(msg, 'gPOptions', 0))
429             if gpoptions & dsdb.GPO_BLOCK_INHERITANCE:
430                 inherit = False
431
432             if dn == self.samdb.get_default_basedn():
433                 break
434             dn = dn.parent()
435
436         if is_computer:
437             msg_str = 'computer'
438         else:
439             msg_str = 'user'
440
441         self.outf.write("GPOs for %s %s\n" % (msg_str, username))
442         for g in gpos:
443             self.outf.write("    %s %s\n" % (g[0], g[1]))
444
445
446 class cmd_show(Command):
447     """Show information for a GPO."""
448
449     synopsis = "%prog <gpo> [options]"
450
451     takes_optiongroups = {
452         "sambaopts": options.SambaOptions,
453         "versionopts": options.VersionOptions,
454         "credopts": options.CredentialsOptions,
455     }
456
457     takes_args = ['gpo']
458
459     takes_options = [
460         Option("-H", help="LDB URL for database or target server", type=str)
461         ]
462
463     def run(self, gpo, H=None, sambaopts=None, credopts=None, versionopts=None):
464
465         self.lp = sambaopts.get_loadparm()
466         self.creds = credopts.get_credentials(self.lp, fallback_machine=True)
467
468         self.url = dc_url(self.lp, self.creds, H)
469
470         samdb_connect(self)
471
472         try:
473             msg = get_gpo_info(self.samdb, gpo)[0]
474         except Exception:
475             raise CommandError("GPO '%s' does not exist" % gpo)
476
477         try:
478             secdesc_ndr = msg['nTSecurityDescriptor'][0]
479             secdesc = ndr_unpack(security.descriptor, secdesc_ndr)
480             secdesc_sddl = secdesc.as_sddl()
481         except Exception:
482             secdesc_sddl = "<hidden>"
483
484         self.outf.write("GPO          : %s\n" % msg['name'][0])
485         self.outf.write("display name : %s\n" % msg['displayName'][0])
486         self.outf.write("path         : %s\n" % msg['gPCFileSysPath'][0])
487         self.outf.write("dn           : %s\n" % msg.dn)
488         self.outf.write("version      : %s\n" % attr_default(msg, 'versionNumber', '0'))
489         self.outf.write("flags        : %s\n" % gpo_flags_string(int(attr_default(msg, 'flags', 0))))
490         self.outf.write("ACL          : %s\n" % secdesc_sddl)
491         self.outf.write("\n")
492
493
494 class cmd_getlink(Command):
495     """List GPO Links for a container."""
496
497     synopsis = "%prog <container_dn> [options]"
498
499     takes_optiongroups = {
500         "sambaopts": options.SambaOptions,
501         "versionopts": options.VersionOptions,
502         "credopts": options.CredentialsOptions,
503     }
504
505     takes_args = ['container_dn']
506
507     takes_options = [
508         Option("-H", help="LDB URL for database or target server", type=str)
509         ]
510
511     def run(self, container_dn, H=None, sambaopts=None, credopts=None,
512                 versionopts=None):
513
514         self.lp = sambaopts.get_loadparm()
515         self.creds = credopts.get_credentials(self.lp, fallback_machine=True)
516
517         self.url = dc_url(self.lp, self.creds, H)
518
519         samdb_connect(self)
520
521         try:
522             msg = self.samdb.search(base=container_dn, scope=ldb.SCOPE_BASE,
523                                     expression="(objectClass=*)",
524                                     attrs=['gPLink'])[0]
525         except Exception:
526             raise CommandError("Container '%s' does not exist" % container_dn)
527
528         if msg['gPLink']:
529             self.outf.write("GPO(s) linked to DN %s\n" % container_dn)
530             gplist = parse_gplink(msg['gPLink'][0])
531             for g in gplist:
532                 msg = get_gpo_info(self.samdb, dn=g['dn'])
533                 self.outf.write("    GPO     : %s\n" % msg[0]['name'][0])
534                 self.outf.write("    Name    : %s\n" % msg[0]['displayName'][0])
535                 self.outf.write("    Options : %s\n" % gplink_options_string(g['options']))
536                 self.outf.write("\n")
537         else:
538             self.outf.write("No GPO(s) linked to DN=%s\n" % container_dn)
539
540
541 class cmd_setlink(Command):
542     """Add or update a GPO link to a container."""
543
544     synopsis = "%prog <container_dn> <gpo> [options]"
545
546     takes_optiongroups = {
547         "sambaopts": options.SambaOptions,
548         "versionopts": options.VersionOptions,
549         "credopts": options.CredentialsOptions,
550     }
551
552     takes_args = ['container_dn', 'gpo']
553
554     takes_options = [
555         Option("-H", help="LDB URL for database or target server", type=str),
556         Option("--disable", dest="disabled", default=False, action='store_true',
557             help="Disable policy"),
558         Option("--enforce", dest="enforced", default=False, action='store_true',
559             help="Enforce policy")
560         ]
561
562     def run(self, container_dn, gpo, H=None, disabled=False, enforced=False,
563                 sambaopts=None, credopts=None, versionopts=None):
564
565         self.lp = sambaopts.get_loadparm()
566         self.creds = credopts.get_credentials(self.lp, fallback_machine=True)
567
568         self.url = dc_url(self.lp, self.creds, H)
569
570         samdb_connect(self)
571
572         gplink_options = 0
573         if disabled:
574             gplink_options |= dsdb.GPLINK_OPT_DISABLE
575         if enforced:
576             gplink_options |= dsdb.GPLINK_OPT_ENFORCE
577
578         # Check if valid GPO DN
579         try:
580             msg = get_gpo_info(self.samdb, gpo=gpo)[0]
581         except Exception:
582             raise CommandError("GPO '%s' does not exist" % gpo)
583         gpo_dn = str(get_gpo_dn(self.samdb, gpo))
584
585         # Check if valid Container DN
586         try:
587             msg = self.samdb.search(base=container_dn, scope=ldb.SCOPE_BASE,
588                                     expression="(objectClass=*)",
589                                     attrs=['gPLink'])[0]
590         except Exception:
591             raise CommandError("Container '%s' does not exist" % container_dn)
592
593         # Update existing GPlinks or Add new one
594         existing_gplink = False
595         if 'gPLink' in msg:
596             gplist = parse_gplink(msg['gPLink'][0])
597             existing_gplink = True
598             found = False
599             for g in gplist:
600                 if g['dn'].lower() == gpo_dn.lower():
601                     g['options'] = gplink_options
602                     found = True
603                     break
604             if found:
605                 raise CommandError("GPO '%s' already linked to this container" % gpo)
606             else:
607                 gplist.insert(0, { 'dn' : gpo_dn, 'options' : gplink_options })
608         else:
609             gplist = []
610             gplist.append({ 'dn' : gpo_dn, 'options' : gplink_options })
611
612         gplink_str = encode_gplink(gplist)
613
614         m = ldb.Message()
615         m.dn = ldb.Dn(self.samdb, container_dn)
616
617         if existing_gplink:
618             m['new_value'] = ldb.MessageElement(gplink_str, ldb.FLAG_MOD_REPLACE, 'gPLink')
619         else:
620             m['new_value'] = ldb.MessageElement(gplink_str, ldb.FLAG_MOD_ADD, 'gPLink')
621
622         try:
623             self.samdb.modify(m)
624         except Exception as e:
625             raise CommandError("Error adding GPO Link", e)
626
627         self.outf.write("Added/Updated GPO link\n")
628         cmd_getlink().run(container_dn, H, sambaopts, credopts, versionopts)
629
630
631 class cmd_dellink(Command):
632     """Delete GPO link from a container."""
633
634     synopsis = "%prog <container_dn> <gpo> [options]"
635
636     takes_optiongroups = {
637         "sambaopts": options.SambaOptions,
638         "versionopts": options.VersionOptions,
639         "credopts": options.CredentialsOptions,
640     }
641
642     takes_args = ['container', 'gpo']
643
644     takes_options = [
645         Option("-H", help="LDB URL for database or target server", type=str),
646         ]
647
648     def run(self, container, gpo, H=None, sambaopts=None, credopts=None,
649                 versionopts=None):
650
651         self.lp = sambaopts.get_loadparm()
652         self.creds = credopts.get_credentials(self.lp, fallback_machine=True)
653
654         self.url = dc_url(self.lp, self.creds, H)
655
656         samdb_connect(self)
657
658         # Check if valid GPO
659         try:
660             get_gpo_info(self.samdb, gpo=gpo)[0]
661         except Exception:
662             raise CommandError("GPO '%s' does not exist" % gpo)
663
664         container_dn = ldb.Dn(self.samdb, container)
665         del_gpo_link(self.samdb, container_dn, gpo)
666         self.outf.write("Deleted GPO link.\n")
667         cmd_getlink().run(container_dn, H, sambaopts, credopts, versionopts)
668
669
670 class cmd_listcontainers(Command):
671     """List all linked containers for a GPO."""
672
673     synopsis = "%prog <gpo> [options]"
674
675     takes_optiongroups = {
676         "sambaopts": options.SambaOptions,
677         "versionopts": options.VersionOptions,
678         "credopts": options.CredentialsOptions,
679     }
680
681     takes_args = ['gpo']
682
683     takes_options = [
684         Option("-H", help="LDB URL for database or target server", type=str)
685         ]
686
687     def run(self, gpo, H=None, sambaopts=None, credopts=None,
688                 versionopts=None):
689
690         self.lp = sambaopts.get_loadparm()
691         self.creds = credopts.get_credentials(self.lp, fallback_machine=True)
692
693         self.url = dc_url(self.lp, self.creds, H)
694
695         samdb_connect(self)
696
697         msg = get_gpo_containers(self.samdb, gpo)
698         if len(msg):
699             self.outf.write("Container(s) using GPO %s\n" % gpo)
700             for m in msg:
701                 self.outf.write("    DN: %s\n" % m['dn'])
702         else:
703             self.outf.write("No Containers using GPO %s\n" % gpo)
704
705
706 class cmd_getinheritance(Command):
707     """Get inheritance flag for a container."""
708
709     synopsis = "%prog <container_dn> [options]"
710
711     takes_optiongroups = {
712         "sambaopts": options.SambaOptions,
713         "versionopts": options.VersionOptions,
714         "credopts": options.CredentialsOptions,
715     }
716
717     takes_args = ['container_dn']
718
719     takes_options = [
720         Option("-H", help="LDB URL for database or target server", type=str)
721         ]
722
723     def run(self, container_dn, H=None, sambaopts=None, credopts=None,
724                 versionopts=None):
725
726         self.lp = sambaopts.get_loadparm()
727         self.creds = credopts.get_credentials(self.lp, fallback_machine=True)
728
729         self.url = dc_url(self.lp, self.creds, H)
730
731         samdb_connect(self)
732
733         try:
734             msg = self.samdb.search(base=container_dn, scope=ldb.SCOPE_BASE,
735                                     expression="(objectClass=*)",
736                                     attrs=['gPOptions'])[0]
737         except Exception:
738             raise CommandError("Container '%s' does not exist" % container_dn)
739
740         inheritance = 0
741         if 'gPOptions' in msg:
742             inheritance = int(msg['gPOptions'][0])
743
744         if inheritance == dsdb.GPO_BLOCK_INHERITANCE:
745             self.outf.write("Container has GPO_BLOCK_INHERITANCE\n")
746         else:
747             self.outf.write("Container has GPO_INHERIT\n")
748
749
750 class cmd_setinheritance(Command):
751     """Set inheritance flag on a container."""
752
753     synopsis = "%prog <container_dn> <block|inherit> [options]"
754
755     takes_optiongroups = {
756         "sambaopts": options.SambaOptions,
757         "versionopts": options.VersionOptions,
758         "credopts": options.CredentialsOptions,
759     }
760
761     takes_args = [ 'container_dn', 'inherit_state' ]
762
763     takes_options = [
764         Option("-H", help="LDB URL for database or target server", type=str)
765         ]
766
767     def run(self, container_dn, inherit_state, H=None, sambaopts=None, credopts=None,
768                 versionopts=None):
769
770         if inherit_state.lower() == 'block':
771             inheritance = dsdb.GPO_BLOCK_INHERITANCE
772         elif inherit_state.lower() == 'inherit':
773             inheritance = dsdb.GPO_INHERIT
774         else:
775             raise CommandError("Unknown inheritance state (%s)" % inherit_state)
776
777         self.lp = sambaopts.get_loadparm()
778         self.creds = credopts.get_credentials(self.lp, fallback_machine=True)
779
780         self.url = dc_url(self.lp, self.creds, H)
781
782         samdb_connect(self)
783         try:
784             msg = self.samdb.search(base=container_dn, scope=ldb.SCOPE_BASE,
785                                     expression="(objectClass=*)",
786                                     attrs=['gPOptions'])[0]
787         except Exception:
788             raise CommandError("Container '%s' does not exist" % container_dn)
789
790         m = ldb.Message()
791         m.dn = ldb.Dn(self.samdb, container_dn)
792
793         if 'gPOptions' in msg:
794             m['new_value'] = ldb.MessageElement(str(inheritance), ldb.FLAG_MOD_REPLACE, 'gPOptions')
795         else:
796             m['new_value'] = ldb.MessageElement(str(inheritance), ldb.FLAG_MOD_ADD, 'gPOptions')
797
798         try:
799             self.samdb.modify(m)
800         except Exception as e:
801             raise CommandError("Error setting inheritance state %s" % inherit_state, e)
802
803
804 class cmd_fetch(Command):
805     """Download a GPO."""
806
807     synopsis = "%prog <gpo> [options]"
808
809     takes_optiongroups = {
810         "sambaopts": options.SambaOptions,
811         "versionopts": options.VersionOptions,
812         "credopts": options.CredentialsOptions,
813     }
814
815     takes_args = ['gpo']
816
817     takes_options = [
818         Option("-H", help="LDB URL for database or target server", type=str),
819         Option("--tmpdir", help="Temporary directory for copying policy files", type=str)
820         ]
821
822     def run(self, gpo, H=None, tmpdir=None, sambaopts=None, credopts=None, versionopts=None):
823
824         self.lp = sambaopts.get_loadparm()
825         self.creds = credopts.get_credentials(self.lp, fallback_machine=True)
826
827         # We need to know writable DC to setup SMB connection
828         if H and H.startswith('ldap://'):
829             dc_hostname = H[7:]
830             self.url = H
831         else:
832             dc_hostname = netcmd_finddc(self.lp, self.creds)
833             self.url = dc_url(self.lp, self.creds, dc=dc_hostname)
834
835         samdb_connect(self)
836         try:
837             msg = get_gpo_info(self.samdb, gpo)[0]
838         except Exception:
839             raise CommandError("GPO '%s' does not exist" % gpo)
840
841         # verify UNC path
842         unc = msg['gPCFileSysPath'][0]
843         try:
844             [dom_name, service, sharepath] = parse_unc(unc)
845         except ValueError:
846             raise CommandError("Invalid GPO path (%s)" % unc)
847
848         # SMB connect to DC
849         try:
850             conn = smb.SMB(dc_hostname, service, lp=self.lp, creds=self.creds)
851         except Exception:
852             raise CommandError("Error connecting to '%s' using SMB" % dc_hostname)
853
854         # Copy GPT
855         if tmpdir is None:
856             tmpdir = "/tmp"
857         if not os.path.isdir(tmpdir):
858             raise CommandError("Temoprary directory '%s' does not exist" % tmpdir)
859
860         localdir = os.path.join(tmpdir, "policy")
861         if not os.path.isdir(localdir):
862             os.mkdir(localdir)
863
864         gpodir = os.path.join(localdir, gpo)
865         if os.path.isdir(gpodir):
866             raise CommandError("GPO directory '%s' already exists, refusing to overwrite" % gpodir)
867
868         try:
869             os.mkdir(gpodir)
870             copy_directory_remote_to_local(conn, sharepath, gpodir)
871         except Exception as e:
872             # FIXME: Catch more specific exception
873             raise CommandError("Error copying GPO from DC", e)
874         self.outf.write('GPO copied to %s\n' % gpodir)
875
876
877 class cmd_create(Command):
878     """Create an empty GPO."""
879
880     synopsis = "%prog <displayname> [options]"
881
882     takes_optiongroups = {
883         "sambaopts": options.SambaOptions,
884         "versionopts": options.VersionOptions,
885         "credopts": options.CredentialsOptions,
886     }
887
888     takes_args = ['displayname']
889
890     takes_options = [
891         Option("-H", help="LDB URL for database or target server", type=str),
892         Option("--tmpdir", help="Temporary directory for copying policy files", type=str)
893         ]
894
895     def run(self, displayname, H=None, tmpdir=None, sambaopts=None, credopts=None,
896             versionopts=None):
897
898         self.lp = sambaopts.get_loadparm()
899         self.creds = credopts.get_credentials(self.lp, fallback_machine=True)
900
901         net = Net(creds=self.creds, lp=self.lp)
902
903         # We need to know writable DC to setup SMB connection
904         if H and H.startswith('ldap://'):
905             dc_hostname = H[7:]
906             self.url = H
907             flags = (nbt.NBT_SERVER_LDAP |
908                      nbt.NBT_SERVER_DS |
909                      nbt.NBT_SERVER_WRITABLE)
910             cldap_ret = net.finddc(address=dc_hostname, flags=flags)
911         else:
912             flags = (nbt.NBT_SERVER_LDAP |
913                      nbt.NBT_SERVER_DS |
914                      nbt.NBT_SERVER_WRITABLE)
915             cldap_ret = net.finddc(domain=self.lp.get('realm'), flags=flags)
916             dc_hostname = cldap_ret.pdc_dns_name
917             self.url = dc_url(self.lp, self.creds, dc=dc_hostname)
918
919         samdb_connect(self)
920
921         msg = get_gpo_info(self.samdb, displayname=displayname)
922         if msg.count > 0:
923             raise CommandError("A GPO already existing with name '%s'" % displayname)
924
925         # Create new GUID
926         guid  = str(uuid.uuid4())
927         gpo = "{%s}" % guid.upper()
928         realm = cldap_ret.dns_domain
929         unc_path = "\\\\%s\\sysvol\\%s\\Policies\\%s" % (realm, realm, gpo)
930
931         # Create GPT
932         if tmpdir is None:
933             tmpdir = "/tmp"
934         if not os.path.isdir(tmpdir):
935             raise CommandError("Temporary directory '%s' does not exist" % tmpdir)
936
937         localdir = os.path.join(tmpdir, "policy")
938         if not os.path.isdir(localdir):
939             os.mkdir(localdir)
940
941         gpodir = os.path.join(localdir, gpo)
942         if os.path.isdir(gpodir):
943             raise CommandError("GPO directory '%s' already exists, refusing to overwrite" % gpodir)
944
945         try:
946             os.mkdir(gpodir)
947             os.mkdir(os.path.join(gpodir, "Machine"))
948             os.mkdir(os.path.join(gpodir, "User"))
949             gpt_contents = "[General]\r\nVersion=0\r\n"
950             open(os.path.join(gpodir, "GPT.INI"), "w").write(gpt_contents)
951         except Exception as e:
952             raise CommandError("Error Creating GPO files", e)
953
954         # Connect to DC over SMB
955         [dom_name, service, sharepath] = parse_unc(unc_path)
956         try:
957             conn = smb.SMB(dc_hostname, service, lp=self.lp, creds=self.creds)
958         except Exception as e:
959             raise CommandError("Error connecting to '%s' using SMB" % dc_hostname, e)
960
961         self.samdb.transaction_start()
962         try:
963             # Add cn=<guid>
964             gpo_dn = get_gpo_dn(self.samdb, gpo)
965
966             m = ldb.Message()
967             m.dn = gpo_dn
968             m['a01'] = ldb.MessageElement("groupPolicyContainer", ldb.FLAG_MOD_ADD, "objectClass")
969             self.samdb.add(m)
970
971             # Add cn=User,cn=<guid>
972             m = ldb.Message()
973             m.dn = ldb.Dn(self.samdb, "CN=User,%s" % str(gpo_dn))
974             m['a01'] = ldb.MessageElement("container", ldb.FLAG_MOD_ADD, "objectClass")
975             self.samdb.add(m)
976
977             # Add cn=Machine,cn=<guid>
978             m = ldb.Message()
979             m.dn = ldb.Dn(self.samdb, "CN=Machine,%s" % str(gpo_dn))
980             m['a01'] = ldb.MessageElement("container", ldb.FLAG_MOD_ADD, "objectClass")
981             self.samdb.add(m)
982
983             # Get new security descriptor
984             ds_sd_flags = ( security.SECINFO_OWNER |
985                             security.SECINFO_GROUP |
986                             security.SECINFO_DACL )
987             msg = get_gpo_info(self.samdb, gpo=gpo, sd_flags=ds_sd_flags)[0]
988             ds_sd_ndr = msg['nTSecurityDescriptor'][0]
989             ds_sd = ndr_unpack(security.descriptor, ds_sd_ndr).as_sddl()
990
991             # Create a file system security descriptor
992             domain_sid = security.dom_sid(self.samdb.get_domain_sid())
993             sddl = dsacl2fsacl(ds_sd, domain_sid)
994             fs_sd = security.descriptor.from_sddl(sddl, domain_sid)
995
996             # Copy GPO directory
997             create_directory_hier(conn, sharepath)
998
999             # Set ACL
1000             sio = ( security.SECINFO_OWNER |
1001                     security.SECINFO_GROUP |
1002                     security.SECINFO_DACL |
1003                     security.SECINFO_PROTECTED_DACL )
1004             conn.set_acl(sharepath, fs_sd, sio)
1005
1006             # Copy GPO files over SMB
1007             copy_directory_local_to_remote(conn, gpodir, sharepath)
1008
1009             m = ldb.Message()
1010             m.dn = gpo_dn
1011             m['a02'] = ldb.MessageElement(displayname, ldb.FLAG_MOD_REPLACE, "displayName")
1012             m['a03'] = ldb.MessageElement(unc_path, ldb.FLAG_MOD_REPLACE, "gPCFileSysPath")
1013             m['a05'] = ldb.MessageElement("0", ldb.FLAG_MOD_REPLACE, "versionNumber")
1014             m['a07'] = ldb.MessageElement("2", ldb.FLAG_MOD_REPLACE, "gpcFunctionalityVersion")
1015             m['a04'] = ldb.MessageElement("0", ldb.FLAG_MOD_REPLACE, "flags")
1016             controls=["permissive_modify:0"]
1017             self.samdb.modify(m, controls=controls)
1018         except Exception:
1019             self.samdb.transaction_cancel()
1020             raise
1021         else:
1022             self.samdb.transaction_commit()
1023
1024         self.outf.write("GPO '%s' created as %s\n" % (displayname, gpo))
1025
1026
1027 class cmd_del(Command):
1028     """Delete a GPO."""
1029
1030     synopsis = "%prog <gpo> [options]"
1031
1032     takes_optiongroups = {
1033         "sambaopts": options.SambaOptions,
1034         "versionopts": options.VersionOptions,
1035         "credopts": options.CredentialsOptions,
1036     }
1037
1038     takes_args = ['gpo']
1039
1040     takes_options = [
1041         Option("-H", help="LDB URL for database or target server", type=str),
1042         ]
1043
1044     def run(self, gpo, H=None, sambaopts=None, credopts=None,
1045                 versionopts=None):
1046
1047         self.lp = sambaopts.get_loadparm()
1048         self.creds = credopts.get_credentials(self.lp, fallback_machine=True)
1049
1050         # We need to know writable DC to setup SMB connection
1051         if H and H.startswith('ldap://'):
1052             dc_hostname = H[7:]
1053             self.url = H
1054         else:
1055             dc_hostname = netcmd_finddc(self.lp, self.creds)
1056             self.url = dc_url(self.lp, self.creds, dc=dc_hostname)
1057
1058         samdb_connect(self)
1059
1060         # Check if valid GPO
1061         try:
1062             msg = get_gpo_info(self.samdb, gpo=gpo)[0]
1063             unc_path = msg['gPCFileSysPath'][0]
1064         except Exception:
1065             raise CommandError("GPO '%s' does not exist" % gpo)
1066
1067         # Connect to DC over SMB
1068         [dom_name, service, sharepath] = parse_unc(unc_path)
1069         try:
1070             conn = smb.SMB(dc_hostname, service, lp=self.lp, creds=self.creds)
1071         except Exception as e:
1072             raise CommandError("Error connecting to '%s' using SMB" % dc_hostname, e)
1073
1074         self.samdb.transaction_start()
1075         try:
1076             # Check for existing links
1077             msg = get_gpo_containers(self.samdb, gpo)
1078
1079             if len(msg):
1080                 self.outf.write("GPO %s is linked to containers\n" % gpo)
1081                 for m in msg:
1082                     del_gpo_link(self.samdb, m['dn'], gpo)
1083                     self.outf.write("    Removed link from %s.\n" % m['dn'])
1084
1085             # Remove LDAP entries
1086             gpo_dn = get_gpo_dn(self.samdb, gpo)
1087             self.samdb.delete(ldb.Dn(self.samdb, "CN=User,%s" % str(gpo_dn)))
1088             self.samdb.delete(ldb.Dn(self.samdb, "CN=Machine,%s" % str(gpo_dn)))
1089             self.samdb.delete(gpo_dn)
1090
1091             # Remove GPO files
1092             conn.deltree(sharepath)
1093
1094         except Exception:
1095             self.samdb.transaction_cancel()
1096             raise
1097         else:
1098             self.samdb.transaction_commit()
1099
1100         self.outf.write("GPO %s deleted.\n" % gpo)
1101
1102
1103 class cmd_aclcheck(Command):
1104     """Check all GPOs have matching LDAP and DS ACLs."""
1105
1106     synopsis = "%prog [options]"
1107
1108     takes_optiongroups = {
1109         "sambaopts": options.SambaOptions,
1110         "versionopts": options.VersionOptions,
1111         "credopts": options.CredentialsOptions,
1112     }
1113
1114     takes_options = [
1115         Option("-H", "--URL", help="LDB URL for database or target server", type=str,
1116                metavar="URL", dest="H")
1117         ]
1118
1119     def run(self, H=None, sambaopts=None, credopts=None, versionopts=None):
1120
1121         self.lp = sambaopts.get_loadparm()
1122         self.creds = credopts.get_credentials(self.lp, fallback_machine=True)
1123
1124         self.url = dc_url(self.lp, self.creds, H)
1125
1126         # We need to know writable DC to setup SMB connection
1127         if H and H.startswith('ldap://'):
1128             dc_hostname = H[7:]
1129             self.url = H
1130         else:
1131             dc_hostname = netcmd_finddc(self.lp, self.creds)
1132             self.url = dc_url(self.lp, self.creds, dc=dc_hostname)
1133
1134         samdb_connect(self)
1135
1136         msg = get_gpo_info(self.samdb, None)
1137
1138         for m in msg:
1139             # verify UNC path
1140             unc = m['gPCFileSysPath'][0]
1141             try:
1142                 [dom_name, service, sharepath] = parse_unc(unc)
1143             except ValueError:
1144                 raise CommandError("Invalid GPO path (%s)" % unc)
1145
1146             # SMB connect to DC
1147             try:
1148                 conn = smb.SMB(dc_hostname, service, lp=self.lp, creds=self.creds)
1149             except Exception:
1150                 raise CommandError("Error connecting to '%s' using SMB" % dc_hostname)
1151
1152             fs_sd = conn.get_acl(sharepath, security.SECINFO_OWNER | security.SECINFO_GROUP | security.SECINFO_DACL, security.SEC_FLAG_MAXIMUM_ALLOWED)
1153
1154             ds_sd_ndr = m['nTSecurityDescriptor'][0]
1155             ds_sd = ndr_unpack(security.descriptor, ds_sd_ndr).as_sddl()
1156
1157             # Create a file system security descriptor
1158             domain_sid = security.dom_sid(self.samdb.get_domain_sid())
1159             expected_fs_sddl = dsacl2fsacl(ds_sd, domain_sid)
1160
1161             if (fs_sd.as_sddl(domain_sid) != expected_fs_sddl):
1162                 raise CommandError("Invalid GPO ACL %s on path (%s), should be %s" % (fs_sd.as_sddl(domain_sid), sharepath, expected_fs_sddl))
1163
1164
1165 class cmd_gpo(SuperCommand):
1166     """Group Policy Object (GPO) management."""
1167
1168     subcommands = {}
1169     subcommands["listall"] = cmd_listall()
1170     subcommands["list"] = cmd_list()
1171     subcommands["show"] = cmd_show()
1172     subcommands["getlink"] = cmd_getlink()
1173     subcommands["setlink"] = cmd_setlink()
1174     subcommands["dellink"] = cmd_dellink()
1175     subcommands["listcontainers"] = cmd_listcontainers()
1176     subcommands["getinheritance"] = cmd_getinheritance()
1177     subcommands["setinheritance"] = cmd_setinheritance()
1178     subcommands["fetch"] = cmd_fetch()
1179     subcommands["create"] = cmd_create()
1180     subcommands["del"] = cmd_del()
1181     subcommands["aclcheck"] = cmd_aclcheck()