]> git.ipfire.org Git - thirdparty/squid.git/blame - helpers/basic_auth/RADIUS/basic_radius_auth.cc
Refactored --enable-xmalloc-statistics
[thirdparty/squid.git] / helpers / basic_auth / RADIUS / basic_radius_auth.cc
CommitLineData
d80aac12 1/*
2 * RADIUS
3 * Remote Authentication Dial In User Service
4 *
5 *
6 * Livingston Enterprises, Inc.
7 * 6920 Koll Center Parkway
8 * Pleasanton, CA 94566
9 *
10 * Copyright 1992 Livingston Enterprises, Inc.
11 *
12 * Permission to use, copy, modify, and distribute this software for any
13 * purpose and without fee is hereby granted, provided that this
14 * copyright and permission notice appear on all copies and supporting
15 * documentation, the name of Livingston Enterprises, Inc. not be used
16 * in advertising or publicity pertaining to distribution of the
17 * program without specific prior permission, and notice be given
18 * in supporting documentation that copying and distribution is by
26ac0430 19 * permission of Livingston Enterprises, Inc.
d80aac12 20 *
21 * Livingston Enterprises, Inc. makes no representations about
22 * the suitability of this software for any purpose. It is
23 * provided "as is" without express or implied warranty.
24 *
25 * The new parts of the code is Copyright (C) 1998 R.M. van Selm <selm@cistron.nl>
26 * with modifications
27 * Copyright (C) 2004 Henrik Nordstrom <hno@squid-cache.org>
28 * Copyright (C) 2006 Henrik Nordstrom <hno@squid-cache.org>
29 */
30
5a48ed18 31/* basic_radius_auth is a RADIUS authenticator for Squid-2.5 and later.
d80aac12 32 * The authenticator reads a line with a user and password combination.
33 * If access is granted OK is returned. Else ERR.
34 *
5a48ed18 35 * basic_radius_auth-1.0 is based on modules from the Cistron-radiusd-1.5.4.
d80aac12 36 *
37 * Currently you should only start 1 authentificator at a time because the
38 * the ID's of the different programs can start to conflict. I'm not sure it
39 * would help anyway. I think the RADIUS server is close by and I don't think
40 * it will handle requests in parallel anyway (correct me if I'm wrong here)
41 *
42 * Marc van Selm <selm@cistron.nl>
43 * with contributions from
44 * Henrik Nordstrom <hno@squid-cache.org>
45 * and many others
46 */
47
43fed740
AJ
48#include "config.h"
49#include "helpers/defines.h"
50#include "md5.h"
51#include "radius.h"
52#include "radius-util.h"
a8b30ac9 53
a8b30ac9 54#if HAVE_SYS_SOCKET_H
5a48ed18 55#include <sys/socket.h>
a8b30ac9 56#endif
57#if HAVE_NETINET_IN_H
5a48ed18 58#include <netinet/in.h>
a8b30ac9 59#endif
a8b30ac9 60#if HAVE_UNISTD_H
5a48ed18 61#include <unistd.h>
a8b30ac9 62#endif
63#if HAVE_FCNTL_H
5a48ed18 64#include <fcntl.h>
a8b30ac9 65#endif
757bcd6b
GS
66#ifdef _SQUID_WIN32_
67#include <io.h>
68#endif
a8b30ac9 69#if HAVE_CTYPE_H
5a48ed18 70#include <ctype.h>
a8b30ac9 71#endif
72#if HAVE_STDIO_H
5a48ed18 73#include <stdio.h>
a8b30ac9 74#endif
75#if HAVE_UNISTD_H
5a48ed18 76#include <unistd.h>
a8b30ac9 77#endif
78#if HAVE_NETDB_H
5a48ed18 79#include <netdb.h>
a8b30ac9 80#endif
81#if HAVE_PWD_H
5a48ed18 82#include <pwd.h>
a8b30ac9 83#endif
a8b30ac9 84#if HAVE_TIME_H
5a48ed18 85#include <time.h>
a8b30ac9 86#endif
87#if HAVE_STRING_H
5a48ed18 88#include <string.h>
a8b30ac9 89#endif
757bcd6b
GS
90#if HAVE_GETOPT_H
91#include <getopt.h>
92#endif
93#if HAVE_ERRNO_H
94#include <errno.h>
95#endif
d80aac12 96
43fed740 97/* AYJ: helper input buffer may be a lot larger than this used to expect... */
d80aac12 98#define MAXPWNAM 254
99#define MAXPASS 254
43fed740 100#define MAXLINE 254
d80aac12 101
2e7c8541 102static void md5_calc(uint8_t out[16], void *in, size_t len);
d80aac12 103
104static int i_send_buffer[2048];
105static int i_recv_buffer[2048];
106static char *send_buffer = (char *) i_send_buffer;
107static char *recv_buffer = (char *) i_recv_buffer;
108static int sockfd;
0afdb165 109static u_char request_id;
d80aac12 110static char vector[AUTH_VECTOR_LEN];
111static char secretkey[MAXPASS + 1] = "";
112static char server[MAXLINE] = "";
113static char identifier[MAXLINE] = "";
114static char svc_name[MAXLINE] = "radius";
115static int nasport = 111;
116static int nasporttype = 0;
a8b30ac9 117static u_int32_t nas_ipaddr;
118static u_int32_t auth_ipaddr;
d80aac12 119static int retries = 30;
120
5a48ed18 121char progname[] = "basic_radius_auth";
d80aac12 122int debug_flag = 0;
123
ec556193
GS
124#ifdef _SQUID_MSWIN_
125void
126Win32SockCleanup(void)
127{
128 WSACleanup();
129 return;
130}
131#endif /* ifdef _SQUID_MSWIN_ */
132
d80aac12 133/*
134 * Diff two timeval, b - a
135 */
136static int
137timeval_diff(const struct timeval *a, const struct timeval *b)
138{
139 return (b->tv_sec - a->tv_sec) * 1000000 + (b->tv_usec - a->tv_usec);
140}
141
142/*
143 * Time since a timeval
144 */
145static int
146time_since(const struct timeval *when)
147{
148 struct timeval now;
149 gettimeofday(&now, NULL);
150 return timeval_diff(when, &now);
151}
152
a8b30ac9 153/*
154 * MD5 digest
155 */
156static void
157md5_calc(uint8_t out[16], void *in, size_t len)
158{
628ac45c 159 SquidMD5_CTX ctx;
160 SquidMD5Init(&ctx);
161 SquidMD5Update(&ctx, in, len);
162 SquidMD5Final(out, &ctx);
a8b30ac9 163}
164
d80aac12 165/*
166 * Receive and verify the result.
167 */
168static int
a8b30ac9 169result_recv(u_int32_t host, u_short udp_port, char *buffer, int length)
d80aac12 170{
171 AUTH_HDR *auth;
172 int totallen;
173 unsigned char reply_digest[AUTH_VECTOR_LEN];
174 unsigned char calc_digest[AUTH_VECTOR_LEN];
175 int secretlen;
176 /* VALUE_PAIR *req; */
177
178 auth = (AUTH_HDR *) buffer;
179 totallen = ntohs(auth->length);
180
181 if (totallen != length) {
43fed740 182 debug("Received invalid reply length from server (want %d/ got %d)\n", totallen, length);
26ac0430 183 return -1;
d80aac12 184 }
d80aac12 185 if (auth->id != request_id) {
26ac0430
AJ
186 /* Duplicate response of an earlier query, ignore */
187 return -1;
d80aac12 188 }
d80aac12 189 /* Verify the reply digest */
190 memcpy(reply_digest, auth->vector, AUTH_VECTOR_LEN);
191 memcpy(auth->vector, vector, AUTH_VECTOR_LEN);
192 secretlen = strlen(secretkey);
193 memcpy(buffer + length, secretkey, secretlen);
194 md5_calc(calc_digest, (unsigned char *) auth, length + secretlen);
195
196 if (memcmp(reply_digest, calc_digest, AUTH_VECTOR_LEN) != 0) {
43fed740 197 debug("WARNING: Received invalid reply digest from server\n");
26ac0430 198 return -1;
d80aac12 199 }
d80aac12 200 if (auth->code != PW_AUTHENTICATION_ACK)
26ac0430 201 return 1;
d80aac12 202
203 return 0;
204}
205
206
207/*
208 * Generate a random vector.
209 */
210static void
d5f8d05f 211random_vector(char *aVector)
d80aac12 212{
213 int randno;
214 int i;
215
216 srand((time(0) ^ rand()) + rand());
217 for (i = 0; i < AUTH_VECTOR_LEN;) {
26ac0430 218 randno = rand();
d5f8d05f
FC
219 memcpy(aVector, &randno, sizeof(int));
220 aVector += sizeof(int);
26ac0430 221 i += sizeof(int);
d80aac12 222 }
223}
224
225/* read the config file
226 * The format should be something like:
5a48ed18 227 * # basic_radius_auth configuration file
d80aac12 228 * # MvS: 28-10-1998
229 * server suncone.cistron.nl
230 * secret testje
231 */
232static int
233rad_auth_config(const char *cfname)
234{
235 FILE *cf;
236 char line[MAXLINE];
237 int srv = 0, crt = 0;
238
239 if ((cf = fopen(cfname, "r")) == NULL) {
26ac0430
AJ
240 perror(cfname);
241 return -1;
d80aac12 242 }
243 while (fgets(line, MAXLINE, cf) != NULL) {
26ac0430
AJ
244 if (!memcmp(line, "server", 6))
245 srv = sscanf(line, "server %s", server);
246 if (!memcmp(line, "secret", 6))
247 crt = sscanf(line, "secret %s", secretkey);
248 if (!memcmp(line, "identifier", 10))
249 sscanf(line, "identifier %s", identifier);
250 if (!memcmp(line, "service", 7))
251 sscanf(line, "service %s", svc_name);
252 if (!memcmp(line, "port", 4))
253 sscanf(line, "port %s", svc_name);
d80aac12 254 }
255 if (srv && crt)
26ac0430 256 return 0;
d80aac12 257 return -1;
258}
259
260static void
261urldecode(char *dst, const char *src, int size)
262{
263 char tmp[3];
264 tmp[2] = '\0';
265 while (*src && size > 1) {
26ac0430
AJ
266 if (*src == '%' && src[1] != '\0' && src[2] != '\0') {
267 src++;
268 tmp[0] = *src++;
269 tmp[1] = *src++;
270 *dst++ = strtol(tmp, NULL, 16);
271 } else {
272 *dst++ = *src++;
273 }
274 size--;
d80aac12 275 }
276 *dst++ = '\0';
277}
278
279static int
e053c141 280authenticate(int socket_fd, const char *username, const char *passwd)
d80aac12 281{
282 AUTH_HDR *auth;
283 u_short total_length;
284 u_char *ptr;
285 int length;
286 char passbuf[MAXPASS];
287 u_char md5buf[256];
288 int secretlen;
289 u_char cbc[AUTH_VECTOR_LEN];
290 int i, j;
a8b30ac9 291 u_int32_t ui;
d80aac12 292 struct sockaddr_in saremote;
293 fd_set readfds;
294 socklen_t salen;
295 int retry = retries;
296
297 /*
298 * Build an authentication request
299 */
300 auth = (AUTH_HDR *) send_buffer;
301 auth->code = PW_AUTHENTICATION_REQUEST;
302 auth->id = ++request_id;
303 random_vector(vector);
304 memcpy(auth->vector, vector, AUTH_VECTOR_LEN);
305 total_length = AUTH_HDR_LEN;
306 ptr = auth->data;
307
308 /*
309 * User Name
310 */
311 *ptr++ = PW_USER_NAME;
312 length = strlen(username);
313 if (length > MAXPWNAM) {
26ac0430 314 length = MAXPWNAM;
d80aac12 315 }
316 *ptr++ = length + 2;
317 memcpy(ptr, username, length);
318 ptr += length;
319 total_length += length + 2;
320
321 /*
322 * Password
323 */
324 length = strlen(passwd);
325 if (length > MAXPASS) {
26ac0430 326 length = MAXPASS;
d80aac12 327 }
328 memset(passbuf, 0, MAXPASS);
329 memcpy(passbuf, passwd, length);
330
26ac0430 331 /*
d80aac12 332 * Length is rounded up to multiple of 16,
26ac0430 333 * and the password is encoded in blocks of 16
d80aac12 334 * with cipher block chaining
335 */
336 length = ((length / AUTH_VECTOR_LEN) + 1) * AUTH_VECTOR_LEN;
337
338 *ptr++ = PW_PASSWORD;
339 *ptr++ = length + 2;
340
341 secretlen = strlen(secretkey);
342 /* Set up the Cipher block chain */
343 memcpy(cbc, auth->vector, AUTH_VECTOR_LEN);
344 for (j = 0; j < length; j += AUTH_VECTOR_LEN) {
26ac0430
AJ
345 /* Calculate the MD5 Digest */
346 strcpy((char *) md5buf, secretkey);
347 memcpy(md5buf + secretlen, cbc, AUTH_VECTOR_LEN);
348 md5_calc(cbc, md5buf, secretlen + AUTH_VECTOR_LEN);
349
350 /* Xor the password into the MD5 digest */
351 for (i = 0; i < AUTH_VECTOR_LEN; i++) {
352 *ptr++ = (cbc[i] ^= passbuf[j + i]);
353 }
d80aac12 354 }
355 total_length += length + 2;
356
357 *ptr++ = PW_NAS_PORT_ID;
358 *ptr++ = 6;
359
360 ui = htonl(nasport);
361 memcpy(ptr, &ui, 4);
362 ptr += 4;
363 total_length += 6;
364
365 *ptr++ = PW_NAS_PORT_TYPE;
366 *ptr++ = 6;
367
368 ui = htonl(nasporttype);
369 memcpy(ptr, &ui, 4);
370 ptr += 4;
371 total_length += 6;
372
373 if (*identifier) {
26ac0430
AJ
374 int len = strlen(identifier);
375 *ptr++ = PW_NAS_ID;
376 *ptr++ = len + 2;
377 memcpy(ptr, identifier, len);
378 ptr += len;
d80aac12 379 } else {
26ac0430
AJ
380 *ptr++ = PW_NAS_IP_ADDRESS;
381 *ptr++ = 6;
d80aac12 382
26ac0430
AJ
383 ui = htonl(nas_ipaddr);
384 memcpy(ptr, &ui, 4);
385 ptr += 4;
386 total_length += 6;
d80aac12 387 }
388
389 /* Klaus Weidner <kw@w-m-p.com> changed this
390 * from htonl to htons. It might have caused
391 * you trouble or not. That depends on the byte
392 * order of your system.
393 * The symptom was that the radius server
26ac0430 394 * ignored the requests, because they had zero
d80aac12 395 * length according to the data header.
396 */
397 auth->length = htons(total_length);
398
ec556193 399 while (retry--) {
26ac0430
AJ
400 int time_spent;
401 struct timeval sent;
402 /*
403 * Send the request we've built.
404 */
405 gettimeofday(&sent, NULL);
e053c141 406 send(socket_fd, (char *) auth, total_length, 0);
26ac0430
AJ
407 while ((time_spent = time_since(&sent)) < 1000000) {
408 struct timeval tv;
409 int rc, len;
410 if (!time_spent) {
411 tv.tv_sec = 1;
412 tv.tv_usec = 0;
413 } else {
414 tv.tv_sec = 0;
415 tv.tv_usec = 1000000 - time_spent;
416 }
417 FD_ZERO(&readfds);
e053c141
FC
418 FD_SET(socket_fd, &readfds);
419 if (select(socket_fd + 1, &readfds, NULL, NULL, &tv) == 0) /* Select timeout */
26ac0430
AJ
420 break;
421 salen = sizeof(saremote);
e053c141 422 len = recvfrom(socket_fd, recv_buffer, sizeof(i_recv_buffer),
26ac0430
AJ
423 0, (struct sockaddr *) &saremote, &salen);
424
425 if (len < 0)
426 continue;
427
428 rc = result_recv(saremote.sin_addr.s_addr, saremote.sin_port, recv_buffer, len);
429 if (rc == 0)
430 return 1;
431 if (rc == 1)
432 return 0;
433 }
d80aac12 434 }
435
436 fprintf(stderr, "%s: No response from RADIUS server\n", progname);
437
438 return 0;
439}
440
441int
442main(int argc, char **argv)
443{
444 struct sockaddr_in salocal;
445 struct sockaddr_in saremote;
446 struct servent *svp;
447 u_short svc_port;
448 char username[MAXPWNAM];
449 char passwd[MAXPASS];
450 char *ptr;
43fed740 451 char buf[HELPER_INPUT_BUFFER];
d80aac12 452 const char *cfname = NULL;
453 int err = 0;
454 socklen_t salen;
455 int c;
456
457 while ((c = getopt(argc, argv, "h:p:f:w:i:t:")) != -1) {
26ac0430
AJ
458 switch (c) {
459 case 'f':
460 cfname = optarg;
461 break;
462 case 'h':
463 strcpy(server, optarg);
464 break;
465 case 'p':
466 strcpy(svc_name, optarg);
467 break;
468 case 'w':
469 strcpy(secretkey, optarg);
470 break;
471 case 'i':
472 strcpy(identifier, optarg);
473 break;
474 case 't':
475 retries = atoi(optarg);
476 break;
477 }
d80aac12 478 }
479 /* make standard output line buffered */
480 if (setvbuf(stdout, NULL, _IOLBF, 0) != 0)
26ac0430 481 return 1;
d80aac12 482
483 if (cfname) {
26ac0430 484 if (rad_auth_config(cfname) < 0) {
43fed740 485 fprintf(stderr, "FATAL: %s: can't open configuration file '%s'.\n", argv[0], cfname);
26ac0430
AJ
486 exit(1);
487 }
d80aac12 488 }
d80aac12 489 if (!*server) {
43fed740 490 fprintf(stderr, "FATAL: %s: Server not specified\n", argv[0]);
26ac0430 491 exit(1);
d80aac12 492 }
d80aac12 493 if (!*secretkey) {
43fed740 494 fprintf(stderr, "FATAL: %s: Shared secret not specified\n", argv[0]);
26ac0430 495 exit(1);
d80aac12 496 }
757bcd6b
GS
497#ifdef _SQUID_MSWIN_
498 {
26ac0430
AJ
499 WSADATA wsaData;
500 WSAStartup(2, &wsaData);
501 atexit(Win32SockCleanup);
757bcd6b
GS
502 }
503#endif
d80aac12 504 /*
505 * Open a connection to the server.
506 */
507 svp = getservbyname(svc_name, "udp");
508 if (svp != NULL)
26ac0430 509 svc_port = ntohs((u_short) svp->s_port);
d80aac12 510 else
26ac0430 511 svc_port = atoi(svc_name);
d80aac12 512 if (svc_port == 0)
26ac0430 513 svc_port = PW_AUTH_UDP_PORT;
d80aac12 514
515 /* Get the IP address of the authentication server */
516 if ((auth_ipaddr = get_ipaddr(server)) == 0) {
43fed740 517 fprintf(stderr, "FATAL: %s: Couldn't find host %s\n", argv[0], server);
26ac0430 518 exit(1);
d80aac12 519 }
520 sockfd = socket(AF_INET, SOCK_DGRAM, 0);
521 if (sockfd < 0) {
26ac0430
AJ
522 perror("socket");
523 exit(1);
d80aac12 524 }
525 memset(&saremote, 0, sizeof(saremote));
526 saremote.sin_family = AF_INET;
527 saremote.sin_addr.s_addr = htonl(auth_ipaddr);
528 saremote.sin_port = htons(svc_port);
529
530 if (connect(sockfd, (struct sockaddr *) &saremote, sizeof(saremote)) < 0) {
26ac0430
AJ
531 perror("connect");
532 exit(1);
d80aac12 533 }
534 salen = sizeof(salocal);
535 if (getsockname(sockfd, (struct sockaddr *) &salocal, &salen) < 0) {
26ac0430
AJ
536 perror("getsockname");
537 exit(1);
d80aac12 538 }
539#ifdef O_NONBLOCK
540 fcntl(sockfd, F_SETFL, fcntl(sockfd, F_GETFL, 0) | O_NONBLOCK);
541#endif
542 nas_ipaddr = ntohl(salocal.sin_addr.s_addr);
43fed740 543 while (fgets(buf, HELPER_INPUT_BUFFER, stdin) != NULL) {
26ac0430
AJ
544 char *end;
545 /* protect me form to long lines */
43fed740 546 if ((end = strchr(buf, '\n')) == NULL) {
26ac0430
AJ
547 err = 1;
548 continue;
549 }
550 if (err) {
43fed740 551 SEND_ERR("");
26ac0430
AJ
552 err = 0;
553 continue;
554 }
43fed740
AJ
555 if (strlen(buf) > HELPER_INPUT_BUFFER) {
556 SEND_ERR("");
26ac0430
AJ
557 continue;
558 }
559 /* Strip off the trailing newline */
560 *end = '\0';
561
562 /* Parse out the username and password */
43fed740 563 ptr = buf;
26ac0430
AJ
564 while (isspace(*ptr))
565 ptr++;
566 if ((end = strchr(ptr, ' ')) == NULL) {
43fed740 567 SEND_ERR("No password");
26ac0430
AJ
568 continue;
569 }
570 *end = '\0';
571 urldecode(username, ptr, MAXPWNAM);
572 ptr = end + 1;
573 while (isspace(*ptr))
574 ptr++;
575 urldecode(passwd, ptr, MAXPASS);
576
577 if (authenticate(sockfd, username, passwd))
43fed740 578 SEND_OK("");
26ac0430 579 else
43fed740 580 SEND_ERR("");
d80aac12 581 }
582 close(sockfd);
583 exit(1);
584}