build: Change bin/default/python -> bin/python symlink to bin/default/python_modules
[nivanova/samba-autobuild/.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             self.samdb.add(m)
966
967             # Add cn=User,cn=<guid>
968             m = ldb.Message()
969             m.dn = ldb.Dn(self.samdb, "CN=User,%s" % str(gpo_dn))
970             m['a01'] = ldb.MessageElement("container", ldb.FLAG_MOD_ADD, "objectClass")
971             self.samdb.add(m)
972
973             # Add cn=Machine,cn=<guid>
974             m = ldb.Message()
975             m.dn = ldb.Dn(self.samdb, "CN=Machine,%s" % str(gpo_dn))
976             m['a01'] = ldb.MessageElement("container", ldb.FLAG_MOD_ADD, "objectClass")
977             self.samdb.add(m)
978
979             # Get new security descriptor
980             ds_sd_flags = ( security.SECINFO_OWNER |
981                             security.SECINFO_GROUP |
982                             security.SECINFO_DACL )
983             msg = get_gpo_info(self.samdb, gpo=gpo, sd_flags=ds_sd_flags)[0]
984             ds_sd_ndr = msg['nTSecurityDescriptor'][0]
985             ds_sd = ndr_unpack(security.descriptor, ds_sd_ndr).as_sddl()
986
987             # Create a file system security descriptor
988             domain_sid = security.dom_sid(self.samdb.get_domain_sid())
989             sddl = dsacl2fsacl(ds_sd, domain_sid)
990             fs_sd = security.descriptor.from_sddl(sddl, domain_sid)
991
992             # Copy GPO directory
993             create_directory_hier(conn, sharepath)
994
995             # Set ACL
996             sio = ( security.SECINFO_OWNER |
997                     security.SECINFO_GROUP |
998                     security.SECINFO_DACL |
999                     security.SECINFO_PROTECTED_DACL )
1000             conn.set_acl(sharepath, fs_sd, sio)
1001
1002             # Copy GPO files over SMB
1003             copy_directory_local_to_remote(conn, gpodir, sharepath)
1004
1005             m = ldb.Message()
1006             m.dn = gpo_dn
1007             m['a02'] = ldb.MessageElement(displayname, ldb.FLAG_MOD_REPLACE, "displayName")
1008             m['a03'] = ldb.MessageElement(unc_path, ldb.FLAG_MOD_REPLACE, "gPCFileSysPath")
1009             m['a05'] = ldb.MessageElement("0", ldb.FLAG_MOD_REPLACE, "versionNumber")
1010             m['a07'] = ldb.MessageElement("2", ldb.FLAG_MOD_REPLACE, "gpcFunctionalityVersion")
1011             m['a04'] = ldb.MessageElement("0", ldb.FLAG_MOD_REPLACE, "flags")
1012             controls=["permissive_modify:0"]
1013             self.samdb.modify(m, controls=controls)
1014         except Exception:
1015             self.samdb.transaction_cancel()
1016             raise
1017         else:
1018             self.samdb.transaction_commit()
1019
1020         self.outf.write("GPO '%s' created as %s\n" % (displayname, gpo))
1021
1022
1023 class cmd_del(Command):
1024     """Delete a GPO."""
1025
1026     synopsis = "%prog <gpo> [options]"
1027
1028     takes_optiongroups = {
1029         "sambaopts": options.SambaOptions,
1030         "versionopts": options.VersionOptions,
1031         "credopts": options.CredentialsOptions,
1032     }
1033
1034     takes_args = ['gpo']
1035
1036     takes_options = [
1037         Option("-H", help="LDB URL for database or target server", type=str),
1038         ]
1039
1040     def run(self, gpo, H=None, sambaopts=None, credopts=None,
1041                 versionopts=None):
1042
1043         self.lp = sambaopts.get_loadparm()
1044         self.creds = credopts.get_credentials(self.lp, fallback_machine=True)
1045
1046         # We need to know writable DC to setup SMB connection
1047         if H and H.startswith('ldap://'):
1048             dc_hostname = H[7:]
1049             self.url = H
1050         else:
1051             dc_hostname = netcmd_finddc(self.lp, self.creds)
1052             self.url = dc_url(self.lp, self.creds, dc=dc_hostname)
1053
1054         samdb_connect(self)
1055
1056         # Check if valid GPO
1057         try:
1058             msg = get_gpo_info(self.samdb, gpo=gpo)[0]
1059             unc_path = msg['gPCFileSysPath'][0]
1060         except Exception:
1061             raise CommandError("GPO '%s' does not exist" % gpo)
1062
1063         # Connect to DC over SMB
1064         [dom_name, service, sharepath] = parse_unc(unc_path)
1065         try:
1066             conn = smb.SMB(dc_hostname, service, lp=self.lp, creds=self.creds)
1067         except Exception, e:
1068             raise CommandError("Error connecting to '%s' using SMB" % dc_hostname, e)
1069
1070         self.samdb.transaction_start()
1071         try:
1072             # Check for existing links
1073             msg = get_gpo_containers(self.samdb, gpo)
1074
1075             if len(msg):
1076                 self.outf.write("GPO %s is linked to containers\n" % gpo)
1077                 for m in msg:
1078                     del_gpo_link(self.samdb, m['dn'], gpo)
1079                     self.outf.write("    Removed link from %s.\n" % m['dn'])
1080
1081             # Remove LDAP entries
1082             gpo_dn = get_gpo_dn(self.samdb, gpo)
1083             self.samdb.delete(ldb.Dn(self.samdb, "CN=User,%s" % str(gpo_dn)))
1084             self.samdb.delete(ldb.Dn(self.samdb, "CN=Machine,%s" % str(gpo_dn)))
1085             self.samdb.delete(gpo_dn)
1086
1087             # Remove GPO files
1088             conn.deltree(sharepath)
1089
1090         except Exception:
1091             self.samdb.transaction_cancel()
1092             raise
1093         else:
1094             self.samdb.transaction_commit()
1095
1096         self.outf.write("GPO %s deleted.\n" % gpo)
1097
1098
1099 class cmd_aclcheck(Command):
1100     """Check all GPOs have matching LDAP and DS ACLs."""
1101
1102     synopsis = "%prog [options]"
1103
1104     takes_optiongroups = {
1105         "sambaopts": options.SambaOptions,
1106         "versionopts": options.VersionOptions,
1107         "credopts": options.CredentialsOptions,
1108     }
1109
1110     takes_options = [
1111         Option("-H", "--URL", help="LDB URL for database or target server", type=str,
1112                metavar="URL", dest="H")
1113         ]
1114
1115     def run(self, H=None, sambaopts=None, credopts=None, versionopts=None):
1116
1117         self.lp = sambaopts.get_loadparm()
1118         self.creds = credopts.get_credentials(self.lp, fallback_machine=True)
1119
1120         self.url = dc_url(self.lp, self.creds, H)
1121
1122         # We need to know writable DC to setup SMB connection
1123         if H and H.startswith('ldap://'):
1124             dc_hostname = H[7:]
1125             self.url = H
1126         else:
1127             dc_hostname = netcmd_finddc(self.lp, self.creds)
1128             self.url = dc_url(self.lp, self.creds, dc=dc_hostname)
1129
1130         samdb_connect(self)
1131
1132         msg = get_gpo_info(self.samdb, None)
1133
1134         for m in msg:
1135             # verify UNC path
1136             unc = m['gPCFileSysPath'][0]
1137             try:
1138                 [dom_name, service, sharepath] = parse_unc(unc)
1139             except ValueError:
1140                 raise CommandError("Invalid GPO path (%s)" % unc)
1141
1142             # SMB connect to DC
1143             try:
1144                 conn = smb.SMB(dc_hostname, service, lp=self.lp, creds=self.creds)
1145             except Exception:
1146                 raise CommandError("Error connecting to '%s' using SMB" % dc_hostname)
1147
1148             fs_sd = conn.get_acl(sharepath, security.SECINFO_OWNER | security.SECINFO_GROUP | security.SECINFO_DACL, security.SEC_FLAG_MAXIMUM_ALLOWED)
1149
1150             ds_sd_ndr = m['nTSecurityDescriptor'][0]
1151             ds_sd = ndr_unpack(security.descriptor, ds_sd_ndr).as_sddl()
1152
1153             # Create a file system security descriptor
1154             domain_sid = security.dom_sid(self.samdb.get_domain_sid())
1155             expected_fs_sddl = dsacl2fsacl(ds_sd, domain_sid)
1156
1157             if (fs_sd.as_sddl(domain_sid) != expected_fs_sddl):
1158                 raise CommandError("Invalid GPO ACL %s on path (%s), should be %s" % (fs_sd.as_sddl(domain_sid), sharepath, expected_fs_sddl))
1159
1160
1161 class cmd_gpo(SuperCommand):
1162     """Group Policy Object (GPO) management."""
1163
1164     subcommands = {}
1165     subcommands["listall"] = cmd_listall()
1166     subcommands["list"] = cmd_list()
1167     subcommands["show"] = cmd_show()
1168     subcommands["getlink"] = cmd_getlink()
1169     subcommands["setlink"] = cmd_setlink()
1170     subcommands["dellink"] = cmd_dellink()
1171     subcommands["listcontainers"] = cmd_listcontainers()
1172     subcommands["getinheritance"] = cmd_getinheritance()
1173     subcommands["setinheritance"] = cmd_setinheritance()
1174     subcommands["fetch"] = cmd_fetch()
1175     subcommands["create"] = cmd_create()
1176     subcommands["del"] = cmd_del()
1177     subcommands["aclcheck"] = cmd_aclcheck()