]> git.ipfire.org Git - thirdparty/squid.git/blame - helpers/basic_auth/RADIUS/basic_radius_auth.cc
Renamed squid.h to squid-old.h and config.h to squid.h
[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
f7f3304a 48#include "squid.h"
43fed740
AJ
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
be266cb2 66#if _SQUID_WINDOWS_
757bcd6b
GS
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;
09aabd84
FC
117static uint32_t nas_ipaddr;
118static uint32_t auth_ipaddr;
d80aac12 119static int retries = 30;
120
5a48ed18 121char progname[] = "basic_radius_auth";
d80aac12 122
1191b93b 123#if _SQUID_MSWIN_
ec556193
GS
124void
125Win32SockCleanup(void)
126{
127 WSACleanup();
128 return;
129}
1191b93b 130#endif
ec556193 131
d80aac12 132/*
133 * Diff two timeval, b - a
134 */
135static int
136timeval_diff(const struct timeval *a, const struct timeval *b)
137{
138 return (b->tv_sec - a->tv_sec) * 1000000 + (b->tv_usec - a->tv_usec);
139}
140
141/*
142 * Time since a timeval
143 */
144static int
145time_since(const struct timeval *when)
146{
147 struct timeval now;
148 gettimeofday(&now, NULL);
149 return timeval_diff(when, &now);
150}
151
a8b30ac9 152/*
153 * MD5 digest
154 */
155static void
156md5_calc(uint8_t out[16], void *in, size_t len)
157{
628ac45c 158 SquidMD5_CTX ctx;
159 SquidMD5Init(&ctx);
160 SquidMD5Update(&ctx, in, len);
161 SquidMD5Final(out, &ctx);
a8b30ac9 162}
163
d80aac12 164/*
165 * Receive and verify the result.
166 */
167static int
f45dd259 168result_recv(uint32_t host, unsigned short udp_port, char *buffer, int length)
d80aac12 169{
170 AUTH_HDR *auth;
171 int totallen;
172 unsigned char reply_digest[AUTH_VECTOR_LEN];
173 unsigned char calc_digest[AUTH_VECTOR_LEN];
174 int secretlen;
175 /* VALUE_PAIR *req; */
176
177 auth = (AUTH_HDR *) buffer;
178 totallen = ntohs(auth->length);
179
180 if (totallen != length) {
43fed740 181 debug("Received invalid reply length from server (want %d/ got %d)\n", totallen, length);
26ac0430 182 return -1;
d80aac12 183 }
d80aac12 184 if (auth->id != request_id) {
26ac0430
AJ
185 /* Duplicate response of an earlier query, ignore */
186 return -1;
d80aac12 187 }
d80aac12 188 /* Verify the reply digest */
189 memcpy(reply_digest, auth->vector, AUTH_VECTOR_LEN);
190 memcpy(auth->vector, vector, AUTH_VECTOR_LEN);
191 secretlen = strlen(secretkey);
192 memcpy(buffer + length, secretkey, secretlen);
193 md5_calc(calc_digest, (unsigned char *) auth, length + secretlen);
194
195 if (memcmp(reply_digest, calc_digest, AUTH_VECTOR_LEN) != 0) {
43fed740 196 debug("WARNING: Received invalid reply digest from server\n");
26ac0430 197 return -1;
d80aac12 198 }
d80aac12 199 if (auth->code != PW_AUTHENTICATION_ACK)
26ac0430 200 return 1;
d80aac12 201
202 return 0;
203}
204
205
206/*
207 * Generate a random vector.
208 */
209static void
d5f8d05f 210random_vector(char *aVector)
d80aac12 211{
212 int randno;
213 int i;
214
215 srand((time(0) ^ rand()) + rand());
216 for (i = 0; i < AUTH_VECTOR_LEN;) {
26ac0430 217 randno = rand();
d5f8d05f
FC
218 memcpy(aVector, &randno, sizeof(int));
219 aVector += sizeof(int);
26ac0430 220 i += sizeof(int);
d80aac12 221 }
222}
223
224/* read the config file
225 * The format should be something like:
5a48ed18 226 * # basic_radius_auth configuration file
d80aac12 227 * # MvS: 28-10-1998
228 * server suncone.cistron.nl
229 * secret testje
230 */
231static int
232rad_auth_config(const char *cfname)
233{
234 FILE *cf;
235 char line[MAXLINE];
236 int srv = 0, crt = 0;
237
238 if ((cf = fopen(cfname, "r")) == NULL) {
26ac0430
AJ
239 perror(cfname);
240 return -1;
d80aac12 241 }
242 while (fgets(line, MAXLINE, cf) != NULL) {
26ac0430
AJ
243 if (!memcmp(line, "server", 6))
244 srv = sscanf(line, "server %s", server);
245 if (!memcmp(line, "secret", 6))
246 crt = sscanf(line, "secret %s", secretkey);
247 if (!memcmp(line, "identifier", 10))
248 sscanf(line, "identifier %s", identifier);
249 if (!memcmp(line, "service", 7))
250 sscanf(line, "service %s", svc_name);
251 if (!memcmp(line, "port", 4))
252 sscanf(line, "port %s", svc_name);
d80aac12 253 }
32ae6cde 254 fclose(cf);
d80aac12 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;
f45dd259 283 unsigned short total_length;
d80aac12 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;
09aabd84 291 uint32_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;
f9acc802 379 total_length += len + 2;
d80aac12 380 } else {
26ac0430
AJ
381 *ptr++ = PW_NAS_IP_ADDRESS;
382 *ptr++ = 6;
d80aac12 383
26ac0430
AJ
384 ui = htonl(nas_ipaddr);
385 memcpy(ptr, &ui, 4);
386 ptr += 4;
387 total_length += 6;
d80aac12 388 }
389
390 /* Klaus Weidner <kw@w-m-p.com> changed this
391 * from htonl to htons. It might have caused
392 * you trouble or not. That depends on the byte
393 * order of your system.
394 * The symptom was that the radius server
26ac0430 395 * ignored the requests, because they had zero
d80aac12 396 * length according to the data header.
397 */
398 auth->length = htons(total_length);
399
ec556193 400 while (retry--) {
26ac0430
AJ
401 int time_spent;
402 struct timeval sent;
403 /*
404 * Send the request we've built.
405 */
406 gettimeofday(&sent, NULL);
e053c141 407 send(socket_fd, (char *) auth, total_length, 0);
26ac0430
AJ
408 while ((time_spent = time_since(&sent)) < 1000000) {
409 struct timeval tv;
410 int rc, len;
411 if (!time_spent) {
412 tv.tv_sec = 1;
413 tv.tv_usec = 0;
414 } else {
415 tv.tv_sec = 0;
416 tv.tv_usec = 1000000 - time_spent;
417 }
418 FD_ZERO(&readfds);
e053c141
FC
419 FD_SET(socket_fd, &readfds);
420 if (select(socket_fd + 1, &readfds, NULL, NULL, &tv) == 0) /* Select timeout */
26ac0430
AJ
421 break;
422 salen = sizeof(saremote);
e053c141 423 len = recvfrom(socket_fd, recv_buffer, sizeof(i_recv_buffer),
26ac0430
AJ
424 0, (struct sockaddr *) &saremote, &salen);
425
426 if (len < 0)
427 continue;
428
429 rc = result_recv(saremote.sin_addr.s_addr, saremote.sin_port, recv_buffer, len);
430 if (rc == 0)
431 return 1;
432 if (rc == 1)
433 return 0;
434 }
d80aac12 435 }
436
437 fprintf(stderr, "%s: No response from RADIUS server\n", progname);
438
439 return 0;
440}
441
442int
443main(int argc, char **argv)
444{
445 struct sockaddr_in salocal;
446 struct sockaddr_in saremote;
447 struct servent *svp;
f45dd259 448 unsigned short svc_port;
d80aac12 449 char username[MAXPWNAM];
450 char passwd[MAXPASS];
451 char *ptr;
43fed740 452 char buf[HELPER_INPUT_BUFFER];
d80aac12 453 const char *cfname = NULL;
454 int err = 0;
455 socklen_t salen;
456 int c;
457
458 while ((c = getopt(argc, argv, "h:p:f:w:i:t:")) != -1) {
26ac0430 459 switch (c) {
e673ba3a
AJ
460 case 'd':
461 debug_enabled = 1;
462 break;
26ac0430
AJ
463 case 'f':
464 cfname = optarg;
465 break;
466 case 'h':
467 strcpy(server, optarg);
468 break;
469 case 'p':
470 strcpy(svc_name, optarg);
471 break;
472 case 'w':
473 strcpy(secretkey, optarg);
474 break;
475 case 'i':
476 strcpy(identifier, optarg);
477 break;
478 case 't':
479 retries = atoi(optarg);
480 break;
481 }
d80aac12 482 }
483 /* make standard output line buffered */
484 if (setvbuf(stdout, NULL, _IOLBF, 0) != 0)
26ac0430 485 return 1;
d80aac12 486
487 if (cfname) {
26ac0430 488 if (rad_auth_config(cfname) < 0) {
43fed740 489 fprintf(stderr, "FATAL: %s: can't open configuration file '%s'.\n", argv[0], cfname);
26ac0430
AJ
490 exit(1);
491 }
d80aac12 492 }
d80aac12 493 if (!*server) {
43fed740 494 fprintf(stderr, "FATAL: %s: Server not specified\n", argv[0]);
26ac0430 495 exit(1);
d80aac12 496 }
d80aac12 497 if (!*secretkey) {
43fed740 498 fprintf(stderr, "FATAL: %s: Shared secret not specified\n", argv[0]);
26ac0430 499 exit(1);
d80aac12 500 }
1191b93b 501#if _SQUID_MSWIN_
757bcd6b 502 {
26ac0430
AJ
503 WSADATA wsaData;
504 WSAStartup(2, &wsaData);
505 atexit(Win32SockCleanup);
757bcd6b
GS
506 }
507#endif
d80aac12 508 /*
509 * Open a connection to the server.
510 */
511 svp = getservbyname(svc_name, "udp");
512 if (svp != NULL)
f45dd259 513 svc_port = ntohs((unsigned short) svp->s_port);
d80aac12 514 else
26ac0430 515 svc_port = atoi(svc_name);
d80aac12 516 if (svc_port == 0)
26ac0430 517 svc_port = PW_AUTH_UDP_PORT;
d80aac12 518
519 /* Get the IP address of the authentication server */
520 if ((auth_ipaddr = get_ipaddr(server)) == 0) {
43fed740 521 fprintf(stderr, "FATAL: %s: Couldn't find host %s\n", argv[0], server);
26ac0430 522 exit(1);
d80aac12 523 }
524 sockfd = socket(AF_INET, SOCK_DGRAM, 0);
525 if (sockfd < 0) {
26ac0430
AJ
526 perror("socket");
527 exit(1);
d80aac12 528 }
529 memset(&saremote, 0, sizeof(saremote));
530 saremote.sin_family = AF_INET;
531 saremote.sin_addr.s_addr = htonl(auth_ipaddr);
532 saremote.sin_port = htons(svc_port);
533
534 if (connect(sockfd, (struct sockaddr *) &saremote, sizeof(saremote)) < 0) {
26ac0430
AJ
535 perror("connect");
536 exit(1);
d80aac12 537 }
538 salen = sizeof(salocal);
539 if (getsockname(sockfd, (struct sockaddr *) &salocal, &salen) < 0) {
26ac0430
AJ
540 perror("getsockname");
541 exit(1);
d80aac12 542 }
543#ifdef O_NONBLOCK
544 fcntl(sockfd, F_SETFL, fcntl(sockfd, F_GETFL, 0) | O_NONBLOCK);
545#endif
546 nas_ipaddr = ntohl(salocal.sin_addr.s_addr);
43fed740 547 while (fgets(buf, HELPER_INPUT_BUFFER, stdin) != NULL) {
26ac0430
AJ
548 char *end;
549 /* protect me form to long lines */
43fed740 550 if ((end = strchr(buf, '\n')) == NULL) {
26ac0430
AJ
551 err = 1;
552 continue;
553 }
554 if (err) {
43fed740 555 SEND_ERR("");
26ac0430
AJ
556 err = 0;
557 continue;
558 }
43fed740
AJ
559 if (strlen(buf) > HELPER_INPUT_BUFFER) {
560 SEND_ERR("");
26ac0430
AJ
561 continue;
562 }
563 /* Strip off the trailing newline */
564 *end = '\0';
565
566 /* Parse out the username and password */
43fed740 567 ptr = buf;
26ac0430
AJ
568 while (isspace(*ptr))
569 ptr++;
570 if ((end = strchr(ptr, ' ')) == NULL) {
43fed740 571 SEND_ERR("No password");
26ac0430
AJ
572 continue;
573 }
574 *end = '\0';
575 urldecode(username, ptr, MAXPWNAM);
576 ptr = end + 1;
577 while (isspace(*ptr))
578 ptr++;
579 urldecode(passwd, ptr, MAXPASS);
580
581 if (authenticate(sockfd, username, passwd))
43fed740 582 SEND_OK("");
26ac0430 583 else
43fed740 584 SEND_ERR("");
d80aac12 585 }
586 close(sockfd);
587 exit(1);
588}