]> git.lizzy.rs Git - plan9front.git/blob - sys/src/cmd/auth/secureidcheck.c
awk: make empty FS unicodely-correct.
[plan9front.git] / sys / src / cmd / auth / secureidcheck.c
1 /*
2  * This code uses RADIUS as a portable way to validate tokens such as SecurID.
3  * It is relatively simple to send a UDP packet and get a response, but various
4  * things can go wrong.  Speaking the proprietary ACE protocol would allow
5  * handling "next token code" and other error messages.  More importantly, the
6  * timeout threshold is inherently hard to pick.  We observe responses taking
7  * longer than 10 seconds in normal times.  That is a long time to wait before
8  * retrying on a second server.  Moreover, if the UDP response is lost, retrying
9  * on a second server will also fail because the valid token code may be
10  * presented only once.  This whole approach is flawed, but best we can do.
11  */
12 /* RFC2138 */
13 #include <u.h>
14 #include <libc.h>
15 #include <ctype.h>
16 #include <bio.h>
17 #include <ip.h>
18 #include <ndb.h>
19 #include <libsec.h>
20
21 #define AUTHLOG "auth"
22
23 enum{
24         R_AccessRequest =1,     /* Packet code */
25         R_AccessAccept  =2,
26         R_AccessReject  =3,
27         R_AccessChallenge=11,
28         R_UserName      =1,
29         R_UserPassword  =2,
30         R_NASIPAddress  =4,
31         R_ReplyMessage  =18,
32         R_State         =24,
33         R_NASIdentifier =32,
34 };
35
36 typedef struct Secret{
37         uchar   *s;
38         int     len;
39 } Secret;
40
41 typedef struct Attribute{
42         struct Attribute *next;
43         uchar   type;
44         uchar   len;            /* number of bytes in value */
45         uchar   val[256];
46 } Attribute;
47
48 typedef struct Packet{
49         uchar   code, ID;
50         uchar   authenticator[16];
51         Attribute first;
52 } Packet;
53
54 /* assumes pass is at most 16 chars */
55 void
56 hide(Secret *shared, uchar *auth, Secret *pass, uchar *x)
57 {
58         DigestState *M;
59         int i, n = pass->len;
60
61         M = md5(shared->s, shared->len, nil, nil);
62         md5(auth, 16, x, M);
63         if(n > 16)
64                 n = 16;
65         for(i = 0; i < n; i++)
66                 x[i] ^= pass->s[i];
67 }
68
69 int
70 authcmp(Secret *shared, uchar *buf, int m, uchar *auth)
71 {
72         DigestState *M;
73         uchar x[16];
74
75         M = md5(buf, 4, nil, nil);      /* Code+ID+Length */
76         M = md5(auth, 16, nil, M);      /* RequestAuth */
77         M = md5(buf+20, m-20, nil, M);  /* Attributes */
78         md5(shared->s, shared->len, x, M);
79         return memcmp(x, buf+4, 16);
80 }
81
82 Packet*
83 newRequest(uchar *auth)
84 {
85         static uchar ID = 0;
86         Packet *p;
87
88         p = (Packet*)malloc(sizeof(*p));
89         if(p == nil)
90                 return nil;
91         p->code = R_AccessRequest;
92         p->ID = ++ID;
93         memmove(p->authenticator, auth, 16);
94         p->first.next = nil;
95         p->first.type = 0;
96         return p;
97 }
98
99 void
100 freePacket(Packet *p)
101 {
102         Attribute *a, *x;
103
104         if(!p)
105                 return;
106         a = p->first.next;
107         while(a){
108                 x = a;
109                 a = a->next;
110                 free(x);
111         }
112         free(p);
113 }
114
115 int
116 ding(void*, char *msg)
117 {
118         syslog(0, AUTHLOG, "ding %s", msg);
119         if(strstr(msg, "alarm"))
120                 return 1;
121         return 0;
122 }
123
124 Packet *
125 rpc(char *dest, Secret *shared, Packet *req)
126 {
127         uchar buf[4096], buf2[4096], *b, *e;
128         Packet *resp;
129         Attribute *a;
130         int m, n, fd, try;
131
132         /* marshal request */
133         e = buf + sizeof buf;
134         buf[0] = req->code;
135         buf[1] = req->ID;
136         memmove(buf+4, req->authenticator, 16);
137         b = buf+20;
138         for(a = &req->first; a; a = a->next){
139                 if(b + 2 + a->len > e)
140                         return nil;
141                 *b++ = a->type;
142                 *b++ = 2 + a->len;
143                 memmove(b, a->val, a->len);
144                 b += a->len;
145         }
146         n = b-buf;
147         buf[2] = n>>8;
148         buf[3] = n;
149
150         /* send request, wait for reply */
151         fd = dial(dest, 0, 0, 0);
152         if(fd < 0){
153                 syslog(0, AUTHLOG, "%s: rpc can't get udp channel", dest);
154                 return nil;
155         }
156         atnotify(ding, 1);
157         m = -1;
158         for(try = 0; try < 2; try++){
159                 /*
160                  * increased timeout from 4sec to 15sec because
161                  * corporate server really takes that long.
162                  */
163                 alarm(15000);
164                 m = write(fd, buf, n);
165                 if(m != n){
166                         syslog(0, AUTHLOG, "%s: rpc write err %d %d: %r",
167                                 dest, m, n);
168                         m = -1;
169                         break;
170                 }
171                 m = read(fd, buf2, sizeof buf2);
172                 alarm(0);
173                 if(m < 0){
174                         syslog(0, AUTHLOG, "%s rpc read err %d: %r", dest, m);
175                         break;                  /* failure */
176                 }
177                 if(m == 0 || buf2[1] != buf[1]){        /* need matching ID */
178                         syslog(0, AUTHLOG, "%s unmatched reply %d", dest, m);
179                         continue;
180                 }
181                 if(authcmp(shared, buf2, m, buf+4) == 0)
182                         break;
183                 syslog(0, AUTHLOG, "%s bad rpc chksum", dest);
184         }
185         close(fd);
186         if(m <= 0)
187                 return nil;
188
189         /* unmarshal reply */
190         b = buf2;
191         e = buf2+m;
192         resp = (Packet*)malloc(sizeof(*resp));
193         if(resp == nil)
194                 return nil;
195         resp->code = *b++;
196         resp->ID = *b++;
197         n = *b++;
198         n = (n<<8) | *b++;
199         if(m != n){
200                 syslog(0, AUTHLOG, "rpc got %d bytes, length said %d", m, n);
201                 if(m > n)
202                         e = buf2+n;
203         }
204         memmove(resp->authenticator, b, 16);
205         b += 16;
206         a = &resp->first;
207         a->type = 0;
208         for(;;){
209                 if(b >= e){
210                         a->next = nil;
211                         break;          /* exit loop */
212                 }
213                 a->type = *b++;
214                 a->len = (*b++) - 2;
215                 if(b + a->len > e){     /* corrupt packet */
216                         a->next = nil;
217                         freePacket(resp);
218                         return nil;
219                 }
220                 memmove(a->val, b, a->len);
221                 b += a->len;
222                 if(b < e){              /* any more attributes? */
223                         a->next = (Attribute*)malloc(sizeof(*a));
224                         if(a->next == nil){
225                                 free(req);
226                                 return nil;
227                         }
228                         a = a->next;
229                 }
230         }
231         return resp;
232 }
233
234 int
235 setAttribute(Packet *p, uchar type, uchar *s, int n)
236 {
237         Attribute *a;
238
239         a = &p->first;
240         if(a->type != 0){
241                 a = (Attribute*)malloc(sizeof(*a));
242                 if(a == nil)
243                         return -1;
244                 a->next = p->first.next;
245                 p->first.next = a;
246         }
247         a->type = type;
248         a->len = n;
249         if(a->len > 253)        /* RFC2138, section 5 */
250                 a->len = 253;
251         memmove(a->val, s, a->len);
252         return 0;
253 }
254
255 /* return a reply message attribute string */
256 char*
257 replymsg(Packet *p)
258 {
259         Attribute *a;
260         static char buf[255];
261
262         for(a = &p->first; a; a = a->next)
263                 if(a->type == R_ReplyMessage){
264                         if(a->len >= sizeof buf)
265                                 a->len = sizeof(buf)-1;
266                         memmove(buf, a->val, a->len);
267                         buf[a->len] = 0;
268                 }
269         return buf;
270 }
271
272 /* for convenience while debugging */
273 char *replymess;
274 Attribute *stateattr;
275
276 void
277 logPacket(Packet *p)
278 {
279         int i;
280         char *np, *e;
281         char buf[255], pbuf[4*1024];
282         uchar *au = p->authenticator;
283         Attribute *a;
284
285         e = pbuf + sizeof(pbuf);
286
287         np = seprint(pbuf, e, "Packet ID=%d auth=%x %x %x... ",
288                 p->ID, au[0], au[1], au[2]);
289         switch(p->code){
290         case R_AccessRequest:
291                 np = seprint(np, e, "request\n");
292                 break;
293         case R_AccessAccept:
294                 np = seprint(np, e, "accept\n");
295                 break;
296         case R_AccessReject:
297                 np = seprint(np, e, "reject\n");
298                 break;
299         case R_AccessChallenge:
300                 np = seprint(np, e, "challenge\n");
301                 break;
302         default:
303                 np = seprint(np, e, "code=%d\n", p->code);
304                 break;
305         }
306         replymess = "0000000";
307         for(a = &p->first; a; a = a->next){
308                 if(a->len > 253 )
309                         a->len = 253;
310                 memmove(buf, a->val, a->len);
311                 np = seprint(np, e, " [%d]", a->type);
312                 for(i = 0; i < a->len; i++)
313                         if(isprint(a->val[i]))
314                                 np = seprint(np, e, "%c", a->val[i]);
315                         else
316                                 np = seprint(np, e, "\\%o", a->val[i]);
317                 np = seprint(np, e, "\n");
318                 buf[a->len] = 0;
319                 if(a->type == R_ReplyMessage)
320                         replymess = strdup(buf);
321                 else if(a->type == R_State)
322                         stateattr = a;
323         }
324
325         syslog(0, AUTHLOG, "%s", pbuf);
326 }
327
328 static uchar*
329 getipv4addr(void)
330 {
331         Ipifc *nifc;
332         Iplifc *lifc;
333         static Ipifc *ifc;
334
335         ifc = readipifc("/net", ifc, -1);
336         for(nifc = ifc; nifc; nifc = nifc->next)
337                 for(lifc = nifc->lifc; lifc; lifc = lifc->next)
338                         if (ipcmp(lifc->ip, IPnoaddr) != 0 &&
339                             ipcmp(lifc->ip, v4prefix) != 0)
340                                 return lifc->ip;
341         return nil;
342 }
343
344 extern Ndb *db;
345
346 /* returns 0 on success, error message on failure */
347 char*
348 secureidcheck(char *user, char *response)
349 {
350         char *radiussecret = nil;
351         char *rv = "authentication failed";
352         char dest[3*IPaddrlen+20], ruser[64];
353         uchar *ip;
354         uchar x[16];
355         ulong u[4];
356         Ndbs s;
357         Ndbtuple *t = nil, *nt, *tt;
358         Packet *req = nil, *resp = nil;
359         Secret shared, pass;
360         static Ndb *netdb;
361
362         if(netdb == nil)
363                 netdb = ndbopen(0);
364
365         /* bad responses make them disable the fob, avoid silly checks */
366         if(strlen(response) < 4 || strpbrk(response,"abcdefABCDEF") != nil)
367                 goto out;
368
369         /* get radius secret */
370         radiussecret = ndbgetvalue(db, &s, "radius", "lra-radius", "secret", &t);
371         if(radiussecret == nil){
372                 syslog(0, AUTHLOG, "secureidcheck: nil radius secret: %r");
373                 goto out;
374         }
375
376         /* translate user name if we have to */
377         strcpy(ruser, user);
378         for(nt = t; nt; nt = nt->entry)
379                 if(strcmp(nt->attr, "uid") == 0 && strcmp(nt->val, user) == 0)
380                         for(tt = nt->line; tt != nt; tt = tt->line)
381                                 if(strcmp(tt->attr, "rid") == 0){
382                                         strcpy(ruser, tt->val);
383                                         break;
384                                 }
385         ndbfree(t);
386         t = nil;
387
388         u[0] = fastrand();
389         u[1] = fastrand();
390         u[2] = fastrand();
391         u[3] = fastrand();
392         req = newRequest((uchar*)u);
393         if(req == nil)
394                 goto out;
395         shared.s = (uchar*)radiussecret;
396         shared.len = strlen(radiussecret);
397         ip = getipv4addr();
398         if(ip == nil){
399                 syslog(0, AUTHLOG, "no interfaces: %r");
400                 goto out;
401         }
402         if(setAttribute(req, R_NASIPAddress, ip + IPv4off, 4) < 0)
403                 goto out;
404
405         if(setAttribute(req, R_UserName, (uchar*)ruser, strlen(ruser)) < 0)
406                 goto out;
407         pass.s = (uchar*)response;
408         pass.len = strlen(response);
409         hide(&shared, req->authenticator, &pass, x);
410         if(setAttribute(req, R_UserPassword, x, 16) < 0)
411                 goto out;
412
413         t = ndbsearch(netdb, &s, "sys", "lra-radius");
414         if(t == nil){
415                 syslog(0, AUTHLOG, "secureidcheck: nil radius sys search: %r");
416                 goto out;
417         }
418         for(nt = t; nt; nt = nt->entry){
419                 if(strcmp(nt->attr, "ip") != 0)
420                         continue;
421
422                 snprint(dest, sizeof dest, "udp!%s!radius", nt->val);
423                 resp = rpc(dest, &shared, req);
424                 if(resp == nil){
425                         syslog(0, AUTHLOG, "%s nil response", dest);
426                         continue;
427                 }
428                 if(resp->ID != req->ID){
429                         syslog(0, AUTHLOG, "%s mismatched ID  req=%d resp=%d",
430                                 dest, req->ID, resp->ID);
431                         freePacket(resp);
432                         resp = nil;
433                         continue;
434                 }
435
436                 switch(resp->code){
437                 case R_AccessAccept:
438                         syslog(0, AUTHLOG, "%s accepted ruser=%s", dest, ruser);
439                         rv = nil;
440                         break;
441                 case R_AccessReject:
442                         syslog(0, AUTHLOG, "%s rejected ruser=%s %s",
443                                 dest, ruser, replymsg(resp));
444                         rv = "secureid failed";
445                         break;
446                 case R_AccessChallenge:
447                         syslog(0, AUTHLOG, "%s challenge ruser=%s %s",
448                                 dest, ruser, replymsg(resp));
449                         rv = "secureid out of sync";
450                         break;
451                 default:
452                         syslog(0, AUTHLOG, "%s code=%d ruser=%s %s",
453                                 dest, resp->code, ruser, replymsg(resp));
454                         break;
455                 }
456                 break;  /* we have a proper reply, no need to ask again */
457         }
458 out:
459         if (t)
460                 ndbfree(t);
461         free(radiussecret);
462         freePacket(req);
463         freePacket(resp);
464         return rv;
465 }