s4:samba-tool/gpo: use 'gPCFileSysPath' when deleting gpos
[metze/samba/wip.git] / source4 / scripting / 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, 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, 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, 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, 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, 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, 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
235 def copy_directory_remote_to_local(conn, remotedir, localdir):
236     if not os.path.isdir(localdir):
237         os.mkdir(localdir)
238     r_dirs = [ remotedir ]
239     l_dirs = [ localdir ]
240     while r_dirs:
241         r_dir = r_dirs.pop()
242         l_dir = l_dirs.pop()
243
244         dirlist = conn.list(r_dir)
245         for e in dirlist:
246             r_name = r_dir + '\\' + e['name']
247             l_name = os.path.join(l_dir, e['name'])
248
249             if e['attrib'] & smb.FILE_ATTRIBUTE_DIRECTORY:
250                 r_dirs.append(r_name)
251                 l_dirs.append(l_name)
252                 os.mkdir(l_name)
253             else:
254                 data = conn.loadfile(r_name)
255                 file(l_name, 'w').write(data)
256
257
258 def copy_directory_local_to_remote(conn, localdir, remotedir):
259     if not conn.chkpath(remotedir):
260         conn.mkdir(remotedir)
261     l_dirs = [ localdir ]
262     r_dirs = [ remotedir ]
263     while l_dirs:
264         l_dir = l_dirs.pop()
265         r_dir = r_dirs.pop()
266
267         dirlist = os.listdir(l_dir)
268         for e in dirlist:
269             l_name = os.path.join(l_dir, e)
270             r_name = r_dir + '\\' + e
271
272             if os.path.isdir(l_name):
273                 l_dirs.append(l_name)
274                 r_dirs.append(r_name)
275                 conn.mkdir(r_name)
276             else:
277                 data = file(l_name, 'r').read()
278                 conn.savefile(r_name, data)
279
280
281 def create_directory_hier(conn, remotedir):
282     elems = remotedir.replace('/', '\\').split('\\')
283     path = ""
284     for e in elems:
285         path = path + '\\' + e
286         if not conn.chkpath(path):
287             conn.mkdir(path)
288
289
290 class cmd_listall(Command):
291     """List all GPOs."""
292
293     synopsis = "%prog [options]"
294
295     takes_optiongroups = {
296         "sambaopts": options.SambaOptions,
297         "versionopts": options.VersionOptions,
298         "credopts": options.CredentialsOptions,
299     }
300
301     takes_options = [
302         Option("-H", "--URL", help="LDB URL for database or target server", type=str,
303                metavar="URL", dest="H")
304         ]
305
306     def run(self, H=None, sambaopts=None, credopts=None, versionopts=None):
307
308         self.lp = sambaopts.get_loadparm()
309         self.creds = credopts.get_credentials(self.lp, fallback_machine=True)
310
311         self.url = dc_url(self.lp, self.creds, H)
312
313         samdb_connect(self)
314
315         msg = get_gpo_info(self.samdb, None)
316
317         for m in msg:
318             self.outf.write("GPO          : %s\n" % m['name'][0])
319             self.outf.write("display name : %s\n" % m['displayName'][0])
320             self.outf.write("path         : %s\n" % m['gPCFileSysPath'][0])
321             self.outf.write("dn           : %s\n" % m.dn)
322             self.outf.write("version      : %s\n" % attr_default(m, 'versionNumber', '0'))
323             self.outf.write("flags        : %s\n" % gpo_flags_string(int(attr_default(m, 'flags', 0))))
324             self.outf.write("\n")
325
326
327 class cmd_list(Command):
328     """List GPOs for an account."""
329
330     synopsis = "%prog <username> [options]"
331
332     takes_args = ['username']
333     takes_optiongroups = {
334         "sambaopts": options.SambaOptions,
335         "versionopts": options.VersionOptions,
336         "credopts": options.CredentialsOptions,
337     }
338
339     takes_options = [
340         Option("-H", "--URL", help="LDB URL for database or target server",
341             type=str, metavar="URL", dest="H")
342         ]
343
344     def run(self, username, H=None, sambaopts=None, credopts=None, versionopts=None):
345
346         self.lp = sambaopts.get_loadparm()
347         self.creds = credopts.get_credentials(self.lp, fallback_machine=True)
348
349         self.url = dc_url(self.lp, self.creds, H)
350
351         samdb_connect(self)
352
353         try:
354             msg = self.samdb.search(expression='(&(|(samAccountName=%s)(samAccountName=%s$))(objectClass=User))' %
355                                                 (ldb.binary_encode(username),ldb.binary_encode(username)))
356             user_dn = msg[0].dn
357         except Exception:
358             raise CommandError("Failed to find account %s" % username)
359
360         # check if its a computer account
361         try:
362             msg = self.samdb.search(base=user_dn, scope=ldb.SCOPE_BASE, attrs=['objectClass'])[0]
363             is_computer = 'computer' in msg['objectClass']
364         except Exception:
365             raise CommandError("Failed to find objectClass for user %s" % username)
366
367         session_info_flags = ( AUTH_SESSION_INFO_DEFAULT_GROUPS |
368                                AUTH_SESSION_INFO_AUTHENTICATED )
369
370         # When connecting to a remote server, don't look up the local privilege DB
371         if self.url is not None and self.url.startswith('ldap'):
372             session_info_flags |= AUTH_SESSION_INFO_SIMPLE_PRIVILEGES
373
374         session = samba.auth.user_session(self.samdb, lp_ctx=self.lp, dn=user_dn,
375                                           session_info_flags=session_info_flags)
376
377         token = session.security_token
378
379         gpos = []
380
381         inherit = True
382         dn = ldb.Dn(self.samdb, str(user_dn)).parent()
383         while True:
384             msg = self.samdb.search(base=dn, scope=ldb.SCOPE_BASE, attrs=['gPLink', 'gPOptions'])[0]
385             if 'gPLink' in msg:
386                 glist = parse_gplink(msg['gPLink'][0])
387                 for g in glist:
388                     if not inherit and not (g['options'] & dsdb.GPLINK_OPT_ENFORCE):
389                         continue
390                     if g['options'] & dsdb.GPLINK_OPT_DISABLE:
391                         continue
392
393                     try:
394                         sd_flags=security.SECINFO_OWNER|security.SECINFO_GROUP|security.SECINFO_DACL
395                         gmsg = self.samdb.search(base=g['dn'], scope=ldb.SCOPE_BASE,
396                                                  attrs=['name', 'displayName', 'flags',
397                                                         'nTSecurityDescriptor'],
398                                                  controls=['sd_flags:1:%d' % sd_flags])
399                         secdesc_ndr = gmsg[0]['nTSecurityDescriptor'][0]
400                         secdesc = ndr_unpack(security.descriptor, secdesc_ndr)
401                     except Exception:
402                         self.outf.write("Failed to fetch gpo object with nTSecurityDescriptor %s\n" %
403                             g['dn'])
404                         continue
405
406                     try:
407                         samba.security.access_check(secdesc, token,
408                                                     security.SEC_STD_READ_CONTROL |
409                                                     security.SEC_ADS_LIST |
410                                                     security.SEC_ADS_READ_PROP)
411                     except RuntimeError:
412                         self.outf.write("Failed access check on %s\n" % msg.dn)
413                         continue
414
415                     # check the flags on the GPO
416                     flags = int(attr_default(gmsg[0], 'flags', 0))
417                     if is_computer and (flags & dsdb.GPO_FLAG_MACHINE_DISABLE):
418                         continue
419                     if not is_computer and (flags & dsdb.GPO_FLAG_USER_DISABLE):
420                         continue
421                     gpos.append((gmsg[0]['displayName'][0], gmsg[0]['name'][0]))
422
423             # check if this blocks inheritance
424             gpoptions = int(attr_default(msg, 'gPOptions', 0))
425             if gpoptions & dsdb.GPO_BLOCK_INHERITANCE:
426                 inherit = False
427
428             if dn == self.samdb.get_default_basedn():
429                 break
430             dn = dn.parent()
431
432         if is_computer:
433             msg_str = 'computer'
434         else:
435             msg_str = 'user'
436
437         self.outf.write("GPOs for %s %s\n" % (msg_str, username))
438         for g in gpos:
439             self.outf.write("    %s %s\n" % (g[0], g[1]))
440
441
442 class cmd_show(Command):
443     """Show information for a GPO."""
444
445     synopsis = "%prog <gpo> [options]"
446
447     takes_optiongroups = {
448         "sambaopts": options.SambaOptions,
449         "versionopts": options.VersionOptions,
450         "credopts": options.CredentialsOptions,
451     }
452
453     takes_args = ['gpo']
454
455     takes_options = [
456         Option("-H", help="LDB URL for database or target server", type=str)
457         ]
458
459     def run(self, gpo, H=None, sambaopts=None, credopts=None, versionopts=None):
460
461         self.lp = sambaopts.get_loadparm()
462         self.creds = credopts.get_credentials(self.lp, fallback_machine=True)
463
464         self.url = dc_url(self.lp, self.creds, H)
465
466         samdb_connect(self)
467
468         try:
469             msg = get_gpo_info(self.samdb, gpo)[0]
470         except Exception:
471             raise CommandError("GPO '%s' does not exist" % gpo)
472
473         try:
474             secdesc_ndr = msg['nTSecurityDescriptor'][0]
475             secdesc = ndr_unpack(security.descriptor, secdesc_ndr)
476             secdesc_sddl = secdesc.as_sddl()
477         except Exception:
478             secdesc_sddl = "<hidden>"
479
480         self.outf.write("GPO          : %s\n" % msg['name'][0])
481         self.outf.write("display name : %s\n" % msg['displayName'][0])
482         self.outf.write("path         : %s\n" % msg['gPCFileSysPath'][0])
483         self.outf.write("dn           : %s\n" % msg.dn)
484         self.outf.write("version      : %s\n" % attr_default(msg, 'versionNumber', '0'))
485         self.outf.write("flags        : %s\n" % gpo_flags_string(int(attr_default(msg, 'flags', 0))))
486         self.outf.write("ACL          : %s\n" % secdesc_sddl)
487         self.outf.write("\n")
488
489
490 class cmd_getlink(Command):
491     """List GPO Links for a container."""
492
493     synopsis = "%prog <container_dn> [options]"
494
495     takes_optiongroups = {
496         "sambaopts": options.SambaOptions,
497         "versionopts": options.VersionOptions,
498         "credopts": options.CredentialsOptions,
499     }
500
501     takes_args = ['container_dn']
502
503     takes_options = [
504         Option("-H", help="LDB URL for database or target server", type=str)
505         ]
506
507     def run(self, container_dn, H=None, sambaopts=None, credopts=None,
508                 versionopts=None):
509
510         self.lp = sambaopts.get_loadparm()
511         self.creds = credopts.get_credentials(self.lp, fallback_machine=True)
512
513         self.url = dc_url(self.lp, self.creds, H)
514
515         samdb_connect(self)
516
517         try:
518             msg = self.samdb.search(base=container_dn, scope=ldb.SCOPE_BASE,
519                                     expression="(objectClass=*)",
520                                     attrs=['gPLink'])[0]
521         except Exception:
522             raise CommandError("Container '%s' does not exist" % container_dn)
523
524         if msg['gPLink']:
525             self.outf.write("GPO(s) linked to DN %s\n" % container_dn)
526             gplist = parse_gplink(msg['gPLink'][0])
527             for g in gplist:
528                 msg = get_gpo_info(self.samdb, dn=g['dn'])
529                 self.outf.write("    GPO     : %s\n" % msg[0]['name'][0])
530                 self.outf.write("    Name    : %s\n" % msg[0]['displayName'][0])
531                 self.outf.write("    Options : %s\n" % gplink_options_string(g['options']))
532                 self.outf.write("\n")
533         else:
534             self.outf.write("No GPO(s) linked to DN=%s\n" % container_dn)
535
536
537 class cmd_setlink(Command):
538     """Add or update a GPO link to a container."""
539
540     synopsis = "%prog <container_dn> <gpo> [options]"
541
542     takes_optiongroups = {
543         "sambaopts": options.SambaOptions,
544         "versionopts": options.VersionOptions,
545         "credopts": options.CredentialsOptions,
546     }
547
548     takes_args = ['container_dn', 'gpo']
549
550     takes_options = [
551         Option("-H", help="LDB URL for database or target server", type=str),
552         Option("--disable", dest="disabled", default=False, action='store_true',
553             help="Disable policy"),
554         Option("--enforce", dest="enforced", default=False, action='store_true',
555             help="Enforce policy")
556         ]
557
558     def run(self, container_dn, gpo, H=None, disabled=False, enforced=False,
559                 sambaopts=None, credopts=None, versionopts=None):
560
561         self.lp = sambaopts.get_loadparm()
562         self.creds = credopts.get_credentials(self.lp, fallback_machine=True)
563
564         self.url = dc_url(self.lp, self.creds, H)
565
566         samdb_connect(self)
567
568         gplink_options = 0
569         if disabled:
570             gplink_options |= dsdb.GPLINK_OPT_DISABLE
571         if enforced:
572             gplink_options |= dsdb.GPLINK_OPT_ENFORCE
573
574         # Check if valid GPO DN
575         try:
576             msg = get_gpo_info(self.samdb, gpo=gpo)[0]
577         except Exception:
578             raise CommandError("GPO '%s' does not exist" % gpo)
579         gpo_dn = str(get_gpo_dn(self.samdb, gpo))
580
581         # Check if valid Container DN
582         try:
583             msg = self.samdb.search(base=container_dn, scope=ldb.SCOPE_BASE,
584                                     expression="(objectClass=*)",
585                                     attrs=['gPLink'])[0]
586         except Exception:
587             raise CommandError("Container '%s' does not exist" % container_dn)
588
589         # Update existing GPlinks or Add new one
590         existing_gplink = False
591         if 'gPLink' in msg:
592             gplist = parse_gplink(msg['gPLink'][0])
593             existing_gplink = True
594             found = False
595             for g in gplist:
596                 if g['dn'].lower() == gpo_dn.lower():
597                     g['options'] = gplink_options
598                     found = True
599                     break
600             if found:
601                 raise CommandError("GPO '%s' already linked to this container" % gpo)
602             else:
603                 gplist.insert(0, { 'dn' : gpo_dn, 'options' : gplink_options })
604         else:
605             gplist = []
606             gplist.append({ 'dn' : gpo_dn, 'options' : gplink_options })
607
608         gplink_str = encode_gplink(gplist)
609
610         m = ldb.Message()
611         m.dn = ldb.Dn(self.samdb, container_dn)
612
613         if existing_gplink:
614             m['new_value'] = ldb.MessageElement(gplink_str, ldb.FLAG_MOD_REPLACE, 'gPLink')
615         else:
616             m['new_value'] = ldb.MessageElement(gplink_str, ldb.FLAG_MOD_ADD, 'gPLink')
617
618         try:
619             self.samdb.modify(m)
620         except Exception, e:
621             raise CommandError("Error adding GPO Link", e)
622
623         self.outf.write("Added/Updated GPO link\n")
624         cmd_getlink().run(container_dn, H, sambaopts, credopts, versionopts)
625
626
627 class cmd_dellink(Command):
628     """Delete GPO link from a container."""
629
630     synopsis = "%prog <container_dn> <gpo> [options]"
631
632     takes_optiongroups = {
633         "sambaopts": options.SambaOptions,
634         "versionopts": options.VersionOptions,
635         "credopts": options.CredentialsOptions,
636     }
637
638     takes_args = ['container', 'gpo']
639
640     takes_options = [
641         Option("-H", help="LDB URL for database or target server", type=str),
642         ]
643
644     def run(self, container, gpo, H=None, sambaopts=None, credopts=None,
645                 versionopts=None):
646
647         self.lp = sambaopts.get_loadparm()
648         self.creds = credopts.get_credentials(self.lp, fallback_machine=True)
649
650         self.url = dc_url(self.lp, self.creds, H)
651
652         samdb_connect(self)
653
654         # Check if valid GPO
655         try:
656             get_gpo_info(self.samdb, gpo=gpo)[0]
657         except Exception:
658             raise CommandError("GPO '%s' does not exist" % gpo)
659
660         container_dn = ldb.Dn(self.samdb, container)
661         del_gpo_link(self.samdb, container_dn, gpo)
662         self.outf.write("Deleted GPO link.\n")
663         cmd_getlink().run(container_dn, H, sambaopts, credopts, versionopts)
664
665
666 class cmd_listcontainers(Command):
667     """List all linked containers for a GPO."""
668
669     synopsis = "%prog <gpo> [options]"
670
671     takes_optiongroups = {
672         "sambaopts": options.SambaOptions,
673         "versionopts": options.VersionOptions,
674         "credopts": options.CredentialsOptions,
675     }
676
677     takes_args = ['gpo']
678
679     takes_options = [
680         Option("-H", help="LDB URL for database or target server", type=str)
681         ]
682
683     def run(self, gpo, H=None, sambaopts=None, credopts=None,
684                 versionopts=None):
685
686         self.lp = sambaopts.get_loadparm()
687         self.creds = credopts.get_credentials(self.lp, fallback_machine=True)
688
689         self.url = dc_url(self.lp, self.creds, H)
690
691         samdb_connect(self)
692
693         msg = get_gpo_containers(self.samdb, gpo)
694         if len(msg):
695             self.outf.write("Container(s) using GPO %s\n" % gpo)
696             for m in msg:
697                 self.outf.write("    DN: %s\n" % m['dn'])
698         else:
699             self.outf.write("No Containers using GPO %s\n" % gpo)
700
701
702 class cmd_getinheritance(Command):
703     """Get inheritance flag for a container."""
704
705     synopsis = "%prog <container_dn> [options]"
706
707     takes_optiongroups = {
708         "sambaopts": options.SambaOptions,
709         "versionopts": options.VersionOptions,
710         "credopts": options.CredentialsOptions,
711     }
712
713     takes_args = ['container_dn']
714
715     takes_options = [
716         Option("-H", help="LDB URL for database or target server", type=str)
717         ]
718
719     def run(self, container_dn, H=None, sambaopts=None, credopts=None,
720                 versionopts=None):
721
722         self.lp = sambaopts.get_loadparm()
723         self.creds = credopts.get_credentials(self.lp, fallback_machine=True)
724
725         self.url = dc_url(self.lp, self.creds, H)
726
727         samdb_connect(self)
728
729         try:
730             msg = self.samdb.search(base=container_dn, scope=ldb.SCOPE_BASE,
731                                     expression="(objectClass=*)",
732                                     attrs=['gPOptions'])[0]
733         except Exception:
734             raise CommandError("Container '%s' does not exist" % container_dn)
735
736         inheritance = 0
737         if 'gPOptions' in msg:
738             inheritance = int(msg['gPOptions'][0])
739
740         if inheritance == dsdb.GPO_BLOCK_INHERITANCE:
741             self.outf.write("Container has GPO_BLOCK_INHERITANCE\n")
742         else:
743             self.outf.write("Container has GPO_INHERIT\n")
744
745
746 class cmd_setinheritance(Command):
747     """Set inheritance flag on a container."""
748
749     synopsis = "%prog <container_dn> <block|inherit> [options]"
750
751     takes_optiongroups = {
752         "sambaopts": options.SambaOptions,
753         "versionopts": options.VersionOptions,
754         "credopts": options.CredentialsOptions,
755     }
756
757     takes_args = [ 'container_dn', 'inherit_state' ]
758
759     takes_options = [
760         Option("-H", help="LDB URL for database or target server", type=str)
761         ]
762
763     def run(self, container_dn, inherit_state, H=None, sambaopts=None, credopts=None,
764                 versionopts=None):
765
766         if inherit_state.lower() == 'block':
767             inheritance = dsdb.GPO_BLOCK_INHERITANCE
768         elif inherit_state.lower() == 'inherit':
769             inheritance = dsdb.GPO_INHERIT
770         else:
771             raise CommandError("Unknown inheritance state (%s)" % inherit_state)
772
773         self.lp = sambaopts.get_loadparm()
774         self.creds = credopts.get_credentials(self.lp, fallback_machine=True)
775
776         self.url = dc_url(self.lp, self.creds, H)
777
778         samdb_connect(self)
779         try:
780             msg = self.samdb.search(base=container_dn, scope=ldb.SCOPE_BASE,
781                                     expression="(objectClass=*)",
782                                     attrs=['gPOptions'])[0]
783         except Exception:
784             raise CommandError("Container '%s' does not exist" % container_dn)
785
786         m = ldb.Message()
787         m.dn = ldb.Dn(self.samdb, container_dn)
788
789         if 'gPOptions' in msg:
790             m['new_value'] = ldb.MessageElement(str(inheritance), ldb.FLAG_MOD_REPLACE, 'gPOptions')
791         else:
792             m['new_value'] = ldb.MessageElement(str(inheritance), ldb.FLAG_MOD_ADD, 'gPOptions')
793
794         try:
795             self.samdb.modify(m)
796         except Exception, e:
797             raise CommandError("Error setting inheritance state %s" % inherit_state, e)
798
799
800 class cmd_fetch(Command):
801     """Download a GPO."""
802
803     synopsis = "%prog <gpo> [options]"
804
805     takes_optiongroups = {
806         "sambaopts": options.SambaOptions,
807         "versionopts": options.VersionOptions,
808         "credopts": options.CredentialsOptions,
809     }
810
811     takes_args = ['gpo']
812
813     takes_options = [
814         Option("-H", help="LDB URL for database or target server", type=str),
815         Option("--tmpdir", help="Temporary directory for copying policy files", type=str)
816         ]
817
818     def run(self, gpo, H=None, tmpdir=None, sambaopts=None, credopts=None, versionopts=None):
819
820         self.lp = sambaopts.get_loadparm()
821         self.creds = credopts.get_credentials(self.lp, fallback_machine=True)
822
823         # We need to know writable DC to setup SMB connection
824         if H and H.startswith('ldap://'):
825             dc_hostname = H[7:]
826             self.url = H
827         else:
828             dc_hostname = netcmd_finddc(self.lp, self.creds)
829             self.url = dc_url(self.lp, self.creds, dc=dc_hostname)
830
831         samdb_connect(self)
832         try:
833             msg = get_gpo_info(self.samdb, gpo)[0]
834         except Exception:
835             raise CommandError("GPO '%s' does not exist" % gpo)
836
837         # verify UNC path
838         unc = msg['gPCFileSysPath'][0]
839         try:
840             [dom_name, service, sharepath] = parse_unc(unc)
841         except ValueError:
842             raise CommandError("Invalid GPO path (%s)" % unc)
843
844         # SMB connect to DC
845         try:
846             conn = smb.SMB(dc_hostname, service, lp=self.lp, creds=self.creds)
847         except Exception:
848             raise CommandError("Error connecting to '%s' using SMB" % dc_hostname)
849
850         # Copy GPT
851         if tmpdir is None:
852             tmpdir = "/tmp"
853         if not os.path.isdir(tmpdir):
854             raise CommandError("Temoprary directory '%s' does not exist" % tmpdir)
855
856         localdir = os.path.join(tmpdir, "policy")
857         if not os.path.isdir(localdir):
858             os.mkdir(localdir)
859
860         gpodir = os.path.join(localdir, gpo)
861         if os.path.isdir(gpodir):
862             raise CommandError("GPO directory '%s' already exists, refusing to overwrite" % gpodir)
863
864         try:
865             os.mkdir(gpodir)
866             copy_directory_remote_to_local(conn, sharepath, gpodir)
867         except Exception, e:
868             # FIXME: Catch more specific exception
869             raise CommandError("Error copying GPO from DC", e)
870         self.outf.write('GPO copied to %s\n' % gpodir)
871
872
873 class cmd_create(Command):
874     """Create an empty GPO."""
875
876     synopsis = "%prog <displayname> [options]"
877
878     takes_optiongroups = {
879         "sambaopts": options.SambaOptions,
880         "versionopts": options.VersionOptions,
881         "credopts": options.CredentialsOptions,
882     }
883
884     takes_args = ['displayname']
885
886     takes_options = [
887         Option("-H", help="LDB URL for database or target server", type=str),
888         Option("--tmpdir", help="Temporary directory for copying policy files", type=str)
889         ]
890
891     def run(self, displayname, H=None, tmpdir=None, sambaopts=None, credopts=None,
892             versionopts=None):
893
894         self.lp = sambaopts.get_loadparm()
895         self.creds = credopts.get_credentials(self.lp, fallback_machine=True)
896
897         net = Net(creds=self.creds, lp=self.lp)
898
899         # We need to know writable DC to setup SMB connection
900         if H and H.startswith('ldap://'):
901             dc_hostname = H[7:]
902             self.url = H
903             flags = (nbt.NBT_SERVER_LDAP |
904                      nbt.NBT_SERVER_DS |
905                      nbt.NBT_SERVER_WRITABLE)
906             cldap_ret = net.finddc(address=dc_hostname, flags=flags)
907         else:
908             flags = (nbt.NBT_SERVER_LDAP |
909                      nbt.NBT_SERVER_DS |
910                      nbt.NBT_SERVER_WRITABLE)
911             cldap_ret = net.finddc(domain=self.lp.get('realm'), flags=flags)
912             dc_hostname = cldap_ret.pdc_dns_name
913             self.url = dc_url(self.lp, self.creds, dc=dc_hostname)
914
915         samdb_connect(self)
916
917         msg = get_gpo_info(self.samdb, displayname=displayname)
918         if msg.count > 0:
919             raise CommandError("A GPO already existing with name '%s'" % displayname)
920
921         # Create new GUID
922         guid  = str(uuid.uuid4())
923         gpo = "{%s}" % guid.upper()
924         realm = cldap_ret.dns_domain
925         unc_path = "\\\\%s\\sysvol\\%s\\Policies\\%s" % (realm, realm, gpo)
926
927         # Create GPT
928         if tmpdir is None:
929             tmpdir = "/tmp"
930         if not os.path.isdir(tmpdir):
931             raise CommandError("Temporary directory '%s' does not exist" % tmpdir)
932
933         localdir = os.path.join(tmpdir, "policy")
934         if not os.path.isdir(localdir):
935             os.mkdir(localdir)
936
937         gpodir = os.path.join(localdir, gpo)
938         if os.path.isdir(gpodir):
939             raise CommandError("GPO directory '%s' already exists, refusing to overwrite" % gpodir)
940
941         try:
942             os.mkdir(gpodir)
943             os.mkdir(os.path.join(gpodir, "Machine"))
944             os.mkdir(os.path.join(gpodir, "User"))
945             gpt_contents = "[General]\r\nVersion=0\r\n"
946             file(os.path.join(gpodir, "GPT.INI"), "w").write(gpt_contents)
947         except Exception, e:
948             raise CommandError("Error Creating GPO files", e)
949
950         # Connect to DC over SMB
951         [dom_name, service, sharepath] = parse_unc(unc_path)
952         try:
953             conn = smb.SMB(dc_hostname, service, lp=self.lp, creds=self.creds)
954         except Exception, e:
955             raise CommandError("Error connecting to '%s' using SMB" % dc_hostname, e)
956
957         self.samdb.transaction_start()
958         try:
959             # Add cn=<guid>
960             gpo_dn = get_gpo_dn(self.samdb, gpo)
961
962             m = ldb.Message()
963             m.dn = gpo_dn
964             m['a01'] = ldb.MessageElement("groupPolicyContainer", ldb.FLAG_MOD_ADD, "objectClass")
965             m['a02'] = ldb.MessageElement(displayname, ldb.FLAG_MOD_ADD, "displayName")
966             m['a03'] = ldb.MessageElement(unc_path, ldb.FLAG_MOD_ADD, "gPCFileSysPath")
967             m['a04'] = ldb.MessageElement("0", ldb.FLAG_MOD_ADD, "flags")
968             m['a05'] = ldb.MessageElement("0", ldb.FLAG_MOD_ADD, "versionNumber")
969             m['a06'] = ldb.MessageElement("TRUE", ldb.FLAG_MOD_ADD, "showInAdvancedViewOnly")
970             m['a07'] = ldb.MessageElement("2", ldb.FLAG_MOD_ADD, "gpcFunctionalityVersion")
971             self.samdb.add(m)
972
973             # Add cn=User,cn=<guid>
974             m = ldb.Message()
975             m.dn = ldb.Dn(self.samdb, "CN=User,%s" % str(gpo_dn))
976             m['a01'] = ldb.MessageElement("container", ldb.FLAG_MOD_ADD, "objectClass")
977             m['a02'] = ldb.MessageElement("TRUE", ldb.FLAG_MOD_ADD, "showInAdvancedViewOnly")
978             self.samdb.add(m)
979
980             # Add cn=Machine,cn=<guid>
981             m = ldb.Message()
982             m.dn = ldb.Dn(self.samdb, "CN=Machine,%s" % str(gpo_dn))
983             m['a01'] = ldb.MessageElement("container", ldb.FLAG_MOD_ADD, "objectClass")
984             m['a02'] = ldb.MessageElement("TRUE", ldb.FLAG_MOD_ADD, "showInAdvancedViewOnly")
985             self.samdb.add(m)
986
987             # Copy GPO files over SMB
988             create_directory_hier(conn, sharepath)
989             copy_directory_local_to_remote(conn, gpodir, sharepath)
990
991             # Get new security descriptor
992             msg = get_gpo_info(self.samdb, gpo=gpo)[0]
993             ds_sd_ndr = msg['nTSecurityDescriptor'][0]
994             ds_sd = ndr_unpack(security.descriptor, ds_sd_ndr).as_sddl()
995
996             # Create a file system security descriptor
997             domain_sid = security.dom_sid(self.samdb.get_domain_sid())
998             sddl = dsacl2fsacl(ds_sd, domain_sid)
999             fs_sd = security.descriptor.from_sddl(sddl, domain_sid)
1000
1001             # Set ACL
1002             sio = ( security.SECINFO_OWNER |
1003                     security.SECINFO_GROUP |
1004                     security.SECINFO_DACL |
1005                     security.SECINFO_PROTECTED_DACL )
1006             conn.set_acl(sharepath, fs_sd, sio)
1007         except Exception:
1008             self.samdb.transaction_cancel()
1009             raise
1010         else:
1011             self.samdb.transaction_commit()
1012
1013         self.outf.write("GPO '%s' created as %s\n" % (displayname, gpo))
1014
1015
1016 class cmd_del(Command):
1017     """Delete a GPO."""
1018
1019     synopsis = "%prog <gpo> [options]"
1020
1021     takes_optiongroups = {
1022         "sambaopts": options.SambaOptions,
1023         "versionopts": options.VersionOptions,
1024         "credopts": options.CredentialsOptions,
1025     }
1026
1027     takes_args = ['gpo']
1028
1029     takes_options = [
1030         Option("-H", help="LDB URL for database or target server", type=str),
1031         ]
1032
1033     def run(self, gpo, H=None, sambaopts=None, credopts=None,
1034                 versionopts=None):
1035
1036         self.lp = sambaopts.get_loadparm()
1037         self.creds = credopts.get_credentials(self.lp, fallback_machine=True)
1038
1039         # We need to know writable DC to setup SMB connection
1040         if H and H.startswith('ldap://'):
1041             dc_hostname = H[7:]
1042             self.url = H
1043         else:
1044             dc_hostname = netcmd_finddc(self.lp, self.creds)
1045             self.url = dc_url(self.lp, self.creds, dc=dc_hostname)
1046
1047         samdb_connect(self)
1048
1049         # Check if valid GPO
1050         try:
1051             msg = get_gpo_info(self.samdb, gpo=gpo)[0]
1052             unc_path = msg['gPCFileSysPath'][0]
1053         except Exception:
1054             raise CommandError("GPO '%s' does not exist" % gpo)
1055
1056         # Connect to DC over SMB
1057         [dom_name, service, sharepath] = parse_unc(unc_path)
1058         try:
1059             conn = smb.SMB(dc_hostname, service, lp=self.lp, creds=self.creds)
1060         except Exception, e:
1061             raise CommandError("Error connecting to '%s' using SMB" % dc_hostname, e)
1062
1063         self.samdb.transaction_start()
1064         try:
1065             # Check for existing links
1066             msg = get_gpo_containers(self.samdb, gpo)
1067
1068             if len(msg):
1069                 self.outf.write("GPO %s is linked to containers\n" % gpo)
1070                 for m in msg:
1071                     del_gpo_link(self.samdb, m['dn'], gpo)
1072                     self.outf.write("    Removed link from %s.\n" % m['dn'])
1073
1074             # Remove LDAP entries
1075             gpo_dn = get_gpo_dn(self.samdb, gpo)
1076             self.samdb.delete(ldb.Dn(self.samdb, "CN=User,%s" % str(gpo_dn)))
1077             self.samdb.delete(ldb.Dn(self.samdb, "CN=Machine,%s" % str(gpo_dn)))
1078             self.samdb.delete(gpo_dn)
1079
1080             # Remove GPO files
1081             conn.deltree(sharepath)
1082
1083         except Exception:
1084             self.samdb.transaction_cancel()
1085             raise
1086         else:
1087             self.samdb.transaction_commit()
1088
1089         self.outf.write("GPO %s deleted.\n" % gpo)
1090
1091
1092 class cmd_aclcheck(Command):
1093     """Check all GPOs have matching LDAP and DS ACLs."""
1094
1095     synopsis = "%prog [options]"
1096
1097     takes_optiongroups = {
1098         "sambaopts": options.SambaOptions,
1099         "versionopts": options.VersionOptions,
1100         "credopts": options.CredentialsOptions,
1101     }
1102
1103     takes_options = [
1104         Option("-H", "--URL", help="LDB URL for database or target server", type=str,
1105                metavar="URL", dest="H")
1106         ]
1107
1108     def run(self, H=None, sambaopts=None, credopts=None, versionopts=None):
1109
1110         self.lp = sambaopts.get_loadparm()
1111         self.creds = credopts.get_credentials(self.lp, fallback_machine=True)
1112
1113         self.url = dc_url(self.lp, self.creds, H)
1114
1115         # We need to know writable DC to setup SMB connection
1116         if H and H.startswith('ldap://'):
1117             dc_hostname = H[7:]
1118             self.url = H
1119         else:
1120             dc_hostname = netcmd_finddc(self.lp, self.creds)
1121             self.url = dc_url(self.lp, self.creds, dc=dc_hostname)
1122
1123         samdb_connect(self)
1124
1125         msg = get_gpo_info(self.samdb, None)
1126
1127         for m in msg:
1128             # verify UNC path
1129             unc = m['gPCFileSysPath'][0]
1130             try:
1131                 [dom_name, service, sharepath] = parse_unc(unc)
1132             except ValueError:
1133                 raise CommandError("Invalid GPO path (%s)" % unc)
1134
1135             # SMB connect to DC
1136             try:
1137                 conn = smb.SMB(dc_hostname, service, lp=self.lp, creds=self.creds)
1138             except Exception:
1139                 raise CommandError("Error connecting to '%s' using SMB" % dc_hostname)
1140
1141             fs_sd = conn.get_acl(sharepath, security.SECINFO_OWNER | security.SECINFO_GROUP | security.SECINFO_DACL, security.SEC_FLAG_MAXIMUM_ALLOWED)
1142
1143             ds_sd_ndr = m['nTSecurityDescriptor'][0]
1144             ds_sd = ndr_unpack(security.descriptor, ds_sd_ndr).as_sddl()
1145
1146             # Create a file system security descriptor
1147             domain_sid = security.dom_sid(self.samdb.get_domain_sid())
1148             expected_fs_sddl = dsacl2fsacl(ds_sd, domain_sid)
1149
1150             if (fs_sd.as_sddl(domain_sid) != expected_fs_sddl):
1151                 raise CommandError("Invalid GPO ACL %s on path (%s), should be %s" % (fs_sd.as_sddl(domain_sid), sharepath, expected_fs_sddl))
1152
1153
1154 class cmd_gpo(SuperCommand):
1155     """Group Policy Object (GPO) management."""
1156
1157     subcommands = {}
1158     subcommands["listall"] = cmd_listall()
1159     subcommands["list"] = cmd_list()
1160     subcommands["show"] = cmd_show()
1161     subcommands["getlink"] = cmd_getlink()
1162     subcommands["setlink"] = cmd_setlink()
1163     subcommands["dellink"] = cmd_dellink()
1164     subcommands["listcontainers"] = cmd_listcontainers()
1165     subcommands["getinheritance"] = cmd_getinheritance()
1166     subcommands["setinheritance"] = cmd_setinheritance()
1167     subcommands["fetch"] = cmd_fetch()
1168     subcommands["create"] = cmd_create()
1169     subcommands["del"] = cmd_del()
1170     subcommands["aclcheck"] = cmd_aclcheck()