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