82ea3db8b9a36fc0a3e9293cbcd2a87bfc7a4ecd
[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 <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
46
47 def samdb_connect(ctx):
48     '''make a ldap connection to the server'''
49     try:
50         ctx.samdb = SamDB(url=ctx.url,
51                           session_info=system_session(),
52                           credentials=ctx.creds, lp=ctx.lp)
53     except Exception, e:
54         raise CommandError("LDAP connection to %s failed " % ctx.url, e)
55
56
57 def attr_default(msg, attrname, default):
58     '''get an attribute from a ldap msg with a default'''
59     if attrname in msg:
60         return msg[attrname][0]
61     return default
62
63
64 def gpo_flags_string(value):
65     '''return gpo flags string'''
66     flags = policy.get_gpo_flags(value)
67     if not flags:
68         ret = 'NONE'
69     else:
70         ret = ' '.join(flags)
71     return ret
72
73
74 def gplink_options_string(value):
75     '''return gplink options string'''
76     options = policy.get_gplink_options(value)
77     if not options:
78         ret = 'NONE'
79     else:
80         ret = ' '.join(options)
81     return ret
82
83
84 def parse_gplink(gplink):
85     '''parse a gPLink into an array of dn and options'''
86     ret = []
87     a = gplink.split(']')
88     for g in a:
89         if not g:
90             continue
91         d = g.split(';')
92         if len(d) != 2 or not d[0].startswith("[LDAP://"):
93             raise RuntimeError("Badly formed gPLink '%s'" % g)
94         ret.append({ 'dn' : d[0][8:], 'options' : int(d[1])})
95     return ret
96
97
98 def encode_gplink(gplist):
99     '''Encode an array of dn and options into gPLink string'''
100     ret = ''
101     for g in gplist:
102         ret += "[LDAP://%s;%d]" % (g['dn'], g['options'])
103     return ret
104
105
106 def dc_url(lp, creds, url=None, dc=None):
107     '''If URL is not specified, return URL for writable DC.
108     If dc is provided, use that to construct ldap URL'''
109
110     if url is None:
111         if dc is None:
112             try:
113                 dc = netcmd_finddc(lp, creds)
114             except Exception, e:
115                 raise RuntimeError("Could not find a DC for domain", e)
116         url = 'ldap://' + dc
117     return url
118
119
120 def get_gpo_dn(samdb, gpo):
121     '''Construct the DN for gpo'''
122
123     dn = samdb.get_default_basedn()
124     dn.add_child(ldb.Dn(samdb, "CN=Policies,DC=System"))
125     dn.add_child(ldb.Dn(samdb, "CN=%s" % gpo))
126     return dn
127
128
129 def get_gpo_info(samdb, gpo=None, displayname=None, dn=None):
130     '''Get GPO information using gpo, displayname or dn'''
131
132     policies_dn = samdb.get_default_basedn()
133     policies_dn.add_child(ldb.Dn(samdb, "CN=Policies,CN=System"))
134
135     base_dn = policies_dn
136     search_expr = "(objectClass=groupPolicyContainer)"
137     search_scope = ldb.SCOPE_ONELEVEL
138
139     if gpo is not None:
140         search_expr = "(&(objectClass=groupPolicyContainer)(name=%s))" % ldb.binary_encode(gpo)
141
142     if displayname is not None:
143         search_expr = "(&(objectClass=groupPolicyContainer)(displayname=%s))" % ldb.binary_encode(displayname)
144
145     if dn is not None:
146         base_dn = dn
147         search_scope = ldb.SCOPE_BASE
148
149     try:
150         msg = samdb.search(base=base_dn, scope=search_scope,
151                             expression=search_expr,
152                             attrs=['nTSecurityDescriptor',
153                                     'versionNumber',
154                                     'flags',
155                                     'name',
156                                     'displayName',
157                                     'gPCFileSysPath'])
158     except Exception, e:
159         if gpo is not None:
160             mesg = "Cannot get information for GPO %s" % gpo
161         else:
162             mesg = "Cannot get information for GPOs"
163         raise CommandError(mesg, e)
164
165     return msg
166
167
168 def parse_unc(unc):
169     '''Parse UNC string into a hostname, a service, and a filepath'''
170     if unc.startswith('\\\\') and unc.startswith('//'):
171         raise ValueError("UNC doesn't start with \\\\ or //")
172     tmp = unc[2:].split('/', 2)
173     if len(tmp) == 3:
174         return tmp
175     tmp = unc[2:].split('\\', 2)
176     if len(tmp) == 3:
177         return tmp
178     raise ValueError("Invalid UNC string: %s" % unc)
179
180
181 def copy_directory_remote_to_local(conn, remotedir, localdir):
182     if not os.path.isdir(localdir):
183         os.mkdir(localdir)
184     r_dirs = [ remotedir ]
185     l_dirs = [ localdir ]
186     while r_dirs:
187         r_dir = r_dirs.pop()
188         l_dir = l_dirs.pop()
189
190         dirlist = conn.list(r_dir)
191         for e in dirlist:
192             r_name = r_dir + '\\' + e['name']
193             l_name = os.path.join(l_dir, e['name'])
194
195             if e['attrib'] & smb.FILE_ATTRIBUTE_DIRECTORY:
196                 r_dirs.append(r_name)
197                 l_dirs.append(l_name)
198                 os.mkdir(l_name)
199             else:
200                 data = conn.loadfile(r_name)
201                 file(l_name, 'w').write(data)
202
203
204 def copy_directory_local_to_remote(conn, localdir, remotedir):
205     if not conn.chkpath(remotedir):
206         conn.mkdir(remotedir)
207     l_dirs = [ localdir ]
208     r_dirs = [ remotedir ]
209     while l_dirs:
210         l_dir = l_dirs.pop()
211         r_dir = r_dirs.pop()
212
213         dirlist = os.listdir(l_dir)
214         for e in dirlist:
215             l_name = os.path.join(l_dir, e)
216             r_name = r_dir + '\\' + e
217
218             if os.path.isdir(l_name):
219                 l_dirs.append(l_name)
220                 r_dirs.append(r_name)
221                 conn.mkdir(r_name)
222             else:
223                 data = file(l_name, 'r').read()
224                 conn.savefile(r_name, data)
225
226
227 def create_directory_hier(conn, remotedir):
228     elems = remotedir.replace('/', '\\').split('\\')
229     path = ""
230     for e in elems:
231         path = path + '\\' + e
232         if not conn.chkpath(path):
233             conn.mkdir(path)
234
235
236 class cmd_listall(Command):
237     """list all GPOs"""
238
239     synopsis = "%prog [options]"
240
241     takes_optiongroups = {
242         "sambaopts": options.SambaOptions,
243         "versionopts": options.VersionOptions,
244         "credopts": options.CredentialsOptions,
245     }
246
247     takes_options = [
248         Option("-H", "--URL", help="LDB URL for database or target server", type=str,
249                metavar="URL", dest="H")
250         ]
251
252     def run(self, H=None, sambaopts=None, credopts=None, versionopts=None):
253
254         self.lp = sambaopts.get_loadparm()
255         self.creds = credopts.get_credentials(self.lp, fallback_machine=True)
256
257         self.url = dc_url(self.lp, self.creds, H)
258
259         samdb_connect(self)
260
261         msg = get_gpo_info(self.samdb, None)
262
263         for m in msg:
264             self.outf.write("GPO          : %s\n" % m['name'][0])
265             self.outf.write("display name : %s\n" % m['displayName'][0])
266             self.outf.write("path         : %s\n" % m['gPCFileSysPath'][0])
267             self.outf.write("dn           : %s\n" % m.dn)
268             self.outf.write("version      : %s\n" % attr_default(m, 'versionNumber', '0'))
269             self.outf.write("flags        : %s\n" % gpo_flags_string(int(attr_default(m, 'flags', 0))))
270             self.outf.write("\n")
271
272
273 class cmd_list(Command):
274     """list GPOs for an account"""
275
276     synopsis = "%prog <username> [options]"
277
278     takes_args = ['username']
279     takes_optiongroups = {
280         "sambaopts": options.SambaOptions,
281         "versionopts": options.VersionOptions,
282         "credopts": options.CredentialsOptions,
283     }
284
285     takes_options = [
286         Option("-H", "--URL", help="LDB URL for database or target server",
287             type=str, metavar="URL", dest="H")
288         ]
289
290     def run(self, username, H=None, sambaopts=None, credopts=None, versionopts=None):
291
292         self.lp = sambaopts.get_loadparm()
293         self.creds = credopts.get_credentials(self.lp, fallback_machine=True)
294
295         self.url = dc_url(self.lp, self.creds, H)
296
297         samdb_connect(self)
298
299         try:
300             msg = self.samdb.search(expression='(&(|(samAccountName=%s)(samAccountName=%s$))(objectClass=User))' %
301                                                 (ldb.binary_encode(username),ldb.binary_encode(username)))
302             user_dn = msg[0].dn
303         except Exception, e:
304             raise CommandError("Failed to find account %s" % username, e)
305
306         # check if its a computer account
307         try:
308             msg = self.samdb.search(base=user_dn, scope=ldb.SCOPE_BASE, attrs=['objectClass'])[0]
309             is_computer = 'computer' in msg['objectClass']
310         except Exception, e:
311             raise CommandError("Failed to find objectClass for user %s" % username, e)
312
313         session_info_flags = ( AUTH_SESSION_INFO_DEFAULT_GROUPS |
314                                AUTH_SESSION_INFO_AUTHENTICATED )
315
316         # When connecting to a remote server, don't look up the local privilege DB
317         if self.url is not None and self.url.startswith('ldap'):
318             session_info_flags |= AUTH_SESSION_INFO_SIMPLE_PRIVILEGES
319
320         session = samba.auth.user_session(self.samdb, lp_ctx=self.lp, dn=user_dn,
321                                           session_info_flags=session_info_flags)
322
323         token = session.security_token
324
325         gpos = []
326
327         inherit = True
328         dn = ldb.Dn(self.samdb, str(user_dn)).parent()
329         while True:
330             msg = self.samdb.search(base=dn, scope=ldb.SCOPE_BASE, attrs=['gPLink', 'gPOptions'])[0]
331             if 'gPLink' in msg:
332                 glist = parse_gplink(msg['gPLink'][0])
333                 for g in glist:
334                     if not inherit and not (g['options'] & dsdb.GPLINK_OPT_ENFORCE):
335                         continue
336                     if g['options'] & dsdb.GPLINK_OPT_DISABLE:
337                         continue
338
339                     try:
340                         gmsg = self.samdb.search(base=g['dn'], scope=ldb.SCOPE_BASE,
341                                                  attrs=['name', 'displayName', 'flags',
342                                                         'ntSecurityDescriptor'])
343                     except Exception:
344                         self.outf.write("Failed to fetch gpo object %s\n" %
345                             g['dn'])
346                         continue
347
348                     secdesc_ndr = gmsg[0]['ntSecurityDescriptor'][0]
349                     secdesc = ndr_unpack(security.descriptor, secdesc_ndr)
350
351                     try:
352                         samba.security.access_check(secdesc, token,
353                                                     security.SEC_STD_READ_CONTROL |
354                                                     security.SEC_ADS_LIST |
355                                                     security.SEC_ADS_READ_PROP)
356                     except RuntimeError:
357                         self.outf.write("Failed access check on %s\n" % msg.dn)
358                         continue
359
360                     # check the flags on the GPO
361                     flags = int(attr_default(gmsg[0], 'flags', 0))
362                     if is_computer and (flags & dsdb.GPO_FLAG_MACHINE_DISABLE):
363                         continue
364                     if not is_computer and (flags & dsdb.GPO_FLAG_USER_DISABLE):
365                         continue
366                     gpos.append((gmsg[0]['displayName'][0], gmsg[0]['name'][0]))
367
368             # check if this blocks inheritance
369             gpoptions = int(attr_default(msg, 'gPOptions', 0))
370             if gpoptions & dsdb.GPO_BLOCK_INHERITANCE:
371                 inherit = False
372
373             if dn == self.samdb.get_default_basedn():
374                 break
375             dn = dn.parent()
376
377         if is_computer:
378             msg_str = 'computer'
379         else:
380             msg_str = 'user'
381
382         self.outf.write("GPOs for %s %s\n" % (msg_str, username))
383         for g in gpos:
384             self.outf.write("    %s %s\n" % (g[0], g[1]))
385
386
387 class cmd_show(Command):
388     """Show information for a GPO"""
389
390     synopsis = "%prog <gpo> [options]"
391
392     takes_optiongroups = {
393         "sambaopts": options.SambaOptions,
394         "versionopts": options.VersionOptions,
395         "credopts": options.CredentialsOptions,
396     }
397
398     takes_args = ['gpo']
399
400     takes_options = [
401         Option("-H", help="LDB URL for database or target server", type=str)
402         ]
403
404     def run(self, gpo, H=None, sambaopts=None, credopts=None, versionopts=None):
405
406         self.lp = sambaopts.get_loadparm()
407         self.creds = credopts.get_credentials(self.lp, fallback_machine=True)
408
409         self.url = dc_url(self.lp, self.creds, H)
410
411         samdb_connect(self)
412
413         try:
414             msg = get_gpo_info(self.samdb, gpo)[0]
415         except Exception, e:
416             raise CommandError("GPO %s does not exist" % gpo, e)
417
418         secdesc_ndr = msg['ntSecurityDescriptor'][0]
419         secdesc = ndr_unpack(security.descriptor, secdesc_ndr)
420
421         self.outf.write("GPO          : %s\n" % msg['name'][0])
422         self.outf.write("display name : %s\n" % msg['displayName'][0])
423         self.outf.write("path         : %s\n" % msg['gPCFileSysPath'][0])
424         self.outf.write("dn           : %s\n" % msg.dn)
425         self.outf.write("version      : %s\n" % attr_default(msg, 'versionNumber', '0'))
426         self.outf.write("flags        : %s\n" % gpo_flags_string(int(attr_default(msg, 'flags', 0))))
427         self.outf.write("ACL          : %s\n" % secdesc.as_sddl())
428         self.outf.write("\n")
429
430
431 class cmd_getlink(Command):
432     """List GPO Links for a container"""
433
434     synopsis = "%prog <container_dn> [options]"
435
436     takes_optiongroups = {
437         "sambaopts": options.SambaOptions,
438         "versionopts": options.VersionOptions,
439         "credopts": options.CredentialsOptions,
440     }
441
442     takes_args = ['container_dn']
443
444     takes_options = [
445         Option("-H", help="LDB URL for database or target server", type=str)
446         ]
447
448     def run(self, container_dn, H=None, sambaopts=None, credopts=None,
449                 versionopts=None):
450
451         self.lp = sambaopts.get_loadparm()
452         self.creds = credopts.get_credentials(self.lp, fallback_machine=True)
453
454         self.url = dc_url(self.lp, self.creds, H)
455
456         samdb_connect(self)
457
458         try:
459             msg = self.samdb.search(base=container_dn, scope=ldb.SCOPE_BASE,
460                                     expression="(objectClass=*)",
461                                     attrs=['gPlink'])[0]
462         except Exception, e:
463             raise CommandError("Could not find Container DN %s (%s)" % container_dn, e)
464
465         if 'gPLink' in msg:
466             self.outf.write("GPO(s) linked to DN %s\n" % container_dn)
467             gplist = parse_gplink(msg['gPLink'][0])
468             for g in gplist:
469                 msg = get_gpo_info(self.samdb, dn=g['dn'])
470                 self.outf.write("    GPO     : %s\n" % msg[0]['name'][0])
471                 self.outf.write("    Name    : %s\n" % msg[0]['displayName'][0])
472                 self.outf.write("    Options : %s\n" % gplink_options_string(g['options']))
473                 self.outf.write("\n")
474         else:
475             self.outf.write("No GPO(s) linked to DN=%s\n" % container_dn)
476
477
478 class cmd_setlink(Command):
479     """Add or Update a GPO link to a container"""
480
481     synopsis = "%prog <container_dn> <gpo> [options]"
482
483     takes_optiongroups = {
484         "sambaopts": options.SambaOptions,
485         "versionopts": options.VersionOptions,
486         "credopts": options.CredentialsOptions,
487     }
488
489     takes_args = ['container_dn', 'gpo']
490
491     takes_options = [
492         Option("-H", help="LDB URL for database or target server", type=str),
493         Option("--disable", dest="disabled", default=False, action='store_true',
494             help="Disable policy"),
495         Option("--enforce", dest="enforced", default=False, action='store_true',
496             help="Enforce policy")
497         ]
498
499     def run(self, container_dn, gpo, H=None, disabled=False, enforced=False,
500                 sambaopts=None, credopts=None, versionopts=None):
501
502         self.lp = sambaopts.get_loadparm()
503         self.creds = credopts.get_credentials(self.lp, fallback_machine=True)
504
505         self.url = dc_url(self.lp, self.creds, H)
506
507         samdb_connect(self)
508
509         gplink_options = 0
510         if disabled:
511             gplink_options |= dsdb.GPLINK_OPT_DISABLE
512         if enforced:
513             gplink_options |= dsdb.GPLINK_OPT_ENFORCE
514
515         # Check if valid GPO DN
516         try:
517             msg = get_gpo_info(self.samdb, gpo=gpo)[0]
518         except Exception, e:
519             raise CommandError("GPO %s does not exist" % gpo_dn, e)
520         gpo_dn = get_gpo_dn(self.samdb, gpo)
521
522         # Check if valid Container DN
523         try:
524             msg = self.samdb.search(base=container_dn, scope=ldb.SCOPE_BASE,
525                                     expression="(objectClass=*)",
526                                     attrs=['gPlink'])[0]
527         except Exception, e:
528             raise CommandError("Could not find container DN %s" % container_dn, e)
529
530         # Update existing GPlinks or Add new one
531         existing_gplink = False
532         if 'gPLink' in msg:
533             gplist = parse_gplink(msg['gPLink'][0])
534             existing_gplink = True
535             found = False
536             for g in gplist:
537                 if g['dn'].lower() == gpo_dn.lower():
538                     g['options'] = gplink_options
539                     found = True
540                     break
541             if not found:
542                 gplist.insert(0, { 'dn' : gpo_dn, 'options' : gplink_options })
543         else:
544             gplist = []
545             gplist.append({ 'dn' : gpo_dn, 'options' : gplink_options })
546
547         gplink_str = encode_gplink(gplist)
548
549         m = ldb.Message()
550         m.dn = ldb.Dn(self.samdb, container_dn)
551
552         if existing_gplink:
553             m['new_value'] = ldb.MessageElement(gplink_str, ldb.FLAG_MOD_REPLACE, 'gPLink')
554         else:
555             m['new_value'] = ldb.MessageElement(gplink_str, ldb.FLAG_MOD_ADD, 'gPLink')
556
557         try:
558             self.samdb.modify(m)
559         except Exception, e:
560             raise CommandError("Error adding GPO Link", e)
561
562         self.outf.write("Added/Updated GPO link\n")
563         cmd_getlink().run(container_dn, H, sambaopts, credopts, versionopts)
564
565
566 class cmd_dellink(Command):
567     """Delete GPO link from a container"""
568
569     synopsis = "%prog <container_dn> <gpo> [options]"
570
571     takes_optiongroups = {
572         "sambaopts": options.SambaOptions,
573         "versionopts": options.VersionOptions,
574         "credopts": options.CredentialsOptions,
575     }
576
577     takes_args = ['container_dn', 'gpo']
578
579     takes_options = [
580         Option("-H", help="LDB URL for database or target server", type=str),
581         ]
582
583     def run(self, container_dn, gpo_dn, H=None, sambaopts=None, credopts=None,
584                 versionopts=None):
585
586         self.lp = sambaopts.get_loadparm()
587         self.creds = credopts.get_credentials(self.lp, fallback_machine=True)
588
589         self.url = dc_url(self.lp, self.creds, H)
590
591         samdb_connect(self)
592
593         # Check if valid GPO
594         try:
595             msg = get_gpo_info(self.sambdb, gpo=gpo)[0]
596         except Exception, e:
597                 raise CommandError("GPO %s does not exist" % gpo, e)
598         gpo_dn = get_gpo_dn(self.samdb, gpo)
599
600         # Check if valid Container DN and get existing GPlinks
601         try:
602             msg = self.samdb.search(base=container_dn, scope=ldb.SCOPE_BASE,
603                                     expression="(objectClass=*)",
604                                     attrs=['gPlink'])[0]
605         except Exception, e:
606             raise CommandError("Could not find container DN %s" % dn, e)
607
608         if 'gPLink' in msg:
609             gplist = parse_gplink(msg['gPLink'][0])
610             for g in gplist:
611                 if g['dn'].lower() == gpo_dn.lower():
612                     gplist.remove(g)
613                     break
614         else:
615             raise CommandError("Specified GPO is not linked to this container")
616
617         m = ldb.Message()
618         m.dn = ldb.Dn(self.samdb, container_dn)
619
620         if gplist:
621             gplink_str = encode_gplink(gplist)
622             m['new_value'] = ldb.MessageElement(gplink_str, ldb.FLAG_MOD_REPLACE, 'gPLink')
623         else:
624             m['new_value'] = ldb.MessageElement('', ldb.FLAG_MOD_DELETE, 'gPLink')
625
626         try:
627             self.samdb.modify(m)
628         except Exception, e:
629             raise CommandError("Error Removing GPO Link (%s)" % e)
630
631         self.outf.write("Deleted GPO link.\n")
632         cmd_getlink().run(container_dn, H, sambaopts, credopts, versionopts)
633
634
635 class cmd_getinheritance(Command):
636     """Get inheritance flag for a container"""
637
638     synopsis = "%prog <container_dn> [options]"
639
640     takes_optiongroups = {
641         "sambaopts": options.SambaOptions,
642         "versionopts": options.VersionOptions,
643         "credopts": options.CredentialsOptions,
644     }
645
646     takes_args = ['container_dn']
647
648     takes_options = [
649         Option("-H", help="LDB URL for database or target server", type=str)
650         ]
651
652     def run(self, container_dn, H=None, sambaopts=None, credopts=None,
653                 versionopts=None):
654
655         self.url = H
656         self.lp = sambaopts.get_loadparm()
657
658         self.creds = credopts.get_credentials(self.lp, fallback_machine=True)
659
660         samdb_connect(self)
661
662         try:
663             msg = self.samdb.search(base=container_dn, scope=ldb.SCOPE_BASE,
664                                     expression="(objectClass=*)",
665                                     attrs=['gPOptions'])[0]
666         except Exception, e:
667             raise CommandError("Could not find Container DN %s" % container_dn, e)
668
669         inheritance = 0
670         if 'gPOptions' in msg:
671             inheritance = int(msg['gPOptions'][0])
672
673         if inheritance == dsdb.GPO_BLOCK_INHERITANCE:
674             self.outf.write("Container has GPO_BLOCK_INHERITANCE\n")
675         else:
676             self.outf.write("Container has GPO_INHERIT\n")
677
678
679 class cmd_setinheritance(Command):
680     """Set inheritance flag on a container"""
681
682     synopsis = "%prog <container_dn> <block|inherit> [options]"
683
684     takes_optiongroups = {
685         "sambaopts": options.SambaOptions,
686         "versionopts": options.VersionOptions,
687         "credopts": options.CredentialsOptions,
688     }
689
690     takes_args = [ 'container_dn', 'inherit_state' ]
691
692     takes_options = [
693         Option("-H", help="LDB URL for database or target server", type=str)
694         ]
695
696     def run(self, container_dn, inherit_state, H=None, sambaopts=None, credopts=None,
697                 versionopts=None):
698
699         if inherit_state.lower() == 'block':
700             inheritance = dsdb.GPO_BLOCK_INHERITANCE
701         elif inherit_state.lower() == 'inherit':
702             inheritance = dsdb.GPO_INHERIT
703         else:
704             raise CommandError("Unknown inheritance state (%s)" % inherit_state)
705
706         self.url = H
707         self.lp = sambaopts.get_loadparm()
708
709         self.creds = credopts.get_credentials(self.lp, fallback_machine=True)
710
711         samdb_connect(self)
712
713         try:
714             msg = self.samdb.search(base=container_dn, scope=ldb.SCOPE_BASE,
715                                     expression="(objectClass=*)",
716                                     attrs=['gPOptions'])[0]
717         except Exception, e:
718             raise CommandError("Could not find Container DN %s" % container_dn, e)
719
720         m = ldb.Message()
721         m.dn = ldb.Dn(self.samdb, container_dn)
722
723         if 'gPOptions' in msg:
724             m['new_value'] = ldb.MessageElement(str(inheritance), ldb.FLAG_MOD_REPLACE, 'gPOptions')
725         else:
726             m['new_value'] = ldb.MessageElement(str(inheritance), ldb.FLAG_MOD_ADD, 'gPOptions')
727
728         try:
729             self.samdb.modify(m)
730         except Exception, e:
731             raise CommandError("Error setting inheritance state %s" % inherit_state, e)
732
733
734 class cmd_fetch(Command):
735     """Download a GPO"""
736
737     synopsis = "%prog <gpo> [options]"
738
739     takes_optiongroups = {
740         "sambaopts": options.SambaOptions,
741         "versionopts": options.VersionOptions,
742         "credopts": options.CredentialsOptions,
743     }
744
745     takes_args = ['gpo']
746
747     takes_options = [
748         Option("-H", help="LDB URL for database or target server", type=str),
749         Option("--tmpdir", help="Temporary directory for copying policy files", type=str)
750         ]
751
752     def run(self, gpo, H=None, tmpdir=None, sambaopts=None, credopts=None, versionopts=None):
753
754         self.lp = sambaopts.get_loadparm()
755         self.creds = credopts.get_credentials(self.lp, fallback_machine=True)
756
757         dc_hostname = netcmd_finddc(self.lp, self.creds)
758         self.url = dc_url(self.lp, self.creds, H, dc=dc_hostname)
759
760         samdb_connect(self)
761         try:
762             msg = get_gpo_info(self.samdb, gpo)[0]
763         except Exception, e:
764             raise CommandError("GPO %s does not exist" % gpo)
765
766         # verify UNC path
767         unc = msg['gPCFileSysPath'][0]
768         try:
769             [dom_name, service, sharepath] = parse_unc(unc)
770         except ValueError:
771             raise CommandError("Invalid GPO path (%s)" % unc)
772
773         # SMB connect to DC
774         try:
775             conn = smb.SMB(dc_hostname, service, lp=self.lp, creds=self.creds)
776         except Exception, e:
777             raise CommandError("Error connecting to '%s' using SMB" % dc_hostname, e)
778
779         # Copy GPT
780         if tmpdir is None:
781             tmpdir = "/tmp"
782         if not os.path.isdir(tmpdir):
783             raise CommandError("Temoprary directory '%s' does not exist" % tmpdir)
784
785         localdir = os.path.join(tmpdir, "policy")
786         if not os.path.isdir(localdir):
787             os.mkdir(localdir)
788
789         gpodir = os.path.join(localdir, gpo)
790         if os.path.isdir(gpodir):
791             raise CommandError("GPO directory '%s' already exists, refusing to overwrite" % gpodir)
792
793         try:
794             os.mkdir(gpodir)
795             copy_directory_remote_to_local(conn, sharepath, gpodir)
796         except Exception, e:
797             # FIXME: Catch more specific exception
798             raise CommandError("Error copying GPO from DC", e)
799         self.outf.write('GPO copied to %s\n' % gpodir)
800
801
802 class cmd_create(Command):
803     """Create an empty GPO"""
804
805     synopsis = "%prog <displayname> [options]"
806
807     takes_optiongroups = {
808         "sambaopts": options.SambaOptions,
809         "versionopts": options.VersionOptions,
810         "credopts": options.CredentialsOptions,
811     }
812
813     takes_args = ['displayname']
814
815     takes_options = [
816         Option("-H", help="LDB URL for database or target server", type=str),
817         Option("--tmpdir", help="Temporary directory for copying policy files", type=str)
818         ]
819
820     def run(self, displayname, H=None, tmpdir=None, sambaopts=None, credopts=None, 
821             versionopts=None):
822
823         self.lp = sambaopts.get_loadparm()
824         self.creds = credopts.get_credentials(self.lp, fallback_machine=True)
825
826         self.url = dc_url(self.lp, self.creds, url=H)
827
828         dc_hostname = netcmd_finddc(self.lp, self.creds)
829         samdb_connect(self)
830
831         msg = get_gpo_info(self.samdb, displayname=displayname)
832         if msg.count > 0:
833             raise CommandError("A GPO already existing with name '%s'" % displayname)
834
835         # Create new GUID
836         guid  = str(uuid.uuid4())
837         gpo = "{%s}" % guid.upper()
838         realm = self.lp.get('realm')
839         unc_path = "\\\\%s\\sysvol\\%s\\Policies\\%s" % (realm, realm, gpo)
840
841         # Create GPT
842         if tmpdir is None:
843             tmpdir = "/tmp"
844         if not os.path.isdir(tmpdir):
845             raise CommandError("Temporary directory '%s' does not exist" % tmpdir)
846
847         localdir = os.path.join(tmpdir, "policy")
848         if not os.path.isdir(localdir):
849             os.mkdir(localdir)
850
851         gpodir = os.path.join(localdir, gpo)
852         if os.path.isdir(gpodir):
853             raise CommandError("GPO directory '%s' already exists, refusing to overwrite" % gpodir)
854
855         try:
856             os.mkdir(gpodir)
857             os.mkdir(os.path.join(gpodir, "Machine"))
858             os.mkdir(os.path.join(gpodir, "User"))
859             gpt_contents = "[General]\r\nVersion=0\r\n"
860             file(os.path.join(gpodir, "GPT.INI"), "w").write(gpt_contents)
861         except Exception, e:
862             raise CommandError("Error Creating GPO files", e) 
863
864         # Connect to DC over SMB
865         [dom_name, service, sharepath] = parse_unc(unc_path)
866         try:
867             conn = smb.SMB(dc_hostname, service, lp=self.lp, creds=self.creds)
868         except Exception, e:
869             raise CommandError("Error connecting to '%s' using SMB" % dc_hostname, e)
870
871         self.samdb.transaction_start()
872         try:
873             # Add cn=<guid>
874             gpo_dn = self.samdb.get_default_basedn()
875             gpo_dn.add_child(ldb.Dn(self.samdb, "CN=Policies,CN=System"))
876             gpo_dn.add_child(ldb.Dn(self.samdb, "CN=%s" % gpo))
877
878             m = ldb.Message()
879             m.dn = ldb.Dn(self.samdb, gpo_dn.get_linearized())
880             m['a01'] = ldb.MessageElement("groupPolicyContainer", ldb.FLAG_MOD_ADD, "objectClass")
881             m['a02'] = ldb.MessageElement(displayname, ldb.FLAG_MOD_ADD, "displayName")
882             m['a03'] = ldb.MessageElement(unc_path, ldb.FLAG_MOD_ADD, "gPCFileSysPath")
883             m['a04'] = ldb.MessageElement("0", ldb.FLAG_MOD_ADD, "flags")
884             m['a05'] = ldb.MessageElement("0", ldb.FLAG_MOD_ADD, "versionNumber")
885             m['a06'] = ldb.MessageElement("TRUE", ldb.FLAG_MOD_ADD, "showInAdvancedViewOnly")
886             m['a07'] = ldb.MessageElement("2", ldb.FLAG_MOD_ADD, "gpcFunctionalityVersion")
887             self.samdb.add(m)
888
889             # Add cn=User,cn=<guid>
890             m = ldb.Message()
891             m.dn = ldb.Dn(self.samdb, "CN=User,%s" % str(gpo_dn))
892             m['a01'] = ldb.MessageElement("container", ldb.FLAG_MOD_ADD, "objectClass")
893             m['a02'] = ldb.MessageElement("TRUE", ldb.FLAG_MOD_ADD, "showInAdvancedViewOnly")
894             self.samdb.add(m)
895
896             # Add cn=Machine,cn=<guid>
897             m = ldb.Message()
898             m.dn = ldb.Dn(self.samdb, "CN=Machine,%s" % str(gpo_dn))
899             m['a01'] = ldb.MessageElement("container", ldb.FLAG_MOD_ADD, "objectClass")
900             m['a02'] = ldb.MessageElement("TRUE", ldb.FLAG_MOD_ADD, "showInAdvancedViewOnly")
901             self.samdb.add(m)
902
903             # Copy GPO files over SMB
904             create_directory_hier(conn, sharepath)
905             copy_directory_local_to_remote(conn, gpodir, sharepath)
906
907             # Get new security descriptor
908             msg = get_gpo_info(self.samdb, gpo=gpo)[0]
909             ds_sd_ndr = msg['ntSecurityDescriptor'][0]
910             ds_sd = ndr_unpack(security.descriptor, ds_sd_ndr).as_sddl()
911
912             # Create a file system security descriptor
913             fs_sd = security.descriptor(dsacl2fsacl(ds_sd, self.samdb.get_domain_sid()))
914
915             # Set ACL
916             conn.set_acl(sharepath, fs_sd)
917         except:
918             self.samdb.transaction_cancel()
919             raise
920         else:
921             self.samdb.transaction_commit()
922
923         self.outf.write("GPO '%s' created as %s\n" % (displayname, gpo))
924
925
926 class cmd_gpo(SuperCommand):
927     """Group Policy Object (GPO) management"""
928
929     subcommands = {}
930     subcommands["listall"] = cmd_listall()
931     subcommands["list"] = cmd_list()
932     subcommands["show"] = cmd_show()
933     subcommands["getlink"] = cmd_getlink()
934     subcommands["setlink"] = cmd_setlink()
935     subcommands["dellink"] = cmd_dellink()
936     subcommands["getinheritance"] = cmd_getinheritance()
937     subcommands["setinheritance"] = cmd_setinheritance()
938     subcommands["fetch"] = cmd_fetch()
939     subcommands["create"] = cmd_create()