521ba7139e705ae3edb97ad5087a54a3d1f040af
[debian/amanda] / common-src / krb4-security.c
1 /*
2  * Amanda, The Advanced Maryland Automatic Network Disk Archiver
3  * Copyright (c) 1993 University of Maryland
4  * All Rights Reserved.
5  *
6  * Permission to use, copy, modify, distribute, and sell this software and its
7  * documentation for any purpose is hereby granted without fee, provided that
8  * the above copyright notice appear in all copies and that both that
9  * copyright notice and this permission notice appear in supporting
10  * documentation, and that the name of U.M. not be used in advertising or
11  * publicity pertaining to distribution of the software without specific,
12  * written prior permission.  U.M. makes no representations about the
13  * suitability of this software for any purpose.  It is provided "as is"
14  * without express or implied warranty.
15  *
16  * U.M. DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL
17  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL U.M.
18  * BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
19  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
20  * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
21  * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
22  *
23  * Author: James da Silva, Systems Design and Analysis Group
24  *                         Computer Science Department
25  *                         University of Maryland at College Park
26  */
27 /*
28  * krb4-security.c - helper functions for kerberos v4 security.
29  */
30 #include "amanda.h"
31 #include "krb4-security.h"
32 #include "protocol.h"
33
34 #define HOSTNAME_INSTANCE inst
35
36 static char *ticketfilename = NULL;
37
38 int krb4_auth = 0;
39 int kencrypt = 0;
40 des_cblock session_key;
41 uint32_t auth_cksum;            /* was 'long' on 32-bit platforms */
42
43 void krb4_killtickets(void)
44 {
45     if(ticketfilename != NULL)
46         unlink(ticketfilename);
47     amfree(ticketfilename);
48 }
49
50 void kerberos_service_init()
51 {
52     int rc;
53     char hostname[MAX_HOSTNAME_LENGTH+1], inst[256], realm[256];
54 #if defined(HAVE_PUTENV)
55     char *tkt_env = NULL;
56 #endif
57     char uid_str[NUM_STR_SIZE];
58     char pid_str[NUM_STR_SIZE];
59
60     gethostname(hostname, sizeof(hostname)-1);
61     hostname[sizeof(hostname)-1] = '\0';
62
63     if(ticketfilename == NULL)
64         atexit(krb4_killtickets);
65
66     host2krbname(hostname, inst, realm);
67
68     /*
69      * [XXX] It could be argued that if KRBTKFILE is set outside of amanda,
70      * that it's value should be used instead of us setting one up.
71      * This file also needs to be removed so that no extra tickets are
72      * hanging around.
73      */
74     ap_snprintf(uid_str, sizeof(uid_str), "%ld", (long)getuid());
75     ap_snprintf(pid_str, sizeof(pid_str), "%ld", (long)getpid());
76     ticketfilename = newvstralloc(ticketfilename,
77                                   "/tmp/tkt",
78                                   uid_str, "-", pid_str,
79                                   ".amanda",
80                                   NULL);
81     krb_set_tkt_string(ticketfilename);
82 #if defined(HAVE_PUTENV)
83     tkt_env = stralloc2("KRBTKFILE=", ticketfilename);
84     putenv(tkt_env);
85     amfree(tkt_env);
86 #else
87     setenv("KRBTKFILE",ticketfilename,1);
88 #endif
89
90     rc = krb_get_svc_in_tkt(SERVER_HOST_PRINCIPLE, SERVER_HOST_INSTANCE,
91                             realm, "krbtgt", realm, TICKET_LIFETIME,
92                             SERVER_HOST_KEY_FILE);
93     if(rc) error("could not get krbtgt for %s.%s@%s from %s: %s",
94                  SERVER_HOST_PRINCIPLE, SERVER_HOST_INSTANCE, realm,
95                  SERVER_HOST_KEY_FILE, krb_err_txt[rc]);
96
97     krb_set_lifetime(TICKET_LIFETIME);
98 }
99
100
101 uint32_t kerberos_cksum(str)
102 char *str;
103 {
104     des_cblock seed;
105
106     memset(seed, 0, sizeof(seed));
107     return quad_cksum(str, NULL, strlen(str), 1, seed);
108 }
109
110 struct hostent *host2krbname(alias, inst, realm)
111 char *alias, *inst, *realm;
112 {
113     struct hostent *hp;
114     char *s, *d, *krb_realmofhost();
115     char saved_hostname[1024];
116
117     if((hp = gethostbyname(alias)) == 0) return 0;
118
119     /* get inst name: like krb_get_phost, but avoid multiple gethostbyname */
120
121     for(s = hp->h_name, d = inst; *s && *s != '.'; s++, d++)
122         *d = isupper(*s)? tolower(*s) : *s;
123     *d = '\0';
124
125     /*
126      * It isn't safe to pass hp->h_name to krb_realmofhost, since
127      * it might use gethostbyname internally.
128      */
129     bzero(saved_hostname, sizeof(saved_hostname));
130     strncpy(saved_hostname, hp->h_name, sizeof(saved_hostname)-1);
131
132     /* get realm name: krb_realmofhost always returns *something* */
133     strcpy(realm, krb_realmofhost(saved_hostname));
134
135     return hp;
136 }
137
138 void encrypt_data(data, length, key)
139 void *data;
140 int length;
141 des_cblock key;
142 {
143     des_key_schedule sched;
144
145     des_key_sched(key, sched);
146     des_pcbc_encrypt(data, data, length, sched, key, DES_ENCRYPT);
147 }
148
149
150 void decrypt_data(data, length, key)
151 void *data;
152 int length;
153 des_cblock key;
154 {
155     des_key_schedule sched;
156
157     des_key_sched(key, sched);
158     des_pcbc_encrypt(data, data, length, sched, key, DES_DECRYPT);
159 }
160
161
162 /*
163  * struct timeval is a host structure, and may not be used in
164  * protocols, because members are defined as 'long', rather than
165  * uint32_t.
166  */
167 typedef struct net_tv {
168   int32_t tv_sec;
169   int32_t tv_usec;
170 } net_tv;
171
172 int kerberos_handshake(fd, key)
173 int fd;
174 des_cblock key;
175 {
176     int rc;
177     struct timeval local;
178     net_tv localenc, remote, rcvlocal;
179     struct timezone tz;
180     char *strerror();
181     char *d;
182     int l, n, s;
183
184     /*
185      * There are two mutual authentication transactions going at once:
186      * one in which we prove the to peer that we are the legitimate
187      * party, and one in which the peer proves to us that that they
188      * are legitimate.
189      *
190      * In addition to protecting against spoofing, this exchange
191      * ensures that the two peers have the same keys, protecting
192      * against having data encrypted with one key and decrypted with
193      * another on the backup tape.
194      */
195
196     gettimeofday(&local, &tz);
197
198     /* 
199      * Convert time to  network order and sizes, encrypt,  and send to
200      * peer as the first step in  the peer proving to us that they are
201      * legitimate.
202      */
203     localenc.tv_sec = (int32_t) local.tv_sec;
204     localenc.tv_usec = (int32_t) local.tv_usec;
205     localenc.tv_sec = htonl(localenc.tv_sec);
206     localenc.tv_usec = htonl(localenc.tv_usec);
207     assert(sizeof(localenc) == 8);
208     encrypt_data(&localenc, sizeof localenc, key);
209
210     d = (char *)&localenc;
211     for(l = 0, n = sizeof(localenc); l < n; l += s) {
212         if((s = write(fd, d+l, n-l)) < 0) {
213             error("kerberos_handshake write error: [%s]", strerror(errno));
214         }
215     }
216
217     /*
218      * Read block from peer and decrypt.  This is the first step in us
219      * proving to the peer that we are legitimate.
220      */
221     d = (char *)&remote;
222     assert(sizeof(remote) == 8);
223     for(l = 0, n = sizeof(remote); l < n; l += s) {
224         if((s = read(fd, d+l, n-l)) < 0) {
225             error("kerberos_handshake read error: [%s]", strerror(errno));
226         }
227     }
228     if(l != n) {
229         error("kerberos_handshake read error: [short read]");
230     }
231
232     decrypt_data(&remote, sizeof remote, key);
233
234     /* XXX do timestamp checking here */
235
236     /*
237      * Add 1.000001 seconds to the peer's timestamp, leaving it in
238      * network order, re-encrypt and send back.
239      */
240     remote.tv_sec = ntohl(remote.tv_sec);
241     remote.tv_usec = ntohl(remote.tv_usec);
242     remote.tv_sec += 1;
243     remote.tv_usec += 1;
244     remote.tv_sec = htonl(remote.tv_sec);
245     remote.tv_usec = htonl(remote.tv_usec);
246
247     encrypt_data(&remote, sizeof remote, key);
248
249     d = (char *)&remote;
250     for(l = 0, n = sizeof(remote); l < n; l += s) {
251         if((s = write(fd, d+l, n-l)) < 0) {
252             error("kerberos_handshake write2 error: [%s]", strerror(errno));
253         }
254     }
255
256     /*
257      * Read the peers reply, decrypt, convert to host order, and
258      * verify that the peer was able to add 1.000001 seconds, thus
259      * showing that it knows the DES key.
260      */
261     d = (char *)&rcvlocal;
262     for(l = 0, n = sizeof(rcvlocal); l < n; l += s) {
263         if((s = read(fd, d+l, n-l)) < 0) {
264             error("kerberos_handshake read2 error: [%s]", strerror(errno));
265         }
266     }
267     if(l != n) {
268         error("kerberos_handshake read2 error: [short read]");
269     }
270
271     decrypt_data(&rcvlocal, sizeof rcvlocal, key);
272
273     rcvlocal.tv_sec = ntohl(rcvlocal.tv_sec);
274     rcvlocal.tv_usec = ntohl(rcvlocal.tv_usec);
275
276     dbprintf(("handshake: %d %d %d %d\n",
277               local.tv_sec, local.tv_usec,
278               rcvlocal.tv_sec, rcvlocal.tv_usec));
279
280     return (rcvlocal.tv_sec  == (int32_t) (local.tv_sec + 1)) &&
281            (rcvlocal.tv_usec == (int32_t) (local.tv_usec + 1));
282 }
283
284 des_cblock *host2key(hostname)
285 char *hostname;
286 {
287     static des_cblock key;
288     char inst[256], realm[256];
289     CREDENTIALS cred;
290
291     if(host2krbname(hostname, inst, realm))
292         krb_get_cred(CLIENT_HOST_PRINCIPLE, CLIENT_HOST_INSTANCE, realm,&cred);
293
294     memcpy(key, cred.session, sizeof key);
295     return &key;
296 }
297
298 int check_mutual_authenticator(key, pkt, p)
299 des_cblock *key;
300 pkt_t *pkt;
301 proto_t *p;
302 {
303     char *astr = NULL;
304     union {
305         char pad[8];
306         uint32_t i;
307     } mutual;
308     int len;
309     char *s, *fp;
310     int ch;
311
312     if(pkt->security == NULL) {
313         fprintf(stderr," pkt->security is NULL\n");
314         return 0;
315     }
316
317     s = pkt->security;
318     ch = *s++;
319
320     skip_whitespace(s, ch);
321     if(ch == '\0') {
322         fprintf(stderr,"pkt->security is actually %s\n", pkt->security);
323         return 0;
324     }
325     fp = s-1;
326     skip_non_whitespace(s, ch);
327     s[-1] = '\0';
328     if(strcmp(fp, "MUTUAL-AUTH") != 0) {
329         s[-1] = ch;
330         fprintf(stderr,"pkt->security is actually %s\n", pkt->security);
331         return 0;
332     }
333     s[-1] = ch;
334
335     skip_whitespace(s, ch);
336     if(ch == '\0') {
337         fprintf(stderr,"pkt->security is actually %s\n", pkt->security);
338         return 0;
339     }
340     astr = s-1;
341     while(ch && ch != '\n') ch = *s++;
342     s[-1] = '\0';
343
344     /* XXX - goddamn it this is a worm-hole */
345     astr2bin(astr, (unsigned char *)mutual.pad, &len);
346
347     s[-1] = ch;
348
349     decrypt_data(&mutual, len, *key);
350     mutual.i = ntohl(mutual.i);
351     return mutual.i == p->auth_cksum + 1;
352 }
353
354 char *get_krb_security(str, host_inst, realm, cksum)
355 char *str;
356 char *host_inst, *realm;
357 uint32_t *cksum;
358 {
359     KTEXT_ST ticket;
360     int rc;
361     char inst[INST_SZ];
362
363     *cksum = kerberos_cksum(str);
364
365 #if CLIENT_HOST_INSTANCE == HOSTNAME_INSTANCE
366 #undef HOSTNAME_INSTANCE
367 #define HOSTNAME_INSTANCE host_inst
368 #endif
369
370     /*
371      * the instance must be in writable memory of size INST_SZ
372      * krb_mk_req might change it
373      */
374     strncpy(inst, CLIENT_HOST_INSTANCE, sizeof(inst) - 1);
375     inst[sizeof(inst) - 1] = '\0';
376     if((rc = krb_mk_req(&ticket, CLIENT_HOST_PRINCIPLE, inst, realm, *cksum))) {
377         if(rc == NO_TKT_FIL) {
378             /* It's been kdestroyed.  Get a new one and try again */
379             kerberos_service_init();
380             rc = krb_mk_req(&ticket, CLIENT_HOST_PRINCIPLE, 
381                             CLIENT_HOST_INSTANCE, realm, *cksum);
382         }
383         if(rc) return NULL;
384     }
385     return stralloc2("SECURITY TICKET ",
386                      bin2astr((unsigned char *)ticket.dat, ticket.length));
387 }
388
389
390 int krb4_security_ok(addr, str, cksum, errstr)
391 struct sockaddr_in *addr;
392 char *str;
393 uint32_t cksum;
394 char **errstr;
395 {
396     KTEXT_ST ticket;
397     AUTH_DAT auth;
398     char *ticket_str, *user, inst[INST_SZ], hname[256];
399     struct passwd *pwptr;
400     int myuid, rc;
401     char *s;
402     int ch;
403
404     /* extract the ticket string from the message */
405
406     s = str;
407     ch = *s++;
408
409     skip_whitespace(s, ch);
410     if(ch == '\0') {
411         *errstr = newstralloc(*errstr, "[bad krb4 security line]");
412         return 0;
413     }
414 #define sc "TICKET"
415     if(strncmp(s - 1, sc, sizeof(sc)-1) != 0) {
416         *errstr = newstralloc(*errstr, "[bad krb4 security line]");
417         return 0;
418     }
419     s += sizeof(sc)-1;
420     ch = s[-1];
421 #undef sc
422     skip_whitespace(s, ch);
423     ticket_str = s - 1;
424     skip_line(s, ch);
425     s[-1] = '\0';
426
427     /* convert to binary ticket */
428
429     astr2bin(ticket_str, (unsigned char *)ticket.dat, &ticket.length);
430
431     /* consult kerberos server */
432
433 #if CLIENT_HOST_INSTANCE == HOSTNAME_INSTANCE
434     if (gethostname(hname, sizeof(hname)) < 0) {
435         *errstr = newvstralloc(*errstr,
436             "[kerberos error: can't get hostname: ", strerror(errno), "}",
437             NULL);
438         return 0;
439     }
440 #undef HOSTNAME_INSTANCE
441 #define HOSTNAME_INSTANCE krb_get_phost(hname)
442 #endif
443
444     /*
445      * the instance must be in writable memory of size INST_SZ
446      * krb_rd_req might change it.
447      */
448     strncpy(inst, CLIENT_HOST_INSTANCE, sizeof(inst) - 1);
449     inst[sizeof(inst) - 1] = '\0';
450     rc = krb_rd_req(&ticket, CLIENT_HOST_PRINCIPLE, inst,
451                     addr->sin_addr.s_addr, &auth, CLIENT_HOST_KEY_FILE);
452     if(rc) {
453         *errstr = newvstralloc(*errstr,
454                                "[kerberos error: ", krb_err_txt[rc], "]",
455                                NULL);
456         return 0;
457     }
458
459     /* verify checksum */
460
461     dbprintf(("msg checksum %d auth checksum %d\n", 
462               cksum, auth.checksum));
463
464     if(cksum != auth.checksum) {
465         *errstr = newstralloc(*errstr, "[kerberos error: checksum mismatch]");
466         dbprintf(("checksum error: exp %d got %d\n", 
467                   auth.checksum, cksum));
468         return 0;
469     }
470
471     /* save key/cksum for mutual auth and dump encryption */
472
473     memcpy(session_key, auth.session, sizeof(session_key));
474     auth_cksum = auth.checksum;
475
476     /* lookup our local user name */
477
478 #ifdef FORCE_USERID
479     /*
480      * if FORCE_USERID is set, then we want to check the uid that we're
481      * forcing ourselves to.  Since we'll need root access to grab at the
482      * srvtab file, we're actually root, although we'll be changing into
483      * CLIENT_LOGIN once we're done the kerberos authentication.
484      */
485     if((pwptr = getpwnam(CLIENT_LOGIN)) == NULL)
486         error("error [getpwnam(%s) fails]", CLIENT_LOGIN);
487 #else
488     myuid = getuid();
489     if((pwptr = getpwuid(myuid)) == NULL)
490         error("error [getpwuid(%d) fails]", myuid);
491 #endif
492
493     /* check the .klogin file */
494
495     /*
496      * some implementations of kerberos will call one of the getpw()
497      * routines (getpwuid(), I think), which overwrites the value of 
498      * pwptr->pw_name if the user you want to check disagrees with
499      * who has the current uid.  (as in the case when we're still running
500      * as root, and we have FORCE_USERID set).
501      */
502     user = stralloc(pwptr->pw_name);
503
504     if(kuserok(&auth, user)) {
505         *errstr = newvstralloc(*errstr,
506                                "[",
507                                "access as ", user, " not allowed from ",
508                                auth.pname, ".", auth.pinst, "@", auth.prealm,
509                                "]", NULL);
510         dbprintf(("kuserok check failed: %s\n", *errstr));
511         amfree(user);
512         return 0;
513     }
514     amfree(user);
515
516     dbprintf(("krb4 security check passed\n"));
517     return 1;
518 }
519
520 /* ---------------- */
521
522 /* XXX - I'm getting astrs with the high bit set in the debug output!?? */
523
524 #define hex_digit(d)    ("0123456789ABCDEF"[(d)])
525 #define unhex_digit(h)  (((h) - '0') > 9? ((h) - 'A' + 10) : ((h) - '0'))
526
527 char *bin2astr(buf, len)
528 unsigned char *buf;
529 int len;
530 {
531     char *str, *q;
532     unsigned char *p;
533     int slen, i, needs_quote;
534
535     /* first pass, calculate string len */
536
537     slen = needs_quote = 0; p = buf;
538     if(*p == '"') needs_quote = 1;      /* special case */
539     for(i=0;i<len;i++) {
540         if(!isgraph(*p)) needs_quote = 1;
541         if(isprint(*p) && *p != '$' && *p != '"')
542             slen += 1;
543         else
544             slen += 3;
545         p++;
546     }
547     if(needs_quote) slen += 2;
548
549     /* 2nd pass, allocate string and fill it in */
550
551     str = (char *)alloc(slen+1);
552     p = buf;
553     q = str;
554     if(needs_quote) *q++ = '"';
555     for(i=0;i<len;i++) {
556         if(isprint(*p) && *p != '$' && *p != '"')
557             *q++ = *p++;
558         else {
559             *q++ = '$';
560             *q++ = hex_digit((*p >> 4) & 0xF);
561             *q++ = hex_digit(*p & 0xF);
562             p++;
563         }
564     }
565     if(needs_quote) *q++ = '"';
566     *q = '\0';
567     if(q-str != slen)
568         printf("bin2str: hmmm.... calculated %d got %d\n",
569                slen, q-str);
570     return str;
571 }
572
573 void astr2bin(astr, buf, lenp)
574 char *astr;
575 unsigned char *buf;
576 int  *lenp;
577 {
578     char *p;
579     unsigned char *q;
580
581     p = astr; q = buf;
582
583     if(*p != '"') {
584         /* strcpy, but without the null */
585         while(*p) *q++ = *p++;
586         *lenp = q-buf;
587         return;
588     }
589
590     p++;
591     while(*p != '"') {
592         if(*p != '$')
593             *q++ = *p++;
594         else {
595             *q++ = (unhex_digit(p[1]) << 4) + unhex_digit(p[2]);
596              p  += 3;
597         }
598     }
599     if(p-astr+1 != strlen(astr))
600         printf("astr2bin: hmmm... short inp exp %d got %d\n",
601                strlen(astr), p-astr+1);
602     *lenp = q-buf;
603 }
604
605 /* -------------------------- */
606 /* debug routines */
607
608 void
609 print_hex(str,buf,len)
610 char *str;
611 unsigned char *buf;
612 int len;
613 {
614     int i;
615
616     printf("%s:", str);
617     for(i=0;i<len;i++) {
618         if(i%25 == 0) putchar('\n');
619         printf(" %02X", buf[i]);
620     }
621     putchar('\n');
622 }
623
624 void
625 print_ticket(str, tktp)
626 char *str;
627 KTEXT tktp;
628 {
629     int i;
630     printf("%s: length %d chk %lX\n", str, tktp->length, tktp->mbz);
631     print_hex("ticket data", tktp->dat, tktp->length);
632     fflush(stdout);
633 }
634
635 void
636 print_auth(authp)
637 AUTH_DAT *authp;
638 {
639     printf("\nAuth Data:\n");
640     printf("  Principal \"%s\" Instance \"%s\" Realm \"%s\"\n",
641            authp->pname, authp->pinst, authp->prealm);
642     printf("  cksum %d life %d keylen %d\n", authp->checksum,
643            authp->life, sizeof(authp->session));
644     print_hex("session key", authp->session, sizeof(authp->session));
645     fflush(stdout);
646 }
647
648 void
649 print_credentials(credp)
650 CREDENTIALS *credp;
651 {
652     printf("\nCredentials:\n");
653     printf("  service \"%s\" instance \"%s\" realm \"%s\" life %d kvno %d\n",
654            credp->service, credp->instance, credp->realm, credp->lifetime,
655            credp->kvno);
656     print_hex("session key", credp->session, sizeof(credp->session));
657     print_hex("ticket", credp->ticket_st.dat, credp->ticket_st.length);
658     fflush(stdout);
659 }