675b7b2e29105fce6915ac698d329580b22bf41c
[metze/samba/wip.git] / python / samba / netcmd / gpo.py
1 # implement samba_tool gpo commands
2 #
3 # Copyright Andrew Tridgell 2010
4 # Copyright Amitay Isaacs 2011-2012 <amitay@gmail.com>
5 #
6 # based on C implementation by Guenther Deschner and Wilco Baan Hofman
7 #
8 # This program is free software; you can redistribute it and/or modify
9 # it under the terms of the GNU General Public License as published by
10 # the Free Software Foundation; either version 3 of the License, or
11 # (at your option) any later version.
12 #
13 # This program is distributed in the hope that it will be useful,
14 # but WITHOUT ANY WARRANTY; without even the implied warranty of
15 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16 # GNU General Public License for more details.
17 #
18 # You should have received a copy of the GNU General Public License
19 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
20 #
21
22 import os
23 import samba.getopt as options
24 import ldb
25
26 from samba.auth import system_session
27 from samba.netcmd import (
28     Command,
29     CommandError,
30     Option,
31     SuperCommand,
32     )
33 from samba.samdb import SamDB
34 from samba import dsdb
35 from samba.dcerpc import security
36 from samba.ndr import ndr_unpack
37 import samba.security
38 import samba.auth
39 from samba.auth import AUTH_SESSION_INFO_DEFAULT_GROUPS, AUTH_SESSION_INFO_AUTHENTICATED, AUTH_SESSION_INFO_SIMPLE_PRIVILEGES
40 from samba.netcmd.common import netcmd_finddc
41 from samba import policy
42 from samba import smb
43 import uuid
44 from samba.ntacls import dsacl2fsacl
45 from samba.dcerpc import nbt
46 from samba.net import Net
47
48
49 def samdb_connect(ctx):
50     '''make a ldap connection to the server'''
51     try:
52         ctx.samdb = SamDB(url=ctx.url,
53                           session_info=system_session(),
54                           credentials=ctx.creds, lp=ctx.lp)
55     except Exception, e:
56         raise CommandError("LDAP connection to %s failed " % ctx.url, e)
57
58
59 def attr_default(msg, attrname, default):
60     '''get an attribute from a ldap msg with a default'''
61     if attrname in msg:
62         return msg[attrname][0]
63     return default
64
65
66 def gpo_flags_string(value):
67     '''return gpo flags string'''
68     flags = policy.get_gpo_flags(value)
69     if not flags:
70         ret = 'NONE'
71     else:
72         ret = ' '.join(flags)
73     return ret
74
75
76 def gplink_options_string(value):
77     '''return gplink options string'''
78     options = policy.get_gplink_options(value)
79     if not options:
80         ret = 'NONE'
81     else:
82         ret = ' '.join(options)
83     return ret
84
85
86 def parse_gplink(gplink):
87     '''parse a gPLink into an array of dn and options'''
88     ret = []
89     a = gplink.split(']')
90     for g in a:
91         if not g:
92             continue
93         d = g.split(';')
94         if len(d) != 2 or not d[0].startswith("[LDAP://"):
95             raise RuntimeError("Badly formed gPLink '%s'" % g)
96         ret.append({ 'dn' : d[0][8:], 'options' : int(d[1])})
97     return ret
98
99
100 def encode_gplink(gplist):
101     '''Encode an array of dn and options into gPLink string'''
102     ret = ''
103     for g in gplist:
104         ret += "[LDAP://%s;%d]" % (g['dn'], g['options'])
105     return ret
106
107
108 def dc_url(lp, creds, url=None, dc=None):
109     '''If URL is not specified, return URL for writable DC.
110     If dc is provided, use that to construct ldap URL'''
111
112     if url is None:
113         if dc is None:
114             try:
115                 dc = netcmd_finddc(lp, creds)
116             except Exception, e:
117                 raise RuntimeError("Could not find a DC for domain", e)
118         url = 'ldap://' + dc
119     return url
120
121
122 def get_gpo_dn(samdb, gpo):
123     '''Construct the DN for gpo'''
124
125     dn = samdb.get_default_basedn()
126     dn.add_child(ldb.Dn(samdb, "CN=Policies,CN=System"))
127     dn.add_child(ldb.Dn(samdb, "CN=%s" % gpo))
128     return dn
129
130
131 def get_gpo_info(samdb, gpo=None, displayname=None, dn=None,
132                  sd_flags=security.SECINFO_OWNER|security.SECINFO_GROUP|security.SECINFO_DACL|security.SECINFO_SACL):
133     '''Get GPO information using gpo, displayname or dn'''
134
135     policies_dn = samdb.get_default_basedn()
136     policies_dn.add_child(ldb.Dn(samdb, "CN=Policies,CN=System"))
137
138     base_dn = policies_dn
139     search_expr = "(objectClass=groupPolicyContainer)"
140     search_scope = ldb.SCOPE_ONELEVEL
141
142     if gpo is not None:
143         search_expr = "(&(objectClass=groupPolicyContainer)(name=%s))" % ldb.binary_encode(gpo)
144
145     if displayname is not None:
146         search_expr = "(&(objectClass=groupPolicyContainer)(displayname=%s))" % ldb.binary_encode(displayname)
147
148     if dn is not None:
149         base_dn = dn
150         search_scope = ldb.SCOPE_BASE
151
152     try:
153         msg = samdb.search(base=base_dn, scope=search_scope,
154                             expression=search_expr,
155                             attrs=['nTSecurityDescriptor',
156                                     'versionNumber',
157                                     'flags',
158                                     'name',
159                                     'displayName',
160                                     'gPCFileSysPath'],
161                             controls=['sd_flags:1:%d' % sd_flags])
162     except Exception, e:
163         if gpo is not None:
164             mesg = "Cannot get information for GPO %s" % gpo
165         else:
166             mesg = "Cannot get information for GPOs"
167         raise CommandError(mesg, e)
168
169     return msg
170
171
172 def get_gpo_containers(samdb, gpo):
173     '''lists dn of containers for a GPO'''
174
175     search_expr = "(&(objectClass=*)(gPLink=*%s*))" % gpo
176     try:
177         msg = samdb.search(expression=search_expr, attrs=['gPLink'])
178     except Exception, e:
179         raise CommandError("Could not find container(s) with GPO %s" % gpo, e)
180
181     return msg
182
183
184 def del_gpo_link(samdb, container_dn, gpo):
185     '''delete GPO link for the container'''
186     # Check if valid Container DN and get existing GPlinks
187     try:
188         msg = samdb.search(base=container_dn, scope=ldb.SCOPE_BASE,
189                             expression="(objectClass=*)",
190                             attrs=['gPLink'])[0]
191     except Exception, e:
192         raise CommandError("Container '%s' does not exist" % container_dn, e)
193
194     found = False
195     gpo_dn = str(get_gpo_dn(samdb, gpo))
196     if 'gPLink' in msg:
197         gplist = parse_gplink(msg['gPLink'][0])
198         for g in gplist:
199             if g['dn'].lower() == gpo_dn.lower():
200                 gplist.remove(g)
201                 found = True
202                 break
203     else:
204         raise CommandError("No GPO(s) linked to this container")
205
206     if not found:
207         raise CommandError("GPO '%s' not linked to this container" % gpo)
208
209     m = ldb.Message()
210     m.dn = container_dn
211     if gplist:
212         gplink_str = encode_gplink(gplist)
213         m['r0'] = ldb.MessageElement(gplink_str, ldb.FLAG_MOD_REPLACE, 'gPLink')
214     else:
215         m['d0'] = ldb.MessageElement(msg['gPLink'][0], ldb.FLAG_MOD_DELETE, 'gPLink')
216     try:
217         samdb.modify(m)
218     except Exception, e:
219         raise CommandError("Error removing GPO from container", e)
220
221
222 def parse_unc(unc):
223     '''Parse UNC string into a hostname, a service, and a filepath'''
224     if unc.startswith('\\\\') and unc.startswith('//'):
225         raise ValueError("UNC doesn't start with \\\\ or //")
226     tmp = unc[2:].split('/', 2)
227     if len(tmp) == 3:
228         return tmp
229     tmp = unc[2:].split('\\', 2)
230     if len(tmp) == 3:
231         return tmp
232     raise ValueError("Invalid UNC string: %s" % unc)
233
234
235 def copy_directory_remote_to_local(conn, remotedir, localdir):
236     if not os.path.isdir(localdir):
237         os.mkdir(localdir)
238     r_dirs = [ remotedir ]
239     l_dirs = [ localdir ]
240     while r_dirs:
241         r_dir = r_dirs.pop()
242         l_dir = l_dirs.pop()
243
244         dirlist = conn.list(r_dir)
245         for e in dirlist:
246             r_name = r_dir + '\\' + e['name']
247             l_name = os.path.join(l_dir, e['name'])
248
249             if e['attrib'] & smb.FILE_ATTRIBUTE_DIRECTORY:
250                 r_dirs.append(r_name)
251                 l_dirs.append(l_name)
252                 os.mkdir(l_name)
253             else:
254                 data = conn.loadfile(r_name)
255                 file(l_name, 'w').write(data)
256
257
258 def copy_directory_local_to_remote(conn, localdir, remotedir):
259     if not conn.chkpath(remotedir):
260         conn.mkdir(remotedir)
261     l_dirs = [ localdir ]
262     r_dirs = [ remotedir ]
263     while l_dirs:
264         l_dir = l_dirs.pop()
265         r_dir = r_dirs.pop()
266
267         dirlist = os.listdir(l_dir)
268         for e in dirlist:
269             l_name = os.path.join(l_dir, e)
270             r_name = r_dir + '\\' + e
271
272             if os.path.isdir(l_name):
273                 l_dirs.append(l_name)
274                 r_dirs.append(r_name)
275                 conn.mkdir(r_name)
276             else:
277                 data = file(l_name, 'r').read()
278                 conn.savefile(r_name, data)
279
280
281 def create_directory_hier(conn, remotedir):
282     elems = remotedir.replace('/', '\\').split('\\')
283     path = ""
284     for e in elems:
285         path = path + '\\' + e
286         if not conn.chkpath(path):
287             conn.mkdir(path)
288
289
290 class cmd_listall(Command):
291     """List all GPOs."""
292
293     synopsis = "%prog [options]"
294
295     takes_optiongroups = {
296         "sambaopts": options.SambaOptions,
297         "versionopts": options.VersionOptions,
298         "credopts": options.CredentialsOptions,
299     }
300
301     takes_options = [
302         Option("-H", "--URL", help="LDB URL for database or target server", type=str,
303                metavar="URL", dest="H")
304         ]
305
306     def run(self, H=None, sambaopts=None, credopts=None, versionopts=None):
307
308         self.lp = sambaopts.get_loadparm()
309         self.creds = credopts.get_credentials(self.lp, fallback_machine=True)
310
311         self.url = dc_url(self.lp, self.creds, H)
312
313         samdb_connect(self)
314
315         msg = get_gpo_info(self.samdb, None)
316
317         for m in msg:
318             self.outf.write("GPO          : %s\n" % m['name'][0])
319             self.outf.write("display name : %s\n" % m['displayName'][0])
320             self.outf.write("path         : %s\n" % m['gPCFileSysPath'][0])
321             self.outf.write("dn           : %s\n" % m.dn)
322             self.outf.write("version      : %s\n" % attr_default(m, 'versionNumber', '0'))
323             self.outf.write("flags        : %s\n" % gpo_flags_string(int(attr_default(m, 'flags', 0))))
324             self.outf.write("\n")
325
326
327 class cmd_list(Command):
328     """List GPOs for an account."""
329
330     synopsis = "%prog <username> [options]"
331
332     takes_args = ['username']
333     takes_optiongroups = {
334         "sambaopts": options.SambaOptions,
335         "versionopts": options.VersionOptions,
336         "credopts": options.CredentialsOptions,
337     }
338
339     takes_options = [
340         Option("-H", "--URL", help="LDB URL for database or target server",
341             type=str, metavar="URL", dest="H")
342         ]
343
344     def run(self, username, H=None, sambaopts=None, credopts=None, versionopts=None):
345
346         self.lp = sambaopts.get_loadparm()
347         self.creds = credopts.get_credentials(self.lp, fallback_machine=True)
348
349         self.url = dc_url(self.lp, self.creds, H)
350
351         samdb_connect(self)
352
353         try:
354             msg = self.samdb.search(expression='(&(|(samAccountName=%s)(samAccountName=%s$))(objectClass=User))' %
355                                                 (ldb.binary_encode(username),ldb.binary_encode(username)))
356             user_dn = msg[0].dn
357         except Exception:
358             raise CommandError("Failed to find account %s" % username)
359
360         # check if its a computer account
361         try:
362             msg = self.samdb.search(base=user_dn, scope=ldb.SCOPE_BASE, attrs=['objectClass'])[0]
363             is_computer = 'computer' in msg['objectClass']
364         except Exception:
365             raise CommandError("Failed to find objectClass for user %s" % username)
366
367         session_info_flags = ( AUTH_SESSION_INFO_DEFAULT_GROUPS |
368                                AUTH_SESSION_INFO_AUTHENTICATED )
369
370         # When connecting to a remote server, don't look up the local privilege DB
371         if self.url is not None and self.url.startswith('ldap'):
372             session_info_flags |= AUTH_SESSION_INFO_SIMPLE_PRIVILEGES
373
374         session = samba.auth.user_session(self.samdb, lp_ctx=self.lp, dn=user_dn,
375                                           session_info_flags=session_info_flags)
376
377         token = session.security_token
378
379         gpos = []
380
381         inherit = True
382         dn = ldb.Dn(self.samdb, str(user_dn)).parent()
383         while True:
384             msg = self.samdb.search(base=dn, scope=ldb.SCOPE_BASE, attrs=['gPLink', 'gPOptions'])[0]
385             if 'gPLink' in msg:
386                 glist = parse_gplink(msg['gPLink'][0])
387                 for g in glist:
388                     if not inherit and not (g['options'] & dsdb.GPLINK_OPT_ENFORCE):
389                         continue
390                     if g['options'] & dsdb.GPLINK_OPT_DISABLE:
391                         continue
392
393                     try:
394                         sd_flags=security.SECINFO_OWNER|security.SECINFO_GROUP|security.SECINFO_DACL
395                         gmsg = self.samdb.search(base=g['dn'], scope=ldb.SCOPE_BASE,
396                                                  attrs=['name', 'displayName', 'flags',
397                                                         'nTSecurityDescriptor'],
398                                                  controls=['sd_flags:1:%d' % sd_flags])
399                         secdesc_ndr = gmsg[0]['nTSecurityDescriptor'][0]
400                         secdesc = ndr_unpack(security.descriptor, secdesc_ndr)
401                     except Exception:
402                         self.outf.write("Failed to fetch gpo object with nTSecurityDescriptor %s\n" %
403                             g['dn'])
404                         continue
405
406                     try:
407                         samba.security.access_check(secdesc, token,
408                                                     security.SEC_STD_READ_CONTROL |
409                                                     security.SEC_ADS_LIST |
410                                                     security.SEC_ADS_READ_PROP)
411                     except RuntimeError:
412                         self.outf.write("Failed access check on %s\n" % msg.dn)
413                         continue
414
415                     # check the flags on the GPO
416                     flags = int(attr_default(gmsg[0], 'flags', 0))
417                     if is_computer and (flags & dsdb.GPO_FLAG_MACHINE_DISABLE):
418                         continue
419                     if not is_computer and (flags & dsdb.GPO_FLAG_USER_DISABLE):
420                         continue
421                     gpos.append((gmsg[0]['displayName'][0], gmsg[0]['name'][0]))
422
423             # check if this blocks inheritance
424             gpoptions = int(attr_default(msg, 'gPOptions', 0))
425             if gpoptions & dsdb.GPO_BLOCK_INHERITANCE:
426                 inherit = False
427
428             if dn == self.samdb.get_default_basedn():
429                 break
430             dn = dn.parent()
431
432         if is_computer:
433             msg_str = 'computer'
434         else:
435             msg_str = 'user'
436
437         self.outf.write("GPOs for %s %s\n" % (msg_str, username))
438         for g in gpos:
439             self.outf.write("    %s %s\n" % (g[0], g[1]))
440
441
442 class cmd_show(Command):
443     """Show information for a GPO."""
444
445     synopsis = "%prog <gpo> [options]"
446
447     takes_optiongroups = {
448         "sambaopts": options.SambaOptions,
449         "versionopts": options.VersionOptions,
450         "credopts": options.CredentialsOptions,
451     }
452
453     takes_args = ['gpo']
454
455     takes_options = [
456         Option("-H", help="LDB URL for database or target server", type=str)
457         ]
458
459     def run(self, gpo, H=None, sambaopts=None, credopts=None, versionopts=None):
460
461         self.lp = sambaopts.get_loadparm()
462         self.creds = credopts.get_credentials(self.lp, fallback_machine=True)
463
464         self.url = dc_url(self.lp, self.creds, H)
465
466         samdb_connect(self)
467
468         try:
469             msg = get_gpo_info(self.samdb, gpo)[0]
470         except Exception:
471             raise CommandError("GPO '%s' does not exist" % gpo)
472
473         try:
474             secdesc_ndr = msg['nTSecurityDescriptor'][0]
475             secdesc = ndr_unpack(security.descriptor, secdesc_ndr)
476             secdesc_sddl = secdesc.as_sddl()
477         except Exception:
478             secdesc_sddl = "<hidden>"
479
480         self.outf.write("GPO          : %s\n" % msg['name'][0])
481         self.outf.write("display name : %s\n" % msg['displayName'][0])
482         self.outf.write("path         : %s\n" % msg['gPCFileSysPath'][0])
483         self.outf.write("dn           : %s\n" % msg.dn)
484         self.outf.write("version      : %s\n" % attr_default(msg, 'versionNumber', '0'))
485         self.outf.write("flags        : %s\n" % gpo_flags_string(int(attr_default(msg, 'flags', 0))))
486         self.outf.write("ACL          : %s\n" % secdesc_sddl)
487         self.outf.write("\n")
488
489
490 class cmd_getlink(Command):
491     """List GPO Links for a container."""
492
493     synopsis = "%prog <container_dn> [options]"
494
495     takes_optiongroups = {
496         "sambaopts": options.SambaOptions,
497         "versionopts": options.VersionOptions,
498         "credopts": options.CredentialsOptions,
499     }
500
501     takes_args = ['container_dn']
502
503     takes_options = [
504         Option("-H", help="LDB URL for database or target server", type=str)
505         ]
506
507     def run(self, container_dn, H=None, sambaopts=None, credopts=None,
508                 versionopts=None):
509
510         self.lp = sambaopts.get_loadparm()
511         self.creds = credopts.get_credentials(self.lp, fallback_machine=True)
512
513         self.url = dc_url(self.lp, self.creds, H)
514
515         samdb_connect(self)
516
517         try:
518             msg = self.samdb.search(base=container_dn, scope=ldb.SCOPE_BASE,
519                                     expression="(objectClass=*)",
520                                     attrs=['gPLink'])[0]
521         except Exception:
522             raise CommandError("Container '%s' does not exist" % container_dn)
523
524         if msg['gPLink']:
525             self.outf.write("GPO(s) linked to DN %s\n" % container_dn)
526             gplist = parse_gplink(msg['gPLink'][0])
527             for g in gplist:
528                 msg = get_gpo_info(self.samdb, dn=g['dn'])
529                 self.outf.write("    GPO     : %s\n" % msg[0]['name'][0])
530                 self.outf.write("    Name    : %s\n" % msg[0]['displayName'][0])
531                 self.outf.write("    Options : %s\n" % gplink_options_string(g['options']))
532                 self.outf.write("\n")
533         else:
534             self.outf.write("No GPO(s) linked to DN=%s\n" % container_dn)
535
536
537 class cmd_setlink(Command):
538     """Add or update a GPO link to a container."""
539
540     synopsis = "%prog <container_dn> <gpo> [options]"
541
542     takes_optiongroups = {
543         "sambaopts": options.SambaOptions,
544         "versionopts": options.VersionOptions,
545         "credopts": options.CredentialsOptions,
546     }
547
548     takes_args = ['container_dn', 'gpo']
549
550     takes_options = [
551         Option("-H", help="LDB URL for database or target server", type=str),
552         Option("--disable", dest="disabled", default=False, action='store_true',
553             help="Disable policy"),
554         Option("--enforce", dest="enforced", default=False, action='store_true',
555             help="Enforce policy")
556         ]
557
558     def run(self, container_dn, gpo, H=None, disabled=False, enforced=False,
559                 sambaopts=None, credopts=None, versionopts=None):
560
561         self.lp = sambaopts.get_loadparm()
562         self.creds = credopts.get_credentials(self.lp, fallback_machine=True)
563
564         self.url = dc_url(self.lp, self.creds, H)
565
566         samdb_connect(self)
567
568         gplink_options = 0
569         if disabled:
570             gplink_options |= dsdb.GPLINK_OPT_DISABLE
571         if enforced:
572             gplink_options |= dsdb.GPLINK_OPT_ENFORCE
573
574         # Check if valid GPO DN
575         try:
576             msg = get_gpo_info(self.samdb, gpo=gpo)[0]
577         except Exception:
578             raise CommandError("GPO '%s' does not exist" % gpo)
579         gpo_dn = str(get_gpo_dn(self.samdb, gpo))
580
581         # Check if valid Container DN
582         try:
583             msg = self.samdb.search(base=container_dn, scope=ldb.SCOPE_BASE,
584                                     expression="(objectClass=*)",
585                                     attrs=['gPLink'])[0]
586         except Exception:
587             raise CommandError("Container '%s' does not exist" % container_dn)
588
589         # Update existing GPlinks or Add new one
590         existing_gplink = False
591         if 'gPLink' in msg:
592             gplist = parse_gplink(msg['gPLink'][0])
593             existing_gplink = True
594             found = False
595             for g in gplist:
596                 if g['dn'].lower() == gpo_dn.lower():
597                     g['options'] = gplink_options
598                     found = True
599                     break
600             if found:
601                 raise CommandError("GPO '%s' already linked to this container" % gpo)
602             else:
603                 gplist.insert(0, { 'dn' : gpo_dn, 'options' : gplink_options })
604         else:
605             gplist = []
606             gplist.append({ 'dn' : gpo_dn, 'options' : gplink_options })
607
608         gplink_str = encode_gplink(gplist)
609
610         m = ldb.Message()
611         m.dn = ldb.Dn(self.samdb, container_dn)
612
613         if existing_gplink:
614             m['new_value'] = ldb.MessageElement(gplink_str, ldb.FLAG_MOD_REPLACE, 'gPLink')
615         else:
616             m['new_value'] = ldb.MessageElement(gplink_str, ldb.FLAG_MOD_ADD, 'gPLink')
617
618         try:
619             self.samdb.modify(m)
620         except Exception, e:
621             raise CommandError("Error adding GPO Link", e)
622
623         self.outf.write("Added/Updated GPO link\n")
624         cmd_getlink().run(container_dn, H, sambaopts, credopts, versionopts)
625
626
627 class cmd_dellink(Command):
628     """Delete GPO link from a container."""
629
630     synopsis = "%prog <container_dn> <gpo> [options]"
631
632     takes_optiongroups = {
633         "sambaopts": options.SambaOptions,
634         "versionopts": options.VersionOptions,
635         "credopts": options.CredentialsOptions,
636     }
637
638     takes_args = ['container', 'gpo']
639
640     takes_options = [
641         Option("-H", help="LDB URL for database or target server", type=str),
642         ]
643
644     def run(self, container, gpo, H=None, sambaopts=None, credopts=None,
645                 versionopts=None):
646
647         self.lp = sambaopts.get_loadparm()
648         self.creds = credopts.get_credentials(self.lp, fallback_machine=True)
649
650         self.url = dc_url(self.lp, self.creds, H)
651
652         samdb_connect(self)
653
654         # Check if valid GPO
655         try:
656             get_gpo_info(self.samdb, gpo=gpo)[0]
657         except Exception:
658             raise CommandError("GPO '%s' does not exist" % gpo)
659
660         container_dn = ldb.Dn(self.samdb, container)
661         del_gpo_link(self.samdb, container_dn, gpo)
662         self.outf.write("Deleted GPO link.\n")
663         cmd_getlink().run(container_dn, H, sambaopts, credopts, versionopts)
664
665
666 class cmd_listcontainers(Command):
667     """List all linked containers for a GPO."""
668
669     synopsis = "%prog <gpo> [options]"
670
671     takes_optiongroups = {
672         "sambaopts": options.SambaOptions,
673         "versionopts": options.VersionOptions,
674         "credopts": options.CredentialsOptions,
675     }
676
677     takes_args = ['gpo']
678
679     takes_options = [
680         Option("-H", help="LDB URL for database or target server", type=str)
681         ]
682
683     def run(self, gpo, H=None, sambaopts=None, credopts=None,
684                 versionopts=None):
685
686         self.lp = sambaopts.get_loadparm()
687         self.creds = credopts.get_credentials(self.lp, fallback_machine=True)
688
689         self.url = dc_url(self.lp, self.creds, H)
690
691         samdb_connect(self)
692
693         msg = get_gpo_containers(self.samdb, gpo)
694         if len(msg):
695             self.outf.write("Container(s) using GPO %s\n" % gpo)
696             for m in msg:
697                 self.outf.write("    DN: %s\n" % m['dn'])
698         else:
699             self.outf.write("No Containers using GPO %s\n" % gpo)
700
701
702 class cmd_getinheritance(Command):
703     """Get inheritance flag for a container."""
704
705     synopsis = "%prog <container_dn> [options]"
706
707     takes_optiongroups = {
708         "sambaopts": options.SambaOptions,
709         "versionopts": options.VersionOptions,
710         "credopts": options.CredentialsOptions,
711     }
712
713     takes_args = ['container_dn']
714
715     takes_options = [
716         Option("-H", help="LDB URL for database or target server", type=str)
717         ]
718
719     def run(self, container_dn, H=None, sambaopts=None, credopts=None,
720                 versionopts=None):
721
722         self.lp = sambaopts.get_loadparm()
723         self.creds = credopts.get_credentials(self.lp, fallback_machine=True)
724
725         self.url = dc_url(self.lp, self.creds, H)
726
727         samdb_connect(self)
728
729         try:
730             msg = self.samdb.search(base=container_dn, scope=ldb.SCOPE_BASE,
731                                     expression="(objectClass=*)",
732                                     attrs=['gPOptions'])[0]
733         except Exception:
734             raise CommandError("Container '%s' does not exist" % container_dn)
735
736         inheritance = 0
737         if 'gPOptions' in msg:
738             inheritance = int(msg['gPOptions'][0])
739
740         if inheritance == dsdb.GPO_BLOCK_INHERITANCE:
741             self.outf.write("Container has GPO_BLOCK_INHERITANCE\n")
742         else:
743             self.outf.write("Container has GPO_INHERIT\n")
744
745
746 class cmd_setinheritance(Command):
747     """Set inheritance flag on a container."""
748
749     synopsis = "%prog <container_dn> <block|inherit> [options]"
750
751     takes_optiongroups = {
752         "sambaopts": options.SambaOptions,
753         "versionopts": options.VersionOptions,
754         "credopts": options.CredentialsOptions,
755     }
756
757     takes_args = [ 'container_dn', 'inherit_state' ]
758
759     takes_options = [
760         Option("-H", help="LDB URL for database or target server", type=str)
761         ]
762
763     def run(self, container_dn, inherit_state, H=None, sambaopts=None, credopts=None,
764                 versionopts=None):
765
766         if inherit_state.lower() == 'block':
767             inheritance = dsdb.GPO_BLOCK_INHERITANCE
768         elif inherit_state.lower() == 'inherit':
769             inheritance = dsdb.GPO_INHERIT
770         else:
771             raise CommandError("Unknown inheritance state (%s)" % inherit_state)
772
773         self.lp = sambaopts.get_loadparm()
774         self.creds = credopts.get_credentials(self.lp, fallback_machine=True)
775
776         self.url = dc_url(self.lp, self.creds, H)
777
778         samdb_connect(self)
779         try:
780             msg = self.samdb.search(base=container_dn, scope=ldb.SCOPE_BASE,
781                                     expression="(objectClass=*)",
782                                     attrs=['gPOptions'])[0]
783         except Exception:
784             raise CommandError("Container '%s' does not exist" % container_dn)
785
786         m = ldb.Message()
787         m.dn = ldb.Dn(self.samdb, container_dn)
788
789         if 'gPOptions' in msg:
790             m['new_value'] = ldb.MessageElement(str(inheritance), ldb.FLAG_MOD_REPLACE, 'gPOptions')
791         else:
792             m['new_value'] = ldb.MessageElement(str(inheritance), ldb.FLAG_MOD_ADD, 'gPOptions')
793
794         try:
795             self.samdb.modify(m)
796         except Exception, e:
797             raise CommandError("Error setting inheritance state %s" % inherit_state, e)
798
799
800 class cmd_fetch(Command):
801     """Download a GPO."""
802
803     synopsis = "%prog <gpo> [options]"
804
805     takes_optiongroups = {
806         "sambaopts": options.SambaOptions,
807         "versionopts": options.VersionOptions,
808         "credopts": options.CredentialsOptions,
809     }
810
811     takes_args = ['gpo']
812
813     takes_options = [
814         Option("-H", help="LDB URL for database or target server", type=str),
815         Option("--tmpdir", help="Temporary directory for copying policy files", type=str)
816         ]
817
818     def run(self, gpo, H=None, tmpdir=None, sambaopts=None, credopts=None, versionopts=None):
819
820         self.lp = sambaopts.get_loadparm()
821         self.creds = credopts.get_credentials(self.lp, fallback_machine=True)
822
823         # We need to know writable DC to setup SMB connection
824         if H and H.startswith('ldap://'):
825             dc_hostname = H[7:]
826             self.url = H
827         else:
828             dc_hostname = netcmd_finddc(self.lp, self.creds)
829             self.url = dc_url(self.lp, self.creds, dc=dc_hostname)
830
831         samdb_connect(self)
832         try:
833             msg = get_gpo_info(self.samdb, gpo)[0]
834         except Exception:
835             raise CommandError("GPO '%s' does not exist" % gpo)
836
837         # verify UNC path
838         unc = msg['gPCFileSysPath'][0]
839         try:
840             [dom_name, service, sharepath] = parse_unc(unc)
841         except ValueError:
842             raise CommandError("Invalid GPO path (%s)" % unc)
843
844         # SMB connect to DC
845         try:
846             conn = smb.SMB(dc_hostname, service, lp=self.lp, creds=self.creds)
847         except Exception:
848             raise CommandError("Error connecting to '%s' using SMB" % dc_hostname)
849
850         # Copy GPT
851         if tmpdir is None:
852             tmpdir = "/tmp"
853         if not os.path.isdir(tmpdir):
854             raise CommandError("Temoprary directory '%s' does not exist" % tmpdir)
855
856         localdir = os.path.join(tmpdir, "policy")
857         if not os.path.isdir(localdir):
858             os.mkdir(localdir)
859
860         gpodir = os.path.join(localdir, gpo)
861         if os.path.isdir(gpodir):
862             raise CommandError("GPO directory '%s' already exists, refusing to overwrite" % gpodir)
863
864         try:
865             os.mkdir(gpodir)
866             copy_directory_remote_to_local(conn, sharepath, gpodir)
867         except Exception, e:
868             # FIXME: Catch more specific exception
869             raise CommandError("Error copying GPO from DC", e)
870         self.outf.write('GPO copied to %s\n' % gpodir)
871
872
873 class cmd_create(Command):
874     """Create an empty GPO."""
875
876     synopsis = "%prog <displayname> [options]"
877
878     takes_optiongroups = {
879         "sambaopts": options.SambaOptions,
880         "versionopts": options.VersionOptions,
881         "credopts": options.CredentialsOptions,
882     }
883
884     takes_args = ['displayname']
885
886     takes_options = [
887         Option("-H", help="LDB URL for database or target server", type=str),
888         Option("--tmpdir", help="Temporary directory for copying policy files", type=str)
889         ]
890
891     def run(self, displayname, H=None, tmpdir=None, sambaopts=None, credopts=None,
892             versionopts=None):
893
894         self.lp = sambaopts.get_loadparm()
895         self.creds = credopts.get_credentials(self.lp, fallback_machine=True)
896
897         net = Net(creds=self.creds, lp=self.lp)
898
899         # We need to know writable DC to setup SMB connection
900         if H and H.startswith('ldap://'):
901             dc_hostname = H[7:]
902             self.url = H
903             flags = (nbt.NBT_SERVER_LDAP |
904                      nbt.NBT_SERVER_DS |
905                      nbt.NBT_SERVER_WRITABLE)
906             cldap_ret = net.finddc(address=dc_hostname, flags=flags)
907         else:
908             flags = (nbt.NBT_SERVER_LDAP |
909                      nbt.NBT_SERVER_DS |
910                      nbt.NBT_SERVER_WRITABLE)
911             cldap_ret = net.finddc(domain=self.lp.get('realm'), flags=flags)
912             dc_hostname = cldap_ret.pdc_dns_name
913             self.url = dc_url(self.lp, self.creds, dc=dc_hostname)
914
915         samdb_connect(self)
916
917         msg = get_gpo_info(self.samdb, displayname=displayname)
918         if msg.count > 0:
919             raise CommandError("A GPO already existing with name '%s'" % displayname)
920
921         # Create new GUID
922         guid  = str(uuid.uuid4())
923         gpo = "{%s}" % guid.upper()
924         realm = cldap_ret.dns_domain
925         unc_path = "\\\\%s\\sysvol\\%s\\Policies\\%s" % (realm, realm, gpo)
926
927         # Create GPT
928         if tmpdir is None:
929             tmpdir = "/tmp"
930         if not os.path.isdir(tmpdir):
931             raise CommandError("Temporary directory '%s' does not exist" % tmpdir)
932
933         localdir = os.path.join(tmpdir, "policy")
934         if not os.path.isdir(localdir):
935             os.mkdir(localdir)
936
937         gpodir = os.path.join(localdir, gpo)
938         if os.path.isdir(gpodir):
939             raise CommandError("GPO directory '%s' already exists, refusing to overwrite" % gpodir)
940
941         try:
942             os.mkdir(gpodir)
943             os.mkdir(os.path.join(gpodir, "Machine"))
944             os.mkdir(os.path.join(gpodir, "User"))
945             gpt_contents = "[General]\r\nVersion=0\r\n"
946             file(os.path.join(gpodir, "GPT.INI"), "w").write(gpt_contents)
947         except Exception, e:
948             raise CommandError("Error Creating GPO files", e)
949
950         # Connect to DC over SMB
951         [dom_name, service, sharepath] = parse_unc(unc_path)
952         try:
953             conn = smb.SMB(dc_hostname, service, lp=self.lp, creds=self.creds)
954         except Exception, e:
955             raise CommandError("Error connecting to '%s' using SMB" % dc_hostname, e)
956
957         self.samdb.transaction_start()
958         try:
959             # Add cn=<guid>
960             gpo_dn = get_gpo_dn(self.samdb, gpo)
961
962             m = ldb.Message()
963             m.dn = gpo_dn
964             m['a01'] = ldb.MessageElement("groupPolicyContainer", ldb.FLAG_MOD_ADD, "objectClass")
965             self.samdb.add(m)
966
967             # Add cn=User,cn=<guid>
968             m = ldb.Message()
969             m.dn = ldb.Dn(self.samdb, "CN=User,%s" % str(gpo_dn))
970             m['a01'] = ldb.MessageElement("container", ldb.FLAG_MOD_ADD, "objectClass")
971             self.samdb.add(m)
972
973             # Add cn=Machine,cn=<guid>
974             m = ldb.Message()
975             m.dn = ldb.Dn(self.samdb, "CN=Machine,%s" % str(gpo_dn))
976             m['a01'] = ldb.MessageElement("container", ldb.FLAG_MOD_ADD, "objectClass")
977             self.samdb.add(m)
978
979             # Get new security descriptor
980             ds_sd_flags = ( security.SECINFO_OWNER |
981                             security.SECINFO_GROUP |
982                             security.SECINFO_DACL )
983             msg = get_gpo_info(self.samdb, gpo=gpo, sd_flags=ds_sd_flags)[0]
984             ds_sd_ndr = msg['nTSecurityDescriptor'][0]
985             ds_sd = ndr_unpack(security.descriptor, ds_sd_ndr).as_sddl()
986
987             # Create a file system security descriptor
988             domain_sid = security.dom_sid(self.samdb.get_domain_sid())
989             fs_sd = dsacl2fsacl(ds_sd, domain_sid, as_sddl=False)
990             fs_sd.type = security.SEC_DESC_SELF_RELATIVE
991             fs_sd.type |= security.SEC_DESC_DACL_PROTECTED
992             fs_sd.type |= security.SEC_DESC_DACL_AUTO_INHERITED
993             fs_sd.type |= security.SEC_DESC_DACL_AUTO_INHERIT_REQ
994             fs_sd.type |= security.SEC_DESC_SACL_AUTO_INHERITED
995
996             # Copy GPO directory
997             create_directory_hier(conn, sharepath)
998
999             # Set ACL
1000             sio = ( security.SECINFO_OWNER |
1001                     security.SECINFO_GROUP |
1002                     security.SECINFO_DACL |
1003                     security.SECINFO_PROTECTED_DACL )
1004             conn.set_acl(sharepath, fs_sd, sio)
1005
1006             # Copy GPO files over SMB
1007             copy_directory_local_to_remote(conn, gpodir, sharepath)
1008
1009             m = ldb.Message()
1010             m.dn = gpo_dn
1011             m['a02'] = ldb.MessageElement(displayname, ldb.FLAG_MOD_REPLACE, "displayName")
1012             m['a03'] = ldb.MessageElement(unc_path, ldb.FLAG_MOD_REPLACE, "gPCFileSysPath")
1013             m['a05'] = ldb.MessageElement("0", ldb.FLAG_MOD_REPLACE, "versionNumber")
1014             m['a07'] = ldb.MessageElement("2", ldb.FLAG_MOD_REPLACE, "gpcFunctionalityVersion")
1015             m['a04'] = ldb.MessageElement("0", ldb.FLAG_MOD_REPLACE, "flags")
1016             controls=["permissive_modify:0"]
1017             self.samdb.modify(m, controls=controls)
1018         except Exception:
1019             self.samdb.transaction_cancel()
1020             raise
1021         else:
1022             self.samdb.transaction_commit()
1023
1024         self.outf.write("GPO '%s' created as %s\n" % (displayname, gpo))
1025
1026
1027 class cmd_del(Command):
1028     """Delete a GPO."""
1029
1030     synopsis = "%prog <gpo> [options]"
1031
1032     takes_optiongroups = {
1033         "sambaopts": options.SambaOptions,
1034         "versionopts": options.VersionOptions,
1035         "credopts": options.CredentialsOptions,
1036     }
1037
1038     takes_args = ['gpo']
1039
1040     takes_options = [
1041         Option("-H", help="LDB URL for database or target server", type=str),
1042         ]
1043
1044     def run(self, gpo, H=None, sambaopts=None, credopts=None,
1045                 versionopts=None):
1046
1047         self.lp = sambaopts.get_loadparm()
1048         self.creds = credopts.get_credentials(self.lp, fallback_machine=True)
1049
1050         # We need to know writable DC to setup SMB connection
1051         if H and H.startswith('ldap://'):
1052             dc_hostname = H[7:]
1053             self.url = H
1054         else:
1055             dc_hostname = netcmd_finddc(self.lp, self.creds)
1056             self.url = dc_url(self.lp, self.creds, dc=dc_hostname)
1057
1058         samdb_connect(self)
1059
1060         # Check if valid GPO
1061         try:
1062             msg = get_gpo_info(self.samdb, gpo=gpo)[0]
1063             unc_path = msg['gPCFileSysPath'][0]
1064         except Exception:
1065             raise CommandError("GPO '%s' does not exist" % gpo)
1066
1067         # Connect to DC over SMB
1068         [dom_name, service, sharepath] = parse_unc(unc_path)
1069         try:
1070             conn = smb.SMB(dc_hostname, service, lp=self.lp, creds=self.creds)
1071         except Exception, e:
1072             raise CommandError("Error connecting to '%s' using SMB" % dc_hostname, e)
1073
1074         self.samdb.transaction_start()
1075         try:
1076             # Check for existing links
1077             msg = get_gpo_containers(self.samdb, gpo)
1078
1079             if len(msg):
1080                 self.outf.write("GPO %s is linked to containers\n" % gpo)
1081                 for m in msg:
1082                     del_gpo_link(self.samdb, m['dn'], gpo)
1083                     self.outf.write("    Removed link from %s.\n" % m['dn'])
1084
1085             # Remove LDAP entries
1086             gpo_dn = get_gpo_dn(self.samdb, gpo)
1087             self.samdb.delete(ldb.Dn(self.samdb, "CN=User,%s" % str(gpo_dn)))
1088             self.samdb.delete(ldb.Dn(self.samdb, "CN=Machine,%s" % str(gpo_dn)))
1089             self.samdb.delete(gpo_dn)
1090
1091             # Remove GPO files
1092             conn.deltree(sharepath)
1093
1094         except Exception:
1095             self.samdb.transaction_cancel()
1096             raise
1097         else:
1098             self.samdb.transaction_commit()
1099
1100         self.outf.write("GPO %s deleted.\n" % gpo)
1101
1102
1103 class cmd_aclcheck(Command):
1104     """Check all GPOs have matching LDAP and DS ACLs."""
1105
1106     synopsis = "%prog [options]"
1107
1108     takes_optiongroups = {
1109         "sambaopts": options.SambaOptions,
1110         "versionopts": options.VersionOptions,
1111         "credopts": options.CredentialsOptions,
1112     }
1113
1114     takes_options = [
1115         Option("-H", "--URL", help="LDB URL for database or target server", type=str,
1116                metavar="URL", dest="H")
1117         ]
1118
1119     def run(self, H=None, sambaopts=None, credopts=None, versionopts=None):
1120
1121         self.lp = sambaopts.get_loadparm()
1122         self.creds = credopts.get_credentials(self.lp, fallback_machine=True)
1123
1124         self.url = dc_url(self.lp, self.creds, H)
1125
1126         # We need to know writable DC to setup SMB connection
1127         if H and H.startswith('ldap://'):
1128             dc_hostname = H[7:]
1129             self.url = H
1130         else:
1131             dc_hostname = netcmd_finddc(self.lp, self.creds)
1132             self.url = dc_url(self.lp, self.creds, dc=dc_hostname)
1133
1134         samdb_connect(self)
1135
1136         msg = get_gpo_info(self.samdb, None)
1137
1138         for m in msg:
1139             # verify UNC path
1140             unc = m['gPCFileSysPath'][0]
1141             try:
1142                 [dom_name, service, sharepath] = parse_unc(unc)
1143             except ValueError:
1144                 raise CommandError("Invalid GPO path (%s)" % unc)
1145
1146             # SMB connect to DC
1147             try:
1148                 conn = smb.SMB(dc_hostname, service, lp=self.lp, creds=self.creds)
1149             except Exception:
1150                 raise CommandError("Error connecting to '%s' using SMB" % dc_hostname)
1151
1152             fs_sd = conn.get_acl(sharepath, security.SECINFO_OWNER | security.SECINFO_GROUP | security.SECINFO_DACL, security.SEC_FLAG_MAXIMUM_ALLOWED)
1153
1154             ds_sd_ndr = m['nTSecurityDescriptor'][0]
1155             ds_sd = ndr_unpack(security.descriptor, ds_sd_ndr).as_sddl()
1156
1157             # Create a file system security descriptor
1158             domain_sid = security.dom_sid(self.samdb.get_domain_sid())
1159             expected_fs_sddl = dsacl2fsacl(ds_sd, domain_sid)
1160
1161             if (fs_sd.as_sddl(domain_sid) != expected_fs_sddl):
1162                 raise CommandError("Invalid GPO ACL %s on path (%s), should be %s" % (fs_sd.as_sddl(domain_sid), sharepath, expected_fs_sddl))
1163
1164
1165 class cmd_gpo(SuperCommand):
1166     """Group Policy Object (GPO) management."""
1167
1168     subcommands = {}
1169     subcommands["listall"] = cmd_listall()
1170     subcommands["list"] = cmd_list()
1171     subcommands["show"] = cmd_show()
1172     subcommands["getlink"] = cmd_getlink()
1173     subcommands["setlink"] = cmd_setlink()
1174     subcommands["dellink"] = cmd_dellink()
1175     subcommands["listcontainers"] = cmd_listcontainers()
1176     subcommands["getinheritance"] = cmd_getinheritance()
1177     subcommands["setinheritance"] = cmd_setinheritance()
1178     subcommands["fetch"] = cmd_fetch()
1179     subcommands["create"] = cmd_create()
1180     subcommands["del"] = cmd_del()
1181     subcommands["aclcheck"] = cmd_aclcheck()