]>
Commit | Line | Data |
---|---|---|
d80aac12 | 1 | /* |
2 | * RADIUS | |
3 | * Remote Authentication Dial In User Service | |
4 | * | |
5 | * | |
6 | * Livingston Enterprises, Inc. | |
7 | * 6920 Koll Center Parkway | |
8 | * Pleasanton, CA 94566 | |
9 | * | |
10 | * Copyright 1992 Livingston Enterprises, Inc. | |
11 | * | |
12 | * Permission to use, copy, modify, and distribute this software for any | |
13 | * purpose and without fee is hereby granted, provided that this | |
14 | * copyright and permission notice appear on all copies and supporting | |
15 | * documentation, the name of Livingston Enterprises, Inc. not be used | |
16 | * in advertising or publicity pertaining to distribution of the | |
17 | * program without specific prior permission, and notice be given | |
18 | * in supporting documentation that copying and distribution is by | |
19 | * permission of Livingston Enterprises, Inc. | |
20 | * | |
21 | * Livingston Enterprises, Inc. makes no representations about | |
22 | * the suitability of this software for any purpose. It is | |
23 | * provided "as is" without express or implied warranty. | |
24 | * | |
25 | * The new parts of the code is Copyright (C) 1998 R.M. van Selm <selm@cistron.nl> | |
26 | * with modifications | |
27 | * Copyright (C) 2004 Henrik Nordstrom <hno@squid-cache.org> | |
28 | * Copyright (C) 2006 Henrik Nordstrom <hno@squid-cache.org> | |
29 | */ | |
30 | ||
31 | /* Squid_rad_auth is a RADIUS authenticator for Squid-2.5 and later. | |
32 | * The authenticator reads a line with a user and password combination. | |
33 | * If access is granted OK is returned. Else ERR. | |
34 | * | |
35 | * Squid_rad_auth-1.0 is based on modules from the Cistron-radiusd-1.5.4. | |
36 | * | |
37 | * Currently you should only start 1 authentificator at a time because the | |
38 | * the ID's of the different programs can start to conflict. I'm not sure it | |
39 | * would help anyway. I think the RADIUS server is close by and I don't think | |
40 | * it will handle requests in parallel anyway (correct me if I'm wrong here) | |
41 | * | |
42 | * Marc van Selm <selm@cistron.nl> | |
43 | * with contributions from | |
44 | * Henrik Nordstrom <hno@squid-cache.org> | |
45 | * and many others | |
46 | */ | |
47 | ||
48 | #include <sys/types.h> | |
49 | #include <sys/socket.h> | |
50 | #include <netinet/in.h> | |
51 | #include <sys/time.h> | |
52 | #include <unistd.h> | |
53 | #include <fcntl.h> | |
54 | ||
55 | #include <ctype.h> | |
56 | #include <stdio.h> | |
57 | #include <unistd.h> | |
58 | #include <netdb.h> | |
59 | #include <pwd.h> | |
60 | #include <stdlib.h> | |
61 | #include <time.h> | |
62 | #include <string.h> | |
63 | ||
64 | #include "md5.h" | |
65 | #include "radius.h" | |
66 | #include "util.h" | |
67 | ||
68 | #define MAXPWNAM 254 | |
69 | #define MAXPASS 254 | |
70 | #define MAXLINE 254 | |
71 | ||
72 | ||
73 | static int i_send_buffer[2048]; | |
74 | static int i_recv_buffer[2048]; | |
75 | static char *send_buffer = (char *) i_send_buffer; | |
76 | static char *recv_buffer = (char *) i_recv_buffer; | |
77 | static int sockfd; | |
78 | static int request_id; | |
79 | static char vector[AUTH_VECTOR_LEN]; | |
80 | static char secretkey[MAXPASS + 1] = ""; | |
81 | static char server[MAXLINE] = ""; | |
82 | static char identifier[MAXLINE] = ""; | |
83 | static char svc_name[MAXLINE] = "radius"; | |
84 | static int nasport = 111; | |
85 | static int nasporttype = 0; | |
86 | static UINT4 nas_ipaddr; | |
87 | static UINT4 auth_ipaddr; | |
88 | static int retries = 30; | |
89 | ||
90 | char *progname = "squid_rad_auth"; | |
91 | int debug_flag = 0; | |
92 | ||
93 | /* | |
94 | * Diff two timeval, b - a | |
95 | */ | |
96 | static int | |
97 | timeval_diff(const struct timeval *a, const struct timeval *b) | |
98 | { | |
99 | return (b->tv_sec - a->tv_sec) * 1000000 + (b->tv_usec - a->tv_usec); | |
100 | } | |
101 | ||
102 | /* | |
103 | * Time since a timeval | |
104 | */ | |
105 | static int | |
106 | time_since(const struct timeval *when) | |
107 | { | |
108 | struct timeval now; | |
109 | gettimeofday(&now, NULL); | |
110 | return timeval_diff(when, &now); | |
111 | } | |
112 | ||
113 | /* | |
114 | * Receive and verify the result. | |
115 | */ | |
116 | static int | |
117 | result_recv(UINT4 host, u_short udp_port, char *buffer, int length) | |
118 | { | |
119 | AUTH_HDR *auth; | |
120 | int totallen; | |
121 | unsigned char reply_digest[AUTH_VECTOR_LEN]; | |
122 | unsigned char calc_digest[AUTH_VECTOR_LEN]; | |
123 | int secretlen; | |
124 | /* VALUE_PAIR *req; */ | |
125 | ||
126 | auth = (AUTH_HDR *) buffer; | |
127 | totallen = ntohs(auth->length); | |
128 | ||
129 | if (totallen != length) { | |
130 | fprintf(stderr, | |
131 | "Squid_rad_auth: Received invalid reply length from server (want %d/ got %d)\n", | |
132 | totallen, length); | |
133 | return -1; | |
134 | } | |
135 | ||
136 | if (auth->id != request_id) { | |
137 | /* Duplicate response of an earlier query, ignore */ | |
138 | return -1; | |
139 | } | |
140 | ||
141 | /* Verify the reply digest */ | |
142 | memcpy(reply_digest, auth->vector, AUTH_VECTOR_LEN); | |
143 | memcpy(auth->vector, vector, AUTH_VECTOR_LEN); | |
144 | secretlen = strlen(secretkey); | |
145 | memcpy(buffer + length, secretkey, secretlen); | |
146 | md5_calc(calc_digest, (unsigned char *) auth, length + secretlen); | |
147 | ||
148 | if (memcmp(reply_digest, calc_digest, AUTH_VECTOR_LEN) != 0) { | |
149 | fprintf(stderr, "Warning: Received invalid reply digest from server\n"); | |
150 | return -1; | |
151 | } | |
152 | ||
153 | if (auth->code != PW_AUTHENTICATION_ACK) | |
154 | return 1; | |
155 | ||
156 | return 0; | |
157 | } | |
158 | ||
159 | ||
160 | /* | |
161 | * Generate a random vector. | |
162 | */ | |
163 | static void | |
164 | random_vector(char *vector) | |
165 | { | |
166 | int randno; | |
167 | int i; | |
168 | ||
169 | srand((time(0) ^ rand()) + rand()); | |
170 | for (i = 0; i < AUTH_VECTOR_LEN;) { | |
171 | randno = rand(); | |
172 | memcpy(vector, &randno, sizeof(int)); | |
173 | vector += sizeof(int); | |
174 | i += sizeof(int); | |
175 | } | |
176 | } | |
177 | ||
178 | /* read the config file | |
179 | * The format should be something like: | |
180 | * # squid_rad_auth configuration file | |
181 | * # MvS: 28-10-1998 | |
182 | * server suncone.cistron.nl | |
183 | * secret testje | |
184 | */ | |
185 | static int | |
186 | rad_auth_config(const char *cfname) | |
187 | { | |
188 | FILE *cf; | |
189 | char line[MAXLINE]; | |
190 | int srv = 0, crt = 0; | |
191 | ||
192 | if ((cf = fopen(cfname, "r")) == NULL) { | |
193 | perror(cfname); | |
194 | return -1; | |
195 | } | |
196 | while (fgets(line, MAXLINE, cf) != NULL) { | |
197 | if (!memcmp(line, "server", 6)) | |
198 | srv = sscanf(line, "server %s", server); | |
199 | if (!memcmp(line, "secret", 6)) | |
200 | crt = sscanf(line, "secret %s", secretkey); | |
201 | if (!memcmp(line, "identifier", 10)) | |
202 | sscanf(line, "identifier %s", identifier); | |
203 | if (!memcmp(line, "service", 7)) | |
204 | sscanf(line, "service %s", svc_name); | |
205 | if (!memcmp(line, "port", 4)) | |
206 | sscanf(line, "port %s", svc_name); | |
207 | } | |
208 | if (srv && crt) | |
209 | return 0; | |
210 | return -1; | |
211 | } | |
212 | ||
213 | static void | |
214 | urldecode(char *dst, const char *src, int size) | |
215 | { | |
216 | char tmp[3]; | |
217 | tmp[2] = '\0'; | |
218 | while (*src && size > 1) { | |
219 | if (*src == '%' && src[1] != '\0' && src[2] != '\0') { | |
220 | src++; | |
221 | tmp[0] = *src++; | |
222 | tmp[1] = *src++; | |
223 | *dst++ = strtol(tmp, NULL, 16); | |
224 | } else { | |
225 | *dst++ = *src++; | |
226 | } | |
227 | size--; | |
228 | } | |
229 | *dst++ = '\0'; | |
230 | } | |
231 | ||
232 | static int | |
233 | authenticate(int sockfd, const char *username, const char *passwd) | |
234 | { | |
235 | AUTH_HDR *auth; | |
236 | u_short total_length; | |
237 | u_char *ptr; | |
238 | int length; | |
239 | char passbuf[MAXPASS]; | |
240 | u_char md5buf[256]; | |
241 | int secretlen; | |
242 | u_char cbc[AUTH_VECTOR_LEN]; | |
243 | int i, j; | |
244 | UINT4 ui; | |
245 | struct sockaddr_in saremote; | |
246 | fd_set readfds; | |
247 | socklen_t salen; | |
248 | int retry = retries; | |
249 | ||
250 | /* | |
251 | * Build an authentication request | |
252 | */ | |
253 | auth = (AUTH_HDR *) send_buffer; | |
254 | auth->code = PW_AUTHENTICATION_REQUEST; | |
255 | auth->id = ++request_id; | |
256 | random_vector(vector); | |
257 | memcpy(auth->vector, vector, AUTH_VECTOR_LEN); | |
258 | total_length = AUTH_HDR_LEN; | |
259 | ptr = auth->data; | |
260 | ||
261 | /* | |
262 | * User Name | |
263 | */ | |
264 | *ptr++ = PW_USER_NAME; | |
265 | length = strlen(username); | |
266 | if (length > MAXPWNAM) { | |
267 | length = MAXPWNAM; | |
268 | } | |
269 | *ptr++ = length + 2; | |
270 | memcpy(ptr, username, length); | |
271 | ptr += length; | |
272 | total_length += length + 2; | |
273 | ||
274 | /* | |
275 | * Password | |
276 | */ | |
277 | length = strlen(passwd); | |
278 | if (length > MAXPASS) { | |
279 | length = MAXPASS; | |
280 | } | |
281 | memset(passbuf, 0, MAXPASS); | |
282 | memcpy(passbuf, passwd, length); | |
283 | ||
284 | /* | |
285 | * Length is rounded up to multiple of 16, | |
286 | * and the password is encoded in blocks of 16 | |
287 | * with cipher block chaining | |
288 | */ | |
289 | length = ((length / AUTH_VECTOR_LEN) + 1) * AUTH_VECTOR_LEN; | |
290 | ||
291 | *ptr++ = PW_PASSWORD; | |
292 | *ptr++ = length + 2; | |
293 | ||
294 | secretlen = strlen(secretkey); | |
295 | /* Set up the Cipher block chain */ | |
296 | memcpy(cbc, auth->vector, AUTH_VECTOR_LEN); | |
297 | for (j = 0; j < length; j += AUTH_VECTOR_LEN) { | |
298 | /* Calculate the MD5 Digest */ | |
299 | strcpy((char *)md5buf, secretkey); | |
300 | memcpy(md5buf + secretlen, cbc, AUTH_VECTOR_LEN); | |
301 | md5_calc(cbc, md5buf, secretlen + AUTH_VECTOR_LEN); | |
302 | ||
303 | /* Xor the password into the MD5 digest */ | |
304 | for (i = 0; i < AUTH_VECTOR_LEN; i++) { | |
305 | *ptr++ = (cbc[i] ^= passbuf[j + i]); | |
306 | } | |
307 | } | |
308 | total_length += length + 2; | |
309 | ||
310 | *ptr++ = PW_NAS_PORT_ID; | |
311 | *ptr++ = 6; | |
312 | ||
313 | ui = htonl(nasport); | |
314 | memcpy(ptr, &ui, 4); | |
315 | ptr += 4; | |
316 | total_length += 6; | |
317 | ||
318 | *ptr++ = PW_NAS_PORT_TYPE; | |
319 | *ptr++ = 6; | |
320 | ||
321 | ui = htonl(nasporttype); | |
322 | memcpy(ptr, &ui, 4); | |
323 | ptr += 4; | |
324 | total_length += 6; | |
325 | ||
326 | if (*identifier) { | |
327 | int len = strlen(identifier); | |
328 | *ptr++ = PW_NAS_ID; | |
329 | *ptr++ = len + 2; | |
330 | memcpy(ptr, identifier, len); | |
331 | ptr += len; | |
332 | } else { | |
333 | *ptr++ = PW_NAS_IP_ADDRESS; | |
334 | *ptr++ = 6; | |
335 | ||
336 | ui = htonl(nas_ipaddr); | |
337 | memcpy(ptr, &ui, 4); | |
338 | ptr += 4; | |
339 | total_length += 6; | |
340 | } | |
341 | ||
342 | /* Klaus Weidner <kw@w-m-p.com> changed this | |
343 | * from htonl to htons. It might have caused | |
344 | * you trouble or not. That depends on the byte | |
345 | * order of your system. | |
346 | * The symptom was that the radius server | |
347 | * ignored the requests, because they had zero | |
348 | * length according to the data header. | |
349 | */ | |
350 | auth->length = htons(total_length); | |
351 | ||
352 | while(retry--) { | |
353 | int time_spent; | |
354 | struct timeval sent; | |
355 | /* | |
356 | * Send the request we've built. | |
357 | */ | |
358 | gettimeofday(&sent, NULL); | |
359 | send(sockfd, (char *) auth, total_length, 0); | |
360 | while ((time_spent = time_since(&sent)) < 1000000) { | |
361 | struct timeval tv; | |
362 | int rc, len; | |
363 | if (!time_spent) { | |
364 | tv.tv_sec = 1; | |
365 | tv.tv_usec = 0; | |
366 | } else { | |
367 | tv.tv_sec = 0; | |
368 | tv.tv_usec = 1000000 - time_spent; | |
369 | } | |
370 | FD_ZERO(&readfds); | |
371 | FD_SET(sockfd, &readfds); | |
372 | if (select(sockfd + 1, &readfds, NULL, NULL, &tv) == 0) /* Select timeout */ | |
373 | break; | |
374 | salen = sizeof(saremote); | |
375 | len = recvfrom(sockfd, recv_buffer, sizeof(i_recv_buffer), | |
376 | 0, (struct sockaddr *) &saremote, &salen); | |
377 | ||
378 | if (len < 0) | |
379 | continue; | |
380 | ||
381 | rc = result_recv(saremote.sin_addr.s_addr, saremote.sin_port, recv_buffer, len); | |
382 | if (rc == 0) | |
383 | return 1; | |
384 | if (rc == 1) | |
385 | return 0; | |
386 | } | |
387 | } | |
388 | ||
389 | fprintf(stderr, "%s: No response from RADIUS server\n", progname); | |
390 | ||
391 | return 0; | |
392 | } | |
393 | ||
394 | int | |
395 | main(int argc, char **argv) | |
396 | { | |
397 | struct sockaddr_in salocal; | |
398 | struct sockaddr_in saremote; | |
399 | struct servent *svp; | |
400 | u_short svc_port; | |
401 | char username[MAXPWNAM]; | |
402 | char passwd[MAXPASS]; | |
403 | char *ptr; | |
404 | char authstring[MAXLINE]; | |
405 | const char *cfname = NULL; | |
406 | int err = 0; | |
407 | socklen_t salen; | |
408 | int c; | |
409 | ||
410 | while ((c = getopt(argc, argv, "h:p:f:w:i:t:")) != -1) { | |
411 | switch(c) { | |
412 | case 'f': | |
413 | cfname = optarg; | |
414 | break; | |
415 | case 'h': | |
416 | strcpy(server, optarg); | |
417 | break; | |
418 | case 'p': | |
419 | strcpy(svc_name, optarg); | |
420 | break; | |
421 | case 'w': | |
422 | strcpy(secretkey, optarg); | |
423 | break; | |
424 | case 'i': | |
425 | strcpy(identifier, optarg); | |
426 | break; | |
427 | case 't': | |
428 | retries = atoi(optarg); | |
429 | break; | |
430 | } | |
431 | } | |
432 | /* make standard output line buffered */ | |
433 | if (setvbuf(stdout, NULL, _IOLBF, 0) != 0) | |
434 | return 1; | |
435 | ||
436 | if (cfname) { | |
437 | if (rad_auth_config(cfname) < 0) { | |
438 | fprintf(stderr, "%s: can't open configuration file '%s'.\n", argv[0], cfname); | |
439 | exit(1); | |
440 | } | |
441 | } | |
442 | ||
443 | if (!*server) { | |
444 | fprintf(stderr, "%s: Server not specified\n", argv[0]); | |
445 | exit(1); | |
446 | } | |
447 | ||
448 | if (!*secretkey) { | |
449 | fprintf(stderr, "%s: Shared secret not specified\n", argv[0]); | |
450 | exit(1); | |
451 | } | |
452 | ||
453 | /* | |
454 | * Open a connection to the server. | |
455 | */ | |
456 | svp = getservbyname(svc_name, "udp"); | |
457 | if (svp != NULL) | |
458 | svc_port = ntohs((u_short) svp->s_port); | |
459 | else | |
460 | svc_port = atoi(svc_name); | |
461 | if (svc_port == 0) | |
462 | svc_port = PW_AUTH_UDP_PORT; | |
463 | ||
464 | /* Get the IP address of the authentication server */ | |
465 | if ((auth_ipaddr = get_ipaddr(server)) == 0) { | |
466 | fprintf(stderr, "Couldn't find host %s\n", server); | |
467 | exit(1); | |
468 | } | |
469 | sockfd = socket(AF_INET, SOCK_DGRAM, 0); | |
470 | if (sockfd < 0) { | |
471 | perror("socket"); | |
472 | exit(1); | |
473 | } | |
474 | memset(&saremote, 0, sizeof(saremote)); | |
475 | saremote.sin_family = AF_INET; | |
476 | saremote.sin_addr.s_addr = htonl(auth_ipaddr); | |
477 | saremote.sin_port = htons(svc_port); | |
478 | ||
479 | if (connect(sockfd, (struct sockaddr *) &saremote, sizeof(saremote)) < 0) { | |
480 | perror("connect"); | |
481 | exit(1); | |
482 | } | |
483 | salen = sizeof(salocal); | |
484 | if (getsockname(sockfd, (struct sockaddr *) &salocal, &salen) < 0) { | |
485 | perror("getsockname"); | |
486 | exit(1); | |
487 | } | |
488 | #ifdef O_NONBLOCK | |
489 | fcntl(sockfd, F_SETFL, fcntl(sockfd, F_GETFL, 0) | O_NONBLOCK); | |
490 | #endif | |
491 | nas_ipaddr = ntohl(salocal.sin_addr.s_addr); | |
492 | while (fgets(authstring, MAXLINE, stdin) != NULL) { | |
493 | char *end; | |
494 | /* protect me form to long lines */ | |
495 | if ((end = strchr(authstring, '\n')) == NULL) { | |
496 | err = 1; | |
497 | continue; | |
498 | } | |
499 | if (err) { | |
500 | printf("ERR\n"); | |
501 | err = 0; | |
502 | continue; | |
503 | } | |
504 | if (strlen(authstring) > MAXLINE) { | |
505 | printf("ERR\n"); | |
506 | continue; | |
507 | } | |
508 | /* Strip off the trailing newline */ | |
509 | *end = '\0'; | |
510 | ||
511 | /* Parse out the username and password */ | |
512 | ptr = authstring; | |
513 | while (isspace(*ptr)) | |
514 | ptr++; | |
515 | if ((end = strchr(ptr, ' ')) == NULL) { | |
516 | printf("ERR\n"); /* No password */ | |
517 | continue; | |
518 | } | |
519 | *end = '\0'; | |
520 | urldecode(username, ptr, MAXPWNAM); | |
521 | ptr = end + 1; | |
522 | while (isspace(*ptr)) | |
523 | ptr++; | |
524 | urldecode(passwd, ptr, MAXPASS); | |
525 | ||
526 | if (authenticate(sockfd, username, passwd)) | |
527 | printf("OK\n"); | |
528 | else | |
529 | printf("ERR\n"); | |
530 | } | |
531 | close(sockfd); | |
532 | exit(1); | |
533 | } |