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