2 * Copyright (C) 1996-2020 The Squid Software Foundation and contributors
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.
11 * Remote Authentication Dial In User Service
14 * Livingston Enterprises, Inc.
15 * 6920 Koll Center Parkway
16 * Pleasanton, CA 94566
18 * Copyright 1992 Livingston Enterprises, Inc.
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.
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.
33 * The new parts of the code is Copyright (C) 1998 R.M. van Selm <selm@cistron.nl>
35 * Copyright (C) 2004 Henrik Nordstrom <hno@squid-cache.org>
36 * Copyright (C) 2006 Henrik Nordstrom <hno@squid-cache.org>
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.
43 * basic_radius_auth-1.0 is based on modules from the Cistron-radiusd-1.5.4.
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)
50 * Marc van Selm <selm@cistron.nl>
51 * with contributions from
52 * Henrik Nordstrom <hno@squid-cache.org>
57 #include "auth/basic/RADIUS/radius-util.h"
58 #include "auth/basic/RADIUS/radius.h"
59 #include "helper/protocol_defines.h"
68 #include <sys/socket.h>
71 #include <netinet/in.h>
95 /* AYJ: helper input buffer may be a lot larger than this used to expect... */
100 static void md5_calc(uint8_t out
[16], void *in
, size_t len
);
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
;
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;
119 char progname
[] = "basic_radius_auth";
123 Win32SockCleanup(void)
131 * Diff two timeval, b - a
134 timeval_diff(const struct timeval
*a
, const struct timeval
*b
)
136 return (b
->tv_sec
- a
->tv_sec
) * 1000000 + (b
->tv_usec
- a
->tv_usec
);
140 * Time since a timeval
143 time_since(const struct timeval
*when
)
146 gettimeofday(&now
, NULL
);
147 return timeval_diff(when
, &now
);
154 md5_calc(uint8_t out
[16], void *in
, size_t len
)
158 SquidMD5Update(&ctx
, in
, len
);
159 SquidMD5Final(out
, &ctx
);
163 * Receive and verify the result.
166 result_recv(char *buffer
, int length
)
170 unsigned char reply_digest
[AUTH_VECTOR_LEN
];
171 unsigned char calc_digest
[AUTH_VECTOR_LEN
];
173 /* VALUE_PAIR *req; */
175 auth
= (AUTH_HDR
*) buffer
;
176 totallen
= ntohs(auth
->length
);
178 if (totallen
!= length
) {
179 debug("Received invalid reply length from server (want %d/ got %d)\n", totallen
, length
);
182 if (auth
->id
!= request_id
) {
183 /* Duplicate response of an earlier query, ignore */
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
);
193 if (memcmp(reply_digest
, calc_digest
, AUTH_VECTOR_LEN
) != 0) {
194 debug("WARNING: Received invalid reply digest from server\n");
197 if (auth
->code
!= PW_AUTHENTICATION_ACK
)
204 * Generate a random vector.
207 random_vector(char *aVector
)
209 static std::mt19937
mt(time(0));
210 static xuniform_int_distribution
<uint8_t> dist
;
212 for (int i
= 0; i
< AUTH_VECTOR_LEN
; ++i
)
213 aVector
[i
] = static_cast<char>(dist(mt
) & 0xFF);
216 /* read the config file
217 * The format should be something like:
218 * # basic_radius_auth configuration file
220 * server suncone.cistron.nl
224 rad_auth_config(const char *cfname
)
228 int srv
= 0, crt
= 0;
230 if ((cf
= fopen(cfname
, "r")) == NULL
) {
234 while (fgets(line
, MAXLINE
, cf
) != NULL
) {
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
);
255 urldecode(char *dst
, const char *src
, int size
)
259 while (*src
&& size
> 1) {
260 if (*src
== '%' && src
[1] != '\0' && src
[2] != '\0') {
266 *dst
= strtol(tmp
, NULL
, 16);
279 authenticate(int socket_fd
, const char *username
, const char *passwd
)
282 unsigned short total_length
;
285 char passbuf
[MAXPASS
];
288 u_char cbc
[AUTH_VECTOR_LEN
];
291 struct sockaddr_in saremote
;
297 * Build an authentication request
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
;
312 length
= strlen(username
);
313 if (length
> MAXPWNAM
) {
317 ptr
= (unsigned char*)send_buffer
+ sizeof(AUTH_HDR
);
318 memcpy(ptr
, username
, length
);
320 total_length
+= length
+ 2;
325 length
= strlen(passwd
);
326 if (length
> MAXPASS
) {
329 memset(passbuf
, 0, MAXPASS
);
330 memcpy(passbuf
, passwd
, length
);
333 * Length is rounded up to multiple of 16,
334 * and the password is encoded in blocks of 16
335 * with cipher block chaining
337 length
= ((length
/ AUTH_VECTOR_LEN
) + 1) * AUTH_VECTOR_LEN
;
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
);
353 /* Xor the password into the MD5 digest */
354 for (i
= 0; i
< AUTH_VECTOR_LEN
; ++i
) {
355 *ptr
= (cbc
[i
] ^= passbuf
[j
+ i
]);
359 total_length
+= length
+ 2;
361 *ptr
= PW_NAS_PORT_ID
;
371 *ptr
= PW_NAS_PORT_TYPE
;
376 ui
= htonl(nasporttype
);
382 int len
= strlen(identifier
);
387 memcpy(ptr
, identifier
, len
);
389 total_length
+= len
+ 2;
391 *ptr
= PW_NAS_IP_ADDRESS
;
396 ui
= htonl(nas_ipaddr
);
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.
410 auth
->length
= htons(total_length
);
417 * Send the request we've built.
419 gettimeofday(&sent
, NULL
);
420 if (send(socket_fd
, (char *) auth
, total_length
, 0) < 0) {
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
));
428 while ((time_spent
= time_since(&sent
)) < 1000000) {
436 tv
.tv_usec
= 1000000 - time_spent
;
439 FD_SET(socket_fd
, &readfds
);
440 if (select(socket_fd
+ 1, &readfds
, NULL
, NULL
, &tv
) == 0) /* Select timeout */
442 salen
= sizeof(saremote
);
443 len
= recvfrom(socket_fd
, recv_buffer
, sizeof(i_recv_buffer
),
444 0, (struct sockaddr
*) &saremote
, &salen
);
449 rc
= result_recv(recv_buffer
, len
);
461 fprintf(stderr
, "%s: No response from RADIUS server\n", progname
);
462 SEND_ERR("No response from RADIUS server");
467 main(int argc
, char **argv
)
469 struct sockaddr_in salocal
;
470 struct sockaddr_in saremote
;
472 unsigned short svc_port
;
473 char username
[MAXPWNAM
];
474 char passwd
[MAXPASS
];
476 char buf
[HELPER_INPUT_BUFFER
];
477 const char *cfname
= NULL
;
482 while ((c
= getopt(argc
, argv
, "h:p:f:w:i:t:")) != -1) {
491 strncpy(server
, optarg
, sizeof(server
)-1);
492 server
[sizeof(server
)-1] = '\0';
495 strncpy(svc_name
, optarg
, sizeof(svc_name
)-1);
496 svc_name
[sizeof(svc_name
)-1] = '\0';
499 strncpy(secretkey
, optarg
, sizeof(secretkey
)-1);
500 secretkey
[sizeof(secretkey
)-1] = '\0';
503 strncpy(identifier
, optarg
, sizeof(identifier
)-1);
504 identifier
[sizeof(identifier
)-1] = '\0';
507 retries
= atoi(optarg
);
511 /* make standard output line buffered */
512 if (setvbuf(stdout
, NULL
, _IOLBF
, 0) != 0)
516 if (rad_auth_config(cfname
) < 0) {
517 fprintf(stderr
, "FATAL: %s: can't open configuration file '%s'.\n", argv
[0], cfname
);
522 fprintf(stderr
, "FATAL: %s: Server not specified\n", argv
[0]);
526 fprintf(stderr
, "FATAL: %s: Shared secret not specified\n", argv
[0]);
532 WSAStartup(2, &wsaData
);
533 atexit(Win32SockCleanup
);
537 * Open a connection to the server.
539 svp
= getservbyname(svc_name
, "udp");
541 svc_port
= ntohs((unsigned short) svp
->s_port
);
543 svc_port
= atoi(svc_name
);
545 svc_port
= PW_AUTH_UDP_PORT
;
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
);
552 sockfd
= socket(AF_INET
, SOCK_DGRAM
, 0);
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
);
562 if (connect(sockfd
, (struct sockaddr
*) &saremote
, sizeof(saremote
)) < 0) {
566 salen
= sizeof(salocal
);
567 if (getsockname(sockfd
, (struct sockaddr
*) &salocal
, &salen
) < 0) {
568 perror("getsockname");
572 if (fcntl(sockfd
, F_SETFL
, fcntl(sockfd
, F_GETFL
, 0) | O_NONBLOCK
) < 0) {
574 fprintf(stderr
,"%s| ERROR: fcntl() failure: %s\n", argv
[0], xstrerr(xerrno
));
578 nas_ipaddr
= ntohl(salocal
.sin_addr
.s_addr
);
579 while (fgets(buf
, HELPER_INPUT_BUFFER
, stdin
) != NULL
) {
581 /* protect me form to long lines */
582 if ((end
= strchr(buf
, '\n')) == NULL
) {
591 if (strlen(buf
) > HELPER_INPUT_BUFFER
) {
595 /* Strip off the trailing newline */
598 /* Parse out the username and password */
600 while (isspace(*ptr
))
602 if ((end
= strchr(ptr
, ' ')) == NULL
) {
603 SEND_ERR("No password");
607 urldecode(username
, ptr
, MAXPWNAM
);
609 while (isspace(*ptr
))
611 urldecode(passwd
, ptr
, MAXPASS
);
613 authenticate(sockfd
, username
, passwd
);