1 |
torben |
113 |
/* |
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 |
|
|
} |