]> git.ipfire.org Git - thirdparty/squid.git/blob - helpers/basic_auth/RADIUS/basic_radius_auth.cc
Enable source-formatting tools to collapse multiple whitelines in the source to one.
[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 ++src;
270 tmp[1] = *src;
271 ++src;
272 *dst = strtol(tmp, NULL, 16);
273 ++dst;
274 } else {
275 *dst = *src;
276 ++dst;
277 ++src;
278 }
279 --size;
280 }
281 *dst = '\0';
282 }
283
284 static int
285 authenticate(int socket_fd, const char *username, const char *passwd)
286 {
287 AUTH_HDR *auth;
288 unsigned short total_length;
289 u_char *ptr;
290 int length;
291 char passbuf[MAXPASS];
292 u_char md5buf[256];
293 int secretlen;
294 u_char cbc[AUTH_VECTOR_LEN];
295 int i, j;
296 uint32_t ui;
297 struct sockaddr_in saremote;
298 fd_set readfds;
299 socklen_t salen;
300 int retry = retries;
301
302 /*
303 * Build an authentication request
304 */
305 auth = (AUTH_HDR *) send_buffer;
306 auth->code = PW_AUTHENTICATION_REQUEST;
307 auth->id = ++request_id;
308 random_vector(vector);
309 memcpy(auth->vector, vector, AUTH_VECTOR_LEN);
310 total_length = AUTH_HDR_LEN;
311 ptr = auth->data;
312
313 /*
314 * User Name
315 */
316 *ptr = PW_USER_NAME;
317 ++ptr;
318 length = strlen(username);
319 if (length > MAXPWNAM) {
320 length = MAXPWNAM;
321 }
322 *ptr = length + 2;
323 ++ptr;
324 memcpy(ptr, username, length);
325 ptr += length;
326 total_length += length + 2;
327
328 /*
329 * Password
330 */
331 length = strlen(passwd);
332 if (length > MAXPASS) {
333 length = MAXPASS;
334 }
335 memset(passbuf, 0, MAXPASS);
336 memcpy(passbuf, passwd, length);
337
338 /*
339 * Length is rounded up to multiple of 16,
340 * and the password is encoded in blocks of 16
341 * with cipher block chaining
342 */
343 length = ((length / AUTH_VECTOR_LEN) + 1) * AUTH_VECTOR_LEN;
344
345 *ptr = PW_PASSWORD;
346 ++ptr;
347 *ptr = length + 2;
348 ++ptr;
349
350 secretlen = strlen(secretkey);
351 /* Set up the Cipher block chain */
352 memcpy(cbc, auth->vector, AUTH_VECTOR_LEN);
353 for (j = 0; j < length; j += AUTH_VECTOR_LEN) {
354 /* Calculate the MD5 Digest */
355 strcpy((char *) md5buf, secretkey);
356 memcpy(md5buf + secretlen, cbc, AUTH_VECTOR_LEN);
357 md5_calc(cbc, md5buf, secretlen + AUTH_VECTOR_LEN);
358
359 /* Xor the password into the MD5 digest */
360 for (i = 0; i < AUTH_VECTOR_LEN; ++i) {
361 *ptr = (cbc[i] ^= passbuf[j + i]);
362 ++ptr;
363 }
364 }
365 total_length += length + 2;
366
367 *ptr = PW_NAS_PORT_ID;
368 ++ptr;
369 *ptr = 6;
370 ++ptr;
371
372 ui = htonl(nasport);
373 memcpy(ptr, &ui, 4);
374 ptr += 4;
375 total_length += 6;
376
377 *ptr = PW_NAS_PORT_TYPE;
378 ++ptr;
379 *ptr = 6;
380 ++ptr;
381
382 ui = htonl(nasporttype);
383 memcpy(ptr, &ui, 4);
384 ptr += 4;
385 total_length += 6;
386
387 if (*identifier) {
388 int len = strlen(identifier);
389 *ptr = PW_NAS_ID;
390 ++ptr;
391 *ptr = len + 2;
392 ++ptr;
393 memcpy(ptr, identifier, len);
394 ptr += len;
395 total_length += len + 2;
396 } else {
397 *ptr = PW_NAS_IP_ADDRESS;
398 ++ptr;
399 *ptr = 6;
400 ++ptr;
401
402 ui = htonl(nas_ipaddr);
403 memcpy(ptr, &ui, 4);
404 ptr += 4;
405 total_length += 6;
406 }
407
408 /* Klaus Weidner <kw@w-m-p.com> changed this
409 * from htonl to htons. It might have caused
410 * you trouble or not. That depends on the byte
411 * order of your system.
412 * The symptom was that the radius server
413 * ignored the requests, because they had zero
414 * length according to the data header.
415 */
416 auth->length = htons(total_length);
417
418 while (retry) {
419 --retry;
420 int time_spent;
421 struct timeval sent;
422 /*
423 * Send the request we've built.
424 */
425 gettimeofday(&sent, NULL);
426 send(socket_fd, (char *) auth, total_length, 0);
427 while ((time_spent = time_since(&sent)) < 1000000) {
428 struct timeval tv;
429 int rc, len;
430 if (!time_spent) {
431 tv.tv_sec = 1;
432 tv.tv_usec = 0;
433 } else {
434 tv.tv_sec = 0;
435 tv.tv_usec = 1000000 - time_spent;
436 }
437 FD_ZERO(&readfds);
438 FD_SET(socket_fd, &readfds);
439 if (select(socket_fd + 1, &readfds, NULL, NULL, &tv) == 0) /* Select timeout */
440 break;
441 salen = sizeof(saremote);
442 len = recvfrom(socket_fd, recv_buffer, sizeof(i_recv_buffer),
443 0, (struct sockaddr *) &saremote, &salen);
444
445 if (len < 0)
446 continue;
447
448 rc = result_recv(saremote.sin_addr.s_addr, saremote.sin_port, recv_buffer, len);
449 if (rc == 0)
450 return 1;
451 if (rc == 1)
452 return 0;
453 }
454 }
455
456 fprintf(stderr, "%s: No response from RADIUS server\n", progname);
457
458 return 0;
459 }
460
461 int
462 main(int argc, char **argv)
463 {
464 struct sockaddr_in salocal;
465 struct sockaddr_in saremote;
466 struct servent *svp;
467 unsigned short svc_port;
468 char username[MAXPWNAM];
469 char passwd[MAXPASS];
470 char *ptr;
471 char buf[HELPER_INPUT_BUFFER];
472 const char *cfname = NULL;
473 int err = 0;
474 socklen_t salen;
475 int c;
476
477 while ((c = getopt(argc, argv, "h:p:f:w:i:t:")) != -1) {
478 switch (c) {
479 case 'd':
480 debug_enabled = 1;
481 break;
482 case 'f':
483 cfname = optarg;
484 break;
485 case 'h':
486 strcpy(server, optarg);
487 break;
488 case 'p':
489 strcpy(svc_name, optarg);
490 break;
491 case 'w':
492 strcpy(secretkey, optarg);
493 break;
494 case 'i':
495 strcpy(identifier, optarg);
496 break;
497 case 't':
498 retries = atoi(optarg);
499 break;
500 }
501 }
502 /* make standard output line buffered */
503 if (setvbuf(stdout, NULL, _IOLBF, 0) != 0)
504 return 1;
505
506 if (cfname) {
507 if (rad_auth_config(cfname) < 0) {
508 fprintf(stderr, "FATAL: %s: can't open configuration file '%s'.\n", argv[0], cfname);
509 exit(1);
510 }
511 }
512 if (!*server) {
513 fprintf(stderr, "FATAL: %s: Server not specified\n", argv[0]);
514 exit(1);
515 }
516 if (!*secretkey) {
517 fprintf(stderr, "FATAL: %s: Shared secret not specified\n", argv[0]);
518 exit(1);
519 }
520 #if _SQUID_MSWIN_
521 {
522 WSADATA wsaData;
523 WSAStartup(2, &wsaData);
524 atexit(Win32SockCleanup);
525 }
526 #endif
527 /*
528 * Open a connection to the server.
529 */
530 svp = getservbyname(svc_name, "udp");
531 if (svp != NULL)
532 svc_port = ntohs((unsigned short) svp->s_port);
533 else
534 svc_port = atoi(svc_name);
535 if (svc_port == 0)
536 svc_port = PW_AUTH_UDP_PORT;
537
538 /* Get the IP address of the authentication server */
539 if ((auth_ipaddr = get_ipaddr(server)) == 0) {
540 fprintf(stderr, "FATAL: %s: Couldn't find host %s\n", argv[0], server);
541 exit(1);
542 }
543 sockfd = socket(AF_INET, SOCK_DGRAM, 0);
544 if (sockfd < 0) {
545 perror("socket");
546 exit(1);
547 }
548 memset(&saremote, 0, sizeof(saremote));
549 saremote.sin_family = AF_INET;
550 saremote.sin_addr.s_addr = htonl(auth_ipaddr);
551 saremote.sin_port = htons(svc_port);
552
553 if (connect(sockfd, (struct sockaddr *) &saremote, sizeof(saremote)) < 0) {
554 perror("connect");
555 exit(1);
556 }
557 salen = sizeof(salocal);
558 if (getsockname(sockfd, (struct sockaddr *) &salocal, &salen) < 0) {
559 perror("getsockname");
560 exit(1);
561 }
562 #ifdef O_NONBLOCK
563 fcntl(sockfd, F_SETFL, fcntl(sockfd, F_GETFL, 0) | O_NONBLOCK);
564 #endif
565 nas_ipaddr = ntohl(salocal.sin_addr.s_addr);
566 while (fgets(buf, HELPER_INPUT_BUFFER, stdin) != NULL) {
567 char *end;
568 /* protect me form to long lines */
569 if ((end = strchr(buf, '\n')) == NULL) {
570 err = 1;
571 continue;
572 }
573 if (err) {
574 SEND_ERR("");
575 err = 0;
576 continue;
577 }
578 if (strlen(buf) > HELPER_INPUT_BUFFER) {
579 SEND_ERR("");
580 continue;
581 }
582 /* Strip off the trailing newline */
583 *end = '\0';
584
585 /* Parse out the username and password */
586 ptr = buf;
587 while (isspace(*ptr))
588 ++ptr;
589 if ((end = strchr(ptr, ' ')) == NULL) {
590 SEND_ERR("No password");
591 continue;
592 }
593 *end = '\0';
594 urldecode(username, ptr, MAXPWNAM);
595 ptr = end + 1;
596 while (isspace(*ptr))
597 ++ptr;
598 urldecode(passwd, ptr, MAXPASS);
599
600 if (authenticate(sockfd, username, passwd))
601 SEND_OK("");
602 else
603 SEND_ERR("");
604 }
605 close(sockfd);
606 exit(1);
607 }