copyright notice updates from 1.1 branch
[krb5.git] / src / slave / kprop.c
1 /*
2  * slave/kprop.c
3  *
4  * Copyright 1990,1991 by the Massachusetts Institute of Technology.
5  * All Rights Reserved.
6  *
7  * Export of this software from the United States of America may
8  *   require a specific license from the United States Government.
9  *   It is the responsibility of any person or organization contemplating
10  *   export to obtain such a license before exporting.
11  * 
12  * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
13  * distribute this software and its documentation for any purpose and
14  * without fee is hereby granted, provided that the above copyright
15  * notice appear in all copies and that both that copyright notice and
16  * this permission notice appear in supporting documentation, and that
17  * the name of M.I.T. not be used in advertising or publicity pertaining
18  * to distribution of the software without specific, written prior
19  * permission.  Furthermore if you modify this software you must label
20  * your software as modified software and not distribute it in such a
21  * fashion that it might be confused with the original M.I.T. software.
22  * M.I.T. makes no representations about the suitability of
23  * this software for any purpose.  It is provided "as is" without express
24  * or implied warranty.
25  * 
26  *
27  */
28
29
30 #include <errno.h>
31 #include <stdio.h>
32 #include <ctype.h>
33 #include <sys/file.h>
34 #include <signal.h>
35 #include <string.h>
36 #include <sys/types.h>
37 #include <sys/time.h>
38 #include <sys/stat.h>
39 #include <sys/socket.h>
40 #include <netinet/in.h>
41 #include <sys/param.h>
42 #include <netdb.h>
43 #include <fcntl.h>
44
45 #include "k5-int.h"
46 #include "com_err.h"
47 #include "kprop.h"
48
49 static char *kprop_version = KPROP_PROT_VERSION;
50
51 char    *progname = 0;
52 int     debug = 0;
53 char    *srvtab = 0;
54 char    *slave_host;
55 char    *realm = 0;
56 char    *file = KPROP_DEFAULT_FILE;
57 short   port = 0;
58
59 krb5_principal  my_principal;           /* The Kerberos principal we'll be */
60                                 /* running under, initialized in */
61                                 /* get_tickets() */
62 krb5_ccache     ccache;         /* Credentials cache which we'll be using */
63 /* krb5_creds   my_creds;       /* My credentials */
64 krb5_creds      creds;
65 krb5_address    sender_addr;
66 krb5_address    receiver_addr;
67
68 void    PRS
69         PROTOTYPE((int, char **));
70 void    get_tickets
71         PROTOTYPE((krb5_context));
72 static void usage 
73         PROTOTYPE((void));
74 krb5_error_code open_connection 
75         PROTOTYPE((char *, int *, char *));
76 void    kerberos_authenticate 
77         PROTOTYPE((krb5_context, krb5_auth_context *, 
78                    int, krb5_principal, krb5_creds **));
79 int     open_database 
80         PROTOTYPE((krb5_context, char *, int *));
81 void    close_database 
82         PROTOTYPE((krb5_context, int));
83 void    xmit_database 
84         PROTOTYPE((krb5_context, krb5_auth_context, krb5_creds *, 
85                    int, int, int));
86 void    send_error 
87         PROTOTYPE((krb5_context, krb5_creds *, int, char *, krb5_error_code));
88 void    update_last_prop_file 
89         PROTOTYPE((char *, char *));
90
91 static void usage()
92 {
93         fprintf(stderr, "\nUsage: %s [-r realm] [-f file] [-d] [-P port] [-s srvtab] slave_host\n\n",
94                 progname);
95         exit(1);
96 }
97
98 int
99 main(argc, argv)
100         int     argc;
101         char    **argv;
102 {
103         int     fd, database_fd, database_size;
104         krb5_error_code retval;
105         krb5_context context;
106         krb5_creds *my_creds;
107         krb5_auth_context auth_context;
108         char    Errmsg[256];
109         
110         retval = krb5_init_context(&context);
111         if (retval) {
112                 com_err(argv[0], retval, "while initializing krb5");
113                 exit(1);
114         }
115         PRS(argc, argv);
116         get_tickets(context);
117
118         database_fd = open_database(context, file, &database_size);
119         if (retval = open_connection(slave_host, &fd, Errmsg)) {
120                 com_err(progname, retval, "%s while opening connection to %s",
121                         Errmsg, slave_host);
122                 exit(1);
123         }
124         if (fd < 0) {
125                 fprintf(stderr, "%s: %s while opening connection to %s\n",
126                         progname, Errmsg, slave_host);
127                 exit(1);
128         }
129         kerberos_authenticate(context, &auth_context, fd, my_principal, 
130                               &my_creds);
131         xmit_database(context, auth_context, my_creds, fd, database_fd, 
132                       database_size);
133         update_last_prop_file(slave_host, file);
134         printf("Database propagation to %s: SUCCEEDED\n", slave_host);
135         krb5_free_cred_contents(context, my_creds);
136         close_database(context, database_fd);
137         exit(0);
138 }
139
140 void PRS(argc, argv)
141         int     argc;
142         char    **argv;
143 {
144         register char   *word, ch;
145         
146         progname = *argv++;
147         while (--argc && (word = *argv++)) {
148                 if (*word == '-') {
149                         word++;
150                         while (word && (ch = *word++)) {
151                                 switch(ch){
152                                 case 'r':
153                                         if (*word)
154                                                 realm = word;
155                                         else
156                                                 realm = *argv++;
157                                         if (!realm)
158                                                 usage();
159                                         word = 0;
160                                         break;
161                                 case 'f':
162                                         if (*word)
163                                                 file = word;
164                                         else
165                                                 file = *argv++;
166                                         if (!file)
167                                                 usage();
168                                         word = 0;
169                                         break;
170                                 case 'd':
171                                         debug++;
172                                         break;
173                                 case 'P':
174                                         if (*word)
175                                                 port = htons(atoi(word));
176                                         else
177                                                 port = htons(atoi(*argv++));
178                                         if (!port)
179                                                 usage();
180                                         word = 0;
181                                         break;
182                                 case 's':
183                                         if (*word)
184                                                 srvtab = word;
185                                         else
186                                                 srvtab = *argv++;
187                                         if (!srvtab)
188                                                 usage();
189                                         word = 0;
190                                         break;
191                                 default:
192                                         usage();
193                                 }
194                                 
195                         }
196                 } else {
197                         if (slave_host)
198                                 usage();
199                         else
200                                 slave_host = word;
201                 }
202         }
203         if (!slave_host)
204                 usage();
205 }
206
207 void get_tickets(context)
208     krb5_context context;
209 {
210         char   my_host_name[MAXHOSTNAMELEN];
211         char   buf[BUFSIZ];
212         char   *cp;
213         struct hostent *hp;
214         krb5_error_code retval;
215         static char tkstring[] = "/tmp/kproptktXXXXXX";
216         krb5_keytab keytab = NULL;
217
218         /*
219          * Figure out what tickets we'll be using to send stuff
220          */
221         retval = krb5_sname_to_principal(context, NULL, NULL,
222                                          KRB5_NT_SRV_HST, &my_principal);
223         if (retval) {
224             com_err(progname, errno, "while setting client principal name");
225             exit(1);
226         }
227         if (realm) {
228             (void) krb5_xfree(krb5_princ_realm(context, my_principal)->data);
229             krb5_princ_set_realm_length(context, my_principal, strlen(realm));
230             krb5_princ_set_realm_data(context, my_principal, strdup(realm));
231         }
232 #if 0
233         krb5_princ_type(context, my_principal) = KRB5_NT_PRINCIPAL;
234 #endif
235
236         /*
237          * Initialize cache file which we're going to be using
238          */
239         (void) mktemp(tkstring);
240         sprintf(buf, "FILE:%s", tkstring);
241         if (retval = krb5_cc_resolve(context, buf, &ccache)) {
242                 com_err(progname, retval, "while opening credential cache %s",
243                         buf);
244                 exit(1);
245         }
246         if (retval = krb5_cc_initialize(context, ccache, my_principal)) {
247                 com_err (progname, retval, "when initializing cache %s",
248                          buf);
249                 exit(1);
250         }
251
252         /*
253          * Get the tickets we'll need.
254          *
255          * Construct the principal name for the slave host.
256          */
257         memset((char *)&creds, 0, sizeof(creds));
258         retval = krb5_sname_to_principal(context,
259                                          slave_host, KPROP_SERVICE_NAME,
260                                          KRB5_NT_SRV_HST, &creds.server);
261         if (retval) {
262             com_err(progname, errno, "while setting server principal name");
263             (void) krb5_cc_destroy(context, ccache);
264             exit(1);
265         }
266         if (realm) {
267             (void) krb5_xfree(krb5_princ_realm(context, creds.server)->data);
268             krb5_princ_set_realm_length(context, creds.server, strlen(realm));
269             krb5_princ_set_realm_data(context, creds.server, strdup(realm));
270         }
271
272         /*
273          * Now fill in the client....
274          */
275         if (retval = krb5_copy_principal(context, my_principal, &creds.client)) {
276                 com_err(progname, retval, "While copying client principal");
277                 (void) krb5_cc_destroy(context, ccache);
278                 exit(1);
279         }
280         if (srvtab) {
281                 if (retval = krb5_kt_resolve(context, srvtab, &keytab)) {
282                         com_err(progname, retval, "while resolving keytab");
283                         (void) krb5_cc_destroy(context, ccache);
284                         exit(1);
285                 }
286         }
287
288         retval = krb5_get_in_tkt_with_keytab(context, 0, 0, NULL,
289                                              NULL, keytab, ccache, &creds, 0);
290         if (retval) {
291                 com_err(progname, retval, "while getting initial ticket\n");
292                 (void) krb5_cc_destroy(context, ccache);
293                 exit(1);
294         }
295
296         if (keytab)
297             (void) krb5_kt_close(context, keytab);
298         
299         /*
300          * Now destroy the cache right away --- the credentials we
301          * need will be in my_creds.
302          */
303         if (retval = krb5_cc_destroy(context, ccache)) {
304                 com_err(progname, retval, "while destroying ticket cache");
305                 exit(1);
306         }
307 }
308
309 krb5_error_code
310 open_connection(host, fd, Errmsg)
311         char    *host;
312         int     *fd;
313         char    *Errmsg;
314 {
315         int     s;
316         krb5_error_code retval;
317         
318         struct hostent  *hp;
319         register struct servent *sp;
320         struct sockaddr_in sin;
321         int             socket_length;
322
323         hp = gethostbyname(host);
324         if (hp == NULL) {
325                 (void) sprintf(Errmsg, "%s: unknown host", host);
326                 *fd = -1;
327                 return(0);
328         }
329         sin.sin_family = hp->h_addrtype;
330         memcpy((char *)&sin.sin_addr, hp->h_addr, sizeof(sin.sin_addr));
331         if(!port) {
332                 sp = getservbyname(KPROP_SERVICE, "tcp");
333                 if (sp == 0) {
334                         (void) strcpy(Errmsg, KPROP_SERVICE);
335                         (void) strcat(Errmsg, "/tcp: unknown service");
336                         *fd = -1;
337                         return(0);
338                 }
339                 sin.sin_port = sp->s_port;
340         } else
341                 sin.sin_port = port;
342         s = socket(AF_INET, SOCK_STREAM, 0);
343         
344         if (s < 0) {
345                 (void) sprintf(Errmsg, "in call to socket");
346                 return(errno);
347         }
348         if (connect(s, (struct sockaddr *)&sin, sizeof sin) < 0) {
349                 retval = errno;
350                 close(s);
351                 (void) sprintf(Errmsg, "in call to connect");
352                 return(retval);
353         }
354         *fd = s;
355
356         /*
357          * Set receiver_addr and sender_addr.
358          */
359         receiver_addr.addrtype = ADDRTYPE_INET;
360         receiver_addr.length = sizeof(sin.sin_addr);
361         receiver_addr.contents = (krb5_octet *) malloc(sizeof(sin.sin_addr));
362         memcpy((char *) receiver_addr.contents, (char *) &sin.sin_addr,
363                sizeof(sin.sin_addr));
364
365         socket_length = sizeof(sin);
366         if (getsockname(s, (struct sockaddr *)&sin, &socket_length) < 0) {
367                 retval = errno;
368                 close(s);
369                 (void) sprintf(Errmsg, "in call to getsockname");
370                 return(retval);
371         }
372         sender_addr.addrtype = ADDRTYPE_INET;
373         sender_addr.length = sizeof(sin.sin_addr);
374         sender_addr.contents = (krb5_octet *) malloc(sizeof(sin.sin_addr));
375         memcpy((char *) sender_addr.contents, (char *) &sin.sin_addr,
376                sizeof(sin.sin_addr));
377
378         return(0);
379 }
380
381
382 void kerberos_authenticate(context, auth_context, fd, me, new_creds)
383     krb5_context context;
384     krb5_auth_context *auth_context;
385     int fd;
386     krb5_principal me;
387     krb5_creds ** new_creds;
388 {
389         krb5_error_code retval;
390         krb5_error      *error = NULL;
391         krb5_ap_rep_enc_part    *rep_result;
392
393     if (retval = krb5_auth_con_init(context, auth_context)) 
394         exit(1);
395
396     krb5_auth_con_setflags(context, *auth_context, 
397                            KRB5_AUTH_CONTEXT_DO_SEQUENCE);
398
399     if (retval = krb5_auth_con_setaddrs(context, *auth_context, &sender_addr,
400                                         &receiver_addr)) {
401         com_err(progname, retval, "in krb5_auth_con_setaddrs");
402         exit(1);
403     }
404
405         if (retval = krb5_sendauth(context, auth_context, (void *)&fd, 
406                                    kprop_version, me, creds.server,
407                                    AP_OPTS_MUTUAL_REQUIRED, NULL, &creds, NULL,
408                                    &error, &rep_result, new_creds)) {
409                 com_err(progname, retval, "while authenticating to server");
410                 if (error) {
411                         if (error->error == KRB_ERR_GENERIC) {
412                                 if (error->text.data)
413                                         fprintf(stderr,
414                                                 "Generic remote error: %s\n",
415                                                 error->text.data);
416                         } else if (error->error) {
417                                 com_err(progname,
418                                         error->error + ERROR_TABLE_BASE_krb5,
419                                         "signalled from server");
420                                 if (error->text.data)
421                                         fprintf(stderr,
422                                                 "Error text from server: %s\n",
423                                                 error->text.data);
424                         }
425                         krb5_free_error(context, error);
426                 }
427                 exit(1);
428         }
429         krb5_free_ap_rep_enc_part(context, rep_result);
430 }
431
432 char * dbpathname;
433 /*
434  * Open the Kerberos database dump file.  Takes care of locking it
435  * and making sure that the .ok file is more recent that the database
436  * dump file itself.
437  *
438  * Returns the file descriptor of the database dump file.  Also fills
439  * in the size of the database file.
440  */
441 int
442 open_database(context, data_fn, size)
443     krb5_context context;
444     char *data_fn;
445     int *size;
446 {
447         int             fd;
448         int             err;
449         struct stat     stbuf, stbuf_ok;
450         char            *data_ok_fn;
451         static char ok[] = ".dump_ok";
452
453         dbpathname = strdup(data_fn);
454         if (!dbpathname) {
455             com_err(progname, ENOMEM, "allocating database file name '%s'",
456                     data_fn);
457             exit(1);
458         }
459         if ((fd = open(dbpathname, O_RDONLY)) < 0) {
460                 com_err(progname, errno, "while trying to open %s",
461                         dbpathname);
462                 exit(1);
463         }
464
465         err = krb5_lock_file(context, fd,
466                              KRB5_LOCKMODE_SHARED|KRB5_LOCKMODE_DONTBLOCK);
467         if (err == EAGAIN || err == EWOULDBLOCK || errno == EACCES) {
468             com_err(progname, 0, "database locked");
469             exit(1);
470         } else if (err) {
471             com_err(progname, err, "while trying to lock '%s'", dbpathname);
472             exit(1);
473         }           
474         if (fstat(fd, &stbuf)) {
475                 com_err(progname, errno, "while trying to stat %s",
476                         data_fn);
477                 exit(1);
478         }
479         if ((data_ok_fn = (char *) malloc(strlen(data_fn)+strlen(ok)+1))
480             == NULL) {
481                 com_err(progname, ENOMEM, "while trying to malloc data_ok_fn");
482                 exit(1);
483         }
484         strcat(strcpy(data_ok_fn, data_fn), ok);
485         if (stat(data_ok_fn, &stbuf_ok)) {
486                 com_err(progname, errno, "while trying to stat %s",
487                         data_ok_fn);
488                 free(data_ok_fn);
489                 exit(1);
490         }
491         free(data_ok_fn);
492         if (stbuf.st_mtime > stbuf_ok.st_mtime) {
493                 com_err(progname, 0, "'%s' more recent than '%s'.",
494                         data_fn, data_ok_fn);
495                 exit(1);
496         }
497         *size = stbuf.st_size;
498         return(fd);
499 }
500
501 void
502 close_database(context, fd)
503     krb5_context context;
504     int fd;
505 {
506     int err;
507     if (err = krb5_lock_file(context, fd, KRB5_LOCKMODE_UNLOCK))
508         com_err(progname, err, "while unlocking database '%s'", dbpathname);
509     free(dbpathname);
510     (void)close(fd);
511     return;
512 }
513   
514 /*
515  * Now we send over the database.  We use the following protocol:
516  * Send over a KRB_SAFE message with the size.  Then we send over the
517  * database in blocks of KPROP_BLKSIZE, encrypted using KRB_PRIV.
518  * Then we expect to see a KRB_SAFE message with the size sent back.
519  * 
520  * At any point in the protocol, we may send a KRB_ERROR message; this
521  * will abort the entire operation.
522  */
523 void
524 xmit_database(context, auth_context, my_creds, fd, database_fd, database_size)
525     krb5_context context;
526     krb5_auth_context auth_context;
527     krb5_creds *my_creds;
528     int fd;
529     int database_fd;
530     int database_size;
531 {
532         krb5_int32      send_size, sent_size, n;
533         krb5_data       inbuf, outbuf;
534         char            buf[KPROP_BUFSIZ];
535         krb5_error_code retval;
536         krb5_error      *error;
537         
538         /*
539          * Send over the size
540          */
541         send_size = htonl(database_size);
542         inbuf.data = (char *) &send_size;
543         inbuf.length = sizeof(send_size); /* must be 4, really */
544         /* KPROP_CKSUMTYPE */
545         if (retval = krb5_mk_safe(context, auth_context, &inbuf, 
546                                   &outbuf, NULL)) {
547                 com_err(progname, retval, "while encoding database size");
548                 send_error(context, my_creds, fd, "while encoding database size", retval);
549                 exit(1);
550         }
551         if (retval = krb5_write_message(context, (void *) &fd, &outbuf)) {
552                 krb5_free_data_contents(context, &outbuf);
553                 com_err(progname, retval, "while sending database size");
554                 exit(1);
555         }
556         krb5_free_data_contents(context, &outbuf);
557     /*
558      * Initialize the initial vector.
559      */
560     if (retval = krb5_auth_con_initivector(context, auth_context)) {
561         send_error(context, my_creds, fd, 
562                    "failed while initializing i_vector", retval);
563         com_err(progname, retval, "while allocating i_vector");
564         exit(1);
565     }
566         /*
567          * Send over the file, block by block....
568          */
569         inbuf.data = buf;
570         sent_size = 0;
571         while (n = read(database_fd, buf, sizeof(buf))) {
572                 inbuf.length = n;
573                 if (retval = krb5_mk_priv(context, auth_context, &inbuf,
574                                           &outbuf, NULL)) {
575                         sprintf(buf,
576                                 "while encoding database block starting at %d",
577                                 sent_size);
578                         com_err(progname, retval, buf);
579                         send_error(context, my_creds, fd, buf, retval);
580                         exit(1);
581                 }
582                 if (retval = krb5_write_message(context, (void *)&fd,&outbuf)) {
583                         krb5_free_data_contents(context, &outbuf);
584                         com_err(progname, retval,
585                                 "while sending database block starting at %d",
586                                 sent_size);
587                         exit(1);
588                 }
589                 krb5_free_data_contents(context, &outbuf);
590                 sent_size += n;
591                 if (debug)
592                         printf("%d bytes sent.\n", sent_size);
593         }
594         if (sent_size != database_size) {
595                 com_err(progname, 0, "Premature EOF found for database file!");
596                 send_error(context, my_creds, fd,"Premature EOF found for database file!",
597                            KRB5KRB_ERR_GENERIC);
598                 exit(1);
599         }
600         /*
601          * OK, we've sent the database; now let's wait for a success
602          * indication from the remote end.
603          */
604         if (retval = krb5_read_message(context, (void *) &fd, &inbuf)) {
605                 com_err(progname, retval,
606                         "while reading response from server");
607                 exit(1);
608         }
609         /*
610          * If we got an error response back from the server, display
611          * the error message
612          */
613         if (krb5_is_krb_error(&inbuf)) {
614                 if (retval = krb5_rd_error(context, &inbuf, &error)) {
615                         com_err(progname, retval,
616                                 "while decoding error response from server");
617                         exit(1);
618                 }
619                 if (error->error == KRB_ERR_GENERIC) {
620                         if (error->text.data)
621                                 fprintf(stderr,
622                                         "Generic remote error: %s\n",
623                                         error->text.data);
624                 } else if (error->error) {
625                         com_err(progname, error->error + ERROR_TABLE_BASE_krb5,
626                                 "signalled from server");
627                         if (error->text.data)
628                                 fprintf(stderr,
629                                         "Error text from server: %s\n",
630                                         error->text.data);
631                 }
632                 krb5_free_error(context, error);
633                 exit(1);
634         }
635         if (retval = krb5_rd_safe(context,auth_context,&inbuf,&outbuf,NULL)) {
636                 com_err(progname, retval,
637                         "while decoding final size packet from server");
638                 exit(1);
639         }
640         memcpy((char *)&send_size, outbuf.data, sizeof(send_size));
641         send_size = ntohl(send_size);
642         if (send_size != database_size) {
643                 com_err(progname, 0,
644                         "Kpropd sent database size %d, expecting %d",
645                         send_size, database_size);
646                 exit(1);
647         }
648         free(outbuf.data);
649         free(inbuf.data);
650 }
651
652 void
653 send_error(context, my_creds, fd, err_text, err_code)
654     krb5_context context;
655     krb5_creds *my_creds;
656     int fd;
657     char        *err_text;
658     krb5_error_code     err_code;
659 {
660         krb5_error      error;
661         const char      *text;
662         krb5_data       outbuf;
663
664         memset((char *)&error, 0, sizeof(error));
665         krb5_us_timeofday(context, &error.ctime, &error.cusec);
666         error.server = my_creds->server;
667         error.client = my_principal;
668         error.error = err_code - ERROR_TABLE_BASE_krb5;
669         if (error.error > 127)
670                 error.error = KRB_ERR_GENERIC;
671         if (err_text)
672                 text = err_text;
673         else
674                 text = error_message(err_code);
675         error.text.length = strlen(text) + 1;
676         if (error.text.data = malloc(error.text.length)) {
677                 strcpy(error.text.data, text);
678                 if (!krb5_mk_error(context, &error, &outbuf)) {
679                         (void) krb5_write_message(context, (void *)&fd,&outbuf);
680                         krb5_free_data_contents(context, &outbuf);
681                 }
682                 free(error.text.data);
683         }
684 }
685
686 void update_last_prop_file(hostname, file_name)
687         char *hostname;
688         char *file_name;
689 {
690         /* handle slave locking/failure stuff */
691         char *file_last_prop;
692         int fd;
693         static char last_prop[]=".last_prop";
694
695         if ((file_last_prop = (char *)malloc(strlen(file_name) +
696                                              strlen(hostname) + 1 +
697                                              strlen(last_prop) + 1)) == NULL) {
698                 com_err(progname, ENOMEM,
699                         "while allocating filename for update_last_prop_file");
700                 return;
701         }
702         strcpy(file_last_prop, file_name);
703         strcat(file_last_prop, ".");
704         strcat(file_last_prop, hostname);
705         strcat(file_last_prop, last_prop);
706         if ((fd = THREEPARAMOPEN(file_last_prop, O_WRONLY|O_CREAT|O_TRUNC, 0600)) < 0) {
707                 com_err(progname, errno,
708                         "while creating 'last_prop' file, '%s'",
709                         file_last_prop);
710                 free(file_last_prop);
711                 return;
712         }
713         write(fd, "", 1);
714         free(file_last_prop);
715         close(fd);
716         return;
717 }