summaryrefslogtreecommitdiffstats
path: root/src/modules/rlm_python3/prepaid.py
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 09:49:46 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 09:49:46 +0000
commit50b37d4a27d3295a29afca2286f1a5a086142cec (patch)
tree9212f763934ee090ef72d823f559f52ce387f268 /src/modules/rlm_python3/prepaid.py
parentInitial commit. (diff)
downloadfreeradius-upstream/3.2.1+dfsg.tar.xz
freeradius-upstream/3.2.1+dfsg.zip
Adding upstream version 3.2.1+dfsg.upstream/3.2.1+dfsgupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/modules/rlm_python3/prepaid.py')
-rw-r--r--src/modules/rlm_python3/prepaid.py247
1 files changed, 247 insertions, 0 deletions
diff --git a/src/modules/rlm_python3/prepaid.py b/src/modules/rlm_python3/prepaid.py
new file mode 100644
index 0000000..28e548b
--- /dev/null
+++ b/src/modules/rlm_python3/prepaid.py
@@ -0,0 +1,247 @@
+#! /usr/bin/env python3
+#
+# Example Python module for prepaid usage using MySQL
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
+#
+# Copyright 2002 Miguel A.L. Paraz <mparaz@mparaz.com>
+# Copyright 2002 Imperium Technology, Inc.
+#
+# $Id$
+
+import radiusd
+import MySQLdb
+
+# Configuration
+configDb = 'python' # Database name
+configHost = 'localhost' # Database host
+configUser = 'python' # Database user and password
+configPasswd = 'python'
+
+# xxx Database
+
+# Globals
+dbHandle = None
+
+def log(level, s):
+ """Log function."""
+ radiusd.radlog(level, 'prepaid.py: ' + s)
+
+def instantiate(p):
+ """Module Instantiation. 0 for success, -1 for failure.
+ p is a dummy variable here."""
+ global dbHandle
+
+ try:
+ dbHandle = MySQLdb.connect(db=configDb, host=configHost,
+ user=configUser, passwd=configPasswd)
+
+ except MySQLdb.OperationalError as e:
+ # Report the error and return -1 for failure.
+ # xxx A more advanced module would retry the database.
+ log(radiusd.L_ERR, str(e))
+ return -1
+
+ log(radiusd.L_INFO, 'db connection: ' + str(dbHandle))
+
+ return 0
+
+
+def authorize(authData):
+ """Authorization and authentication are done in one step."""
+
+ # Extract the data we need.
+ userName = None
+ userPasswd = None
+
+ for t in authData:
+ if t[0] == 'User-Name':
+ userName = t[1]
+ elif t[0] == 'Password':
+ userPasswd = t[1]
+
+ # Build and log the SQL statement
+ # radiusd puts double quotes (") around the string representation of
+ # the RADIUS packet.
+ sql = 'select passwd, maxseconds from users where username = ' + userName
+
+ log(radiusd.L_DBG, sql)
+
+ # Get a cursor
+ # xxx Or should this be one cursor all throughout?
+ try:
+ dbCursor = dbHandle.cursor()
+ except MySQLdb.OperationalError as e:
+ log(radiusd.L_ERR, str(e))
+ return radiusd.RLM_MODULE_FAIL
+
+ # Execute the SQL statement
+ try:
+ dbCursor.execute(sql)
+ except MySQLdb.OperationalError as e:
+ log(radiusd.L_ERR, str(e))
+ dbCursor.close()
+ return radiusd.RLM_MODULE_FAIL
+
+ # Get the result. (passwd, maxseconds)
+ result = dbCursor.fetchone()
+ if not result:
+ # User not found
+ log(radiusd.L_INFO, 'user not found: ' + userName)
+ dbCursor.close()
+ return radiusd.RLM_MODULE_NOTFOUND
+
+
+
+ # Compare passwords
+ # Ignore the quotes around userPasswd.
+ if result[0] != userPasswd[1:-1]:
+ log(radiusd.L_DBG, 'user password mismatch: ' + userName)
+ return radiusd.RLM_MODULE_REJECT
+
+ maxSeconds = result[1]
+
+ # Compute their session limit
+
+ # Build and log the SQL statement
+ sql = 'select sum(seconds) from sessions where username = ' + userName
+
+ log(radiusd.L_DBG, sql)
+
+ # Execute the SQL statement
+ try:
+ dbCursor.execute(sql)
+ except MySQLdb.OperationalError as e:
+ log(radiusd.L_ERR, str(e))
+ dbCursor.close()
+ return radiusd.RLM_MODULE_FAIL
+
+ # Get the result. (sum,)
+ result = dbCursor.fetchone()
+ if (not result) or (not result[0]):
+ # No usage yet
+ secondsUsed = 0
+ else:
+ secondsUsed = result[0]
+
+ # Done with cursor
+ dbCursor.close()
+
+ # Note that MySQL returns the result of SUM() as a float.
+ sessionTimeout = maxSeconds - int(secondsUsed)
+
+ if sessionTimeout <= 0:
+ # No more time, reject outright
+ log(radiusd.L_INFO, 'user out of time: ' + userName)
+ return radiusd.RLM_MODULE_REJECT
+
+ # Log the success
+ log(radiusd.L_DBG, 'user accepted: %s, %d seconds' %
+ (userName, sessionTimeout))
+
+ # We are adding to the RADIUS packet
+ # Note that the session timeout integer must be converted to string.
+ # We need to set an Auth-Type.
+
+ return (radiusd.RLM_MODULE_UPDATED,
+ (('Session-Timeout', str(sessionTimeout)),),
+ (('Auth-Type', 'python'),))
+ # If you want to use different operators
+ # you can do
+ # return (radiusd.RLM_MODULE_UPDATED,
+ # (
+ # ('Session-Timeout', ':=', str(sessionTimeout)),
+ # ('Some-other-option', '-=', Value'),
+ # ),
+ # (
+ # ('Auth-Type', ':=', 'python'),
+ # ),
+ # )
+
+def authenticate(p):
+ return radiusd.RLM_MODULE_OK
+
+
+def preacct(p):
+ return radiusd.RLM_MODULE_OK
+
+
+def accounting(acctData):
+ """Accounting."""
+ # Extract the data we need.
+
+ userName = None
+ acctSessionTime = None
+ acctStatusType = None
+
+ # xxx A dict would make this nice.
+ for t in acctData:
+ if t[0] == 'User-Name':
+ userName = t[1]
+ elif t[0] == 'Acct-Session-Time':
+ acctSessionTime = t[1]
+ elif t[0] == 'Acct-Status-Type':
+ acctStatusType = t[1]
+
+
+ # We will not deal with Start for now.
+ # We may later, for simultaneous checks and the like.
+ if acctStatusType == 'Start':
+ return radiusd.RLM_MODULE_OK
+
+ # Build and log the SQL statement
+ # radiusd puts double quotes (") around the string representation of
+ # the RADIUS packet.
+ #
+ # xxx This is simplistic as it does not record the time, etc.
+ #
+ sql = 'insert into sessions (username, seconds) values (%s, %d)' % \
+ (userName, int(acctSessionTime))
+
+ log(radiusd.L_DBG, sql)
+
+ # Get a cursor
+ # xxx Or should this be one cursor all throughout?
+ try:
+ dbCursor = dbHandle.cursor()
+ except MySQLdb.OperationalError as e:
+ log(radiusd.L_ERR, str(e))
+ return radiusd.RLM_MODULE_FAIL
+
+ # Execute the SQL statement
+ try:
+ dbCursor.execute(sql)
+ except MySQLdb.OperationalError as e:
+ log(radiusd.L_ERR, str(e))
+ dbCursor.close()
+ return radiusd.RLM_MODULE_FAIL
+
+
+ return radiusd.RLM_MODULE_OK
+
+
+def detach():
+ """Detach and clean up."""
+ # Shut down the database connection.
+ global dbHandle
+ log(radiusd.L_DBG, 'closing database handle: ' + str(dbHandle))
+ dbHandle.close()
+
+ return radiusd.RLM_MODULE_OK
+
+
+
+# Test the modules
+if __name__ == '__main__':
+ instantiate(None)
+ print(authorize((('User-Name', '"map"'), ('User-Password', '"abc"'))))