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