--- freeradius-0.8.1.orig/doc/rlm_sql	Wed Nov 13 11:07:51 2002
+++ freeradius-0.8.1/doc/rlm_sql	Mon Dec 23 22:45:14 2002
@@ -19,6 +19,10 @@
   and the examples in the 'users' file.
 
 
+  This module supports SQL Authentication, SQL Authorization, and
+  SQL Accounting.
+
+
 1. Miscellaneous configuration
 
   The SQL module has little documentation, sorry.  A helpful (but old)
--- freeradius-0.8.1.orig/raddb/dictionary	Fri Nov 29 11:44:40 2002
+++ freeradius-0.8.1/raddb/dictionary	Mon Dec 23 22:50:39 2002
@@ -181,6 +181,7 @@
 ATTRIBUTE	Ldap-UserDn		1053	string
 ATTRIBUTE	NS-MTA-MD5-Password	1054	string
 ATTRIBUTE	SQL-User-Name	 	1055	string
+ATTRIBUTE	SQL-Password	 	1056	string
 ATTRIBUTE	LM-Password		1057	octets
 ATTRIBUTE	NT-Password		1058	octets
 ATTRIBUTE	SMB-Account-CTRL	1059	integer
@@ -367,6 +368,7 @@
 VALUE		Auth-Type		ActivCard		5
 VALUE		Auth-Type		EAP			6
 VALUE		Auth-Type		ARAP			7
+VALUE		Auth-Type		SQL			8
 
 #
 #	Cistron extensions
--- freeradius-0.8.1.orig/raddb/sql.conf	Mon Nov 18 10:32:03 2002
+++ freeradius-0.8.1/raddb/sql.conf	Mon Dec 23 22:49:41 2002
@@ -103,6 +103,59 @@
 
 
 	#######################################################################
+        #  Authentication Queries
+	#######################################################################
+	#  These queries are used to authenticate a user.  They are used when
+	#  Auth-Type := SQL, which is different from using SQL Authorization.
+	#
+	#  This allows SQL authentication from any Authorization method
+	#  for easy integration with legacy systems.  THIS IS NOT THE SAME
+	#  AS SQL AUTHORIZATION, and can be combined with SQL Authorization
+	#  to do such crazy things as query MySQL for the Authorization
+	#  and query a legacy Oracle database for Authentication.
+	#
+	#  An example users entry is:
+	#	DEFAULT Auth-Type := SQL
+	#	        Service-Type = Framed-User,
+	#	        Framed-Protocol = PPP,
+	#	        Framed-IP-Netmask = 255.255.255.255,
+	#	        Framed-Routing = None,
+	#	        Framed-MTU = 1500,
+	#	        Framed-Compression = Van-Jacobsen-TCP-IP,
+	#	        Idle-Timeout = 1800
+	#
+	#  The result is interpreteted in 3 different manners, depending on the
+	#  value of authenticate_mode:
+	#
+	#    plain - compares the value of the first row with the user supplies
+	#            password.  If no row is returned, the request is answered
+	#            with RLM_MODULE_NOT_FOUND
+	#    crypt - compares the user supplied password with the crypted value
+	#            returned as the first row.  As above, if no row is
+	#            returned, RLM_MODULE_NOT_FOUND is returned.
+	#    query - relies on the query to authenticate the user.  This uses
+	#            the results of the query to decide behaviour.  If the value
+	#            at the first row is empty, 0, false, no, or no row is 
+	#            returned the request is answered with RLM_MODULE_INVALID 
+	#            (invalid password) otherwise the RLM_MODULE_OK is returned 
+	#            for any other value returned in the first row.
+	#######################################################################
+	# Example 1 - plain
+	#authenticate_query = "SELECT password FROM user WHERE userid='%{SQL-User-Name}'"
+	#authenticate_mode = plain
+
+	# Example 2 - crypt
+	#authenticate_query = "SELECT passwd FROM passwd WHERE userid='%{SQL-User-Name}'"
+	#authenticate_mode = crypt
+
+	# Example 3 - query
+	# This example uses MySQL's ability to do unix crypt() calls to ask the database
+	# to verify the user for us.
+	#authenticate_query = "SELECT '0' FROM passwd WHERE userid='%{SQL-User-Name}' AND encrypt('%{SQL-Password}',password) = password"
+	#authenticate_mode = query
+
+
+	#######################################################################
 	#  Authorization Queries
 	#######################################################################
 	#  These queries compare the check items for the user
