Moved files into their new locations prior to commit of 0.2.
[public/pyceo-broken.git] / pylib / csc / adm / accounts.py
1 # $Id: accounts.py 44 2006-12-31 07:09:27Z mspang $
2 # UNIX Accounts Module
3 import re
4 from csc.backends import ldapi, krb
5 from csc.common.conf import read_config
6
7 CONFIG_FILE = '/etc/csc/accounts.cf'
8
9 cfg = {}
10
11 # error constants
12 SUCCESS = 0
13 LDAP_EXISTS = 1
14 LDAP_NO_IDS = 2
15 LDAP_NO_USER = 3
16 KRB_EXISTS = 5
17 KRB_NO_USER = 6
18 BAD_USERNAME = 8
19 BAD_REALNAME = 9
20
21 # error messages
22 errors = [ "Success", "LDAP: entry exists",
23     "LDAP: no user ids available", "LDAP: no such entry",
24     "KRB: principal exists", "KRB: no such principal",
25     "Invalid username", "Invalid real name"]
26
27
28 class AccountException(Exception):
29     """Exception class for account-related errors."""
30
31
32 def load_configuration():
33     """Load Accounts Configuration."""
34     
35     # configuration already loaded?
36     if len(cfg) > 0:
37         return
38     
39     # read in the file
40     cfg_tmp = read_config(CONFIG_FILE)
41
42     if not cfg_tmp:
43         raise AccountException("unable to read configuration file: %s" % CONFIG_FILE)
44
45     # check that essential fields are completed
46     mandatory_fields = [ 'minimum_id', 'maximum_id', 'shell', 'home',
47         'gid', 'server_url', 'users_base', 'groups_base', 'bind_dn',
48         'bind_password', 'realm', 'principal', 'keytab', 'username_regex',
49         'realname_regex'
50     ]
51
52     for field in mandatory_fields:
53         if not field in cfg_tmp:
54             raise AccountException("missing configuration option: %s" % field)
55         if not cfg_tmp[field]:
56             raise AccountException("null configuration option: %s" % field)
57     
58     # check that numeric fields are ints
59     numeric_fields = [ 'minimum_id', 'maximum_id', 'gid' ]
60
61     for field in numeric_fields:
62         if not type(cfg_tmp[field]) in (int, long):
63             raise AccountException("non-numeric value for configuration option: %s" % field)
64
65     # update the current configuration with the loaded values
66     cfg.update(cfg_tmp)
67         
68
69 def create_account(username, password, realname='', gecos_other=''):
70     """
71     Creates a UNIX account for a member. This involves
72     first creating a directory entry, then creating
73     a Kerberos principal.
74
75     Parameters:
76         username - UNIX username for the member
77         realname - real name of the member
78         password - password for the account
79
80     Exceptions:
81         LDAPException - on LDAP failure
82         KrbException  - on Kerberos failure
83         
84     Returns:
85         SUCCESS      - on success
86         BAD_REALNAME - on badly formed real name
87         BAD_USERNAME - on badly formed user name
88         LDAP_EXISTS  - when the user exists in LDAP
89         LDAP_NO_IDS  - when no user ids are free
90         KRB_EXISTS   - when the user exists in Kerberos
91     """
92
93     # Load Configuration
94     load_configuration()
95
96     ### Connect to the Backends ###
97
98     ldap_connection = ldapi.LDAPConnection()
99     krb_connection = krb.KrbConnection()
100
101     try:
102
103         # connect to the LDAP server
104         ldap_connection.connect(cfg['server_url'], cfg['bind_dn'], cfg['bind_password'], cfg['users_base'], cfg['groups_base'])
105
106         # connect to the Kerberos master server
107         krb_connection.connect(cfg['principal'], cfg['keytab'])
108
109         ### Sanity-checks ###
110    
111         # check the username and realame for validity
112         if not re.match(cfg['username_regex'], username):
113             return BAD_USERNAME
114         if not re.match(cfg['realname_regex'], realname):
115             return BAD_REALNAME
116
117         # see if user exists in LDAP
118         if ldap_connection.user_lookup(username):
119             return LDAP_EXISTS
120
121         # determine the first available userid
122         userid = ldap_connection.first_id(cfg['minimum_id'], cfg['maximum_id'])
123         if not userid: return LDAP_NO_IDS
124
125         # build principal name from username
126         principal = username + '@' + cfg['realm']
127     
128         # see if user exists in Kerberos
129         if krb_connection.get_principal(principal):
130             return KRB_EXISTS
131     
132         ### User creation ###
133
134         # process gecos_other (used to store memberid)
135         if gecos_other:
136             gecos_other = ',' + str(gecos_other)
137     
138         # account information defaults
139         shell = cfg['shell']
140         home = cfg['home'] + '/' + username
141         gecos = realname + ',,,' + gecos_other
142         gid = cfg['gid']
143     
144         # create the LDAP entry
145         ldap_connection.user_add(username, realname, shell, userid, gid, home, gecos)
146     
147         # create the Kerberos principal
148         krb_connection.add_principal(principal, password)
149
150     finally:
151         ldap_connection.disconnect()
152         krb_connection.disconnect()
153     
154     return SUCCESS
155     
156
157 def delete_account(username):
158     """
159     Deletes the UNIX account of a member.
160     
161     Parameters:
162         username - UNIX username for the member
163
164     Exceptions:
165         LDAPException - on LDAP failure
166         KrbException  - on Kerberos failure
167         
168     Returns:
169         SUCCESS      - on success
170         LDAP_NO_USER - when the user does not exist in LDAP
171         KRB_NO_USER  - when the user does not exist in Kerberos
172     """
173
174     # Load Configuration
175     load_configuration()
176
177     ### Connect to the Backends ###
178
179     ldap_connection = ldapi.LDAPConnection()
180     krb_connection = krb.KrbConnection()
181
182     try:
183     
184         # connect to the LDAP server
185         ldap_connection.connect(cfg['server_url'], cfg['bind_dn'], cfg['bind_password'], cfg['users_base'], cfg['groups_base'])
186
187         # connect to the Kerberos master server
188         krb_connection.connect(cfg['principal'], cfg['keytab'])
189
190         ### Sanity-checks ###
191     
192         # ensure user exists in LDAP
193         if not ldap_connection.user_lookup(username):
194             return LDAP_NO_USER
195     
196         # build principal name from username
197         principal = username + '@' + cfg['realm']
198
199         # see if user exists in Kerberos
200         if not krb_connection.get_principal(principal):
201             return KRB_NO_USER
202
203         ### User deletion ###
204     
205         # delete the LDAP entry
206         ldap_connection.user_delete(username)
207     
208         # delete the Kerberos principal
209         krb_connection.delete_principal(principal)
210
211     finally:
212         ldap_connection.disconnect()
213         krb_connection.disconnect()
214     
215     return SUCCESS
216
217
218
219 ### Tests ###
220
221 if __name__ == '__main__':
222
223     # A word of notice: this test creates a _working_ account (and then deletes it).
224     # If deletion fails it must be cleaned up manually.
225     
226     # a bit of salt so the test account is reasonably tough to crack
227     import random
228     pw = str(random.randint(100000000000000000, 999999999999999999))
229     
230     print "running create_account('testuser', ..., 'Test User', ...)", "->", errors[create_account('testuser', pw, 'Test User')]
231     print "running delete_account('testuser')", "->", errors[delete_account('testuser')]
232