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