--- freeradius-0.8.1.orig/src/include/radius.h	Fri Aug 16 10:46:21 2002
+++ freeradius-0.8.1/src/include/radius.h	Mon Dec 23 22:19:09 2002
@@ -138,6 +138,7 @@
 #define PW_LDAP_USERDN			1053
 #define PW_NS_MTA_MD5_PASSWORD		1054
 #define PW_SQL_USER_NAME  		1055
+#define PW_SQL_PASSWORD  		1056
 #define PW_LM_PASSWORD			1057
 #define PW_NT_PASSWORD			1058
 #define PW_SMB_ACCOUNT_CTRL		1059
@@ -209,6 +210,8 @@
 #define PW_AUTHTYPE_REJECT		4
 #define PW_AUTHTYPE_ACTIVCARD		5
 #define PW_AUTHTYPE_EAP                 6
+#define PW_AUTHTYPE_ARAP                7
+#define PW_AUTHTYPE_SQL                 8
 #define PW_AUTHTYPE_ACCEPT		254
 
 /*	Port Types		*/
--- freeradius-0.8.1.orig/src/main/auth.c	Tue Oct  8 17:48:27 2002
+++ freeradius-0.8.1/src/main/auth.c	Mon Dec 23 21:18:48 2002
@@ -395,6 +395,7 @@
 					result = 1;
 					break;
 			}
+			DEBUG2("module authenticate result is %d", result);
 			break;
 	}
 
--- freeradius-0.8.1.orig/src/modules/rlm_sql/conf.h	Sat Sep  7 09:23:01 2002
+++ freeradius-0.8.1/src/modules/rlm_sql/conf.h	Mon Dec 23 21:54:50 2002
@@ -26,6 +26,8 @@
 	char   *sql_dict_table;
 	char   *query_user;
 	char   *default_profile;
+        char   *authenticate_query;
+        char   *authenticate_mode;
 	char   *authorize_check_query;
 	char   *authorize_reply_query;
 	char   *authorize_group_check_query;
--- freeradius-0.8.1.orig/src/modules/rlm_sql/rlm_sql.c	Wed Dec  4 11:59:29 2002
+++ freeradius-0.8.1/src/modules/rlm_sql/rlm_sql.c	Mon Dec 23 22:29:44 2002
@@ -71,6 +71,8 @@
 	{"sql_user_name", PW_TYPE_STRING_PTR, offsetof(SQL_CONFIG,query_user), NULL, ""},
 	{"default_user_profile", PW_TYPE_STRING_PTR, offsetof(SQL_CONFIG,default_profile), NULL, ""},
 	{"query_on_not_found", PW_TYPE_BOOLEAN, offsetof(SQL_CONFIG,query_on_not_found), NULL, "no"},
+	{"authenticate_mode", PW_TYPE_STRING_PTR, offsetof(SQL_CONFIG,authenticate_mode), NULL, "plain"},
+	{"authenticate_query", PW_TYPE_STRING_PTR, offsetof(SQL_CONFIG,authenticate_query), NULL, ""},
 	{"authorize_check_query", PW_TYPE_STRING_PTR, offsetof(SQL_CONFIG,authorize_check_query), NULL, ""},
 	{"authorize_reply_query", PW_TYPE_STRING_PTR, offsetof(SQL_CONFIG,authorize_reply_query), NULL, ""},
 	{"authorize_group_check_query", PW_TYPE_STRING_PTR, offsetof(SQL_CONFIG,authorize_group_check_query), NULL, ""},
