1 |
/* |
2 |
* Copyright (C) 2006 Torben H. Nielsen<torben@t-hoerup.dk> |
3 |
* |
4 |
* This program is free software; you can redistribute it and/or modify |
5 |
* it under the terms of the GNU General Public License version 2 |
6 |
* as published by the Free Software Foundation. |
7 |
* |
8 |
* This program is distributed in the hope that it will be useful, |
9 |
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
10 |
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
11 |
* GNU General Public License for more details. |
12 |
* |
13 |
* You should have received a copy of the GNU General Public License |
14 |
* along with this program (see the file COPYING included with this |
15 |
* distribution); if not, write to the Free Software Foundation, Inc., |
16 |
* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
17 |
*/ |
18 |
|
19 |
|
20 |
#include <stdio.h> |
21 |
#include <string.h> |
22 |
#include <stdlib.h> |
23 |
#include <mysql.h> |
24 |
#include <time.h> |
25 |
|
26 |
#include "openvpn-plugin.h" |
27 |
|
28 |
#define ERR_ADMINLOCK 1 |
29 |
#define ERR_EXPIRED 2 |
30 |
#define ERR_ZEROPASS 3 |
31 |
#define ERR_WRONGPASS 4 |
32 |
#define ERR_NOSUCHUSER 5 |
33 |
#define ERR_AUTOLOCKED 6 |
34 |
|
35 |
|
36 |
/* |
37 |
* Our context, where we keep our state. |
38 |
*/ |
39 |
struct plugin_context { |
40 |
char *host; |
41 |
char *username; |
42 |
char *password; |
43 |
char *db; |
44 |
char *table; |
45 |
char *log; |
46 |
int port; |
47 |
}; |
48 |
|
49 |
/* |
50 |
* Given an environmental variable name, search |
51 |
* the envp array for its value, returning it |
52 |
* if found or NULL otherwise. |
53 |
*/ |
54 |
static const char * |
55 |
get_arg (const char *name, const char *envp[]) |
56 |
{ |
57 |
if (envp) |
58 |
{ |
59 |
int i; |
60 |
const int namelen = strlen (name); |
61 |
for (i = 0; envp[i]; ++i) |
62 |
{ |
63 |
if (!strncmp (envp[i], name, namelen)) |
64 |
{ |
65 |
const char *cp = envp[i] + namelen; |
66 |
if (*cp == '=') |
67 |
return cp + 1; |
68 |
} |
69 |
} |
70 |
} |
71 |
return NULL; |
72 |
} |
73 |
|
74 |
OPENVPN_EXPORT openvpn_plugin_handle_t |
75 |
openvpn_plugin_open_v1 (unsigned int *type_mask, const char *argv[], const char *envp[]) |
76 |
{ |
77 |
struct plugin_context *context; |
78 |
const char *tmp; |
79 |
|
80 |
/* |
81 |
* Allocate our context |
82 |
*/ |
83 |
context = (struct plugin_context *) calloc (1, sizeof (struct plugin_context)); |
84 |
|
85 |
/* |
86 |
* Set the sql connection info |
87 |
*/ |
88 |
|
89 |
tmp = get_arg("host", argv); |
90 |
if (tmp == NULL) |
91 |
context->host = "localhost"; |
92 |
else { |
93 |
context->host = malloc(strlen(tmp)+1); |
94 |
strcpy(context->host, tmp); |
95 |
} |
96 |
|
97 |
tmp = get_arg("username", argv); |
98 |
if (tmp == NULL) |
99 |
context->username = "vpnauth"; |
100 |
else { |
101 |
context->username = malloc(strlen(tmp)+1); |
102 |
strcpy(context->username, tmp); |
103 |
} |
104 |
|
105 |
tmp = get_arg("password", argv); |
106 |
if (tmp == NULL) |
107 |
context->password = "vpnauth"; |
108 |
else { |
109 |
context->password = malloc(strlen(tmp)+1); |
110 |
strcpy(context->password, tmp); |
111 |
} |
112 |
|
113 |
tmp = get_arg("db", argv); |
114 |
if (tmp == NULL) |
115 |
context->db = "vpnauth"; |
116 |
else { |
117 |
context->db = malloc(strlen(tmp)+1); |
118 |
strcpy(context->db, tmp); |
119 |
} |
120 |
|
121 |
tmp = get_arg("table", argv); |
122 |
if (tmp == NULL) |
123 |
context->table = "vpnauth"; |
124 |
else { |
125 |
context->table = malloc(strlen(tmp)+1); |
126 |
strcpy(context->table, tmp); |
127 |
} |
128 |
|
129 |
tmp = get_arg("log", argv); |
130 |
if (tmp == NULL) |
131 |
context->log = ""; |
132 |
else { |
133 |
context->log = malloc(strlen(tmp)+1); |
134 |
strcpy(context->log, tmp); |
135 |
} |
136 |
|
137 |
tmp = get_arg("port", argv); |
138 |
if (tmp != NULL) |
139 |
context->port = atol(tmp); |
140 |
else |
141 |
context->port = 3306; |
142 |
|
143 |
/* stdout is redirected to openvpn's logfile - so normal printf is ok */ |
144 |
printf("auth-mysql: connect to %s:%i/%s.%s as %s\n", context->host, context->port, context->db, context->table, context->username); |
145 |
|
146 |
|
147 |
/* |
148 |
* We are only interested in intercepting the |
149 |
* --auth-user-pass-verify callback. |
150 |
*/ |
151 |
*type_mask = OPENVPN_PLUGIN_MASK (OPENVPN_PLUGIN_AUTH_USER_PASS_VERIFY); |
152 |
|
153 |
|
154 |
return (openvpn_plugin_handle_t) context; |
155 |
} |
156 |
|
157 |
OPENVPN_EXPORT int |
158 |
openvpn_plugin_func_v1 (openvpn_plugin_handle_t handle, const int type, const char *argv[], const char *envp[]) |
159 |
{ |
160 |
MYSQL *authdb=0; |
161 |
MYSQL_RES *res=0; |
162 |
MYSQL_ROW row=0; |
163 |
int fail = 0; |
164 |
char *sql; |
165 |
char *escaped_user; |
166 |
char *escaped_pass; |
167 |
int failcount=0; |
168 |
int locked; |
169 |
long expire; |
170 |
struct plugin_context *context; |
171 |
|
172 |
context = (struct plugin_context *) handle; |
173 |
|
174 |
/* get username/password from envp string array */ |
175 |
const char *username = get_arg ("username", envp); |
176 |
const char *password = get_arg ("password", envp); |
177 |
|
178 |
printf("auth-mysql: authenticating %s\n", username); |
179 |
|
180 |
authdb = mysql_init( authdb ); |
181 |
if (! mysql_real_connect(authdb, context->host, context->username, context->password, context->db, context->port, NULL,0)) { |
182 |
printf("auth-mysql: ERROR, could not connect to database: %s\n", mysql_error(authdb) ); |
183 |
return OPENVPN_PLUGIN_FUNC_ERROR; |
184 |
} |
185 |
|
186 |
escaped_user = malloc( (strlen(username)*2)+1); |
187 |
if (escaped_user == 0) { /* could not allocate memory */ |
188 |
return OPENVPN_PLUGIN_FUNC_ERROR; |
189 |
} |
190 |
|
191 |
escaped_pass = malloc( (strlen(password)*2)+1); |
192 |
if (escaped_pass == 0) { /* could not allocate memory */ |
193 |
return OPENVPN_PLUGIN_FUNC_ERROR; |
194 |
free (escaped_user); |
195 |
} |
196 |
|
197 |
mysql_real_escape_string ( authdb, escaped_user, username, strlen(username) ); |
198 |
mysql_real_escape_string ( authdb, escaped_pass, password, strlen(password) ); |
199 |
|
200 |
|
201 |
sql = malloc( strlen(escaped_user) + strlen(context->table) + strlen(escaped_user)+ 120 ); |
202 |
if (sql == 0) { /* could not allocate memory */ |
203 |
free (escaped_user); |
204 |
free (escaped_pass); |
205 |
return OPENVPN_PLUGIN_FUNC_ERROR; |
206 |
} |
207 |
|
208 |
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); |
209 |
printf("auth-mysql: sql=%s\n", sql); |
210 |
|
211 |
if ( mysql_query( authdb, sql)) { /*mysql error */ |
212 |
printf("auth-mysql: error: %s\n", mysql_error(authdb)); |
213 |
mysql_close (authdb); |
214 |
free(escaped_user); |
215 |
free(escaped_pass); |
216 |
free(sql); |
217 |
return OPENVPN_PLUGIN_FUNC_ERROR; |
218 |
} |
219 |
|
220 |
res = mysql_store_result( authdb ); |
221 |
if (res == 0) { /* could not allocate res */ |
222 |
mysql_close( authdb); |
223 |
free(escaped_user); |
224 |
free(escaped_pass); |
225 |
return OPENVPN_PLUGIN_FUNC_ERROR; |
226 |
} |
227 |
|
228 |
|
229 |
if (strlen(password) == 0) { |
230 |
printf("auth-mysql: zero length password (%s)\n", username); |
231 |
fail = ERR_ZEROPASS; |
232 |
} |
233 |
|
234 |
if ( fail==0 && mysql_num_rows(res) == 0) { |
235 |
printf("auth-mysql: user does not exist (%s)\n", username); |
236 |
fail = ERR_NOSUCHUSER; |
237 |
} |
238 |
|
239 |
if (fail==0) |
240 |
row = mysql_fetch_row(res); |
241 |
|
242 |
if (fail==0) { |
243 |
if ( atol(row[2])==0 ) { |
244 |
fail = ERR_WRONGPASS; |
245 |
printf("auth-mysql: user provided wrong password (%s)\n", username); |
246 |
} |
247 |
} |
248 |
|
249 |
if (fail==0) { |
250 |
locked = atol( row[3] ); |
251 |
if (locked != 0) { |
252 |
fail = ERR_ADMINLOCK; |
253 |
printf("auth-mysql: account is locked (%s)\n", username); |
254 |
} |
255 |
} |
256 |
|
257 |
if (fail==0) { |
258 |
expire = atol( row[4] ); |
259 |
if (expire != 0) { |
260 |
if ( (expire-time(0))<0) { |
261 |
fail = ERR_EXPIRED; |
262 |
printf("auth-mysql: account has expired (%s)\n", username); |
263 |
} |
264 |
} |
265 |
} |
266 |
|
267 |
if (fail==0) { |
268 |
failcount = atol( row[5] ); |
269 |
if (failcount >= 3 && fail==0) { |
270 |
fail = ERR_AUTOLOCKED; |
271 |
printf("auth-mysql: account failcount was larger than or equal 3 (%s)\n", username); |
272 |
} |
273 |
} |
274 |
|
275 |
|
276 |
/* if user/pass is ok - reset failcount, and set lastlogin to now() */ |
277 |
if ( fail == 0 ) { |
278 |
sprintf(sql, "UPDATE %s SET failcount=0,lastlogin=now() WHERE user='%s'", context->table, escaped_user); |
279 |
mysql_query( authdb, sql); |
280 |
} |
281 |
|
282 |
/* if user entered wrong password, increment failcount */ |
283 |
if ( fail == ERR_WRONGPASS ) { |
284 |
failcount++; |
285 |
sprintf(sql, "UPDATE %s SET failcount=%i WHERE user='%s'", context->table, failcount, escaped_user); |
286 |
mysql_query( authdb, sql); |
287 |
} |
288 |
|
289 |
if ( strlen(context->log)>0) { /* enable log table */ |
290 |
sprintf(sql, "INSERT INTO %s (username,time,msgid) VALUES ('%s',now(), %d)", context->log, escaped_user, fail); |
291 |
mysql_query( authdb, sql); |
292 |
} |
293 |
|
294 |
free(sql); |
295 |
free(escaped_user); |
296 |
free(escaped_pass); |
297 |
mysql_free_result(res); |
298 |
mysql_close( authdb ); |
299 |
|
300 |
return (fail == 0) ? OPENVPN_PLUGIN_FUNC_SUCCESS : OPENVPN_PLUGIN_FUNC_ERROR; |
301 |
} |
302 |
|
303 |
OPENVPN_EXPORT void |
304 |
openvpn_plugin_close_v1 (openvpn_plugin_handle_t handle) |
305 |
{ |
306 |
struct plugin_context *context = (struct plugin_context *) handle; |
307 |
free (context); |
308 |
} |