import from branch_1_1:
[freeradius.git] / src / modules / rlm_python / prepaid.py
1 #! /usr/bin/env python
2 #
3 # Example Python module for prepaid usage using MySQL
4
5 # This program is free software; you can redistribute it and/or modify
6 # it under the terms of the GNU General Public License as published by
7 # the Free Software Foundation; either version 2 of the License, or
8 # (at your option) any later version.
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 # GNU General Public License for more details.
13 # You should have received a copy of the GNU General Public License
14 # along with this program; if not, write to the Free Software
15 # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
16 #
17 # Copyright 2002 Miguel A.L. Paraz <mparaz@mparaz.com>
18 # Copyright 2002 Imperium Technology, Inc.
19 #
20 # $Id$
21
22 import radiusd
23 import MySQLdb
24
25 # Configuration
26 configDb = 'python'                     # Database name
27 configHost = 'localhost'                # Database host
28 configUser = 'python'                   # Database user and password
29 configPasswd = 'python'         
30
31 # xxx Database 
32
33 # Globals
34 dbHandle = None
35
36 def log(level, s):
37   """Log function."""
38   radiusd.radlog(level, 'prepaid.py: ' + s)
39
40 def instantiate(p):
41   """Module Instantiation.  0 for success, -1 for failure.
42   p is a dummy variable here."""
43   global dbHandle
44
45   p = p
46
47   try:
48     dbHandle = MySQLdb.connect(db=configDb, host=configHost,
49                                user=configUser, passwd=configPasswd)
50
51   except MySQLdb.OperationalError, e:
52     # Report the error and return -1 for failure.
53     # xxx A more advanced module would retry the database.
54     log(radiusd.L_ERR, str(e))
55     return -1
56
57   log(radiusd.L_INFO, 'db connection: ' + str(dbHandle))
58
59   return 0
60
61
62 def authorize(authData):
63   """Authorization and authentication are done in one step."""
64
65   # Extract the data we need.
66   userName = None
67   userPasswd = None
68   
69   for t in authData:
70     if t[0] == 'User-Name':
71       userName = t[1]
72     elif t[0] == 'Password':
73       userPasswd = t[1]
74
75   # Build and log the SQL statement
76   # radiusd puts double quotes (") around the string representation of
77   # the RADIUS packet.
78   sql = 'select passwd, maxseconds from users where username = ' +  userName
79   
80   log(radiusd.L_DBG, sql)
81
82   # Get a cursor
83   # xxx Or should this be one cursor all throughout?
84   try:
85     dbCursor = dbHandle.cursor()
86   except MySQLdb.OperationalError, e:
87     log(radiusd.L_ERR, str(e))
88     return radiusd.RLM_MODULE_FAIL
89
90   # Execute the SQL statement
91   try:
92     dbCursor.execute(sql)
93   except MySQLdb.OperationalError, e:
94     log(radiusd.L_ERR, str(e))
95     dbCursor.close()
96     return radiusd.RLM_MODULE_FAIL
97   
98   # Get the result. (passwd, maxseconds)
99   result = dbCursor.fetchone()
100   if not result:
101     # User not found
102     log(radiusd.L_INFO, 'user not found: ' + userName)
103     dbCursor.close()
104     return radiusd.RLM_MODULE_NOTFOUND
105
106
107  
108   # Compare passwords
109   # Ignore the quotes around userPasswd.
110   if result[0] != userPasswd[1:-1]:
111     log(radiusd.L_DBG, 'user password mismatch: ' + userName)
112     return radiusd.RLM_MODULE_REJECT
113
114   maxSeconds = result[1]
115
116   # Compute their session limit
117   
118   # Build and log the SQL statement
119   sql = 'select sum(seconds) from sessions where username = ' + userName
120
121   log(radiusd.L_DBG, sql)
122   
123   # Execute the SQL statement
124   try:
125     dbCursor.execute(sql)
126   except MySQLdb.OperationalError, e:
127     log(radiusd.L_ERR, str(e))
128     dbCursor.close()
129     return radiusd.RLM_MODULE_FAIL
130   
131   # Get the result. (sum,)
132   result = dbCursor.fetchone()
133   if (not result) or (not result[0]):
134     # No usage yet
135     secondsUsed = 0
136   else:
137     secondsUsed = result[0]
138
139   # Done with cursor
140   dbCursor.close()
141
142   # Note that MySQL returns the result of SUM() as a float.
143   sessionTimeout = maxSeconds - int(secondsUsed)
144   
145   if sessionTimeout <= 0:
146     # No more time, reject outright
147     log(radiusd.L_INFO, 'user out of time: ' + userName)    
148     return radiusd.RLM_MODULE_REJECT
149
150   # Log the success
151   log(radiusd.L_DBG, 'user accepted: %s, %d seconds' %
152       (userName, sessionTimeout))
153
154   # We are adding to the RADIUS packet
155   # Note that the session timeout integer must be converted to string.
156   # We need to set an Auth-Type.
157
158   return (radiusd.RLM_MODULE_UPDATED,
159           (('Session-Timeout', str(sessionTimeout)),),
160           (('Auth-Type', 'python'),))
161
162     
163
164 def authenticate(p):
165   p = p
166   return radiusd.RLM_MODULE_OK
167
168
169 def preacct(p):
170   p = p
171   return radiusd.RLM_MODULE_OK
172
173
174 def accounting(acctData):
175   """Accounting."""
176   # Extract the data we need.
177
178   userName = None
179   acctSessionTime = None
180   acctStatusType = None
181
182   # xxx A dict would make this nice.
183   for t in acctData:
184     if t[0] == 'User-Name':
185       userName = t[1]
186     elif t[0] == 'Acct-Session-Time':
187       acctSessionTime = t[1]
188     elif t[0] == 'Acct-Status-Type':
189       acctStatusType = t[1]
190
191
192   # We will not deal with Start for now.
193   # We may later, for simultaneous checks and the like.
194   if acctStatusType == 'Start':
195     return radiusd.RLM_MODULE_OK
196   
197   # Build and log the SQL statement
198   # radiusd puts double quotes (") around the string representation of
199   # the RADIUS packet.
200   #
201   # xxx This is simplistic as it does not record the time, etc.
202   # 
203   sql = 'insert into sessions (username, seconds) values (%s, %d)' % \
204         (userName, int(acctSessionTime))
205   
206   log(radiusd.L_DBG, sql)
207
208   # Get a cursor
209   # xxx Or should this be one cursor all throughout?
210   try:
211     dbCursor = dbHandle.cursor()
212   except MySQLdb.OperationalError, e:
213     log(radiusd.L_ERR, str(e))
214     return radiusd.RLM_MODULE_FAIL
215
216   # Execute the SQL statement
217   try:
218     dbCursor.execute(sql)
219   except MySQLdb.OperationalError, e:
220     log(radiusd.L_ERR, str(e))
221     dbCursor.close()
222     return radiusd.RLM_MODULE_FAIL
223
224   
225   return radiusd.RLM_MODULE_OK
226
227
228 def detach():
229   """Detach and clean up."""
230   # Shut down the database connection.
231   global dbHandle
232   log(radiusd.L_DBG, 'closing database handle: ' + str(dbHandle))
233   dbHandle.close()
234
235   return radiusd.RLM_MODULE_OK
236
237
238
239 # Test the modules 
240 if __name__ == '__main__':
241   instantiate(None)
242   print authorize((('User-Name', '"map"'), ('User-Password', '"abc"')))