|
1 |
#!/usr/bin/python2.6 |
1 |
#!/usr/bin/python2.6 |
2 |
# -*- coding: utf-8 -*- |
2 |
# -*- coding: utf-8 -*- |
|
|
3 |
# pylint: disable-msg=C0103 |
3 |
# |
4 |
# |
4 |
# Univention Directory Manager Modules |
5 |
# Univention Directory Manager Modules |
5 |
# check if users are member of their primary group |
6 |
"""Check if users are member of their primary group.""" |
6 |
# |
7 |
# |
7 |
# Copyright 2004-2012 Univention GmbH |
8 |
# Copyright 2004-2012 Univention GmbH |
8 |
# |
9 |
# |
|
31 |
# /usr/share/common-licenses/AGPL-3; if not, see |
32 |
# /usr/share/common-licenses/AGPL-3; if not, see |
32 |
# <http://www.gnu.org/licenses/>. |
33 |
# <http://www.gnu.org/licenses/>. |
33 |
|
34 |
|
34 |
|
35 |
import ldap |
35 |
import ldap, string |
|
|
36 |
import sys |
36 |
import sys |
37 |
import univention.baseconfig |
37 |
from univention.config_registry import ConfigRegistry |
38 |
import pprint |
|
|
39 |
from optparse import OptionParser |
38 |
from optparse import OptionParser |
40 |
|
39 |
|
41 |
baseConfig=univention.baseconfig.baseConfig() |
|
|
42 |
baseConfig.load() |
43 |
binddn= "cn=admin," + baseConfig['ldap/base'] |
44 |
basedn = baseConfig['ldap/base'] |
45 |
count_changes = 0 |
46 |
warning = 0 |
47 |
pp = pprint.PrettyPrinter(indent=4) |
48 |
|
49 |
|
50 |
|
51 |
parser = OptionParser() |
52 |
parser.add_option("-b", "--base-dn", help="ldap base dn", dest="basedn", action="store", type="str") |
53 |
(options, args) = parser.parse_args() |
54 |
|
55 |
def ldapbind (binddn): |
56 |
bindpw=open('/etc/ldap.secret').read() |
57 |
bindpw = bindpw.split("\n")[0] |
58 |
lo=ldap.open('localhost', 7389) |
59 |
try: |
60 |
lo.simple_bind_s(binddn, bindpw) |
61 |
except: |
62 |
print "could not bind to %s" % binddn |
63 |
sys.exit(1) |
64 |
|
65 |
return lo |
66 |
|
67 |
|
68 |
lo = ldapbind(binddn) |
69 |
|
70 |
|
71 |
if options.basedn: basedn = options.basedn |
72 |
|
73 |
|
74 |
# GID's will only be found in posixAccount |
75 |
try: |
76 |
res_pA=lo.search_s(basedn, ldap.SCOPE_SUBTREE, 'objectClass=posixAccount', ['gidNumber', 'uid']) |
77 |
except ldap.NO_SUCH_OBJECT: |
78 |
print "ldap search in %s failed (no such base dn)" % basedn |
79 |
sys.exit(1) |
80 |
|
81 |
print "\n proof if users are member of their primary group" |
82 |
for i in range(0,len(res_pA)): |
83 |
gidNumber_pA='' |
84 |
dn_pA=res_pA[i][0] |
85 |
memberUid_pA = res_pA[i][1]['uid'][0] |
86 |
if res_pA[i][1].has_key('gidNumber'): |
87 |
gidNumber_pA=res_pA[i][1]['gidNumber'][0] |
88 |
else: |
89 |
print "Warning, posixAccount without gidNumber",res_pA[i] |
90 |
warning = warning + 1 |
91 |
|
92 |
# search corresponding group |
93 |
res_uG=lo.search_s(basedn, ldap.SCOPE_SUBTREE, '(&(objectClass=univentionGroup)(gidNumber='+gidNumber_pA+"))", ['uniqueMember', 'memberUid']) |
94 |
|
95 |
# there must be excactly one group with this gid |
96 |
if len(res_uG)>1: |
97 |
print "Warning: found more than one univentionGroup for",dn_pA,"gidNumber",gidNumber_pA,"!" |
98 |
warning = warning + 1 |
99 |
if len(res_uG)<1 and not gidNumber_pA=="0": |
100 |
print "Warning: found no univentionGroup for",dn_pA,"gidNumber",gidNumber_pA,"!" |
101 |
warning = warning +1 |
102 |
# well change all if there are more -- the user needs to delete all but one of them |
103 |
for group in res_uG: |
104 |
dn_uG = group[0] |
105 |
# look for the needed entry |
106 |
uniqueMembers_uG = group[1].get('uniqueMember', []) |
107 |
memberUids_uG = group[1].get('memberUid', []) |
108 |
modlist = [] |
109 |
for el in uniqueMembers_uG: |
110 |
if el == dn_pA: |
111 |
break |
112 |
else: |
113 |
modlist.append((ldap.MOD_ADD,'uniqueMember',dn_pA)) |
114 |
|
115 |
for el in memberUids_uG: |
116 |
if el == memberUid_pA: |
117 |
break |
118 |
else: |
119 |
modlist.append((ldap.MOD_ADD,'memberUid',memberUid_pA)) |
120 |
# no entry found, gonna add one |
121 |
if modlist: |
122 |
print "add uniqueMember and memberUid entry for",dn_pA,"in",dn_uG |
123 |
try: |
124 |
lo.modify_s(dn_uG,modlist) |
125 |
count_changes = count_changes +1 |
126 |
except: |
127 |
warning = warning + 1 |
128 |
print "Warning: failed to modify Group %s"%dn_uG |
129 |
|
130 |
print " proof of",len(res_pA),"posixAccounts finished, changed",count_changes,"of them.\n" |
131 |
|
132 |
count_changes=0 |
133 |
print " proof if group-members exist" |
134 |
res_pG=lo.search_s(basedn, ldap.SCOPE_SUBTREE, 'objectClass=posixGroup', ['uniqueMember']) |
135 |
for i in range(0,len(res_pG)): |
136 |
dn_pG = res_pG[i][0] |
137 |
members = [] |
138 |
if res_pG[i][1].has_key('uniqueMember'): |
139 |
members = res_pG[i][1]['uniqueMember'] |
140 |
|
141 |
remmembers=[] |
142 |
|
143 |
for member in members: |
144 |
n=string.find(member,',') |
145 |
base=member[n+1:] |
146 |
filter=member[:n] |
147 |
|
148 |
try: |
149 |
res_pU=lo.search_s(base, ldap.SCOPE_ONELEVEL, filter, ['objectClass']) |
150 |
if len(res_pU) > 1: |
151 |
print "Warning: more than one object found while searching for %s of group %s -- clear manually"%(member,dn_pG) |
152 |
warning+=1 |
153 |
elif len(res_pU) < 1: |
154 |
print " no object found while searching for %s, will be removed"%member |
155 |
remmembers.append(member) |
156 |
except: |
157 |
print "Warning: Search for member %s of group %s failed -- clear manually"%(member,dn_pG) |
158 |
warning+=1 |
159 |
|
160 |
|
161 |
for member in remmembers: |
162 |
modlist = [(ldap.MOD_DELETE,'uniqueMember',member)] |
163 |
try: |
164 |
lo.modify_s(dn_pG,modlist) |
165 |
count_changes = count_changes +1 |
166 |
except: |
167 |
print "Warning: failed to remove %s from Group %s"%(member,dn_pG) |
168 |
warning = warning + 1 |
169 |
|
170 |
print " proof of",len(res_pG),"posixGroups finished, changed",count_changes,"of them.\n" |
171 |
|
172 |
|
173 |
if warning: print "There were %s warning(s) !"%warning |
174 |
|
40 |
|
|
|
41 |
def info(msg, *args, **kwargs): |
42 |
"""Print info.""" |
43 |
print msg % (args or kwargs) |
44 |
|
45 |
|
46 |
def warn(msg, *args, **kwargs): |
47 |
"""Print warning.""" |
48 |
print >> sys.stderr, 'Warning: ' + (msg % (args or kwargs)) |
49 |
warn.warnings += 1 |
50 |
warn.warnings = 0 |
51 |
|
52 |
|
53 |
def fatal(msg, *args, **kwargs): |
54 |
"""Print error.""" |
55 |
print >> sys.stderr, 'Error: ' + (msg % (args or kwargs)) |
56 |
sys.exit(1) |
57 |
|
58 |
|
59 |
def ldapbind(basedn): |
60 |
"""Open local LDAP connection and do bind.""" |
61 |
binddn = "cn=admin,%s" % (basedn,) |
62 |
try: |
63 |
bindpw = open('/etc/ldap.secret').read().rstrip('\n') |
64 |
except IOError: |
65 |
fatal('Could no read /etc/ldap.secrer') |
66 |
conn = ldap.open('localhost', 7389) |
67 |
try: |
68 |
conn.simple_bind_s(binddn, bindpw) |
69 |
except ldap.LDAPError: |
70 |
fatal("Could not bind to %s", binddn) |
71 |
|
72 |
return conn |
73 |
|
74 |
|
75 |
def check_primary(conn, basedn): |
76 |
"""Check if users are member of their primary group.""" |
77 |
info("Checking if users are member of their primary group...") |
78 |
try: |
79 |
# GID's will only be found in posixAccount |
80 |
user_result = conn.search_s(basedn, ldap.SCOPE_SUBTREE, |
81 |
'(objectClass=posixAccount)', ['gidNumber', 'uid']) |
82 |
except ldap.NO_SUCH_OBJECT: |
83 |
fatal("ldap search in %s failed (no such base dn)", basedn) |
84 |
count_changes = 0 |
85 |
for user_dn, account in user_result: |
86 |
user_uid = account['uid'][0] |
87 |
user_gid = account.get('gidNumber', [])[0] |
88 |
if not user_gid: |
89 |
warn("posixAccount without gidNumber: %s", user_dn) |
90 |
|
91 |
# search corresponding group |
92 |
group_result = conn.search_s(basedn, ldap.SCOPE_SUBTREE, |
93 |
'(&(objectClass=univentionGroup)(gidNumber=%s))' % (user_gid,), |
94 |
['uniqueMember', 'memberUid']) |
95 |
|
96 |
# there must be exactly one group with this gid |
97 |
if len(group_result) > 1: |
98 |
warn("found more than one univentionGroup for gidNumber=%s!", |
99 |
user_gid) |
100 |
elif len(group_result) < 1 and not user_gid == "0": |
101 |
warn("found no univentionGroup for gidNumber=%s!", user_gid) |
102 |
# we change them all -- the user needs to delete all but one of them |
103 |
for group_dn, group in group_result: |
104 |
# look for the needed entry |
105 |
group_member_dns = group.get('uniqueMember', []) |
106 |
group_member_uids = group.get('memberUid', []) |
107 |
modlist = [] |
108 |
if user_dn not in group_member_dns: |
109 |
modlist.append((ldap.MOD_ADD, 'uniqueMember', user_dn)) |
110 |
if user_uid not in group_member_uids: |
111 |
modlist.append((ldap.MOD_ADD, 'memberUid', user_uid)) |
112 |
# no entry found, gonna add one |
113 |
if modlist: |
114 |
info("Adding uniqueMember and memberUid entry for '%s' in '%s'", |
115 |
user_dn, group_dn) |
116 |
try: |
117 |
conn.modify_s(group_dn, modlist) |
118 |
count_changes += 1 |
119 |
except ldap.LDAPError: |
120 |
warn("failed to modify group %s", group_dn) |
121 |
info("Checked %d posixAccounts, fixed %d issues.", |
122 |
len(user_result), count_changes) |
123 |
|
124 |
|
125 |
def check_groups(conn, basedn): |
126 |
"""Check if members of group exist.""" |
127 |
info("Checking if group-members exist...") |
128 |
try: |
129 |
group_result = conn.search_s(basedn, ldap.SCOPE_SUBTREE, |
130 |
'(objectClass=posixGroup)', ['uniqueMember', 'memberUid']) |
131 |
except ldap.NO_SUCH_OBJECT: |
132 |
fatal("ldap search in %s failed (no such base dn)", basedn) |
133 |
|
134 |
count_changes = 0 |
135 |
for group_dn, group in group_result: |
136 |
count_changes += check_groups_by_dn(conn, group_dn, group) |
137 |
count_changes += check_groups_by_uid(conn, basedn, group_dn, group) |
138 |
|
139 |
info("Checked %d posixGroups, fixed %d issues.", |
140 |
len(group_result), count_changes) |
141 |
|
142 |
|
143 |
def check_groups_by_dn(conn, group_dn, group): |
144 |
"""Check by 'uniqueMember'.""" |
145 |
group_member_dns = group.get('uniqueMember', []) |
146 |
count_changes = 0 |
147 |
remmembers = set() |
148 |
for member_dn in group_member_dns: |
149 |
# Split uid=USER, cn=user,dc=FQDN |
150 |
member_filter, base = member_dn.split(',', 1) |
151 |
try: |
152 |
member_result = conn.search_s(base, ldap.SCOPE_ONELEVEL, |
153 |
member_filter, ['objectClass']) |
154 |
except ldap.LDAPError: |
155 |
warn("Manual: Search for member DN '%s' of group '%s' failed", |
156 |
member_dn, group_dn) |
157 |
else: |
158 |
if len(member_result) > 1: |
159 |
warn("Manual: Multiple members for DN '%s' of group '%s'", |
160 |
member_dn, group_dn) |
161 |
elif len(member_result) < 1: |
162 |
warn("No member for DN '%s', will be removed", member_dn) |
163 |
remmembers.add(member_dn) |
164 |
for member_dn in remmembers: |
165 |
info("Removing member DN '%s' from '%s'", member_dn, group_dn) |
166 |
modlist = [(ldap.MOD_DELETE, 'uniqueMember', member_dn)] |
167 |
try: |
168 |
conn.modify_s(group_dn, modlist) |
169 |
count_changes += 1 |
170 |
except ldap.LDAPError: |
171 |
warn("failed to remove DN '%s' from group '%s'", |
172 |
member_dn, group_dn) |
173 |
return count_changes |
174 |
|
175 |
|
176 |
def check_groups_by_uid(conn, basedn, group_dn, group): |
177 |
"""Check by 'memberUid'.""" |
178 |
group_member_uids = group.get('memberUid', []) |
179 |
count_changes = 0 |
180 |
remmembers = set() |
181 |
for member_uid in group_member_uids: |
182 |
try: |
183 |
member_result = conn.search_s(basedn, ldap.SCOPE_SUBTREE, |
184 |
'(uid=%s)' % (member_uid,), ['objectClass']) |
185 |
except ldap.LDAPError: |
186 |
warn("Manual: Search for member UID '%s' of group '%s' failed", |
187 |
member_uid, group_dn) |
188 |
else: |
189 |
if len(member_result) > 1: |
190 |
warn("Manual: Multiple members for UID '%s' of group '%s'", |
191 |
member_uid, group_dn) |
192 |
elif len(member_result) < 1: |
193 |
warn("No member for UID '%s', will be removed", member_uid) |
194 |
remmembers.add(member_uid) |
195 |
for member_uid in remmembers: |
196 |
info("Removing member UID '%s' from '%s'", member_uid, group_dn) |
197 |
modlist = [(ldap.MOD_DELETE, 'memberUid', member_uid)] |
198 |
try: |
199 |
conn.modify_s(group_dn, modlist) |
200 |
count_changes += 1 |
201 |
except ldap.LDAPError: |
202 |
warn("Failed to remove UID '%s' from group '%s'", |
203 |
member_uid, group_dn) |
204 |
return count_changes |
205 |
|
206 |
|
207 |
def main(): |
208 |
"""Check group membership.""" |
209 |
parser = OptionParser() |
210 |
parser.add_option("-b", "--base-dn", |
211 |
dest="basedn", action="store", |
212 |
help="ldap base DN for user search") |
213 |
parser.add_option("-c", "--check", |
214 |
dest="check", action="store_true", |
215 |
help="Only check, do not modify") |
216 |
(options, _args) = parser.parse_args() |
217 |
|
218 |
ucr = ConfigRegistry() |
219 |
ucr.load() |
220 |
basedn = ucr['ldap/base'] |
221 |
|
222 |
conn = ldapbind(basedn) |
223 |
|
224 |
if options.basedn: |
225 |
basedn = options.basedn |
226 |
if options.check: |
227 |
conn.modify_s = lambda dn, modlist: None |
228 |
|
229 |
check_primary(conn, basedn) |
230 |
check_groups(conn, basedn) |
231 |
if warn.warnings: |
232 |
info("There were %d warning(s)!", warn.warnings) |
233 |
sys.exit(2) |
234 |
else: |
235 |
sys.exit(0) |
236 |
|
237 |
|
238 |
if __name__ == '__main__': |
239 |
main() |