/* * Copyright (C) 2006 Torben H. Nielsen * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 * as published by the Free Software Foundation. * * 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 (see the file COPYING included with this * distribution); if not, write to the Free Software Foundation, Inc., * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include #include #include #include #include #include "openvpn-plugin.h" #define ERR_ADMINLOCK 1 #define ERR_EXPIRED 2 #define ERR_ZEROPASS 3 #define ERR_WRONGPASS 4 #define ERR_NOSUCHUSER 5 #define ERR_AUTOLOCKED 6 /* * Our context, where we keep our state. */ struct plugin_context { char *host; char *username; char *password; char *db; char *table; char *log; int port; }; /* * Given an environmental variable name, search * the envp array for its value, returning it * if found or NULL otherwise. */ static const char * get_arg (const char *name, const char *envp[]) { if (envp) { int i; const int namelen = strlen (name); for (i = 0; envp[i]; ++i) { if (!strncmp (envp[i], name, namelen)) { const char *cp = envp[i] + namelen; if (*cp == '=') return cp + 1; } } } return NULL; } OPENVPN_EXPORT openvpn_plugin_handle_t openvpn_plugin_open_v1 (unsigned int *type_mask, const char *argv[], const char *envp[]) { struct plugin_context *context; const char *tmp; /* * Allocate our context */ context = (struct plugin_context *) calloc (1, sizeof (struct plugin_context)); /* * Set the sql connection info */ tmp = get_arg("host", argv); if (tmp == NULL) context->host = "localhost"; else { context->host = malloc(strlen(tmp)+1); strcpy(context->host, tmp); } tmp = get_arg("username", argv); if (tmp == NULL) context->username = "vpnauth"; else { context->username = malloc(strlen(tmp)+1); strcpy(context->username, tmp); } tmp = get_arg("password", argv); if (tmp == NULL) context->password = "vpnauth"; else { context->password = malloc(strlen(tmp)+1); strcpy(context->password, tmp); } tmp = get_arg("db", argv); if (tmp == NULL) context->db = "vpnauth"; else { context->db = malloc(strlen(tmp)+1); strcpy(context->db, tmp); } tmp = get_arg("table", argv); if (tmp == NULL) context->table = "vpnauth"; else { context->table = malloc(strlen(tmp)+1); strcpy(context->table, tmp); } tmp = get_arg("log", argv); if (tmp == NULL) context->log = ""; else { context->log = malloc(strlen(tmp)+1); strcpy(context->log, tmp); } tmp = get_arg("port", argv); if (tmp != NULL) context->port = atol(tmp); else context->port = 3306; /* stdout is redirected to openvpn's logfile - so normal printf is ok */ printf("auth-mysql: connect to %s:%i/%s.%s as %s\n", context->host, context->port, context->db, context->table, context->username); /* * We are only interested in intercepting the * --auth-user-pass-verify callback. */ *type_mask = OPENVPN_PLUGIN_MASK (OPENVPN_PLUGIN_AUTH_USER_PASS_VERIFY); return (openvpn_plugin_handle_t) context; } OPENVPN_EXPORT int openvpn_plugin_func_v1 (openvpn_plugin_handle_t handle, const int type, const char *argv[], const char *envp[]) { MYSQL *authdb=0; MYSQL_RES *res=0; MYSQL_ROW row=0; int fail = 0; char *sql; char *escaped_user; char *escaped_pass; int failcount=0; int locked; long expire; struct plugin_context *context; context = (struct plugin_context *) handle; /* get username/password from envp string array */ const char *username = get_arg ("username", envp); const char *password = get_arg ("password", envp); printf("auth-mysql: authenticating %s\n", username); authdb = mysql_init( authdb ); if (! mysql_real_connect(authdb, context->host, context->username, context->password, context->db, context->port, NULL,0)) { printf("auth-mysql: ERROR, could not connect to database: %s\n", mysql_error(authdb) ); return OPENVPN_PLUGIN_FUNC_ERROR; } escaped_user = malloc( (strlen(username)*2)+1); if (escaped_user == 0) { /* could not allocate memory */ return OPENVPN_PLUGIN_FUNC_ERROR; } escaped_pass = malloc( (strlen(password)*2)+1); if (escaped_pass == 0) { /* could not allocate memory */ return OPENVPN_PLUGIN_FUNC_ERROR; free (escaped_user); } mysql_real_escape_string ( authdb, escaped_user, username, strlen(username) ); mysql_real_escape_string ( authdb, escaped_pass, password, strlen(password) ); sql = malloc( strlen(escaped_user) + strlen(context->table) + strlen(escaped_user)+ 120 ); if (sql == 0) { /* could not allocate memory */ free (escaped_user); free (escaped_pass); return OPENVPN_PLUGIN_FUNC_ERROR; } sprintf(sql, "SELECT id,user,pass=md5('%s') as passcheck,locked, UNIX_TIMESTAMP(expires),failcount FROM %s WHERE user='%s'", escaped_pass, context->table, escaped_user); printf("auth-mysql: sql=%s\n", sql); if ( mysql_query( authdb, sql)) { /*mysql error */ printf("auth-mysql: error: %s\n", mysql_error(authdb)); mysql_close (authdb); free(escaped_user); free(escaped_pass); free(sql); return OPENVPN_PLUGIN_FUNC_ERROR; } res = mysql_store_result( authdb ); if (res == 0) { /* could not allocate res */ mysql_close( authdb); free(escaped_user); free(escaped_pass); return OPENVPN_PLUGIN_FUNC_ERROR; } if (strlen(password) == 0) { printf("auth-mysql: zero length password (%s)\n", username); fail = ERR_ZEROPASS; } if ( fail==0 && mysql_num_rows(res) == 0) { printf("auth-mysql: user does not exist (%s)\n", username); fail = ERR_NOSUCHUSER; } if (fail==0) row = mysql_fetch_row(res); if (fail==0) { if ( atol(row[2])==0 ) { fail = ERR_WRONGPASS; printf("auth-mysql: user provided wrong password (%s)\n", username); } } if (fail==0) { locked = atol( row[3] ); if (locked != 0) { fail = ERR_ADMINLOCK; printf("auth-mysql: account is locked (%s)\n", username); } } if (fail==0) { expire = atol( row[4] ); if (expire != 0) { if ( (expire-time(0))<0) { fail = ERR_EXPIRED; printf("auth-mysql: account has expired (%s)\n", username); } } } if (fail==0) { failcount = atol( row[5] ); if (failcount >= 3 && fail==0) { fail = ERR_AUTOLOCKED; printf("auth-mysql: account failcount was larger than or equal 3 (%s)\n", username); } } /* if user/pass is ok - reset failcount, and set lastlogin to now() */ if ( fail == 0 ) { sprintf(sql, "UPDATE %s SET failcount=0,lastlogin=now() WHERE user='%s'", context->table, escaped_user); mysql_query( authdb, sql); } /* if user entered wrong password, increment failcount */ if ( fail == ERR_WRONGPASS ) { failcount++; sprintf(sql, "UPDATE %s SET failcount=%i WHERE user='%s'", context->table, failcount, escaped_user); mysql_query( authdb, sql); } if ( strlen(context->log)>0) { /* enable log table */ sprintf(sql, "INSERT INTO %s (username,time,msgid) VALUES ('%s',now(), %d)", context->log, escaped_user, fail); mysql_query( authdb, sql); } free(sql); free(escaped_user); free(escaped_pass); mysql_free_result(res); mysql_close( authdb ); return (fail == 0) ? OPENVPN_PLUGIN_FUNC_SUCCESS : OPENVPN_PLUGIN_FUNC_ERROR; } OPENVPN_EXPORT void openvpn_plugin_close_v1 (openvpn_plugin_handle_t handle) { struct plugin_context *context = (struct plugin_context *) handle; free (context); }