classify-users: determine type of Athena accounts
[user/alex/software/my-snippets.git] / rblanche.py
1 #!/usr/bin/python
2 import errno
3 import optparse
4 import sys
5
6 import moira
7
8 try:
9     import hesiod
10 except ImportError:
11     hesiod = None
12
13 def get_members_of_list(lst):
14     return moira.query('get_members_of_list', lst)
15
16 class RBlanche(object):
17     def __init__(self, chpoboxing=False, deactivations=False):
18         self.reset_caches()
19         self.chpoboxing = chpoboxing
20         self.deactivations = deactivations
21
22     def reset_caches(self, ):
23         self.cache = {}
24         self.chpobox = {}
25         self.user_active = {}
26
27     def expand(self, lst):
28         if lst in self.cache: return self.cache[lst]
29         try:
30             members = get_members_of_list(lst)
31         except moira.MoiraException, e:
32             code, message = e
33             if message == "Insufficient permission to perform requested database access":
34                 self.cache[lst] = [{'member_type': 'ERR', 'member_name':'** Error: no permissions to look up this list **'}]
35                 return self.cache[lst]
36             else: raise
37         for member in members:
38             if member['member_type'] == 'LIST':
39                 member['members'] = self.expand(member['member_name'])
40         self.cache[lst] = members
41         return members
42
43     def get_and_format_pobox(self, user):
44         try:
45             result = moira.query('get_pobox', user)[0]
46         except moira.MoiraException, e:
47             return "%8s ** moira error: %s **" % (user, e)
48         if result['type'] == 'SMTP':
49             fmt = '-> %s'
50         elif result['type'] == 'SPLIT':
51             fmt = '=> %s'
52         elif result['type'] in ('EXCHANGE', 'IMAP', 'POP', ''):
53             fmt = '  (%s)'
54         else:
55             raise ValueError("Unknown pobox type %s" % (result['type'], ))
56         text = ('%8s ' + fmt) % (result['login'], result['address'], )
57         return text
58
59     def check_deactivated(self, user):
60         try:
61             passwd = hesiod.PasswdLookup(user)
62             return ""
63         except IOError, e:
64             if e.errno == errno.ENOENT:
65                 return " ** deactivated **"
66             else:
67                 raise
68
69     def format_user(self, user):
70         text = user
71         if self.chpoboxing:
72             if user not in self.chpobox:
73                 self.chpobox[user] = self.get_and_format_pobox(user)
74             text = self.chpobox[user]
75         if self.deactivations:
76             if user not in self.user_active:
77                 self.user_active[user] = self.check_deactivated(user)
78             append = self.user_active[user]
79         else:
80             append = ""
81         return text + append
82
83     def print_tree(self, path, lst_tree):
84         for member in lst_tree:
85             if 'members' in member:
86                 path.append(member['member_name'])
87                 self.print_tree(path, member['members'])
88                 path.pop()
89             else:
90                 if member['member_type'] == "USER":
91                     text = self.format_user(member['member_name'])
92                 elif member['member_type'] == "ERR":
93                     text = member['member_name']
94                 else:
95                     text = "%s:%s" % (member['member_type'], member['member_name'], )
96                 print ":  ".join(path+[text])
97
98     def rblanche(self, lst):
99         result = self.expand(lst)
100         self.print_tree([lst], result)
101
102 def parse_args():
103     parser = optparse.OptionParser(usage='usage: %prog [--auth=yes|no|try] [-cd] list')
104     parser.add_option('--auth', dest='auth', default=None,
105                 help='authenticate to the moira server? (yes, no, or try)'
106     )
107     parser.add_option('-c', '--chpobox',
108                 dest='chpoboxing', action='store_true', default=False,
109                 help='check mail forwarding for each user',
110     )
111     parser.add_option('-d', '--deactivated',
112                 dest='deactivations', action='store_true', default=False,
113                 help='check whether users are deactivated',
114     )
115     options, args = parser.parse_args()
116
117     if len(args) != 1:
118         parser.error("incorrect number of arguments")
119     if options.auth:
120         if options.auth == 'yes': options.auth = True
121         elif options.auth == 'try': options.auth = None
122         elif options.auth == 'no': options.auth = False
123         else:
124             parser.error("--auth takes yes, no, and try (default; auth if possible)")
125     if options.deactivations and not hesiod:
126         parser.error("deactivation checking requires python-hesiod, which is not available")
127
128     return options, args
129
130 if __name__ == '__main__':
131     options, args = parse_args()
132     moira.connect()
133     if options.auth:
134         moira.auth('rblnchpy')
135     elif options.auth is None:
136         try:
137             moira.auth('rblnchpy')
138         except moira.MoiraException:
139             print >>sys.stderr, "Warning: Failed to authenticate to moira"
140     rblanche = RBlanche(
141         chpoboxing=options.chpoboxing,
142         deactivations=options.deactivations,
143     )
144     rblanche.rblanche(args[0])