Removed all references to DECLARG and OLDDECLARG.
[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
41 #include <krb5/krb5.h>
42 #include <krb5/asn1.h>
43 #include <krb5/osconf.h>
44 #include <krb5/kdb.h>
45 #include <krb5/kdb_dbm.h>
46 #include <krb5/ext-proto.h>
47 #include <krb5/los-proto.h>
48 #include <krb5/libos.h>
49 #include <com_err.h>
50
51 #ifdef NEED_SYS_FCNTL_H
52 #include <sys/fcntl.h>
53 #endif
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         PROTOTYPE((krb5_context, char **));
77 void    get_tickets
78         PROTOTYPE((krb5_context));
79 static void usage 
80         PROTOTYPE((void));
81 krb5_error_code open_connection 
82         PROTOTYPE((char *, int *, char *));
83 void    kerberos_authenticate 
84         PROTOTYPE((krb5_context, int, krb5_principal));
85 int     open_database 
86         PROTOTYPE((krb5_context, char *, int *));
87 void    close_database 
88         PROTOTYPE((krb5_context, int));
89 void    xmit_database 
90         PROTOTYPE((krb5_context, int, int, int));
91 void    send_error 
92         PROTOTYPE((krb5_context, int, char *, krb5_error_code));
93 void    update_last_prop_file 
94         PROTOTYPE((char *, char *));
95
96 static void usage()
97 {
98         fprintf(stderr, "\nUsage: %s [-r realm] [-f file] [-d] slave_host\n\n",
99                 progname);
100         exit(1);
101 }
102
103 void
104 main(argc, argv)
105         int     argc;
106         char    **argv;
107 {
108         int     fd, database_fd, database_size;
109         krb5_error_code retval;
110         krb5_context context;
111         char    Errmsg[256];
112         
113         PRS(context, argv);
114         get_tickets(context);
115
116         database_fd = open_database(context, file, &database_size);
117         if (retval = open_connection(slave_host, &fd, Errmsg)) {
118                 com_err(progname, retval, "%s while opening connection to %s",
119                         Errmsg, slave_host);
120                 exit(1);
121         }
122         if (fd < 0) {
123                 fprintf(stderr, "%s: %s while opening connection to %s\n",
124                         progname, Errmsg, slave_host);
125                 exit(1);
126         }
127         kerberos_authenticate(context, fd, my_principal);
128         if (debug) {
129                 printf("My sequence number: %d\n", my_seq_num);
130                 printf("His sequence number: %d\n", his_seq_num);
131         }
132         xmit_database(context, fd, database_fd, database_size);
133         update_last_prop_file(slave_host, file);
134         printf("Database propagation to %s: SUCCEEDED\n", slave_host);
135         close_database(context, database_fd);
136         exit(0);
137 }
138
139 void PRS(context, argv)
140         krb5_context context;
141         char    **argv;
142 {
143         register char   *word, ch;
144         
145         krb5_init_context(&context);
146         krb5_init_ets(context);
147         progname = *argv++;
148         while (word = *argv++) {
149                 if (*word == '-') {
150                         word++;
151                         while (word && (ch = *word++)) {
152                                 switch(ch){
153                                 case 'r':
154                                         if (*word)
155                                                 realm = word;
156                                         else
157                                                 realm = *argv++;
158                                         if (!realm)
159                                                 usage();
160                                         word = 0;
161                                         break;
162                                 case 'f':
163                                         if (*word)
164                                                 file = word;
165                                         else
166                                                 file = *argv++;
167                                         if (!file)
168                                                 usage();
169                                         word = 0;
170                                         break;
171                                 case 'd':
172                                         debug++;
173                                         break;
174                                 default:
175                                         usage();
176                                 }
177                                 
178                         }
179                 } else {
180                         if (slave_host)
181                                 usage();
182                         else
183                                 slave_host = word;
184                 }
185         }
186         if (!slave_host)
187                 usage();
188 }
189
190 void get_tickets(context)
191     krb5_context context;
192 {
193         char   my_host_name[MAXHOSTNAMELEN];
194         char   buf[BUFSIZ];
195         char   *cp;
196         struct hostent *hp;
197         krb5_address **my_addresses;
198         krb5_error_code retval;
199         static char tkstring[] = "/tmp/kproptktXXXXXX";
200
201         /*
202          * Figure out what tickets we'll be using to send stuff
203          */
204         if (gethostname (my_host_name, sizeof(my_host_name)) != 0) { 
205                 com_err(progname, errno, "while getting my hostname");
206                 exit(1);
207         }
208         /* get canonicalized  service instance name */
209         if (!(hp = gethostbyname(my_host_name))) {
210                 fprintf(stderr, "Couldn't get my cannonicalized host name!\n");
211                 exit(1);
212         }
213         for (cp=hp->h_name; *cp; cp++)
214                 if (isupper(*cp))
215                         *cp = tolower(*cp);
216         if (realm)
217                 sprintf(buf, "host/%s@%s", hp->h_name, realm);
218         else
219                 sprintf(buf, "host/%s", hp->h_name);
220         if (retval = krb5_parse_name(context, buf, &my_principal)) {
221                 com_err (progname, retval, "when parsing name %s",buf);
222                 exit(1);
223         }
224
225         /*
226          * Initialize cache file which we're going to be using
227          */
228         (void) mktemp(tkstring);
229         sprintf(buf, "FILE:%s", tkstring);
230         if (retval = krb5_cc_resolve(context, buf, &ccache)) {
231                 com_err(progname, retval, "while opening crednetials cache %s",
232                         buf);
233                 exit(1);
234         }
235         if (retval = krb5_cc_initialize(context, ccache, my_principal)) {
236                 com_err (progname, retval, "when initializing cache %s",
237                          buf);
238                 exit(1);
239         }
240
241         /*
242          * Get the tickets we'll need.
243          *
244          * Construct the principal name for the slave host.
245          */
246         memset((char *)&my_creds, 0, sizeof(my_creds));
247         if (!(hp = gethostbyname(slave_host))) {
248                 fprintf(stderr,
249                         "Couldn't get cannonicalized name for slave\n");
250                 exit(1);
251         }
252         for (cp=hp->h_name; *cp; cp++)
253                 if (isupper(*cp))
254                         *cp = tolower(*cp);
255         if (!(slave_host = malloc(strlen(hp->h_name) + 1))) {
256                 com_err(progname, ENOMEM,
257                         "while allocate space for canonicalized slave host");
258                 exit(1);
259         }
260         strcpy(slave_host, hp->h_name);
261         if (realm)
262                 sprintf(buf, "%s/%s@%s", KPROP_SERVICE_NAME, slave_host,
263                         realm);
264         else
265                 sprintf(buf, "%s/%s", KPROP_SERVICE_NAME, hp->h_name);
266         if (retval = krb5_parse_name(context, buf, &my_creds.server)) {
267                 com_err(progname, retval,
268                         "while parsing slave principal name");
269                 exit(1);
270         }
271         /*
272          * Now fill in the client....
273          */
274         if (retval = krb5_copy_principal(context, my_principal, &my_creds.client)) {
275                 com_err(progname, retval, "While copying client principal");
276                 exit(1);
277         }
278         /*
279          * Get my addresses
280          */
281         retval = krb5_os_localaddr(&my_addresses);
282         if (retval != 0) {
283                 com_err(progname, retval,
284                         "when getting my address");
285                 exit(1);
286         }
287         retval = krb5_get_in_tkt_with_skey(context, 0, my_addresses,
288                                            0,
289                                            ETYPE_DES_CBC_CRC,
290                                            0, ccache, &my_creds, 0);
291         if (retval) {
292                 com_err(progname, retval, "while getting initial ticket\n");
293                 exit(1);
294         }
295         /*
296          * Now destroy the cache right away --- the credentials we
297          * need will be in my_creds.
298          */
299         if (retval = krb5_cc_destroy(context, ccache)) {
300                 com_err(progname, retval, "while destroying ticket cache");
301                 exit(1);
302         }
303 }
304
305 krb5_error_code
306 open_connection(host, fd, Errmsg)
307         char    *host;
308         int     *fd;
309         char    *Errmsg;
310 {
311         int     s;
312         krb5_error_code retval;
313         
314         struct hostent  *hp;
315         register struct servent *sp;
316         struct sockaddr_in sin;
317         int             socket_length;
318
319         hp = gethostbyname(host);
320         if (hp == NULL) {
321                 (void) sprintf(Errmsg, "%s: unknown host", host);
322                 *fd = -1;
323                 return(0);
324         }
325         sp = getservbyname(KPROP_SERVICE, "tcp");
326         if (sp == 0) {
327                 (void) strcpy(Errmsg, KPROP_SERVICE);
328                 (void) strcat(Errmsg, "/tcp: unknown service");
329                 *fd = -1;
330                 return(0);
331         }
332         sin.sin_family = hp->h_addrtype;
333         memcpy((char *)&sin.sin_addr, hp->h_addr, hp->h_length);
334         sin.sin_port = sp->s_port;
335         s = socket(AF_INET, SOCK_STREAM, 0);
336         
337         if (s < 0) {
338                 (void) sprintf(Errmsg, "in call to socket");
339                 return(errno);
340         }
341         if (connect(s, (struct sockaddr *)&sin, sizeof sin) < 0) {
342                 retval = errno;
343                 close(s);
344                 (void) sprintf(Errmsg, "in call to connect");
345                 return(retval);
346         }
347         *fd = s;
348
349         /*
350          * Set receiver_addr and sender_addr.
351          */
352         receiver_addr.addrtype = ADDRTYPE_INET;
353         receiver_addr.length = sizeof(sin.sin_addr);
354         receiver_addr.contents = (krb5_octet *) malloc(sizeof(sin.sin_addr));
355         memcpy((char *) receiver_addr.contents, (char *) &sin.sin_addr,
356                sizeof(sin.sin_addr));
357
358         socket_length = sizeof(sin);
359         if (getsockname(s, (struct sockaddr *)&sin, &socket_length) < 0) {
360                 retval = errno;
361                 close(s);
362                 (void) sprintf(Errmsg, "in call to getsockname");
363                 return(retval);
364         }
365         sender_addr.addrtype = ADDRTYPE_INET;
366         sender_addr.length = sizeof(sin.sin_addr);
367         sender_addr.contents = (krb5_octet *) malloc(sizeof(sin.sin_addr));
368         memcpy((char *) sender_addr.contents, (char *) &sin.sin_addr,
369                sizeof(sin.sin_addr));
370
371         return(0);
372 }
373
374
375 void kerberos_authenticate(context, fd, me)
376     krb5_context context;
377     int fd;
378     krb5_principal me;
379 {
380         krb5_error_code retval;
381         krb5_error      *error = NULL;
382         krb5_ap_rep_enc_part    *rep_result;
383
384         if (retval = krb5_sendauth(context, (void *)&fd, kprop_version, me,
385                                    my_creds.server, AP_OPTS_MUTUAL_REQUIRED,
386                                    NULL, &my_creds, NULL, &my_seq_num, NULL,
387                                    &error, &rep_result)) {
388                 com_err(progname, retval, "while authenticating to server");
389                 if (error) {
390                         if (error->error == KRB_ERR_GENERIC) {
391                                 if (error->text.data)
392                                         fprintf(stderr,
393                                                 "Generic remote error: %s\n",
394                                                 error->text.data);
395                         } else if (error->error) {
396                                 com_err(progname,
397                                         error->error + ERROR_TABLE_BASE_krb5,
398                                         "signalled from server");
399                                 if (error->text.data)
400                                         fprintf(stderr,
401                                                 "Error text from server: %s\n",
402                                                 error->text.data);
403                         }
404                         krb5_free_error(context, error);
405                 }
406                 exit(1);
407         }
408         his_seq_num = rep_result->seq_number;
409         krb5_free_ap_rep_enc_part(context, rep_result);
410 }
411
412 FILE * dbfp;
413 char * dbpathname;
414 /*
415  * Open the Kerberos database dump file.  Takes care of locking it
416  * and making sure that the .ok file is more recent that the database
417  * dump file itself.
418  *
419  * Returns the file descriptor of the database dump file.  Also fills
420  * in the size of the database file.
421  */
422 int
423 open_database(context, data_fn, size)
424     krb5_context context;
425     char *data_fn;
426     int *size;
427 {
428         int             fd;
429         int             err;
430         struct stat     stbuf, stbuf_ok;
431         char            *data_ok_fn;
432         static char ok[] = ".dump_ok";
433
434         dbpathname = strdup(data_fn);
435         if (!dbpathname) {
436             com_err(progname, ENOMEM, "allocating database file name '%s'",
437                     data_fn);
438             exit(1);
439         }
440         if ((dbfp = fopen(dbpathname, "r")) == 0) {
441                 com_err(progname, errno, "while trying to open %s",
442                         dbpathname);
443                 exit(1);
444         }
445
446         err = krb5_lock_file(context, dbfp, dbpathname,
447                              KRB5_LOCKMODE_SHARED|KRB5_LOCKMODE_DONTBLOCK);
448         if (err == EAGAIN || err == EWOULDBLOCK || errno == EACCES) {
449             com_err(progname, 0, "database locked");
450             exit(1);
451         } else if (err) {
452             com_err(progname, err, "while trying to lock '%s'", dbpathname);
453             exit(1);
454         }           
455         fd = fileno(dbfp);
456         if (fstat(fd, &stbuf)) {
457                 com_err(progname, errno, "while trying to stat %s",
458                         data_fn);
459                 exit(1);
460         }
461         if ((data_ok_fn = (char *) malloc(strlen(data_fn)+strlen(ok)+1))
462             == NULL) {
463                 com_err(progname, ENOMEM, "while trying to malloc data_ok_fn");
464                 exit(1);
465         }
466         strcat(strcpy(data_ok_fn, data_fn), ok);
467         if (stat(data_ok_fn, &stbuf_ok)) {
468                 com_err(progname, errno, "while trying to stat %s",
469                         data_ok_fn);
470                 free(data_ok_fn);
471                 exit(1);
472         }
473         free(data_ok_fn);
474         if (stbuf.st_mtime > stbuf_ok.st_mtime) {
475                 com_err(progname, 0, "'%s' more recent than '%s'.",
476                         data_fn, data_ok_fn);
477                 exit(1);
478         }
479         *size = stbuf.st_size;
480         return(fd);
481 }
482
483 void
484 close_database(context, fd)
485     krb5_context context;
486     int fd;
487 {
488     int err;
489     if (fd != fileno(dbfp)) {
490         com_err(progname, 0, "bad fd passed to close_database");
491         exit(1);
492     }
493     err = krb5_lock_file(context, dbfp, dbpathname, KRB5_LOCKMODE_UNLOCK);
494     if (err)
495         com_err(progname, err, "while unlocking database '%s'", dbpathname);
496     free(dbpathname);
497     (void) fclose(dbfp);
498     return;
499 }
500   
501 /*
502  * Now we send over the database.  We use the following protocol:
503  * Send over a KRB_SAFE message with the size.  Then we send over the
504  * database in blocks of KPROP_BLKSIZE, encrypted using KRB_PRIV.
505  * Then we expect to see a KRB_SAFE message with the size sent back.
506  * 
507  * At any point in the protocol, we may send a KRB_ERROR message; this
508  * will abort the entire operation.
509  */
510 void
511 xmit_database(context, fd, database_fd, database_size)
512     krb5_context context;
513     int fd;
514     int database_fd;
515     int database_size;
516 {
517         int     send_size, sent_size, n, eblock_size;
518         krb5_data       inbuf, outbuf;
519         char            buf[KPROP_BUFSIZ];
520         char            *i_vector;
521         krb5_error_code retval;
522         krb5_error      *error;
523         
524         /*
525          * Send over the size
526          */
527         send_size = htonl(database_size);
528         inbuf.data = (char *) &send_size;
529         inbuf.length = sizeof(send_size); /* must be 4, really */
530         if (retval = krb5_mk_safe(context, &inbuf, KPROP_CKSUMTYPE,
531                                   &my_creds.keyblock, 
532                                   &sender_addr, &receiver_addr,
533                                   my_seq_num++,
534                                   KRB5_PRIV_DOSEQUENCE|KRB5_SAFE_NOTIME,
535                                   0,    /* no rcache when NOTIME */
536                                   &outbuf)) {
537                 com_err(progname, retval, "while encoding database size");
538                 send_error(context, fd, "while encoding database size", retval);
539                 exit(1);
540         }
541         if (retval = krb5_write_message(context, (void *) &fd, &outbuf)) {
542                 krb5_xfree(outbuf.data);
543                 com_err(progname, retval, "while sending database size");
544                 exit(1);
545         }
546         krb5_xfree(outbuf.data);
547         /*
548          * Initialize the initial vector.
549          */
550         eblock_size = krb5_keytype_array[my_creds.keyblock.keytype]->
551                 system->block_length;
552         if (!(i_vector=malloc(eblock_size))) {
553                 com_err(progname, ENOMEM, "while allocating i_vector");
554                 send_error(context, fd, 
555                            "malloc failed while allocating i_vector", ENOMEM);
556                 exit(1);
557         }
558         memset(i_vector, 0, eblock_size);
559         /*
560          * Send over the file, block by block....
561          */
562         inbuf.data = buf;
563         sent_size = 0;
564         while (n = read(database_fd, buf, sizeof(buf))) {
565                 inbuf.length = n;
566                 if (retval = krb5_mk_priv(context, &inbuf, ETYPE_DES_CBC_CRC,
567                                           &my_creds.keyblock,
568                                           &sender_addr,
569                                           &receiver_addr,
570                                           my_seq_num++,
571                                           KRB5_PRIV_DOSEQUENCE|KRB5_PRIV_NOTIME,
572                                           0, /* again, no rcache */
573                                           i_vector,
574                                           &outbuf)) {
575                         sprintf(buf,
576                                 "while encoding database block starting at %d",
577                                 sent_size);
578                         com_err(progname, retval, buf);
579                         send_error(context, fd, buf, retval);
580                         exit(1);
581                 }
582                 if (retval = krb5_write_message(context, (void *)&fd,&outbuf)) {
583                         krb5_xfree(outbuf.data);
584                         com_err(progname, retval,
585                                 "while sending database block starting at %d",
586                                 sent_size);
587                         exit(1);
588                 }
589                 krb5_xfree(outbuf.data);
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, 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, &inbuf, &my_creds.keyblock,  
636                                   &receiver_addr, &sender_addr, his_seq_num++,
637                                   KRB5_SAFE_DOSEQUENCE|KRB5_SAFE_NOTIME,
638                                   0, &outbuf)) {
639                 com_err(progname, retval,
640                         "while decoding final size packet from server");
641                 exit(1);
642         }
643         memcpy((char *)&send_size, outbuf.data, sizeof(send_size));
644         send_size = ntohl(send_size);
645         if (send_size != database_size) {
646                 com_err(progname, 0,
647                         "Kpropd sent database size %d, expecting %d",
648                         send_size, database_size);
649                 exit(1);
650         }
651         free(outbuf.data);
652         free(inbuf.data);
653 }
654
655 void
656 send_error(context, fd, err_text, err_code)
657     krb5_context context;
658     int fd;
659     char        *err_text;
660     krb5_error_code     err_code;
661 {
662         krb5_error      error;
663         const char      *text;
664         krb5_data       outbuf;
665
666         memset((char *)&error, 0, sizeof(error));
667         krb5_us_timeofday(context, &error.ctime, &error.cusec);
668         error.server = my_creds.server;
669         error.client = my_principal;
670         error.error = err_code - ERROR_TABLE_BASE_krb5;
671         if (error.error > 127)
672                 error.error = KRB_ERR_GENERIC;
673         if (err_text)
674                 text = err_text;
675         else
676                 text = error_message(err_code);
677         error.text.length = strlen(text) + 1;
678         if (error.text.data = malloc(error.text.length)) {
679                 strcpy(error.text.data, text);
680                 if (!krb5_mk_error(context, &error, &outbuf)) {
681                         (void) krb5_write_message(context, (void *)&fd,&outbuf);
682                         krb5_xfree(outbuf.data);
683                 }
684                 free(error.text.data);
685         }
686 }
687
688 void update_last_prop_file(hostname, file_name)
689         char *hostname;
690         char *file_name;
691 {
692         /* handle slave locking/failure stuff */
693         char *file_last_prop;
694         int fd;
695         static char last_prop[]=".last_prop";
696
697         if ((file_last_prop = (char *)malloc(strlen(file_name) +
698                                              strlen(hostname) + 1 +
699                                              strlen(last_prop) + 1)) == NULL) {
700                 com_err(progname, ENOMEM,
701                         "while allocating filename for update_last_prop_file");
702                 return;
703         }
704         strcpy(file_last_prop, file_name);
705         strcat(file_last_prop, ".");
706         strcat(file_last_prop, hostname);
707         strcat(file_last_prop, last_prop);
708         if ((fd = open(file_last_prop, O_WRONLY|O_CREAT|O_TRUNC, 0600)) < 0) {
709                 com_err(progname, errno,
710                         "while creating 'last_prop' file, '%s'",
711                         file_last_prop);
712                 free(file_last_prop);
713                 return;
714         }
715         free(file_last_prop);
716         close(fd);
717         return;
718 }