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