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 
92 static void md5_calc(uint8_t out[16], void *in, size_t len);
93 
94 static int i_send_buffer[2048];
95 static int i_recv_buffer[2048];
96 static char *send_buffer = (char *) i_send_buffer;
97 static char *recv_buffer = (char *) i_recv_buffer;
98 static int sockfd;
99 static u_char request_id;
100 static char vector[AUTH_VECTOR_LEN];
101 static char secretkey[MAXPASS + 1] = "";
102 static char server[MAXLINE] = "";
103 static char identifier[MAXLINE] = "";
104 static char svc_name[MAXLINE] = "radius";
105 static int nasport = 111;
106 static int nasporttype = 0;
107 static uint32_t nas_ipaddr;
108 static uint32_t auth_ipaddr;
109 static int retries = 10;
110 
111 char progname[] = "basic_radius_auth";
112 
113 #if _SQUID_WINDOWS_
114 void
115 Win32SockCleanup(void)
116 {
117  WSACleanup();
118  return;
119 }
120 #endif
121 
122 /*
123  * Diff two timeval, b - a
124  */
125 static int
126 timeval_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  */
134 static int
135 time_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  */
145 static void
146 md5_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  */
157 static int
158 result_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  */
198 static void
199 random_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  */
215 static int
216 rad_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 
246 static void
247 urldecode(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 
270 static void
271 authenticate(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 
458 int
459 main(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 
uint32_t get_ipaddr(char *host)
Definition: radius-util.cc:139
const char * xstrerr(int error)
Definition: xstrerror.cc:83
static void random_vector(char *aVector)
static char vector[AUTH_VECTOR_LEN]
SQUIDCEXTERN void SquidMD5Init(struct SquidMD5Context *context)
Definition: md5.c:73
static char * recv_buffer
#define MAXLINE
static int sockfd
static char * send_buffer
void debug(const char *format,...)
Definition: debug.cc:19
static int nasport
#define PW_USER_NAME
Definition: radius.h:80
static uint32_t auth_ipaddr
static void authenticate(int socket_fd, const char *username, const char *passwd)
static int timeval_diff(const struct timeval *a, const struct timeval *b)
char * optarg
Definition: getopt.c:51
static void urldecode(char *dst, const char *src, int size)
static char svc_name[MAXLINE]
SQUIDCEXTERN void SquidMD5Final(uint8_t digest[16], struct SquidMD5Context *context)
int socklen_t
Definition: types.h:137
#define PW_NAS_PORT_TYPE
Definition: radius.h:124
int getopt(int nargc, char *const *nargv, const char *ostr)
Definition: getopt.c:62
ssize_t xsend(int socketFd, const void *buf, size_t bufLength, int flags)
POSIX send(2) equivalent.
Definition: socket.h:110
std::mt19937::result_type RandomSeed32()
Definition: Random.cc:13
#define MAXPWNAM
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
u_char vector[AUTH_VECTOR_LEN]
Definition: radius.h:51
int xgetsockname(int socketFd, struct sockaddr *sa, socklen_t *saLength)
POSIX getsockname(2) equivalent.
Definition: socket.h:80
int size
Definition: ModDevPoll.cc:70
u_char data[2]
Definition: radius.h:52
u_char id
Definition: radius.h:49
static int result_recv(char *buffer, int length)
#define PW_PASSWORD
Definition: radius.h:81
#define SEND_ERR(x)
#define PW_NAS_ID
Definition: radius.h:110
#define PW_AUTHENTICATION_REQUEST
Definition: radius.h:68
static int rad_auth_config(const char *cfname)
#define PW_NAS_IP_ADDRESS
Definition: radius.h:83
static int i_recv_buffer[2048]
static int nasporttype
int debug_enabled
Definition: debug.cc:13
static int time_since(const struct timeval *when)
#define AUTH_HDR_LEN
Definition: radius.h:55
#define PW_AUTHENTICATION_ACK
Definition: radius.h:69
#define PW_NAS_PORT_ID
Definition: radius.h:84
static char identifier[MAXLINE]
int xsocket(int domain, int type, int protocol)
POSIX socket(2) equivalent.
Definition: socket.h:128
int xselect(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout)
POSIX select(2) equivalent.
Definition: select.h:22
#define HELPER_INPUT_BUFFER
Definition: UserRequest.cc:24
#define PW_AUTH_UDP_PORT
Definition: radius.h:58
static uint32_t nas_ipaddr
static int retries
static char server[MAXLINE]
static char secretkey[MAXPASS+1]
static void Win32SockCleanup(void)
struct servent * xgetservbyname(const char *name, const char *proto)
POSIX getservbyname(3) equivalent.
Definition: netdb.h:31
#define AUTH_VECTOR_LEN
Definition: radius.h:43
SQUIDCEXTERN void SquidMD5Update(struct SquidMD5Context *context, const void *buf, unsigned len)
Definition: md5.c:89
int main(int argc, char **argv)
int xclose(int fd)
POSIX close(2) equivalent.
Definition: unistd.h:43
u_char code
Definition: radius.h:48
static u_char request_id
#define MAXPASS
char progname[]
static int i_send_buffer[2048]
int xconnect(int socketFd, const struct sockaddr *sa, socklen_t saLength)
POSIX connect(2) equivalent.
Definition: socket.h:74
uint16_t length
Definition: radius.h:50
#define SEND_OK(x)
static void md5_calc(uint8_t out[16], void *in, size_t len)

 

Introduction

Documentation

Support

Miscellaneous