]> git.ipfire.org Git - thirdparty/squid.git/blob - helpers/basic_auth/RADIUS/basic_radius_auth.cc
Renamed squid.h to squid-old.h and config.h to squid.h
[thirdparty/squid.git] / helpers / basic_auth / RADIUS / basic_radius_auth.cc
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 /* basic_radius_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 * basic_radius_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 "squid.h"
49 #include "helpers/defines.h"
50 #include "md5.h"
51 #include "radius.h"
52 #include "radius-util.h"
53
54 #if HAVE_SYS_SOCKET_H
55 #include <sys/socket.h>
56 #endif
57 #if HAVE_NETINET_IN_H
58 #include <netinet/in.h>
59 #endif
60 #if HAVE_UNISTD_H
61 #include <unistd.h>
62 #endif
63 #if HAVE_FCNTL_H
64 #include <fcntl.h>
65 #endif
66 #if _SQUID_WINDOWS_
67 #include <io.h>
68 #endif
69 #if HAVE_CTYPE_H
70 #include <ctype.h>
71 #endif
72 #if HAVE_STDIO_H
73 #include <stdio.h>
74 #endif
75 #if HAVE_UNISTD_H
76 #include <unistd.h>
77 #endif
78 #if HAVE_NETDB_H
79 #include <netdb.h>
80 #endif
81 #if HAVE_PWD_H
82 #include <pwd.h>
83 #endif
84 #if HAVE_TIME_H
85 #include <time.h>
86 #endif
87 #if HAVE_STRING_H
88 #include <string.h>
89 #endif
90 #if HAVE_GETOPT_H
91 #include <getopt.h>
92 #endif
93 #if HAVE_ERRNO_H
94 #include <errno.h>
95 #endif
96
97 /* AYJ: helper input buffer may be a lot larger than this used to expect... */
98 #define MAXPWNAM 254
99 #define MAXPASS 254
100 #define MAXLINE 254
101
102 static void md5_calc(uint8_t out[16], void *in, size_t len);
103
104 static int i_send_buffer[2048];
105 static int i_recv_buffer[2048];
106 static char *send_buffer = (char *) i_send_buffer;
107 static char *recv_buffer = (char *) i_recv_buffer;
108 static int sockfd;
109 static u_char request_id;
110 static char vector[AUTH_VECTOR_LEN];
111 static char secretkey[MAXPASS + 1] = "";
112 static char server[MAXLINE] = "";
113 static char identifier[MAXLINE] = "";
114 static char svc_name[MAXLINE] = "radius";
115 static int nasport = 111;
116 static int nasporttype = 0;
117 static uint32_t nas_ipaddr;
118 static uint32_t auth_ipaddr;
119 static int retries = 30;
120
121 char progname[] = "basic_radius_auth";
122
123 #if _SQUID_MSWIN_
124 void
125 Win32SockCleanup(void)
126 {
127 WSACleanup();
128 return;
129 }
130 #endif
131
132 /*
133 * Diff two timeval, b - a
134 */
135 static int
136 timeval_diff(const struct timeval *a, const struct timeval *b)
137 {
138 return (b->tv_sec - a->tv_sec) * 1000000 + (b->tv_usec - a->tv_usec);
139 }
140
141 /*
142 * Time since a timeval
143 */
144 static int
145 time_since(const struct timeval *when)
146 {
147 struct timeval now;
148 gettimeofday(&now, NULL);
149 return timeval_diff(when, &now);
150 }
151
152 /*
153 * MD5 digest
154 */
155 static void
156 md5_calc(uint8_t out[16], void *in, size_t len)
157 {
158 SquidMD5_CTX ctx;
159 SquidMD5Init(&ctx);
160 SquidMD5Update(&ctx, in, len);
161 SquidMD5Final(out, &ctx);
162 }
163
164 /*
165 * Receive and verify the result.
166 */
167 static int
168 result_recv(uint32_t host, unsigned short udp_port, char *buffer, int length)
169 {
170 AUTH_HDR *auth;
171 int totallen;
172 unsigned char reply_digest[AUTH_VECTOR_LEN];
173 unsigned char calc_digest[AUTH_VECTOR_LEN];
174 int secretlen;
175 /* VALUE_PAIR *req; */
176
177 auth = (AUTH_HDR *) buffer;
178 totallen = ntohs(auth->length);
179
180 if (totallen != length) {
181 debug("Received invalid reply length from server (want %d/ got %d)\n", totallen, length);
182 return -1;
183 }
184 if (auth->id != request_id) {
185 /* Duplicate response of an earlier query, ignore */
186 return -1;
187 }
188 /* Verify the reply digest */
189 memcpy(reply_digest, auth->vector, AUTH_VECTOR_LEN);
190 memcpy(auth->vector, vector, AUTH_VECTOR_LEN);
191 secretlen = strlen(secretkey);
192 memcpy(buffer + length, secretkey, secretlen);
193 md5_calc(calc_digest, (unsigned char *) auth, length + secretlen);
194
195 if (memcmp(reply_digest, calc_digest, AUTH_VECTOR_LEN) != 0) {
196 debug("WARNING: Received invalid reply digest from server\n");
197 return -1;
198 }
199 if (auth->code != PW_AUTHENTICATION_ACK)
200 return 1;
201
202 return 0;
203 }
204
205
206 /*
207 * Generate a random vector.
208 */
209 static void
210 random_vector(char *aVector)
211 {
212 int randno;
213 int i;
214
215 srand((time(0) ^ rand()) + rand());
216 for (i = 0; i < AUTH_VECTOR_LEN;) {
217 randno = rand();
218 memcpy(aVector, &randno, sizeof(int));
219 aVector += sizeof(int);
220 i += sizeof(int);
221 }
222 }
223
224 /* read the config file
225 * The format should be something like:
226 * # basic_radius_auth configuration file
227 * # MvS: 28-10-1998
228 * server suncone.cistron.nl
229 * secret testje
230 */
231 static int
232 rad_auth_config(const char *cfname)
233 {
234 FILE *cf;
235 char line[MAXLINE];
236 int srv = 0, crt = 0;
237
238 if ((cf = fopen(cfname, "r")) == NULL) {
239 perror(cfname);
240 return -1;
241 }
242 while (fgets(line, MAXLINE, cf) != NULL) {
243 if (!memcmp(line, "server", 6))
244 srv = sscanf(line, "server %s", server);
245 if (!memcmp(line, "secret", 6))
246 crt = sscanf(line, "secret %s", secretkey);
247 if (!memcmp(line, "identifier", 10))
248 sscanf(line, "identifier %s", identifier);
249 if (!memcmp(line, "service", 7))
250 sscanf(line, "service %s", svc_name);
251 if (!memcmp(line, "port", 4))
252 sscanf(line, "port %s", svc_name);
253 }
254 fclose(cf);
255 if (srv && crt)
256 return 0;
257 return -1;
258 }
259
260 static void
261 urldecode(char *dst, const char *src, int size)
262 {
263 char tmp[3];
264 tmp[2] = '\0';
265 while (*src && size > 1) {
266 if (*src == '%' && src[1] != '\0' && src[2] != '\0') {
267 src++;
268 tmp[0] = *src++;
269 tmp[1] = *src++;
270 *dst++ = strtol(tmp, NULL, 16);
271 } else {
272 *dst++ = *src++;
273 }
274 size--;
275 }
276 *dst++ = '\0';
277 }
278
279 static int
280 authenticate(int socket_fd, const char *username, const char *passwd)
281 {
282 AUTH_HDR *auth;
283 unsigned short total_length;
284 u_char *ptr;
285 int length;
286 char passbuf[MAXPASS];
287 u_char md5buf[256];
288 int secretlen;
289 u_char cbc[AUTH_VECTOR_LEN];
290 int i, j;
291 uint32_t ui;
292 struct sockaddr_in saremote;
293 fd_set readfds;
294 socklen_t salen;
295 int retry = retries;
296
297 /*
298 * Build an authentication request
299 */
300 auth = (AUTH_HDR *) send_buffer;
301 auth->code = PW_AUTHENTICATION_REQUEST;
302 auth->id = ++request_id;
303 random_vector(vector);
304 memcpy(auth->vector, vector, AUTH_VECTOR_LEN);
305 total_length = AUTH_HDR_LEN;
306 ptr = auth->data;
307
308 /*
309 * User Name
310 */
311 *ptr++ = PW_USER_NAME;
312 length = strlen(username);
313 if (length > MAXPWNAM) {
314 length = MAXPWNAM;
315 }
316 *ptr++ = length + 2;
317 memcpy(ptr, username, length);
318 ptr += length;
319 total_length += length + 2;
320
321 /*
322 * Password
323 */
324 length = strlen(passwd);
325 if (length > MAXPASS) {
326 length = MAXPASS;
327 }
328 memset(passbuf, 0, MAXPASS);
329 memcpy(passbuf, passwd, length);
330
331 /*
332 * Length is rounded up to multiple of 16,
333 * and the password is encoded in blocks of 16
334 * with cipher block chaining
335 */
336 length = ((length / AUTH_VECTOR_LEN) + 1) * AUTH_VECTOR_LEN;
337
338 *ptr++ = PW_PASSWORD;
339 *ptr++ = length + 2;
340
341 secretlen = strlen(secretkey);
342 /* Set up the Cipher block chain */
343 memcpy(cbc, auth->vector, AUTH_VECTOR_LEN);
344 for (j = 0; j < length; j += AUTH_VECTOR_LEN) {
345 /* Calculate the MD5 Digest */
346 strcpy((char *) md5buf, secretkey);
347 memcpy(md5buf + secretlen, cbc, AUTH_VECTOR_LEN);
348 md5_calc(cbc, md5buf, secretlen + AUTH_VECTOR_LEN);
349
350 /* Xor the password into the MD5 digest */
351 for (i = 0; i < AUTH_VECTOR_LEN; i++) {
352 *ptr++ = (cbc[i] ^= passbuf[j + i]);
353 }
354 }
355 total_length += length + 2;
356
357 *ptr++ = PW_NAS_PORT_ID;
358 *ptr++ = 6;
359
360 ui = htonl(nasport);
361 memcpy(ptr, &ui, 4);
362 ptr += 4;
363 total_length += 6;
364
365 *ptr++ = PW_NAS_PORT_TYPE;
366 *ptr++ = 6;
367
368 ui = htonl(nasporttype);
369 memcpy(ptr, &ui, 4);
370 ptr += 4;
371 total_length += 6;
372
373 if (*identifier) {
374 int len = strlen(identifier);
375 *ptr++ = PW_NAS_ID;
376 *ptr++ = len + 2;
377 memcpy(ptr, identifier, len);
378 ptr += len;
379 total_length += len + 2;
380 } else {
381 *ptr++ = PW_NAS_IP_ADDRESS;
382 *ptr++ = 6;
383
384 ui = htonl(nas_ipaddr);
385 memcpy(ptr, &ui, 4);
386 ptr += 4;
387 total_length += 6;
388 }
389
390 /* Klaus Weidner <kw@w-m-p.com> changed this
391 * from htonl to htons. It might have caused
392 * you trouble or not. That depends on the byte
393 * order of your system.
394 * The symptom was that the radius server
395 * ignored the requests, because they had zero
396 * length according to the data header.
397 */
398 auth->length = htons(total_length);
399
400 while (retry--) {
401 int time_spent;
402 struct timeval sent;
403 /*
404 * Send the request we've built.
405 */
406 gettimeofday(&sent, NULL);
407 send(socket_fd, (char *) auth, total_length, 0);
408 while ((time_spent = time_since(&sent)) < 1000000) {
409 struct timeval tv;
410 int rc, len;
411 if (!time_spent) {
412 tv.tv_sec = 1;
413 tv.tv_usec = 0;
414 } else {
415 tv.tv_sec = 0;
416 tv.tv_usec = 1000000 - time_spent;
417 }
418 FD_ZERO(&readfds);
419 FD_SET(socket_fd, &readfds);
420 if (select(socket_fd + 1, &readfds, NULL, NULL, &tv) == 0) /* Select timeout */
421 break;
422 salen = sizeof(saremote);
423 len = recvfrom(socket_fd, recv_buffer, sizeof(i_recv_buffer),
424 0, (struct sockaddr *) &saremote, &salen);
425
426 if (len < 0)
427 continue;
428
429 rc = result_recv(saremote.sin_addr.s_addr, saremote.sin_port, recv_buffer, len);
430 if (rc == 0)
431 return 1;
432 if (rc == 1)
433 return 0;
434 }
435 }
436
437 fprintf(stderr, "%s: No response from RADIUS server\n", progname);
438
439 return 0;
440 }
441
442 int
443 main(int argc, char **argv)
444 {
445 struct sockaddr_in salocal;
446 struct sockaddr_in saremote;
447 struct servent *svp;
448 unsigned short svc_port;
449 char username[MAXPWNAM];
450 char passwd[MAXPASS];
451 char *ptr;
452 char buf[HELPER_INPUT_BUFFER];
453 const char *cfname = NULL;
454 int err = 0;
455 socklen_t salen;
456 int c;
457
458 while ((c = getopt(argc, argv, "h:p:f:w:i:t:")) != -1) {
459 switch (c) {
460 case 'd':
461 debug_enabled = 1;
462 break;
463 case 'f':
464 cfname = optarg;
465 break;
466 case 'h':
467 strcpy(server, optarg);
468 break;
469 case 'p':
470 strcpy(svc_name, optarg);
471 break;
472 case 'w':
473 strcpy(secretkey, optarg);
474 break;
475 case 'i':
476 strcpy(identifier, optarg);
477 break;
478 case 't':
479 retries = atoi(optarg);
480 break;
481 }
482 }
483 /* make standard output line buffered */
484 if (setvbuf(stdout, NULL, _IOLBF, 0) != 0)
485 return 1;
486
487 if (cfname) {
488 if (rad_auth_config(cfname) < 0) {
489 fprintf(stderr, "FATAL: %s: can't open configuration file '%s'.\n", argv[0], cfname);
490 exit(1);
491 }
492 }
493 if (!*server) {
494 fprintf(stderr, "FATAL: %s: Server not specified\n", argv[0]);
495 exit(1);
496 }
497 if (!*secretkey) {
498 fprintf(stderr, "FATAL: %s: Shared secret not specified\n", argv[0]);
499 exit(1);
500 }
501 #if _SQUID_MSWIN_
502 {
503 WSADATA wsaData;
504 WSAStartup(2, &wsaData);
505 atexit(Win32SockCleanup);
506 }
507 #endif
508 /*
509 * Open a connection to the server.
510 */
511 svp = getservbyname(svc_name, "udp");
512 if (svp != NULL)
513 svc_port = ntohs((unsigned short) svp->s_port);
514 else
515 svc_port = atoi(svc_name);
516 if (svc_port == 0)
517 svc_port = PW_AUTH_UDP_PORT;
518
519 /* Get the IP address of the authentication server */
520 if ((auth_ipaddr = get_ipaddr(server)) == 0) {
521 fprintf(stderr, "FATAL: %s: Couldn't find host %s\n", argv[0], server);
522 exit(1);
523 }
524 sockfd = socket(AF_INET, SOCK_DGRAM, 0);
525 if (sockfd < 0) {
526 perror("socket");
527 exit(1);
528 }
529 memset(&saremote, 0, sizeof(saremote));
530 saremote.sin_family = AF_INET;
531 saremote.sin_addr.s_addr = htonl(auth_ipaddr);
532 saremote.sin_port = htons(svc_port);
533
534 if (connect(sockfd, (struct sockaddr *) &saremote, sizeof(saremote)) < 0) {
535 perror("connect");
536 exit(1);
537 }
538 salen = sizeof(salocal);
539 if (getsockname(sockfd, (struct sockaddr *) &salocal, &salen) < 0) {
540 perror("getsockname");
541 exit(1);
542 }
543 #ifdef O_NONBLOCK
544 fcntl(sockfd, F_SETFL, fcntl(sockfd, F_GETFL, 0) | O_NONBLOCK);
545 #endif
546 nas_ipaddr = ntohl(salocal.sin_addr.s_addr);
547 while (fgets(buf, HELPER_INPUT_BUFFER, stdin) != NULL) {
548 char *end;
549 /* protect me form to long lines */
550 if ((end = strchr(buf, '\n')) == NULL) {
551 err = 1;
552 continue;
553 }
554 if (err) {
555 SEND_ERR("");
556 err = 0;
557 continue;
558 }
559 if (strlen(buf) > HELPER_INPUT_BUFFER) {
560 SEND_ERR("");
561 continue;
562 }
563 /* Strip off the trailing newline */
564 *end = '\0';
565
566 /* Parse out the username and password */
567 ptr = buf;
568 while (isspace(*ptr))
569 ptr++;
570 if ((end = strchr(ptr, ' ')) == NULL) {
571 SEND_ERR("No password");
572 continue;
573 }
574 *end = '\0';
575 urldecode(username, ptr, MAXPWNAM);
576 ptr = end + 1;
577 while (isspace(*ptr))
578 ptr++;
579 urldecode(passwd, ptr, MAXPASS);
580
581 if (authenticate(sockfd, username, passwd))
582 SEND_OK("");
583 else
584 SEND_ERR("");
585 }
586 close(sockfd);
587 exit(1);
588 }