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