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