chiark / gitweb /
Actually report "no such host" errors properly
[innduct.git] / authprogs / radius.c
1 /*  $Id: radius.c 7745 2008-04-06 10:18:54Z iulius $
2 **
3 **  Authenticate a user against a remote radius server.
4 */
5
6 #include "config.h"
7 #include "clibrary.h"
8 #include "portable/time.h"
9 #include <ctype.h>
10 #include <errno.h>
11 #include <fcntl.h>
12 #include <netdb.h>
13 #include <signal.h>
14
15 /* Needed on AIX 4.1 to get fd_set and friends. */
16 #if HAVE_SYS_SELECT_H
17 # include <sys/select.h>
18 #endif
19
20 #include "inn/innconf.h"
21 #include "inn/md5.h"
22 #include "inn/messages.h"
23 #include "libinn.h"
24 #include "nntp.h"
25 #include "paths.h"
26 #include "conffile.h"
27
28 #include "libauth.h"
29
30 #define RADIUS_LOCAL_PORT       NNTP_PORT
31
32 #define AUTH_VECTOR_LEN 16
33
34 typedef struct _auth_req {
35     unsigned char       code;
36     unsigned char       id;
37     unsigned short      length;
38     unsigned char       vector[AUTH_VECTOR_LEN];
39     unsigned char       data[NNTP_STRLEN*2];
40     int                 datalen;
41 } auth_req;
42
43 typedef struct _rad_config_t {
44     char *secret;       /* pseudo encryption thingy secret that radius uses */
45
46     char *radhost;      /* parameters for talking to the remote radius sever */
47     int radport;
48     char *lochost;
49     int locport;
50
51     char *prefix, *suffix;      /* futz with the username, if necessary */
52     int ignore_source;
53
54     struct _rad_config_t *next;  /* point to any additional servers */
55 } rad_config_t;
56   
57 typedef struct _sending_t {
58     auth_req req;
59     int reqlen;
60     struct sockaddr_in sinr;
61     struct _sending_t *next;
62 } sending_t;
63
64 #define RADlbrace  1
65 #define RADrbrace  2
66 #define RADserver  10
67 #define RADhost    11
68 #define RADsecret  12
69 #define RADport    13
70 #define RADlochost 14
71 #define RADlocport 15
72 #define RADprefix  16
73 #define RADsuffix  17
74 #define RADsource  18
75
76 static CONFTOKEN radtoks[] = {
77   { RADlbrace,   "{" },
78   { RADrbrace,   "}" },
79   { RADserver,   "server" },
80   { RADhost,     "radhost:" },
81   { RADsecret,   "secret:" },
82   { RADport,     "radport:" },
83   { RADlochost,  "lochost:" },
84   { RADlocport,  "locport:" },
85   { RADprefix,   "prefix:" },
86   { RADsuffix,   "suffix:" },
87   { RADsource,   "ignore-source:" },
88   { 0, 0 }
89 };
90
91 static rad_config_t *get_radconf(void)
92 {
93   rad_config_t *new;
94
95   new = xcalloc(1, sizeof(rad_config_t));
96   new->next = NULL;
97
98   return new;
99 }
100
101 static int read_config(char *authfile, rad_config_t *radconf)
102 {
103     int inbrace;
104     rad_config_t *radconfig=NULL;
105     CONFFILE *file;
106     CONFTOKEN *token;
107     char *server;
108     int type;
109     char *iter;
110
111     if ((file = CONFfopen(authfile)) == NULL)
112       sysdie("cannot open config file %s", authfile);
113
114     inbrace = 0;
115     while ((token = CONFgettoken(radtoks, file)) != NULL) {
116       if (!inbrace) {
117         if (token->type != RADserver)
118           die("expected server keyword on line %d", file->lineno);
119         if ((token = CONFgettoken(0, file)) == NULL)
120           die("expected server name on line %d", file->lineno);
121         server = xstrdup(token->name);
122         if ((token = CONFgettoken(radtoks, file)) == NULL 
123             || token->type != RADlbrace)
124           die("expected { on line %d", file->lineno);
125         inbrace = 1;
126
127         if (radconfig == NULL)
128           radconfig = radconf;
129         else {
130           radconfig->next = get_radconf();
131           radconfig = radconfig->next;
132         }
133       }
134       else {
135         type = token->type;
136         if (type == RADrbrace)
137           inbrace = 0;
138         else {
139           if ((token = CONFgettoken(0, file)) == NULL)
140             die("keyword with no value on line %d", file->lineno);
141           iter = token->name;
142
143           /* what are we setting? */
144           switch(type) {
145           case RADsecret:
146             if (radconfig->secret) continue;
147             radconfig->secret = xstrdup(iter);
148             break;
149           case RADhost:
150             if (radconfig->radhost) continue;
151             radconfig->radhost = xstrdup(iter);
152             break;
153           case RADport:
154             if (radconfig->radport) continue;
155             radconfig->radport = atoi(iter);
156             break;
157           case RADlochost:
158             if (radconfig->lochost) continue;
159             radconfig->lochost = xstrdup(iter);
160             break;
161           case RADlocport:
162             if (radconfig->locport) continue;
163             radconfig->locport = atoi(iter);
164             break;
165           case RADprefix:
166             if (radconfig->prefix) continue;
167             radconfig->prefix = xstrdup(iter);
168             break;
169           case RADsuffix:
170             if (radconfig->suffix) continue;
171             radconfig->suffix = xstrdup(iter);
172             break;
173           case RADsource:
174             if (!strcasecmp(iter, "true"))
175                 radconfig->ignore_source = 1;
176             else if (!strcasecmp(iter, "false"))
177                 radconfig->ignore_source = 0;
178             else
179                 die("expected true or false after ignore-source on line %d",
180                     file->lineno);
181             break;
182           default:
183             die("unknown keyword on line %d", file->lineno);
184           }
185         }
186       }
187     }
188
189     CONFfclose(file);
190
191     if (!radconf->radhost)
192         die("no radius host specified");
193     else if (!radconf->secret)
194         die("no shared secret with radius host specified");
195
196     return(0);
197 }
198
199 #define PW_AUTH_UDP_PORT 1645
200
201 #define PW_AUTHENTICATION_REQUEST 1
202 #define PW_AUTHENTICATION_ACK     2
203 #define PW_AUTHENTICATION_REJECT  3
204
205 #define PW_USER_NAME            1
206 #define PW_PASSWORD             2
207
208 #define PW_SERVICE_TYPE         6
209 #define PW_SERVICE_AUTH_ONLY    8
210
211 #define RAD_NAS_IP_ADDRESS      4       /* IP address */
212 #define RAD_NAS_PORT            5       /* Integer */
213
214 static void req_copyto (auth_req to, sending_t *from)
215 {
216     to = from->req;
217 }
218
219 static void req_copyfrom (sending_t *to, auth_req from)
220 {
221     to->req = from;
222 }
223
224 static int rad_auth(rad_config_t *radconfig, char *uname, char *pass)
225 {
226     auth_req req;
227     int i, j, jlen, passstart;
228     unsigned char secbuf[128];
229     char hostname[SMBUF];
230     unsigned char digest[MD5_DIGESTSIZE];
231     struct timeval seed;
232     struct sockaddr_in sinl;
233     int sock;
234     struct hostent *hent;
235     int passlen;
236     time_t now, end;
237     struct timeval tmout;
238     int got;
239     fd_set rdfds;
240     uint32_t nvalue;
241     socklen_t slen;
242     int authtries= 3; /* number of times to try reaching the radius server */
243     rad_config_t *config;
244     sending_t *reqtop, *sreq, *new;
245     int done;
246
247     /* set up the linked list */
248     config = radconfig;
249
250     if (config == NULL) {
251       warn("no configuration file");
252       return(-2);
253     } else {
254       /* setting sreq to NULL guarantees reqtop will be properly set later */
255       sreq = NULL;
256       reqtop = NULL;
257     }
258
259     while (config != NULL){
260       new = xmalloc(sizeof(sending_t));
261       new->next = NULL;
262
263       if (sreq == NULL){
264         reqtop = new;
265         sreq = new;
266       } else {
267         sreq->next = new;
268         sreq = sreq->next;
269       }
270       req_copyto(req, sreq);
271   
272       /* first, build the sockaddrs */
273       memset(&sinl, '\0', sizeof(sinl));
274       memset(&sreq->sinr, '\0', sizeof(sreq->sinr));
275       sinl.sin_family = AF_INET;
276       sreq->sinr.sin_family = AF_INET;
277       if (config->lochost == NULL) {
278         if (gethostname(hostname, sizeof(hostname)) != 0) {
279           syswarn("cannot get local hostname");
280           return(-2);
281         }
282         config->lochost = xstrdup(hostname);
283       }
284       if (config->lochost) {
285         if (inet_aton(config->lochost, &sinl.sin_addr) != 1) {
286           if ((hent = gethostbyname(config->lochost)) == NULL) {
287             warn("cannot gethostbyname lochost %s", config->lochost);
288             return(-2);
289           }
290           memcpy(&sinl.sin_addr.s_addr, hent->h_addr,
291                  sizeof(struct in_addr));
292         }
293       }
294       if (inet_aton(config->radhost, &sreq->sinr.sin_addr) != 1) {
295         if ((hent = gethostbyname(config->radhost)) == NULL) {
296           warn("cannot gethostbyname radhost %s", config->radhost);
297           return(-2);
298         }
299         memcpy(&sreq->sinr.sin_addr.s_addr, hent->h_addr_list[0],
300                sizeof(struct in_addr));
301       }
302
303       if (config->radport)
304         sreq->sinr.sin_port = htons(config->radport);
305       else
306         sreq->sinr.sin_port = htons(PW_AUTH_UDP_PORT);
307
308       /* seed the random number generator for the auth vector */
309       gettimeofday(&seed, 0);
310       srandom((unsigned) seed.tv_sec+seed.tv_usec);
311       /* build the visible part of the auth vector randomly */
312       for (i = 0; i < AUTH_VECTOR_LEN; i++)
313         req.vector[i] = random() % 256;
314       strlcpy((char *) secbuf, config->secret, sizeof(secbuf));
315       memcpy(secbuf+strlen(config->secret), req.vector, AUTH_VECTOR_LEN);
316       md5_hash(secbuf, strlen(config->secret)+AUTH_VECTOR_LEN, digest);
317       /* fill in the auth_req data */
318       req.code = PW_AUTHENTICATION_REQUEST;
319       req.id = 0;
320
321       /* bracket the username in the configured prefix/suffix */
322       req.data[0] = PW_USER_NAME;
323       req.data[1] = 2;
324       req.data[2] = '\0';
325       if (config->prefix) {
326         req.data[1] += strlen(config->prefix);
327         strlcat((char *) &req.data[2], config->prefix, sizeof(req.data) - 2);
328       }
329       req.data[1] += strlen(uname);
330       strlcat((char *)&req.data[2], uname, sizeof(req.data) - 2);
331       if (!strchr(uname, '@') && config->suffix) {
332         req.data[1] += strlen(config->suffix);
333         strlcat((char *)&req.data[2], config->suffix, sizeof(req.data) - 2);
334       }
335       req.datalen = req.data[1];
336
337       /* set the password */
338       passstart = req.datalen;
339       req.data[req.datalen] = PW_PASSWORD;
340       /* Null pad the password */
341       passlen = (strlen(pass) + 15) / 16;
342       passlen *= 16;
343       req.data[req.datalen+1] = passlen+2;
344       strlcpy((char *)&req.data[req.datalen+2], pass,
345               sizeof(req.data) - req.datalen - 2);
346       passlen -= strlen(pass);
347       while (passlen--)
348         req.data[req.datalen+passlen+2+strlen(pass)] = '\0';
349       req.datalen += req.data[req.datalen+1];
350
351       /* Add NAS_PORT and NAS_IP_ADDRESS into request */
352       if ((nvalue = config->locport) == 0)
353         nvalue = RADIUS_LOCAL_PORT;
354       req.data[req.datalen++] = RAD_NAS_PORT;
355       req.data[req.datalen++] = sizeof(nvalue) + 2;
356       nvalue = htonl(nvalue);
357       memcpy(req.data + req.datalen, &nvalue, sizeof(nvalue));
358       req.datalen += sizeof(nvalue);
359       req.data[req.datalen++] = RAD_NAS_IP_ADDRESS;
360       req.data[req.datalen++] = sizeof(struct in_addr) + 2;
361       memcpy(req.data + req.datalen, &sinl.sin_addr.s_addr,
362            sizeof(struct in_addr));
363       req.datalen += sizeof(struct in_addr);
364
365       /* we're only doing authentication */
366       req.data[req.datalen] = PW_SERVICE_TYPE;
367       req.data[req.datalen+1] = 6;
368       req.data[req.datalen+2] = (PW_SERVICE_AUTH_ONLY >> 24) & 0x000000ff;
369       req.data[req.datalen+3] = (PW_SERVICE_AUTH_ONLY >> 16) & 0x000000ff;
370       req.data[req.datalen+4] = (PW_SERVICE_AUTH_ONLY >> 8) & 0x000000ff;
371       req.data[req.datalen+5] = PW_SERVICE_AUTH_ONLY & 0x000000ff;
372       req.datalen += req.data[req.datalen+1];
373
374       /* filled in the data, now we know what the actual length is. */
375       req.length = 4+AUTH_VECTOR_LEN+req.datalen;
376
377       /* "encrypt" the password */
378       for (i = 0; i < req.data[passstart+1]-2; i += sizeof(HASH)) {
379         jlen = sizeof(HASH);
380         if (req.data[passstart+1]-(unsigned)i-2 < sizeof(HASH))
381             jlen = req.data[passstart+1]-i-2;
382         for (j = 0; j < jlen; j++)
383             req.data[passstart+2+i+j] ^= digest[j];
384         if (jlen == sizeof(HASH)) {
385             /* Recalculate the digest from the HASHed previous */
386             strlcpy((char *) secbuf, config->secret, sizeof(secbuf));
387             memcpy(secbuf+strlen(config->secret), &req.data[passstart+2+i],
388                    sizeof(HASH));
389             md5_hash(secbuf, strlen(config->secret)+sizeof(HASH), digest);
390         }
391       }
392       sreq->reqlen = req.length;
393       req.length = htons(req.length);
394
395       req_copyfrom(sreq, req);
396
397       /* Go to the next record in the list */
398       config = config->next;
399     }
400
401     /* YAYY! The auth_req is ready to go! Build the reply socket and send out
402      * the message. */
403
404     /* now, build the sockets */
405     if ((sock = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
406       syswarn("cannot build reply socket");
407       return(-1);
408     }
409     if (bind(sock, (struct sockaddr*) &sinl, sizeof(sinl)) < 0) {
410       syswarn("cannot bind reply socket");
411       close(sock);
412       return(-1);
413     }
414
415     for(done = 0; authtries > 0 && !done; authtries--) {
416       for (config = radconfig, sreq = reqtop; sreq != NULL && !done;
417            config = config->next, sreq = sreq->next){
418         req_copyto(req, sreq);
419
420         /* send out the packet and wait for reply. */
421         if (sendto(sock, (char *)&req, sreq->reqlen, 0, 
422                    (struct sockaddr*) &sreq->sinr, 
423                    sizeof (struct sockaddr_in)) < 0) {
424           syswarn("cannot send auth_reg");
425           close(sock);
426           return(-1);
427         }
428
429         /* wait 5 seconds maximum for a radius reply. */
430         now = time(0);
431         end = now+5;
432         tmout.tv_sec = 6;
433         tmout.tv_usec = 0;
434         FD_ZERO(&rdfds);
435         /* store the old vector to verify next checksum */
436         memcpy(secbuf+sizeof(req.vector), req.vector, sizeof(req.vector));
437         FD_SET(sock, &rdfds);
438         got = select(sock+1, &rdfds, 0, 0, &tmout);
439         if (got < 0) {
440             syswarn("cannot not select");
441             break;
442         } else if (got == 0) {
443             /* timer ran out */
444             now = time(0);
445             tmout.tv_sec = end - now + 1;
446             tmout.tv_usec = 0;
447             continue;
448         }
449         slen = sizeof(sinl);
450         if ((jlen = recvfrom(sock, (char *)&req, sizeof(req)-sizeof(int), 0, 
451                              (struct sockaddr*) &sinl, &slen)) < 0) {
452             syswarn("cannot recvfrom");
453             break;
454         }
455         if (!config->ignore_source) {
456             if (sinl.sin_addr.s_addr != sreq->sinr.sin_addr.s_addr ||
457               (sinl.sin_port != sreq->sinr.sin_port)) {
458                 warn("received unexpected UDP packet from %s:%d",
459                      inet_ntoa(sinl.sin_addr), ntohs(sinl.sin_port));
460                 continue;
461             }
462         }
463         sreq->reqlen = ntohs(req.length);
464         if (jlen < 4+AUTH_VECTOR_LEN || jlen != sreq->reqlen) {
465             warn("received badly-sized packet");
466             continue;
467         }
468         /* verify the checksum */
469         memcpy(((char*)&req)+sreq->reqlen, config->secret, strlen(config->secret));
470         memcpy(secbuf, req.vector, sizeof(req.vector));
471         memcpy(req.vector, secbuf+sizeof(req.vector), sizeof(req.vector));
472         md5_hash((unsigned char *)&req, strlen(config->secret)+sreq->reqlen,
473             digest);
474         if (memcmp(digest, secbuf, sizeof(HASH)) != 0) {
475             warn("checksum didn't match");
476             continue;
477         }
478         /* FINALLY!  Got back a known-good packet.  See if we're in. */
479         close(sock);
480         return (req.code == PW_AUTHENTICATION_ACK) ? 0 : -1;
481         done = 1;
482         req_copyfrom(sreq, req);
483         break;
484       }
485     }
486     if (authtries == 0)
487         warn("cannot talk to remote radius server %s:%d",
488              inet_ntoa(sreq->sinr.sin_addr), ntohs(sreq->sinr.sin_port));
489     return(-2);
490 }
491
492 #define RAD_HAVE_HOST 1
493 #define RAD_HAVE_PORT 2
494 #define RAD_HAVE_PREFIX 4
495 #define RAD_HAVE_SUFFIX 8
496 #define RAD_HAVE_LOCHOST 16
497 #define RAD_HAVE_LOCPORT 32
498
499 int main(int argc, char *argv[])
500 {
501     int opt;
502     int havefile, haveother;
503     struct auth_info *authinfo;
504     rad_config_t radconfig;
505     int retval;
506     char *radius_config;
507
508     message_program_name = "radius";
509
510     if (!innconf_read(NULL))
511         exit(1);
512
513     memset(&radconfig, '\0', sizeof(rad_config_t));
514     haveother = havefile = 0;
515
516     while ((opt = getopt(argc, argv, "f:h")) != -1) {
517         switch (opt) {
518           case 'f':
519             if (haveother)
520                 die("-f flag after another flag");
521             if (!havefile) {
522               /* override the standard config completely if the user
523                * specifies an alternate config file */
524               memset(&radconfig, '\0', sizeof(rad_config_t));
525               havefile = 1;
526             }
527             read_config(optarg, &radconfig);
528             break;
529         case 'h':
530           printf("Usage: radius [-f config]\n");
531           exit(0);
532         }
533     }
534     if (argc != optind)
535       exit(2);
536     if (!havefile) {
537       radius_config = concatpath(innconf->pathetc, _PATH_RADIUS_CONFIG);
538       read_config(radius_config, &radconfig);
539   
540       free(radius_config);
541     }
542
543     authinfo = get_auth_info(stdin);
544     if (authinfo == NULL)
545         die("failed getting auth info");
546     if (authinfo->username[0] == '\0')
547         die("empty username");
548
549     /* got username and password, check that they're valid */
550
551     retval = rad_auth(&radconfig, authinfo->username, authinfo->password);
552     if (retval == -1)
553         die("user %s password doesn't match", authinfo->username);
554     else if (retval == -2)
555         /* couldn't talk to the radius server..  output logged above. */
556         exit(1);
557     else if (retval != 0)
558         die("unexpected return code from authentication function: %d",
559             retval);
560
561     /* radius password matches! */
562     printf("User:%s\n", authinfo->username);
563     exit(0);
564 }