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