basic_radius_auth.cc
Go to the documentation of this file.
1 /*
2  * Copyright (C) 1996-2023 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 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 
56 #include "squid.h"
59 #include "base/Random.h"
61 #include "md5.h"
62 
63 #include <cctype>
64 #include <cerrno>
65 #include <cstring>
66 #include <ctime>
67 #if HAVE_SYS_SOCKET_H
68 #include <sys/socket.h>
69 #endif
70 #if HAVE_NETINET_IN_H
71 #include <netinet/in.h>
72 #endif
73 #if HAVE_UNISTD_H
74 #include <unistd.h>
75 #endif
76 #if HAVE_FCNTL_H
77 #include <fcntl.h>
78 #endif
79 #if _SQUID_WINDOWS_
80 #include <io.h>
81 #endif
82 #if HAVE_UNISTD_H
83 #include <unistd.h>
84 #endif
85 #if HAVE_NETDB_H
86 #include <netdb.h>
87 #endif
88 #if HAVE_PWD_H
89 #include <pwd.h>
90 #endif
91 #if HAVE_GETOPT_H
92 #include <getopt.h>
93 #endif
94 
95 /* AYJ: helper input buffer may be a lot larger than this used to expect... */
96 #define MAXPWNAM 254
97 #define MAXPASS 254
98 #define MAXLINE 254
99 
100 static void md5_calc(uint8_t out[16], void *in, size_t len);
101 
102 static int i_send_buffer[2048];
103 static int i_recv_buffer[2048];
104 static char *send_buffer = (char *) i_send_buffer;
105 static char *recv_buffer = (char *) i_recv_buffer;
106 static int sockfd;
107 static u_char request_id;
108 static char vector[AUTH_VECTOR_LEN];
109 static char secretkey[MAXPASS + 1] = "";
110 static char server[MAXLINE] = "";
111 static char identifier[MAXLINE] = "";
112 static char svc_name[MAXLINE] = "radius";
113 static int nasport = 111;
114 static int nasporttype = 0;
115 static uint32_t nas_ipaddr;
116 static uint32_t auth_ipaddr;
117 static int retries = 10;
118 
119 char progname[] = "basic_radius_auth";
120 
121 #if _SQUID_WINDOWS_
122 void
123 Win32SockCleanup(void)
124 {
125  WSACleanup();
126  return;
127 }
128 #endif
129 
130 /*
131  * Diff two timeval, b - a
132  */
133 static int
134 timeval_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  */
142 static int
143 time_since(const struct timeval *when)
144 {
145  struct timeval now;
146  gettimeofday(&now, nullptr);
147  return timeval_diff(when, &now);
148 }
149 
150 /*
151  * MD5 digest
152  */
153 static void
154 md5_calc(uint8_t out[16], void *in, size_t len)
155 {
156  SquidMD5_CTX ctx;
157  SquidMD5Init(&ctx);
158  SquidMD5Update(&ctx, in, len);
159  SquidMD5Final(out, &ctx);
160 }
161 
162 /*
163  * Receive and verify the result.
164  */
165 static int
166 result_recv(char *buffer, int length)
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) {
179  debug("Received invalid reply length from server (want %d/ got %d)\n", totallen, length);
180  return -1;
181  }
182  if (auth->id != request_id) {
183  /* Duplicate response of an earlier query, ignore */
184  return -1;
185  }
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) {
194  debug("WARNING: Received invalid reply digest from server\n");
195  return -1;
196  }
197  if (auth->code != PW_AUTHENTICATION_ACK)
198  return 1;
199 
200  return 0;
201 }
202 
203 /*
204  * Generate a random vector.
205  */
206 static void
207 random_vector(char *aVector)
208 {
209  static std::mt19937 mt(RandomSeed32());
210  static std::uniform_int_distribution<uint8_t> dist;
211 
212  for (int i = 0; i < AUTH_VECTOR_LEN; ++i)
213  aVector[i] = static_cast<char>(dist(mt) & 0xFF);
214 }
215 
216 /* read the config file
217  * The format should be something like:
218  * # basic_radius_auth configuration file
219  * # MvS: 28-10-1998
220  * server suncone.cistron.nl
221  * secret testje
222  */
223 static int
224 rad_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")) == nullptr) {
231  perror(cfname);
232  return -1;
233  }
234  while (fgets(line, MAXLINE, cf) != nullptr) {
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);
245  if (!memcmp(line, "timeout", 7))
246  sscanf(line, "timeout %d", &retries);
247  }
248  fclose(cf);
249  if (srv && crt)
250  return 0;
251  return -1;
252 }
253 
254 static void
255 urldecode(char *dst, const char *src, int size)
256 {
257  char tmp[3];
258  tmp[2] = '\0';
259  while (*src && size > 1) {
260  if (*src == '%' && src[1] != '\0' && src[2] != '\0') {
261  ++src;
262  tmp[0] = *src;
263  ++src;
264  tmp[1] = *src;
265  ++src;
266  *dst = strtol(tmp, nullptr, 16);
267  ++dst;
268  } else {
269  *dst = *src;
270  ++dst;
271  ++src;
272  }
273  --size;
274  }
275  *dst = '\0';
276 }
277 
278 static void
279 authenticate(int socket_fd, const char *username, const char *passwd)
280 {
281  AUTH_HDR *auth;
282  unsigned short total_length;
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;
290  uint32_t ui;
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;
301  auth->id = ++request_id;
303  memcpy(auth->vector, vector, AUTH_VECTOR_LEN);
304  total_length = AUTH_HDR_LEN;
305  ptr = auth->data;
306 
307  /*
308  * User Name
309  */
310  *ptr = PW_USER_NAME;
311  ++ptr;
312  length = strlen(username);
313  if (length > MAXPWNAM) {
314  length = MAXPWNAM;
315  }
316  *ptr = length + 2;
317  ptr = (unsigned char*)send_buffer + sizeof(AUTH_HDR);
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) {
327  length = MAXPASS;
328  }
329  memset(passbuf, 0, MAXPASS);
330  memcpy(passbuf, passwd, length);
331 
332  /*
333  * Length is rounded up to multiple of 16,
334  * and the password is encoded in blocks of 16
335  * with cipher block chaining
336  */
337  length = ((length / AUTH_VECTOR_LEN) + 1) * AUTH_VECTOR_LEN;
338 
339  *ptr = PW_PASSWORD;
340  ++ptr;
341  *ptr = length + 2;
342  ++ptr;
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) {
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 */
354  for (i = 0; i < AUTH_VECTOR_LEN; ++i) {
355  *ptr = (cbc[i] ^= passbuf[j + i]);
356  ++ptr;
357  }
358  }
359  total_length += length + 2;
360 
361  *ptr = PW_NAS_PORT_ID;
362  ++ptr;
363  *ptr = 6;
364  ++ptr;
365 
366  ui = htonl(nasport);
367  memcpy(ptr, &ui, 4);
368  ptr += 4;
369  total_length += 6;
370 
371  *ptr = PW_NAS_PORT_TYPE;
372  ++ptr;
373  *ptr = 6;
374  ++ptr;
375 
376  ui = htonl(nasporttype);
377  memcpy(ptr, &ui, 4);
378  ptr += 4;
379  total_length += 6;
380 
381  if (*identifier) {
382  int len = strlen(identifier);
383  *ptr = PW_NAS_ID;
384  ++ptr;
385  *ptr = len + 2;
386  ++ptr;
387  memcpy(ptr, identifier, len);
388  ptr += len;
389  total_length += len + 2;
390  } else {
391  *ptr = PW_NAS_IP_ADDRESS;
392  ++ptr;
393  *ptr = 6;
394  ++ptr;
395 
396  ui = htonl(nas_ipaddr);
397  memcpy(ptr, &ui, 4);
398  ptr += 4;
399  total_length += 6;
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
407  * ignored the requests, because they had zero
408  * length according to the data header.
409  */
410  auth->length = htons(total_length);
411 
412  while (retry) {
413  --retry;
414  int time_spent;
415  struct timeval sent;
416  /*
417  * Send the request we've built.
418  */
419  gettimeofday(&sent, nullptr);
420  if (send(socket_fd, (char *) auth, total_length, 0) < 0) {
421  int xerrno = errno;
422  // EAGAIN is expected at high traffic, just retry
423  // TODO: block/sleep a few ms to let the apparently full buffer drain ?
424  if (xerrno != EAGAIN && xerrno != EWOULDBLOCK)
425  fprintf(stderr,"ERROR: RADIUS send() failure: %s\n", xstrerr(xerrno));
426  continue;
427  }
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);
439  FD_SET(socket_fd, &readfds);
440  if (select(socket_fd + 1, &readfds, nullptr, nullptr, &tv) == 0) /* Select timeout */
441  break;
442  salen = sizeof(saremote);
443  len = recvfrom(socket_fd, recv_buffer, sizeof(i_recv_buffer),
444  0, (struct sockaddr *) &saremote, &salen);
445 
446  if (len < 0)
447  continue;
448 
449  rc = result_recv(recv_buffer, len);
450  if (rc == 0) {
451  SEND_OK("");
452  return;
453  }
454  if (rc == 1) {
455  SEND_ERR("");
456  return;
457  }
458  }
459  }
460 
461  fprintf(stderr, "%s: No response from RADIUS server\n", progname);
462  SEND_ERR("No response from RADIUS server");
463  return;
464 }
465 
466 int
467 main(int argc, char **argv)
468 {
469  struct sockaddr_in salocal;
470  struct sockaddr_in saremote;
471  struct servent *svp;
472  unsigned short svc_port;
473  char username[MAXPWNAM];
474  char passwd[MAXPASS];
475  char *ptr;
476  char buf[HELPER_INPUT_BUFFER];
477  const char *cfname = nullptr;
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) {
483  switch (c) {
484  case 'd':
485  debug_enabled = 1;
486  break;
487  case 'f':
488  cfname = optarg;
489  break;
490  case 'h':
491  strncpy(server, optarg, sizeof(server)-1);
492  server[sizeof(server)-1] = '\0';
493  break;
494  case 'p':
495  strncpy(svc_name, optarg, sizeof(svc_name)-1);
496  svc_name[sizeof(svc_name)-1] = '\0';
497  break;
498  case 'w':
499  strncpy(secretkey, optarg, sizeof(secretkey)-1);
500  secretkey[sizeof(secretkey)-1] = '\0';
501  break;
502  case 'i':
503  strncpy(identifier, optarg, sizeof(identifier)-1);
504  identifier[sizeof(identifier)-1] = '\0';
505  break;
506  case 't':
507  retries = atoi(optarg);
508  break;
509  }
510  }
511  /* make standard output line buffered */
512  if (setvbuf(stdout, nullptr, _IOLBF, 0) != 0)
513  exit(EXIT_FAILURE);
514 
515  if (cfname) {
516  if (rad_auth_config(cfname) < 0) {
517  fprintf(stderr, "FATAL: %s: can't open configuration file '%s'.\n", argv[0], cfname);
518  exit(EXIT_FAILURE);
519  }
520  }
521  if (!*server) {
522  fprintf(stderr, "FATAL: %s: Server not specified\n", argv[0]);
523  exit(EXIT_FAILURE);
524  }
525  if (!*secretkey) {
526  fprintf(stderr, "FATAL: %s: Shared secret not specified\n", argv[0]);
527  exit(EXIT_FAILURE);
528  }
529 #if _SQUID_WINDOWS_
530  {
531  WSADATA wsaData;
532  WSAStartup(2, &wsaData);
533  atexit(Win32SockCleanup);
534  }
535 #endif
536  /*
537  * Open a connection to the server.
538  */
539  svp = getservbyname(svc_name, "udp");
540  if (svp != nullptr)
541  svc_port = ntohs((unsigned short) svp->s_port);
542  else
543  svc_port = atoi(svc_name);
544  if (svc_port == 0)
545  svc_port = PW_AUTH_UDP_PORT;
546 
547  /* Get the IP address of the authentication server */
548  if ((auth_ipaddr = get_ipaddr(server)) == 0) {
549  fprintf(stderr, "FATAL: %s: Couldn't find host %s\n", argv[0], server);
550  exit(EXIT_FAILURE);
551  }
552  sockfd = socket(AF_INET, SOCK_DGRAM, 0);
553  if (sockfd < 0) {
554  perror("socket");
555  exit(EXIT_FAILURE);
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) {
563  perror("connect");
564  exit(EXIT_FAILURE);
565  }
566  salen = sizeof(salocal);
567  if (getsockname(sockfd, (struct sockaddr *) &salocal, &salen) < 0) {
568  perror("getsockname");
569  exit(EXIT_FAILURE);
570  }
571 #ifdef O_NONBLOCK
572  if (fcntl(sockfd, F_SETFL, fcntl(sockfd, F_GETFL, 0) | O_NONBLOCK) < 0) {
573  int xerrno = errno;
574  fprintf(stderr,"%s| ERROR: fcntl() failure: %s\n", argv[0], xstrerr(xerrno));
575  exit(EXIT_FAILURE);
576  }
577 #endif
578  nas_ipaddr = ntohl(salocal.sin_addr.s_addr);
579  while (fgets(buf, HELPER_INPUT_BUFFER, stdin) != nullptr) {
580  char *end;
581  /* protect me form to long lines */
582  if ((end = strchr(buf, '\n')) == nullptr) {
583  err = 1;
584  continue;
585  }
586  if (err) {
587  SEND_ERR("");
588  err = 0;
589  continue;
590  }
591  if (strlen(buf) > HELPER_INPUT_BUFFER) {
592  SEND_ERR("");
593  continue;
594  }
595  /* Strip off the trailing newline */
596  *end = '\0';
597 
598  /* Parse out the username and password */
599  ptr = buf;
600  while (isspace(*ptr))
601  ++ptr;
602  if ((end = strchr(ptr, ' ')) == nullptr) {
603  SEND_ERR("No password");
604  continue;
605  }
606  *end = '\0';
607  urldecode(username, ptr, MAXPWNAM);
608  ptr = end + 1;
609  while (isspace(*ptr))
610  ++ptr;
611  urldecode(passwd, ptr, MAXPASS);
612 
613  authenticate(sockfd, username, passwd);
614  }
615  close(sockfd);
616  return EXIT_SUCCESS;
617 }
618 
uint32_t get_ipaddr(char *host)
Definition: radius-util.cc:143
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
std::mt19937::result_type RandomSeed32()
Definition: Random.cc:13
#define MAXPWNAM
u_char vector[AUTH_VECTOR_LEN]
Definition: radius.h:51
int size
Definition: ModDevPoll.cc:69
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]
#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)
#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)
u_char code
Definition: radius.h:48
static u_char request_id
#define MAXPASS
char progname[]
static int i_send_buffer[2048]
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