3 # Samba4 AD database checker
5 # Copyright (C) Andrew Tridgell 2011
7 # This program is free software; you can redistribute it and/or modify
8 # it under the terms of the GNU General Public License as published by
9 # the Free Software Foundation; either version 3 of the License, or
10 # (at your option) any later version.
12 # This program is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU General Public License for more details.
17 # You should have received a copy of the GNU General Public License
18 # along with this program. If not, see <http://www.gnu.org/licenses/>.
22 import samba
.getopt
as options
23 from samba
.auth
import system_session
24 from samba
.samdb
import SamDB
25 from samba
.netcmd
import (
31 def confirm(self
, msg
):
32 '''confirm an action with the user'''
34 print("%s [YES]" % msg
)
36 v
= raw_input(msg
+ ' [y/N] ')
37 return v
.upper() in ['Y', 'YES']
39 class cmd_dbcheck(Command
):
40 """check local AD database for errors"""
41 synopsis
= "dbcheck <DN> [options]"
43 takes_optiongroups
= {
44 "sambaopts": options
.SambaOptions
,
45 "versionopts": options
.VersionOptions
,
46 "credopts": options
.CredentialsOptionsDouble
,
52 Option("--scope", dest
="scope", default
="SUB",
53 help="Pass search scope that builds DN list. Options: SUB, ONE, BASE"),
54 Option("--fix", dest
="fix", default
=False, action
='store_true',
55 help='Fix any errors found'),
56 Option("--yes", dest
="yes", default
=False, action
='store_true',
57 help="don't confirm changes, just do them all"),
58 Option("--cross-ncs", dest
="cross_ncs", default
=False, action
='store_true',
59 help="cross naming context boundaries"),
60 Option("-v", "--verbose", dest
="verbose", action
="store_true", default
=False,
61 help="Print more details of checking"),
64 def run(self
, DN
=None, verbose
=False, fix
=False, yes
=False, cross_ncs
=False,
65 scope
="SUB", credopts
=None, sambaopts
=None, versionopts
=None):
66 self
.lp
= sambaopts
.get_loadparm()
67 self
.creds
= credopts
.get_credentials(self
.lp
, fallback_machine
=True)
69 self
.samdb
= SamDB(session_info
=system_session(), url
=None,
70 credentials
=self
.creds
, lp
=self
.lp
)
71 self
.verbose
= verbose
75 scope_map
= { "SUB": ldb
.SCOPE_SUBTREE
, "BASE":ldb
.SCOPE_BASE
, "ONE":ldb
.SCOPE_ONELEVEL
}
77 if not scope
in scope_map
:
78 raise CommandError("Unknown scope %s" % scope
)
79 self
.search_scope
= scope_map
[scope
]
83 controls
.append("search_options:1:2")
85 res
= self
.samdb
.search(base
=DN
, scope
=self
.search_scope
, attrs
=['dn'], controls
=controls
)
86 print('Checking %u objects' % len(res
))
89 error_count
+= self
.check_object(object.dn
)
90 if error_count
!= 0 and not self
.fix
:
91 print("Please use --fix to fix these errors")
92 print('Checked %u objects (%u errors)' % (len(res
), error_count
))
96 def empty_attribute(self
, dn
, attrname
):
97 '''fix empty attributes'''
98 print("ERROR: Empty attribute %s in %s" % (attrname
, dn
))
101 if not confirm(self
, 'Remove empty attribute %s from %s?' % (attrname
, dn
)):
102 print("Not fixing empty attribute %s" % attrname
)
107 m
[attrname
] = ldb
.MessageElement('', ldb
.FLAG_MOD_DELETE
, attrname
)
109 self
.samdb
.modify(m
, controls
=["relax:0"], validate
=False)
110 except Exception, msg
:
111 print("Failed to remove empty attribute %s : %s" % (attrname
, msg
))
113 print("Removed empty attribute %s" % attrname
)
115 def check_object(self
, dn
):
116 '''check one object'''
118 print("Checking object %s" % dn
)
119 res
= self
.samdb
.search(base
=dn
, scope
=ldb
.SCOPE_BASE
, controls
=["extended_dn:1:1"], attrs
=['*', 'ntSecurityDescriptor'])
121 print("Object %s disappeared during check" % dn
)
129 # check for empty attributes
130 for val
in obj
[attrname
]:
132 self
.empty_attribute(dn
, attrname
)
136 # check for incorrectly normalised attributes
137 for val
in obj
[attrname
]:
138 normalised
= self
.samdb
.dsdb_normalise_attributes(self
.samdb
, attrname
, [val
])
139 if len(normalised
) != 1 or normalised
[0] != val
:
140 self
.normalise_mismatch(dn
, attrname
, obj
[attrname
])
145 def normalise_mismatch(self
, dn
, attrname
, values
):
146 '''fix attribute normalisation errors'''
147 print("ERROR: Normalisation error for attribute %s in %s" % (attrname
, dn
))
150 normalised
= self
.samdb
.dsdb_normalise_attributes(self
.samdb
, attrname
, [val
])
151 if len(normalised
) != 1:
152 print("Unable to normalise value '%s'" % val
)
153 mod_list
.append((val
, ''))
154 elif (normalised
[0] != val
):
155 print("value '%s' should be '%s'" % (val
, normalised
[0]))
156 mod_list
.append((val
, normalised
[0]))
159 if not self
.confirm(self
, 'Fix normalisation for %s from %s?' % (attrname
, dn
)):
160 print("Not fixing attribute %s" % attrname
)
165 for i
in range(0, len(mod_list
)):
166 (val
, nval
) = mod_list
[i
]
167 m
['value_%u' % i
] = ldb
.MessageElement(val
, ldb
.FLAG_MOD_DELETE
, attrname
)
169 m
['normv_%u' % i
] = ldb
.MessageElement(nval
, ldb
.FLAG_MOD_ADD
, attrname
)
172 self
.samdb
.modify(m
, controls
=["relax:0"], validate
=False)
173 except Exception, msg
:
174 print("Failed to normalise attribute %s : %s" % (attrname
, msg
))
176 print("Normalised attribute %s" % attrname
)