]> git.lizzy.rs Git - plan9front.git/blob - sys/src/cmd/auth/authsrv.c
disk/format: implement long name support
[plan9front.git] / sys / src / cmd / auth / authsrv.c
1 #include <u.h>
2 #include <libc.h>
3 #include <bio.h>
4 #include <ndb.h>
5 #include <regexp.h>
6 #include <libsec.h>
7 #include <authsrv.h>
8 #include "authcmdlib.h"
9
10 Ndb *db;
11 char raddr[128];
12 uchar zeros[32];
13
14 typedef struct Keyslot Keyslot;
15 struct Keyslot
16 {
17         Authkey;
18         char    id[ANAMELEN];
19 };
20 Keyslot hkey, akey, ukey;
21 uchar keyseed[SHA2_256dlen];
22
23 char ticketform;
24
25 /* Microsoft auth constants */
26 enum {
27         MShashlen = 16,
28         MSchallen = 8,
29         MSresplen = 24,
30         MSchallenv2 = 16,
31 };
32
33 void    pak(Ticketreq*);
34 void    ticketrequest(Ticketreq*);
35 void    challengebox(Ticketreq*);
36 void    changepasswd(Ticketreq*);
37 void    apop(Ticketreq*, int);
38 void    chap(Ticketreq*);
39 void    ntlm(Ticketreq*);
40 void    mschap(Ticketreq*, int);
41 void    vnc(Ticketreq*);
42 int     speaksfor(char*, char*);
43 void    replyerror(char*, ...);
44 void    getraddr(char*);
45 void    initkeyseed(void);
46 void    mkkey(char*, Authkey*);
47 void    mkticket(Ticketreq*, Ticket*);
48 void    nthash(uchar hash[MShashlen], char *passwd);
49 void    lmhash(uchar hash[MShashlen], char *passwd);
50 void    ntv2hash(uchar hash[MShashlen], char *passwd, char *user, char *dom);
51 void    mschalresp(uchar resp[MSresplen], uchar hash[MShashlen], uchar chal[MSchallen]);
52 void    desencrypt(uchar data[8], uchar key[7]);
53 void    tickauthreply(Ticketreq*, Authkey*);
54 void    tickauthreply2(Ticketreq*, Authkey*, uchar *, int, uchar *, int);
55 void    safecpy(char*, char*, int);
56 void    catch(void*, char*);
57
58 void
59 main(int argc, char *argv[])
60 {
61         char buf[TICKREQLEN];
62         Ticketreq tr;
63         int n;
64
65         ARGBEGIN{
66         case 'N':
67                 ticketform = 1;
68                 break;
69         }ARGEND
70
71         strcpy(raddr, "unknown");
72         if(argc >= 1)
73                 getraddr(argv[argc-1]);
74
75         alarm(10*60*1000);      /* kill a connection after 10 minutes */
76
77         private();
78         initkeyseed();
79
80         db = ndbopen("/lib/ndb/auth");
81         if(db == 0)
82                 syslog(0, AUTHLOG, "no /lib/ndb/auth");
83
84         for(;;){
85                 n = readn(0, buf, sizeof(buf));
86                 if(n <= 0 || convM2TR(buf, n, &tr) <= 0)
87                         exits(0);
88                 switch(tr.type){
89                 case AuthTreq:
90                         ticketrequest(&tr);
91                         break;
92                 case AuthChal:
93                         challengebox(&tr);
94                         break;
95                 case AuthPass:
96                         changepasswd(&tr);
97                         break;
98                 case AuthApop:
99                         apop(&tr, AuthApop);
100                         break;
101                 case AuthChap:
102                         chap(&tr);
103                         break;
104                 case AuthMSchap:
105                         mschap(&tr, MSchallen);
106                         break;
107                 case AuthMSchapv2:
108                         mschap(&tr, MSchallenv2);
109                         break;
110                 case AuthNTLM:
111                         ntlm(&tr);
112                         break;
113                 case AuthCram:
114                         apop(&tr, AuthCram);
115                         break;
116                 case AuthVNC:
117                         vnc(&tr);
118                         break;
119                 case AuthPAK:
120                         pak(&tr);
121                         continue;
122                 default:
123                         syslog(0, AUTHLOG, "unknown ticket request type: %d", tr.type);
124                         exits(0);
125                 }
126                 /* invalidate pak keys */
127                 akey.id[0] = 0;
128                 hkey.id[0] = 0;
129                 ukey.id[0] = 0;
130         }
131         /* not reached */
132 }
133
134 void
135 pak1(char *u, Keyslot *k)
136 {
137         uchar y[PAKYLEN];
138         PAKpriv p;
139
140         safecpy(k->id, u, sizeof(k->id));
141         if(!findkey(KEYDB, k->id, k) || tsmemcmp(k->aes, zeros, AESKEYLEN) == 0) {
142                 syslog(0, AUTHLOG, "pak-fail no AES key for id %s", k->id);
143                 /* make one up so caller doesn't know it was wrong */
144                 mkkey(k->id, k);
145                 authpak_hash(k, k->id);
146         }
147         authpak_new(&p, k, y, 0);
148         if(write(1, y, PAKYLEN) != PAKYLEN)
149                 exits(0);
150         if(readn(0, y, PAKYLEN) != PAKYLEN)
151                 exits(0);
152         if(authpak_finish(&p, k, y))
153                 exits(0);
154 }
155
156 void
157 pak(Ticketreq *tr)
158 {
159         static uchar ok[1] = {AuthOK};
160
161         if(write(1, ok, 1) != 1)
162                 exits(0);
163
164         /* invalidate pak keys */
165         akey.id[0] = 0;
166         hkey.id[0] = 0;
167         ukey.id[0] = 0;
168
169         if(tr->hostid[0]) {
170                 if(tr->authid[0])
171                         pak1(tr->authid, &akey);
172                 pak1(tr->hostid, &hkey);
173         } else if(tr->uid[0]) {
174                 pak1(tr->uid, &ukey);
175         }
176
177         ticketform = 1;
178 }
179
180 int
181 getkey(char *u, Keyslot *k)
182 {
183         /* empty user id is an error */
184         if(*u == 0)
185                 exits(0);
186
187         if(k == &hkey && strcmp(u, k->id) == 0)
188                 return 1;
189         if(k == &akey && strcmp(u, k->id) == 0)
190                 return 1;
191         if(k == &ukey && strcmp(u, k->id) == 0)
192                 return 1;
193
194         if(ticketform != 0){
195                 syslog(0, AUTHLOG, "need DES key for %s, but DES is disabled", u);
196                 replyerror("DES is disabled");
197                 exits(0);
198         }
199
200         return findkey(KEYDB, u, k);
201 }
202
203 void
204 ticketrequest(Ticketreq *tr)
205 {
206         char tbuf[2*MAXTICKETLEN+1];
207         Ticket t;
208         int n;
209
210         if(tr->uid[0] == 0)
211                 exits(0);
212         if(!getkey(tr->authid, &akey)){
213                 /* make one up so caller doesn't know it was wrong */
214                 mkkey(tr->authid, &akey);
215                 syslog(0, AUTHLOG, "tr-fail authid %s", tr->authid);
216         }
217         if(!getkey(tr->hostid, &hkey)){
218                 /* make one up so caller doesn't know it was wrong */
219                 mkkey(tr->hostid, &hkey);
220                 syslog(0, AUTHLOG, "tr-fail hostid %s(%s)", tr->hostid, raddr);
221         }
222         mkticket(tr, &t);
223         if(!speaksfor(tr->hostid, tr->uid)){
224                 mkkey(tr->authid, &akey);
225                 mkkey(tr->hostid, &hkey);
226                 syslog(0, AUTHLOG, "tr-fail %s@%s(%s) -> %s@%s no speaks for",
227                         tr->uid, tr->hostid, raddr, tr->uid, tr->authid);
228         }
229         n = 0;
230         tbuf[n++] = AuthOK;
231         t.num = AuthTc;
232         n += convT2M(&t, tbuf+n, sizeof(tbuf)-n, &hkey);
233         t.num = AuthTs;
234         n += convT2M(&t, tbuf+n, sizeof(tbuf)-n, &akey);
235         if(write(1, tbuf, n) != n)
236                 exits(0);
237
238         syslog(0, AUTHLOG, "tr-ok %s@%s(%s) -> %s@%s", tr->uid, tr->hostid, raddr, tr->uid, tr->authid);
239 }
240
241 void
242 challengebox(Ticketreq *tr)
243 {
244         char kbuf[DESKEYLEN], nkbuf[DESKEYLEN], buf[NETCHLEN+1];
245         char *key, *netkey, *err;
246         long chal;
247
248         if(tr->uid[0] == 0)
249                 exits(0);
250         key = finddeskey(KEYDB, tr->uid, kbuf);
251         netkey = finddeskey(NETKEYDB, tr->uid, nkbuf);
252         if(key == nil && netkey == nil){
253                 /* make one up so caller doesn't know it was wrong */
254                 genrandom((uchar*)nkbuf, DESKEYLEN);
255                 netkey = nkbuf;
256                 syslog(0, AUTHLOG, "cr-fail uid %s@%s", tr->uid, raddr);
257         }
258
259         if(!getkey(tr->hostid, &hkey)){
260                 /* make one up so caller doesn't know it was wrong */
261                 mkkey(tr->hostid, &hkey);
262                 syslog(0, AUTHLOG, "cr-fail hostid %s %s@%s", tr->hostid, tr->uid, raddr);
263         }
264
265         /*
266          * challenge-response
267          */
268         memset(buf, 0, sizeof(buf));
269         buf[0] = AuthOK;
270         chal = nfastrand(MAXNETCHAL);
271         sprint(buf+1, "%lud", chal);
272         if(write(1, buf, NETCHLEN+1) != NETCHLEN+1)
273                 exits(0);
274         if(readn(0, buf, NETCHLEN) != NETCHLEN)
275                 exits(0);
276         if(!(key != nil && netcheck(key, chal, buf))
277         && !(netkey != nil && netcheck(netkey, chal, buf))
278         && (err = secureidcheck(tr->uid, buf)) != nil){
279                 replyerror("cr-fail %s %s %s", err, tr->uid, raddr);
280                 logfail(tr->uid);
281                 return;
282         }
283         succeed(tr->uid);
284
285         /*
286          *  reply with ticket & authenticator
287          */
288         tickauthreply(tr, &hkey);
289
290         syslog(0, AUTHLOG, "cr-ok %s@%s(%s)", tr->uid, tr->hostid, raddr);
291 }
292
293 void
294 changepasswd(Ticketreq *tr)
295 {
296         char tbuf[MAXTICKETLEN+1], prbuf[MAXPASSREQLEN], *err;
297         Passwordreq pr;
298         Authkey nkey;
299         Ticket t;
300         int n, m;
301
302         if(!getkey(tr->uid, &ukey)){
303                 /* make one up so caller doesn't know it was wrong */
304                 mkkey(tr->uid, &ukey);
305                 syslog(0, AUTHLOG, "cp-fail uid %s@%s", tr->uid, raddr);
306         }
307
308         /* send back a ticket with a new key */
309         mkticket(tr, &t);
310         t.num = AuthTp;
311         n = 0;
312         tbuf[n++] = AuthOK;
313         n += convT2M(&t, tbuf+n, sizeof(tbuf)-n, &ukey);
314         if(write(1, tbuf, n) != n)
315                 exits(0);
316
317         /* loop trying passwords out */
318         for(;;){
319                 for(n=0; (m = convM2PR(prbuf, n, &pr, &t)) <= 0; n += m){
320                         m = -m;
321                         if(m <= n || m > sizeof(prbuf))
322                                 exits(0);
323                         m -= n;
324                         if(readn(0, prbuf+n, m) != m)
325                                 exits(0);
326                 }
327                 if(pr.num != AuthPass){
328                         replyerror("protocol botch1: %s", raddr);
329                         exits(0);
330                 }
331                 passtokey(&nkey, pr.old);
332                 if(tsmemcmp(ukey.des, nkey.des, DESKEYLEN) != 0){
333                         replyerror("protocol botch2: %s", raddr);
334                         continue;
335                 }
336                 if(tsmemcmp(ukey.aes, zeros, AESKEYLEN) != 0 && tsmemcmp(ukey.aes, nkey.aes, AESKEYLEN) != 0){
337                         replyerror("protocol botch3: %s", raddr);
338                         continue;
339                 }
340                 if(*pr.new){
341                         err = okpasswd(pr.new);
342                         if(err){
343                                 replyerror("%s %s", err, raddr);
344                                 continue;
345                         }
346                         passtokey(&nkey, pr.new);
347                 }
348                 if(pr.changesecret && setsecret(KEYDB, tr->uid, pr.secret) == 0){
349                         replyerror("can't write secret %s", raddr);
350                         continue;
351                 }
352                 if(*pr.new && setkey(KEYDB, tr->uid, &nkey) == 0){
353                         replyerror("can't write key %s", raddr);
354                         continue;
355                 }
356                 memmove(ukey.des, nkey.des, DESKEYLEN);
357                 memmove(ukey.aes, nkey.aes, AESKEYLEN);
358                 break;
359         }
360         succeed(tr->uid);
361
362         prbuf[0] = AuthOK;
363         if(write(1, prbuf, 1) != 1)
364                 exits(0);
365 }
366
367 static char*
368 domainname(void)
369 {
370         static char sysname[Maxpath];
371         static char *domain;
372         int n;
373
374         if(domain != nil)
375                 return domain;
376         if(*sysname)
377                 return sysname;
378
379         domain = csgetvalue(0, "sys", sysname, "dom", nil);
380         if(domain != nil)
381                 return domain;
382
383         n = readfile("/dev/sysname", sysname, sizeof(sysname)-1);
384         if(n < 0){
385                 strcpy(sysname, "kremvax");
386                 return sysname;
387         }
388         sysname[n] = 0;
389
390         return sysname;
391 }
392
393 static int
394 h2b(char c)
395 {
396         if(c >= '0' && c <= '9')
397                 return c - '0';
398         if(c >= 'A' && c <= 'F')
399                 return c - 'A' + 10;
400         if(c >= 'a' && c <= 'f')
401                 return c - 'a' + 10;
402         return 0;
403 }
404
405 void
406 apop(Ticketreq *tr, int type)
407 {
408         int challen, i, n, tries;
409         char *secret, *p;
410         Ticketreq treq;
411         DigestState *s;
412         char sbuf[SECRETLEN];
413         char trbuf[TICKREQLEN];
414         char buf[MD5dlen*2];
415         uchar digest[MD5dlen], resp[MD5dlen];
416         ulong rb[4];
417         char chal[256];
418
419         USED(tr);
420
421         /*
422          *  Create a challenge and send it.
423          */
424         genrandom((uchar*)rb, sizeof(rb));
425         p = chal;
426         p += snprint(p, sizeof(chal), "<%lux%lux.%lux%lux@%s>",
427                 rb[0], rb[1], rb[2], rb[3], domainname());
428         challen = p - chal;
429         print("%c%-5d%s", AuthOKvar, challen, chal);
430
431         tries = 5;
432 Retry:
433         if(--tries < 0)
434                 exits(0);
435
436         /*
437          *  get ticket request
438          */
439         n = readn(0, trbuf, sizeof(trbuf));
440         if(n <= 0 || convM2TR(trbuf, n, &treq) <= 0)
441                 exits(0);
442         tr = &treq;
443         if(tr->type != type || tr->uid[0] == 0)
444                 exits(0);
445
446         /*
447          * read response
448          */
449         if(readn(0, buf, MD5dlen*2) != MD5dlen*2)
450                 exits(0);
451         for(i = 0; i < MD5dlen; i++)
452                 resp[i] = (h2b(buf[2*i])<<4)|h2b(buf[2*i+1]);
453
454         /*
455          * lookup
456          */
457         secret = findsecret(KEYDB, tr->uid, sbuf);
458         if(!getkey(tr->hostid, &hkey) || secret == nil){
459                 replyerror("apop-fail bad response %s", raddr);
460                 goto Retry;
461         }
462
463         /*
464          *  check for match
465          */
466         if(type == AuthCram){
467                 hmac_md5((uchar*)chal, challen,
468                         (uchar*)secret, strlen(secret),
469                         digest, nil);
470         } else {
471                 s = md5((uchar*)chal, challen, 0, 0);
472                 md5((uchar*)secret, strlen(secret), digest, s);
473         }
474         if(tsmemcmp(digest, resp, MD5dlen) != 0){
475                 replyerror("apop-fail bad response %s", raddr);
476                 logfail(tr->uid);
477                 goto Retry;
478         }
479
480         succeed(tr->uid);
481
482         /*
483          *  reply with ticket & authenticator
484          */
485         tickauthreply(tr, &hkey);
486
487         if(type == AuthCram)
488                 syslog(0, AUTHLOG, "cram-ok %s %s", tr->uid, raddr);
489         else
490                 syslog(0, AUTHLOG, "apop-ok %s %s", tr->uid, raddr);
491 }
492
493 enum {
494         VNCchallen=     16,
495 };
496
497 /* VNC reverses the bits of each byte before using as a des key */
498 uchar swizzletab[256] = {
499  0x0, 0x80, 0x40, 0xc0, 0x20, 0xa0, 0x60, 0xe0, 0x10, 0x90, 0x50, 0xd0, 0x30, 0xb0, 0x70, 0xf0,
500  0x8, 0x88, 0x48, 0xc8, 0x28, 0xa8, 0x68, 0xe8, 0x18, 0x98, 0x58, 0xd8, 0x38, 0xb8, 0x78, 0xf8,
501  0x4, 0x84, 0x44, 0xc4, 0x24, 0xa4, 0x64, 0xe4, 0x14, 0x94, 0x54, 0xd4, 0x34, 0xb4, 0x74, 0xf4,
502  0xc, 0x8c, 0x4c, 0xcc, 0x2c, 0xac, 0x6c, 0xec, 0x1c, 0x9c, 0x5c, 0xdc, 0x3c, 0xbc, 0x7c, 0xfc,
503  0x2, 0x82, 0x42, 0xc2, 0x22, 0xa2, 0x62, 0xe2, 0x12, 0x92, 0x52, 0xd2, 0x32, 0xb2, 0x72, 0xf2,
504  0xa, 0x8a, 0x4a, 0xca, 0x2a, 0xaa, 0x6a, 0xea, 0x1a, 0x9a, 0x5a, 0xda, 0x3a, 0xba, 0x7a, 0xfa,
505  0x6, 0x86, 0x46, 0xc6, 0x26, 0xa6, 0x66, 0xe6, 0x16, 0x96, 0x56, 0xd6, 0x36, 0xb6, 0x76, 0xf6,
506  0xe, 0x8e, 0x4e, 0xce, 0x2e, 0xae, 0x6e, 0xee, 0x1e, 0x9e, 0x5e, 0xde, 0x3e, 0xbe, 0x7e, 0xfe,
507  0x1, 0x81, 0x41, 0xc1, 0x21, 0xa1, 0x61, 0xe1, 0x11, 0x91, 0x51, 0xd1, 0x31, 0xb1, 0x71, 0xf1,
508  0x9, 0x89, 0x49, 0xc9, 0x29, 0xa9, 0x69, 0xe9, 0x19, 0x99, 0x59, 0xd9, 0x39, 0xb9, 0x79, 0xf9,
509  0x5, 0x85, 0x45, 0xc5, 0x25, 0xa5, 0x65, 0xe5, 0x15, 0x95, 0x55, 0xd5, 0x35, 0xb5, 0x75, 0xf5,
510  0xd, 0x8d, 0x4d, 0xcd, 0x2d, 0xad, 0x6d, 0xed, 0x1d, 0x9d, 0x5d, 0xdd, 0x3d, 0xbd, 0x7d, 0xfd,
511  0x3, 0x83, 0x43, 0xc3, 0x23, 0xa3, 0x63, 0xe3, 0x13, 0x93, 0x53, 0xd3, 0x33, 0xb3, 0x73, 0xf3,
512  0xb, 0x8b, 0x4b, 0xcb, 0x2b, 0xab, 0x6b, 0xeb, 0x1b, 0x9b, 0x5b, 0xdb, 0x3b, 0xbb, 0x7b, 0xfb,
513  0x7, 0x87, 0x47, 0xc7, 0x27, 0xa7, 0x67, 0xe7, 0x17, 0x97, 0x57, 0xd7, 0x37, 0xb7, 0x77, 0xf7,
514  0xf, 0x8f, 0x4f, 0xcf, 0x2f, 0xaf, 0x6f, 0xef, 0x1f, 0x9f, 0x5f, 0xdf, 0x3f, 0xbf, 0x7f, 0xff,
515 };
516
517 void
518 vnc(Ticketreq *tr)
519 {
520         uchar chal[VNCchallen+6];
521         uchar reply[VNCchallen];
522         char sbuf[SECRETLEN];
523         char *secret;
524         DESstate s;
525         int i;
526
527         if(tr->uid[0] == 0)
528                 exits(0);
529
530         /*
531          *  Create a challenge and send it.
532          */
533         genrandom(chal+6, VNCchallen);
534         chal[0] = AuthOKvar;
535         sprint((char*)chal+1, "%-5d", VNCchallen);
536         if(write(1, chal, sizeof(chal)) != sizeof(chal))
537                 exits(0);
538
539         /*
540          *  get response
541          */
542         if(readn(0, reply, sizeof(reply)) != sizeof(reply))
543                 exits(0);
544
545         /*
546          *  lookup keys (and swizzle bits)
547          */
548         memset(sbuf, 0, sizeof(sbuf));
549         secret = findsecret(KEYDB, tr->uid, sbuf);
550         if(!getkey(tr->hostid, &hkey) || secret == nil){
551                 mkkey(tr->hostid, &hkey);
552                 genrandom((uchar*)sbuf, sizeof(sbuf));
553                 secret = sbuf;
554         }
555         for(i = 0; i < 8; i++)
556                 secret[i] = swizzletab[(uchar)secret[i]];
557
558         /*
559          *  decrypt response and compare
560          */
561         setupDESstate(&s, (uchar*)secret, nil);
562         desECBdecrypt(reply, sizeof(reply), &s);
563         if(tsmemcmp(reply, chal+6, VNCchallen) != 0){
564                 replyerror("vnc-fail bad response %s", raddr);
565                 logfail(tr->uid);
566                 return;
567         }
568         succeed(tr->uid);
569
570         /*
571          *  reply with ticket & authenticator
572          */
573         tickauthreply(tr, &hkey);
574
575         syslog(0, AUTHLOG, "vnc-ok %s %s", tr->uid, raddr);
576 }
577
578 void
579 chap(Ticketreq *tr)
580 {
581         char *secret;
582         DigestState *s;
583         char sbuf[SECRETLEN];
584         uchar digest[MD5dlen];
585         char chal[CHALLEN];
586         OChapreply reply;
587         int tries;
588
589         /*
590          *  Create a challenge and send it.
591          */
592         genrandom((uchar*)chal, sizeof(chal));
593         if(write(1, chal, sizeof(chal)) != sizeof(chal))
594                 exits(0);
595
596         tries = 5;
597 Retry:
598         if(--tries < 0)
599                 exits(0);       
600
601         /*
602          *  get chap reply
603          */
604         if(readn(0, &reply, OCHAPREPLYLEN) < 0)
605                 exits(0);
606
607         safecpy(tr->uid, reply.uid, sizeof(tr->uid));
608         if(tr->uid[0] == 0)
609                 exits(0);
610
611         /*
612          * lookup
613          */
614         secret = findsecret(KEYDB, tr->uid, sbuf);
615         if(!getkey(tr->hostid, &hkey) || secret == nil){
616                 replyerror("chap-fail bad response %s", raddr);
617                 goto Retry;
618         }
619
620         /*
621          *  check for match
622          */
623         s = md5(&reply.id, 1, 0, 0);
624         md5((uchar*)secret, strlen(secret), 0, s);
625         md5((uchar*)chal, sizeof(chal), digest, s);
626
627         if(tsmemcmp(digest, reply.resp, MD5dlen) != 0){
628                 replyerror("chap-fail bad response %s", raddr);
629                 logfail(tr->uid);
630                 goto Retry;
631         }
632
633         succeed(tr->uid);
634
635         /*
636          *  reply with ticket & authenticator
637          */
638         tickauthreply(tr, &hkey);
639
640         syslog(0, AUTHLOG, "chap-ok %s %s", tr->uid, raddr);
641
642         /* no secret after ticket */
643         exits(0);
644 }
645
646 static uchar ntblobsig[] = {0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
647
648 void
649 ntlm(Ticketreq *tr)
650 {
651         char *secret;
652         char sbuf[SECRETLEN], windom[DOMLEN];
653         uchar chal[MSchallen], ntblob[1024];
654         uchar hash[MShashlen];
655         uchar resp[MSresplen];
656         NTLMreply reply;
657         int dupe, lmok, ntok, ntbloblen;
658         DigestState *s;
659         int tries;
660
661         /*
662          *  Create a challenge and send it.
663          */
664         genrandom(chal, sizeof(chal));
665         if(write(1, chal, MSchallen) != MSchallen)
666                 exits(0);
667
668         tries = 5;
669 Retry:
670         if(--tries < 0)
671                 exits(0);
672
673         /*
674          *  get NTLM reply
675          */
676         if(readn(0, &reply, NTLMREPLYLEN) < 0)
677                 exits(0);
678
679         ntbloblen = 0;
680         if(memcmp(reply.NTresp+16, ntblobsig, sizeof(ntblobsig)) == 0){
681                 ntbloblen = reply.len[0] | reply.len[1]<<8;
682                 ntbloblen -= NTLMREPLYLEN;
683                 if(ntbloblen < 0 || ntbloblen > sizeof(ntblob)-8)
684                         exits(0);
685                 if(readn(0, ntblob+8, ntbloblen) < 0)
686                         exits(0);
687                 memmove(ntblob, reply.NTresp+16, 8);
688                 ntbloblen += 8;
689         }
690
691         safecpy(tr->uid, reply.uid, sizeof(tr->uid));
692         if(tr->uid[0] == 0)
693                 exits(0);
694
695         safecpy(windom, reply.dom, sizeof(windom));
696
697         /*
698          * lookup
699          */
700         secret = findsecret(KEYDB, tr->uid, sbuf);
701         if(!getkey(tr->hostid, &hkey) || secret == nil){
702                 replyerror("ntlm-fail bad response %s@%s/%s(%s)", tr->uid, windom, tr->hostid, raddr);
703                 goto Retry;
704         }
705
706         if(ntbloblen > 0){
707                 /* NTLMv2 */
708                 ntv2hash(hash, secret, tr->uid, windom);
709
710                 /*
711                  * LmResponse = Cat(HMAC_MD5(LmHash, Cat(SC, CC)), CC)
712                  */
713                 s = hmac_md5(chal, MSchallen, hash, MShashlen, nil, nil);
714                 hmac_md5((uchar*)reply.LMresp+16, MSchallen, hash, MShashlen, resp, s);
715                 lmok = tsmemcmp(resp, reply.LMresp, 16) == 0;
716
717                 /*
718                  * NtResponse = Cat(HMAC_MD5(NtHash, Cat(SC, NtBlob)), NtBlob)
719                  */
720                 s = hmac_md5(chal, MSchallen, hash, MShashlen, nil, nil);
721                 hmac_md5(ntblob, ntbloblen, hash, MShashlen, resp, s);
722                 ntok = tsmemcmp(resp, reply.NTresp, 16) == 0;
723
724                 /*
725                  * LM response can be all zeros or signature key,
726                  * so make it valid when the NT respone matches.
727                  */
728                 lmok |= ntok;
729                 dupe = 0;
730         } else if(memcmp(reply.NTresp, zeros, MSresplen) == 0){
731                 /* LMv2 */
732                 ntv2hash(hash, secret, tr->uid, windom);
733
734                 /*
735                  * LmResponse = Cat(HMAC_MD5(LmHash, Cat(SC, CC)), CC)
736                  */
737                 s = hmac_md5(chal, MSchallen, hash, MShashlen, nil, nil);
738                 hmac_md5((uchar*)reply.LMresp+16, MSchallen, hash, MShashlen, resp, s);
739                 lmok = ntok = tsmemcmp(resp, reply.LMresp, 16) == 0;
740                 dupe = 0;
741         } else {
742                 /* LM+NTLM */
743                 lmhash(hash, secret);
744                 mschalresp(resp, hash, chal);
745                 lmok = tsmemcmp(resp, reply.LMresp, MSresplen) == 0;
746
747                 nthash(hash, secret);
748                 mschalresp(resp, hash, chal);
749                 ntok = tsmemcmp(resp, reply.NTresp, MSresplen) == 0;
750                 dupe = tsmemcmp(reply.LMresp, reply.NTresp, MSresplen) == 0;
751         }
752
753         /*
754          * It is valid to send the same response in both the LM and NTLM 
755          * fields provided one of them is correct, if neither matches,
756          * or the two fields are different and either fails to match, 
757          * the whole sha-bang fails.
758          *
759          * This is an improvement in security as it allows clients who
760          * wish to do NTLM auth (which is insecure) not to send
761          * LM tokens (which is very insecure).
762          *
763          * Windows servers supports clients doing this also though
764          * windows clients don't seem to use the feature.
765          */
766         if((!ntok && !lmok) || ((!ntok || !lmok) && !dupe)){
767                 replyerror("ntlm-fail bad response %s@%s/%s(%s)", tr->uid, windom, tr->hostid, raddr);
768                 logfail(tr->uid);
769                 goto Retry;
770         }
771
772         succeed(tr->uid);
773
774         /*
775          *  reply with ticket & authenticator
776          */
777         tickauthreply(tr, &hkey);
778
779         syslog(0, AUTHLOG, "ntlm-ok %s@%s/%s(%s)", tr->uid, windom, tr->hostid, raddr);
780
781         exits(0);
782 }
783
784 void
785 mschap(Ticketreq *tr, int nchal)
786 {
787         char *secret;
788         char sbuf[SECRETLEN];
789         uchar chal[16];
790         uchar hash[MShashlen];
791         uchar resp[MSresplen];
792         OMSchapreply reply;
793         int dupe, lmok, ntok;
794         uchar phash[SHA1dlen], chash[SHA1dlen], ahash[SHA1dlen];
795         DigestState *s;
796         int tries;
797
798         /*
799          *  Create a challenge and send it.
800          */
801         genrandom(chal, sizeof(chal));
802         if(write(1, chal, nchal) != nchal)
803                 exits(0);
804
805         tries = 5;
806 Retry:
807         if(--tries < 0)
808                 exits(0);
809
810         /*
811          *  get chap reply
812          */
813         if(readn(0, &reply, OMSCHAPREPLYLEN) < 0)
814                 exits(0);
815
816         safecpy(tr->uid, reply.uid, sizeof(tr->uid));
817         if(tr->uid[0] == 0)
818                 exits(0);
819
820         /*
821          * lookup
822          */
823         secret = findsecret(KEYDB, tr->uid, sbuf);
824         if(!getkey(tr->hostid, &hkey) || secret == nil){
825                 replyerror("mschap-fail bad response %s/%s(%s)", tr->uid, tr->hostid, raddr);
826                 goto Retry;
827         }
828
829         if(nchal == MSchallenv2){
830                 /* MSCHAPv2 */
831                 s = sha1((uchar*)reply.LMresp, nchal, nil, nil);
832                 s = sha1(chal, nchal, nil, s);
833                 sha1((uchar*)tr->uid, strlen(tr->uid), chash, s);
834
835                 nthash(hash, secret);
836                 mschalresp(resp, hash, chash);
837                 ntok = lmok = tsmemcmp(resp, reply.NTresp, MSresplen) == 0;
838                 dupe = 0;
839         } else {
840                 /* MSCHAP (LM+NTLM) */
841                 lmhash(hash, secret);
842                 mschalresp(resp, hash, chal);
843                 lmok = tsmemcmp(resp, reply.LMresp, MSresplen) == 0;
844
845                 nthash(hash, secret);
846                 mschalresp(resp, hash, chal);
847                 ntok = tsmemcmp(resp, reply.NTresp, MSresplen) == 0;
848                 dupe = tsmemcmp(reply.LMresp, reply.NTresp, MSresplen) == 0;
849         }
850
851         /*
852          * It is valid to send the same response in both the LM and NTLM 
853          * fields provided one of them is correct, if neither matches,
854          * or the two fields are different and either fails to match, 
855          * the whole sha-bang fails.
856          *
857          * This is an improvement in security as it allows clients who
858          * wish to do NTLM auth (which is insecure) not to send
859          * LM tokens (which is very insecure).
860          *
861          * Windows servers supports clients doing this also though
862          * windows clients don't seem to use the feature.
863          */
864         if((!ntok && !lmok) || ((!ntok || !lmok) && !dupe)){
865                 replyerror("mschap-fail bad response %s/%s(%s)", tr->uid, tr->hostid, raddr);
866                 logfail(tr->uid);
867                 goto Retry;
868         }
869
870         succeed(tr->uid);
871
872         nthash(hash, secret);
873         md4(hash, 16, hash, nil);
874
875         /*
876          *  reply with ticket & authenticator
877          */
878         if(nchal == MSchallenv2){
879                 s = sha1(hash, 16, nil, nil);
880                 sha1((uchar*)reply.NTresp, MSresplen, nil, s);
881                 sha1((uchar*)"This is the MPPE Master Key", 27, phash, s);
882
883                 s = sha1(hash, 16, nil, nil);
884                 sha1((uchar*)reply.NTresp, MSresplen, nil, s);
885                 sha1((uchar*)"Magic server to client signing constant", 39, ahash, s);
886
887                 s = sha1(ahash, 20, nil, nil);
888                 sha1(chash, 8, nil, s);
889                 sha1((uchar*)"Pad to make it do more than one iteration", 41, ahash, s);
890
891                 tickauthreply2(tr, &hkey, phash, 16, ahash, 20);
892         } else {
893                 s = sha1(hash, 16, nil, nil);
894                 sha1(hash, 16, nil, s);
895                 sha1(chal, 8, phash, s);
896
897                 tickauthreply2(tr, &hkey, phash, 16, nil, 0);
898         }
899
900         syslog(0, AUTHLOG, "mschap-ok %s/%s(%s)", tr->uid, tr->hostid, raddr);
901
902         exits(0);
903 }
904
905 void
906 nthash(uchar hash[MShashlen], char *passwd)
907 {
908         DigestState *ds;
909         uchar b[2];
910         Rune r;
911
912         ds = md4(nil, 0, nil, nil);
913         while(*passwd){
914                 passwd += chartorune(&r, passwd);
915                 b[0] = r & 0xff;
916                 b[1] = r >> 8;
917                 md4(b, 2, nil, ds);
918         }
919         md4(nil, 0, hash, ds);
920 }
921
922 void
923 ntv2hash(uchar hash[MShashlen], char *passwd, char *user, char *dom)
924 {
925         uchar v1hash[MShashlen];
926         DigestState *ds;
927         uchar b[2];
928         Rune r;
929
930         nthash(v1hash, passwd);
931
932         /*
933          * Some documentation insists that the username must be forced to
934          * uppercase, but the domain name should not be. Other shows both
935          * being forced to uppercase. I am pretty sure this is irrevevant as the
936          * domain name passed from the remote server always seems to be in
937          * uppercase already.
938          */
939         ds = hmac_md5(nil, 0, v1hash, sizeof(v1hash), nil, nil);
940         while(*user){
941                 user += chartorune(&r, user);
942                 r = toupperrune(r);
943                 b[0] = r & 0xff;
944                 b[1] = r >> 8;
945                 hmac_md5(b, 2, v1hash, sizeof(v1hash), nil, ds);
946         }
947         while(*dom){
948                 dom += chartorune(&r, dom);
949                 b[0] = r & 0xff;
950                 b[1] = r >> 8;
951                 hmac_md5(b, 2, v1hash, sizeof(v1hash), nil, ds);
952         }
953         hmac_md5(nil, 0, v1hash, sizeof(v1hash), hash, ds);
954 }
955
956 void
957 lmhash(uchar hash[MShashlen], char *passwd)
958 {
959         uchar buf[14];
960         char *stdtext = "KGS!@#$%";
961         int i;
962
963         memset(buf, 0, sizeof(buf));
964         strncpy((char*)buf, passwd, sizeof(buf));
965         for(i=0; i<sizeof(buf); i++)
966                 if(buf[i] >= 'a' && buf[i] <= 'z')
967                         buf[i] += 'A' - 'a';
968
969         memcpy(hash, stdtext, 8);
970         memcpy(hash+8, stdtext, 8);
971
972         desencrypt(hash, buf);
973         desencrypt(hash+8, buf+7);
974 }
975
976 void
977 mschalresp(uchar resp[MSresplen], uchar hash[MShashlen], uchar chal[MSchallen])
978 {
979         int i;
980         uchar buf[21];
981
982         memset(buf, 0, sizeof(buf));
983         memcpy(buf, hash, MShashlen);
984
985         for(i=0; i<3; i++) {
986                 memmove(resp+i*MSchallen, chal, MSchallen);
987                 desencrypt(resp+i*MSchallen, buf+i*7);
988         }
989 }
990
991 void
992 desencrypt(uchar data[8], uchar key[7])
993 {
994         ulong ekey[32];
995
996         key_setup(key, ekey);
997         block_cipher(ekey, data, 0);
998 }
999
1000 /*
1001  *  return true of the speaker may speak for the user
1002  *
1003  *  a speaker may always speak for himself/herself
1004  */
1005 int
1006 speaksfor(char *speaker, char *user)
1007 {
1008         Ndbtuple *tp, *ntp;
1009         Ndbs s;
1010         int ok;
1011         char notuser[Maxpath];
1012
1013         if(strcmp(speaker, user) == 0)
1014                 return 1;
1015
1016         if(db == nil)
1017                 return 0;
1018
1019         tp = ndbsearch(db, &s, "hostid", speaker);
1020         if(tp == nil)
1021                 return 0;
1022
1023         ok = 0;
1024         snprint(notuser, sizeof notuser, "!%s", user);
1025         for(ntp = tp; ntp != nil; ntp = ntp->entry)
1026                 if(strcmp(ntp->attr, "uid") == 0){
1027                         if(strcmp(ntp->val, notuser) == 0){
1028                                 ok = 0;
1029                                 break;
1030                         }
1031                         if(*ntp->val == '*' || strcmp(ntp->val, user) == 0)
1032                                 ok = 1;
1033                 }
1034         ndbfree(tp);
1035         return ok;
1036 }
1037
1038 /*
1039  *  return an error reply
1040  */
1041 void
1042 replyerror(char *fmt, ...)
1043 {
1044         char buf[AERRLEN+1];
1045         va_list arg;
1046
1047         memset(buf, 0, sizeof(buf));
1048         va_start(arg, fmt);
1049         vseprint(buf + 1, buf + sizeof(buf), fmt, arg);
1050         va_end(arg);
1051         buf[AERRLEN] = 0;
1052         buf[0] = AuthErr;
1053         write(1, buf, AERRLEN+1);
1054         syslog(0, AUTHLOG, buf+1);
1055 }
1056
1057 void
1058 getraddr(char *dir)
1059 {
1060         int n;
1061         char *cp;
1062         char file[Maxpath];
1063
1064         raddr[0] = 0;
1065         snprint(file, sizeof(file), "%s/remote", dir);
1066         n = readfile(file, raddr, sizeof(raddr)-1);
1067         if(n < 0)
1068                 return;
1069         raddr[n] = 0;
1070
1071         cp = strchr(raddr, '\n');
1072         if(cp)
1073                 *cp = 0;
1074         cp = strchr(raddr, '!');
1075         if(cp)
1076                 *cp = 0;
1077 }
1078
1079 void
1080 initkeyseed(void)
1081 {
1082         int fd;
1083
1084         genrandom(keyseed, sizeof(keyseed));
1085         if((fd = open("/adm/keyseed", OREAD)) >= 0){
1086                 werrstr("file truncated");
1087                 if(read(fd, keyseed, sizeof(keyseed)) == sizeof(keyseed)){
1088                         close(fd);
1089                         return;
1090                 }
1091                 close(fd);
1092         }
1093         syslog(0, AUTHLOG, "initkeyseed: no keyseed: %r");
1094         if((fd = create("/adm/keyseed", OWRITE, 0600)) < 0){
1095                 syslog(0, AUTHLOG, "initkeyseed: can't create: %r");
1096                 return;
1097         }
1098         write(fd, keyseed, sizeof(keyseed));
1099         close(fd);
1100 }
1101
1102 void
1103 mkkey(char *id, Authkey *a)
1104 {
1105         uchar h[SHA2_256dlen];
1106
1107         genrandom((uchar*)a, sizeof(Authkey));
1108
1109         /*
1110          * the DES key has to be constant for a user in each response,
1111          * so we make one up pseudo randomly from a keyseed and user name.
1112          */
1113         hmac_sha2_256((uchar*)id, strlen(id), keyseed, sizeof(keyseed), h, nil);
1114         memmove(a->des, h, DESKEYLEN);
1115         memset(h, 0, sizeof(h));
1116 }
1117
1118 void
1119 mkticket(Ticketreq *tr, Ticket *t)
1120 {
1121         memset(t, 0, sizeof(Ticket));
1122         memmove(t->chal, tr->chal, CHALLEN);
1123         safecpy(t->cuid, tr->uid, ANAMELEN);
1124         safecpy(t->suid, tr->uid, ANAMELEN);
1125         genrandom(t->key, NONCELEN);
1126         t->form = ticketform;
1127 }
1128
1129 /*
1130  *  reply with ticket and authenticator
1131  */
1132 /*
1133  *  reply with ticket and authenticator
1134  */
1135 void
1136 tickauthreply(Ticketreq *tr, Authkey *key)
1137 {
1138         tickauthreply2(tr, key, nil, 0, nil, 0);
1139 }
1140
1141 /*
1142  *  reply with ticket and authenticator with
1143  *  secret s[ns] and authenticator data a[na].
1144  */
1145 void
1146 tickauthreply2(Ticketreq *tr, Authkey *key, uchar *ps, int ns, uchar *pa, int na)
1147 {
1148         Ticket t;
1149         Authenticator a;
1150         char buf[MAXTICKETLEN+MAXAUTHENTLEN+1];
1151         int n;
1152
1153         mkticket(tr, &t);
1154         if(t.form != 0 && ns > 0){
1155                 assert(ns <= NONCELEN);
1156                 memmove(t.key, ps, ns);
1157         }
1158         t.num = AuthTs;
1159         n = 0;
1160         buf[n++] = AuthOK;
1161         n += convT2M(&t, buf+n, sizeof(buf)-n, key);
1162         memset(&a, 0, sizeof(a));
1163         memmove(a.chal, t.chal, CHALLEN);
1164         genrandom(a.rand, NONCELEN);
1165         if(t.form != 0 && na > 0){
1166                 assert(na <= NONCELEN);
1167                 memmove(a.rand, pa, na);
1168         }
1169         a.num = AuthAc;
1170         n += convA2M(&a, buf+n, sizeof(buf)-n, &t);
1171         if(write(1, buf, n) != n)
1172                 exits(0);
1173 }
1174
1175
1176 void
1177 safecpy(char *to, char *from, int len)
1178 {
1179         strncpy(to, from, len);
1180         to[len-1] = 0;
1181 }
1182
1183 void
1184 catch(void*, char *msg)
1185 {
1186         if(strstr(msg, "alarm") != nil)
1187                 noted(NCONT);
1188         noted(NDFLT);
1189 }