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