@@ -226,7 +228,7 @@
 }
 
 /*
- *	Set the SQl user name.
+ *	Set the SQL user name.
  */
 static int sql_set_user(SQL_INST *inst, REQUEST *request, char *sqlusername, const char *username) {
 	VALUE_PAIR *vp=NULL;
@@ -263,6 +265,41 @@
 }
 
 /*
+ *	Set the SQL password
+ */
+static int sql_set_password(SQL_INST *inst, REQUEST *request, char *sqlpassword, const char *password) {
+	VALUE_PAIR *vp=NULL;
+	char tmppassword[MAX_STRING_LEN];
+
+	tmppassword[0]=0;
+	sqlpassword[0]=0;
+
+	/* Remove any password attr we added previously */
+	pairdelete(&request->packet->vps, PW_SQL_PASSWORD);
+
+	if (password != NULL) {
+		strNcpy(tmppassword, password, MAX_STRING_LEN);
+	} else {
+		return 0;
+	}
+
+	if (*tmppassword) {
+		strNcpy(sqlpassword, tmppassword, MAX_STRING_LEN * 2);
+		DEBUG2("rlm_sql (%s): sql_set_password escaped password --> '%s'",
+		       inst->config->xlat_name, sqlpassword);
+		vp = pairmake("SQL-Password", sqlpassword, 0);
+		if (vp == NULL) {
+			radlog(L_ERR, "%s", librad_errstr);
+			return -1;
+		}
+
+		pairadd(&request->packet->vps, vp);
+		return 0;
+	}
+	return -1;
+}
+
+/*
  * sql groupcmp function. That way we can do group comparisons (in the users file for example)
  * with the group memberships reciding in sql
  * The group membership query should only return one element which is the username. The returned
@@ -378,6 +415,17 @@
 		inst->config->xlat_name = strdup(xlat_name);
 		xlat_register(xlat_name, sql_xlat, inst);
 	}
+	
+	if (!(strcmp(inst->config->authenticate_mode, SQL_PWCOMPARE_PLAIN) == 0 ||
+		strcmp(inst->config->authenticate_mode, SQL_PWCOMPARE_CRYPT) == 0 ||
+		strcmp(inst->config->authenticate_mode, SQL_PWCOMPARE_QUERY) == 0)) {
+		radlog(L_ERR, "rlm_sql: authenticate_mode must be one of "
+			SQL_PWCOMPARE_PLAIN " or " SQL_PWCOMPARE_CRYPT " or "
+			SQL_PWCOMPARE_QUERY);
+			free(inst->config);
+			free(inst);
+			return -1;
+	}
 
 	if (inst->config->num_sql_socks > MAX_SQL_SOCKS) {
 		radlog(L_ERR | L_CONS, "rlm_sql (%s): sql_instantiate: number of sqlsockets cannot exceed MAX_SQL_SOCKS, %d",
@@ -445,6 +493,134 @@
 }
 
 
+static int rlm_sql_authenticate(void *instance, REQUEST *request)
+{
+	int	ret;
+	int	notfoundret = RLM_MODULE_REJECT;
+	SQLSOCK *sqlsocket;
+	SQL_INST *inst = instance;
+	SQL_ROW	row;
+	char    querystr[MAX_QUERY_LEN];
+	VALUE_PAIR *auth_type;
+
+	/* sqlusername holds the sql escaped username. The original
+	 * username is at most MAX_STRING_LEN chars long and
+	 * *sql_escape_string doubles its length in the worst case.
+	 * Throw in an extra 10 to account for trailing NULs and to have
+	 * a safety margin. sqlpassword is the quoted password. */
+	char   sqlusername[2 * MAX_STRING_LEN + 10];
+	char   sqlpassword[2 * MAX_STRING_LEN + 10];
+
+	/*
+	 *	They MUST have a user name to do SQL authorization.
+	 */
+	if ((request->username == NULL) ||
+	    (request->username->length == 0)) {
+		radlog(L_ERR, "rlm_sql (%s): zero length username not permitted\n", inst->config->xlat_name);
+		return RLM_MODULE_INVALID;
+	}
+
+	/* Don't go any further unless we're handling this request for PW_AUTHTYPE_SQL (Auth-Type := SQL) */
+	auth_type = pairfind(request->config_items, PW_AUTHTYPE);
+	if ((auth_type == NULL) || ((auth_type) && (auth_type->lvalue != PW_AUTHTYPE_SQL))) {
+		DEBUG("sql_authenticate called for PW_AUTHTYPE != PW_AUTHTYPE_SQL %d %d", auth_type->lvalue, PW_AUTHTYPE_SQL);
+		return RLM_MODULE_FAIL;
+	}
+
+	/*
+	 *  After this point, ALL 'return's MUST release the SQL socket!
+	 */
+
+
+	/*
+	 * Set, escape, and check the user attr here
+	 */
+	if (sql_set_user(inst, request, sqlusername, NULL) < 0)
+		return RLM_MODULE_FAIL;
+	if (sql_set_password(inst, request, sqlpassword, (char *)request->password->strvalue) < 0)
+		return RLM_MODULE_FAIL;
+	radius_xlat(querystr, sizeof(querystr), inst->config->authenticate_query, request, sql_escape_func);
+
+	sqlsocket = sql_get_socket(inst);
+	if (sqlsocket == NULL) {
+		DEBUG("Couldn't get SQL socket");
+		/* Remove the username and password we (maybe) added above */
+		pairdelete(&request->packet->vps, PW_SQL_USER_NAME);
+		pairdelete(&request->packet->vps, PW_SQL_PASSWORD);
+		return(RLM_MODULE_FAIL);
+	}
+
+	DEBUG2("Running SQL query: %s", querystr);
+	if(rlm_sql_select_query(sqlsocket, inst, querystr)) {
+		radlog(L_ERR, "rlm_sql (%s) sql_checksimul: Database query failed", inst->config->xlat_name);
+		sql_release_socket(inst, sqlsocket);
+		return RLM_MODULE_FAIL;
+	}
+	
+	/* For either authenticate_mode crypt or plain, no results means not found
+	   but for authenticate_mode query, no results means bad password OR not found.
+	   To fix this, a second query must be added to do a record count.
+	*/
+	if (strcmp(inst->config->authenticate_mode, SQL_PWCOMPARE_PLAIN) == 0 ||
+		(strcmp(inst->config->authenticate_mode, SQL_PWCOMPARE_CRYPT) == 0)) {
+		notfoundret=RLM_MODULE_NOTFOUND;
+	}
+
+	ret = rlm_sql_fetch_row(sqlsocket, inst);
+
+	if (ret != 0) {
+		(inst->module->sql_finish_select_query)(sqlsocket, inst->config);
+		sql_release_socket(inst, sqlsocket);
+		DEBUG2("No sql results");
+		return RLM_MODULE_FAIL;
+	}
+
+	row = sqlsocket->row;
+	if (row == NULL) {
+		DEBUG2("No sql row returned");
+		(inst->module->sql_finish_select_query)(sqlsocket, inst->config);
+		sql_release_socket(inst, sqlsocket);
+		return notfoundret;
+	}
+	
+	if (strcmp(inst->config->authenticate_mode, SQL_PWCOMPARE_PLAIN) == 0) {
+		DEBUG2("sql compare plain %s to %s",(char*)request->password->strvalue,row[0]);
+		if (strcmp(row[0],(char *)request->password->strvalue) == 0) {
+			ret=RLM_MODULE_OK;
+		} else {
+			ret=RLM_MODULE_INVALID;
+		}
+	} else if (strcmp(inst->config->authenticate_mode, SQL_PWCOMPARE_CRYPT) == 0) {
+		char *encpw;
+		
+		encpw=(char *)crypt((char *)request->password->strvalue,row[0]);
+		DEBUG2("sql compare crypt %s to %s",encpw,row[0]);
+		if (strcmp(encpw,row[0]) == 0) {
+			ret=RLM_MODULE_OK;
+		} else {
+			ret=RLM_MODULE_INVALID;
+		}
+	} else if (strcmp(inst->config->authenticate_mode, SQL_PWCOMPARE_QUERY) == 0) {
+		/* We got a response.  In SQL_PWCOMPARE_QUERY mode this means success */
+		/* if the value anything other than 0 false or no or "" */
+		DEBUG2("sql compare got value %s",row[0]);
+		if (strcmp(row[0],"") == 0 || strcmp(row[0],"0") == 0 || strcasecmp(row[0],"false") == 0 ||
+			strcmp(row[0],"no") == 0) {
+			ret=RLM_MODULE_INVALID;
+		} else {
+			ret=RLM_MODULE_OK;
+		}
+	} else {
+		DEBUG("unknown sql comparison method %s",inst->config->authenticate_mode);
+		/* Invalid comparison - shouldn't happen */
+		ret=RLM_MODULE_FAIL;
+	}
+	
+	(inst->module->sql_finish_select_query)(sqlsocket, inst->config);
+	sql_release_socket(inst, sqlsocket);
+	return ret;	
+}
+
 static int rlm_sql_authorize(void *instance, REQUEST * request) {
 
 	VALUE_PAIR *check_tmp = NULL;
@@ -988,7 +1164,7 @@
 	rlm_sql_init,		/* initialization */
 	rlm_sql_instantiate,	/* instantiation */
 	{
-		NULL,			/* authentication */
+		rlm_sql_authenticate,	/* authentication */
 		rlm_sql_authorize,	/* authorization */
 		NULL,			/* preaccounting */
 		rlm_sql_accounting,	/* accounting */
--- freeradius-0.8.1.orig/src/modules/rlm_sql/rlm_sql.h	Wed Aug 28 15:24:09 2002
+++ freeradius-0.8.1/src/modules/rlm_sql/rlm_sql.h	Mon Dec 23 20:41:13 2002
@@ -31,6 +31,10 @@
 #define PW_ITEM_CHECK			0
 #define PW_ITEM_REPLY			1
 
+#define SQL_PWCOMPARE_PLAIN		"plain"
+#define SQL_PWCOMPARE_CRYPT		"crypt"
+#define SQL_PWCOMPARE_QUERY		"query"
+
 typedef char** SQL_ROW;
 
 typedef struct sql_socket {
