2 * Amanda, The Advanced Maryland Automatic Network Disk Archiver
3 * Copyright (c) 1993 University of Maryland
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.
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.
23 * Author: James da Silva, Systems Design and Analysis Group
24 * Computer Science Department
25 * University of Maryland at College Park
28 * krb4-security.c - helper functions for kerberos v4 security.
31 #include "krb4-security.h"
34 #define HOSTNAME_INSTANCE inst
36 static char *ticketfilename = NULL;
40 des_cblock session_key;
41 uint32_t auth_cksum; /* was 'long' on 32-bit platforms */
43 void krb4_killtickets(void)
45 if(ticketfilename != NULL)
46 unlink(ticketfilename);
47 amfree(ticketfilename);
50 void kerberos_service_init()
53 char hostname[MAX_HOSTNAME_LENGTH+1], inst[256], realm[256];
54 #if defined(HAVE_PUTENV)
57 char uid_str[NUM_STR_SIZE];
58 char pid_str[NUM_STR_SIZE];
60 gethostname(hostname, sizeof(hostname)-1);
61 hostname[sizeof(hostname)-1] = '\0';
63 if(ticketfilename == NULL)
64 atexit(krb4_killtickets);
66 host2krbname(hostname, inst, realm);
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
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,
78 uid_str, "-", pid_str,
81 krb_set_tkt_string(ticketfilename);
82 #if defined(HAVE_PUTENV)
83 tkt_env = stralloc2("KRBTKFILE=", ticketfilename);
87 setenv("KRBTKFILE",ticketfilename,1);
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]);
97 krb_set_lifetime(TICKET_LIFETIME);
101 uint32_t kerberos_cksum(str)
106 memset(seed, 0, sizeof(seed));
107 return quad_cksum(str, NULL, strlen(str), 1, seed);
110 struct hostent *host2krbname(alias, inst, realm)
111 char *alias, *inst, *realm;
114 char *s, *d, *krb_realmofhost();
115 char saved_hostname[1024];
117 if((hp = gethostbyname(alias)) == 0) return 0;
119 /* get inst name: like krb_get_phost, but avoid multiple gethostbyname */
121 for(s = hp->h_name, d = inst; *s && *s != '.'; s++, d++)
122 *d = isupper(*s)? tolower(*s) : *s;
126 * It isn't safe to pass hp->h_name to krb_realmofhost, since
127 * it might use gethostbyname internally.
129 bzero(saved_hostname, sizeof(saved_hostname));
130 strncpy(saved_hostname, hp->h_name, sizeof(saved_hostname)-1);
132 /* get realm name: krb_realmofhost always returns *something* */
133 strcpy(realm, krb_realmofhost(saved_hostname));
138 void encrypt_data(data, length, key)
143 des_key_schedule sched;
145 des_key_sched(key, sched);
146 des_pcbc_encrypt(data, data, length, sched, key, DES_ENCRYPT);
150 void decrypt_data(data, length, key)
155 des_key_schedule sched;
157 des_key_sched(key, sched);
158 des_pcbc_encrypt(data, data, length, sched, key, DES_DECRYPT);
163 * struct timeval is a host structure, and may not be used in
164 * protocols, because members are defined as 'long', rather than
167 typedef struct net_tv {
172 int kerberos_handshake(fd, key)
177 struct timeval local;
178 net_tv localenc, remote, rcvlocal;
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
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.
196 gettimeofday(&local, &tz);
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
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);
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));
218 * Read block from peer and decrypt. This is the first step in us
219 * proving to the peer that we are legitimate.
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));
229 error("kerberos_handshake read error: [short read]");
232 decrypt_data(&remote, sizeof remote, key);
234 /* XXX do timestamp checking here */
237 * Add 1.000001 seconds to the peer's timestamp, leaving it in
238 * network order, re-encrypt and send back.
240 remote.tv_sec = ntohl(remote.tv_sec);
241 remote.tv_usec = ntohl(remote.tv_usec);
244 remote.tv_sec = htonl(remote.tv_sec);
245 remote.tv_usec = htonl(remote.tv_usec);
247 encrypt_data(&remote, sizeof remote, key);
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));
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.
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));
268 error("kerberos_handshake read2 error: [short read]");
271 decrypt_data(&rcvlocal, sizeof rcvlocal, key);
273 rcvlocal.tv_sec = ntohl(rcvlocal.tv_sec);
274 rcvlocal.tv_usec = ntohl(rcvlocal.tv_usec);
276 dbprintf(("handshake: %d %d %d %d\n",
277 local.tv_sec, local.tv_usec,
278 rcvlocal.tv_sec, rcvlocal.tv_usec));
280 return (rcvlocal.tv_sec == (int32_t) (local.tv_sec + 1)) &&
281 (rcvlocal.tv_usec == (int32_t) (local.tv_usec + 1));
284 des_cblock *host2key(hostname)
287 static des_cblock key;
288 char inst[256], realm[256];
291 if(host2krbname(hostname, inst, realm))
292 krb_get_cred(CLIENT_HOST_PRINCIPLE, CLIENT_HOST_INSTANCE, realm,&cred);
294 memcpy(key, cred.session, sizeof key);
298 int check_mutual_authenticator(key, pkt, p)
312 if(pkt->security == NULL) {
313 fprintf(stderr," pkt->security is NULL\n");
320 skip_whitespace(s, ch);
322 fprintf(stderr,"pkt->security is actually %s\n", pkt->security);
326 skip_non_whitespace(s, ch);
328 if(strcmp(fp, "MUTUAL-AUTH") != 0) {
330 fprintf(stderr,"pkt->security is actually %s\n", pkt->security);
335 skip_whitespace(s, ch);
337 fprintf(stderr,"pkt->security is actually %s\n", pkt->security);
341 while(ch && ch != '\n') ch = *s++;
344 /* XXX - goddamn it this is a worm-hole */
345 astr2bin(astr, (unsigned char *)mutual.pad, &len);
349 decrypt_data(&mutual, len, *key);
350 mutual.i = ntohl(mutual.i);
351 return mutual.i == p->auth_cksum + 1;
354 char *get_krb_security(str, host_inst, realm, cksum)
356 char *host_inst, *realm;
363 *cksum = kerberos_cksum(str);
365 #if CLIENT_HOST_INSTANCE == HOSTNAME_INSTANCE
366 #undef HOSTNAME_INSTANCE
367 #define HOSTNAME_INSTANCE host_inst
371 * the instance must be in writable memory of size INST_SZ
372 * krb_mk_req might change it
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);
385 return stralloc2("SECURITY TICKET ",
386 bin2astr((unsigned char *)ticket.dat, ticket.length));
390 int krb4_security_ok(addr, str, cksum, errstr)
391 struct sockaddr_in *addr;
398 char *ticket_str, *user, inst[INST_SZ], hname[256];
399 struct passwd *pwptr;
404 /* extract the ticket string from the message */
409 skip_whitespace(s, ch);
411 *errstr = newstralloc(*errstr, "[bad krb4 security line]");
415 if(strncmp(s - 1, sc, sizeof(sc)-1) != 0) {
416 *errstr = newstralloc(*errstr, "[bad krb4 security line]");
422 skip_whitespace(s, ch);
427 /* convert to binary ticket */
429 astr2bin(ticket_str, (unsigned char *)ticket.dat, &ticket.length);
431 /* consult kerberos server */
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), "}",
440 #undef HOSTNAME_INSTANCE
441 #define HOSTNAME_INSTANCE krb_get_phost(hname)
445 * the instance must be in writable memory of size INST_SZ
446 * krb_rd_req might change it.
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);
453 *errstr = newvstralloc(*errstr,
454 "[kerberos error: ", krb_err_txt[rc], "]",
459 /* verify checksum */
461 dbprintf(("msg checksum %d auth checksum %d\n",
462 cksum, auth.checksum));
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));
471 /* save key/cksum for mutual auth and dump encryption */
473 memcpy(session_key, auth.session, sizeof(session_key));
474 auth_cksum = auth.checksum;
476 /* lookup our local user name */
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.
485 if((pwptr = getpwnam(CLIENT_LOGIN)) == NULL)
486 error("error [getpwnam(%s) fails]", CLIENT_LOGIN);
489 if((pwptr = getpwuid(myuid)) == NULL)
490 error("error [getpwuid(%d) fails]", myuid);
493 /* check the .klogin file */
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).
502 user = stralloc(pwptr->pw_name);
504 if(kuserok(&auth, user)) {
505 *errstr = newvstralloc(*errstr,
507 "access as ", user, " not allowed from ",
508 auth.pname, ".", auth.pinst, "@", auth.prealm,
510 dbprintf(("kuserok check failed: %s\n", *errstr));
516 dbprintf(("krb4 security check passed\n"));
520 /* ---------------- */
522 /* XXX - I'm getting astrs with the high bit set in the debug output!?? */
524 #define hex_digit(d) ("0123456789ABCDEF"[(d)])
525 #define unhex_digit(h) (((h) - '0') > 9? ((h) - 'A' + 10) : ((h) - '0'))
527 char *bin2astr(buf, len)
533 int slen, i, needs_quote;
535 /* first pass, calculate string len */
537 slen = needs_quote = 0; p = buf;
538 if(*p == '"') needs_quote = 1; /* special case */
540 if(!isgraph(*p)) needs_quote = 1;
541 if(isprint(*p) && *p != '$' && *p != '"')
547 if(needs_quote) slen += 2;
549 /* 2nd pass, allocate string and fill it in */
551 str = (char *)alloc(slen+1);
554 if(needs_quote) *q++ = '"';
556 if(isprint(*p) && *p != '$' && *p != '"')
560 *q++ = hex_digit((*p >> 4) & 0xF);
561 *q++ = hex_digit(*p & 0xF);
565 if(needs_quote) *q++ = '"';
568 printf("bin2str: hmmm.... calculated %d got %d\n",
573 void astr2bin(astr, buf, lenp)
584 /* strcpy, but without the null */
585 while(*p) *q++ = *p++;
595 *q++ = (unhex_digit(p[1]) << 4) + unhex_digit(p[2]);
599 if(p-astr+1 != strlen(astr))
600 printf("astr2bin: hmmm... short inp exp %d got %d\n",
601 strlen(astr), p-astr+1);
605 /* -------------------------- */
609 print_hex(str,buf,len)
618 if(i%25 == 0) putchar('\n');
619 printf(" %02X", buf[i]);
625 print_ticket(str, tktp)
630 printf("%s: length %d chk %lX\n", str, tktp->length, tktp->mbz);
631 print_hex("ticket data", tktp->dat, tktp->length);
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));
649 print_credentials(credp)
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,
656 print_hex("session key", credp->session, sizeof(credp->session));
657 print_hex("ticket", credp->ticket_st.dat, credp->ticket_st.length);