xref: /freebsd/crypto/krb5/src/kprop/kprop.c (revision f1c4c3daccbaf3820f0e2224de53df12fc952fcc)
1 /* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2 /* kprop/kprop.c */
3 /*
4  * Copyright 1990,1991,2008 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 #include "k5-int.h"
28 #include <inttypes.h>
29 #include <locale.h>
30 #include <sys/file.h>
31 #include <signal.h>
32 #include <sys/types.h>
33 #include <sys/time.h>
34 #include <sys/stat.h>
35 #include <sys/socket.h>
36 #include <netinet/in.h>
37 #include <sys/param.h>
38 #include <netdb.h>
39 #include <fcntl.h>
40 
41 #include "com_err.h"
42 #include "fake-addrinfo.h"
43 #include "kprop.h"
44 
45 #ifndef GETSOCKNAME_ARG3_TYPE
46 #define GETSOCKNAME_ARG3_TYPE unsigned int
47 #endif
48 
49 static char *kprop_version = KPROP_PROT_VERSION;
50 
51 static char *progname = NULL;
52 static int debug = 0;
53 static char *keytab_path = NULL;
54 static char *replica_host;
55 static char *realm = NULL;
56 static char *def_realm = NULL;
57 static char *file = KPROP_DEFAULT_FILE;
58 
59 /* The Kerberos principal we'll be sending as, initialized in get_tickets. */
60 static krb5_principal my_principal;
61 
62 static krb5_creds creds;
63 static krb5_address *sender_addr;
64 static const char *port = KPROP_SERVICE;
65 static char *dbpathname;
66 
67 static void parse_args(krb5_context context, int argc, char **argv);
68 static void get_tickets(krb5_context context);
69 static void usage(void);
70 static void open_connection(krb5_context context, char *host, int *fd_out);
71 static void kerberos_authenticate(krb5_context context,
72                                   krb5_auth_context *auth_context, int fd,
73                                   krb5_principal me, krb5_creds **new_creds);
74 static int open_database(krb5_context context, char *data_fn, off_t *size);
75 static void close_database(krb5_context context, int fd);
76 static void xmit_database(krb5_context context,
77                           krb5_auth_context auth_context, krb5_creds *my_creds,
78                           int fd, int database_fd, off_t in_database_size);
79 static void send_error(krb5_context context, krb5_creds *my_creds, int fd,
80                        char *err_text, krb5_error_code err_code);
81 static void update_last_prop_file(char *hostname, char *file_name);
82 
usage(void)83 static void usage(void)
84 {
85     fprintf(stderr, _("\nUsage: %s [-r realm] [-f file] [-d] [-P port] "
86                       "[-s keytab] replica_host\n\n"), progname);
87     exit(1);
88 }
89 
90 int
main(int argc,char ** argv)91 main(int argc, char **argv)
92 {
93     int fd, database_fd;
94     off_t database_size;
95     krb5_error_code retval;
96     krb5_context context;
97     krb5_creds *my_creds;
98     krb5_auth_context auth_context;
99 
100     setlocale(LC_ALL, "");
101     retval = krb5_init_context(&context);
102     if (retval) {
103         com_err(argv[0], retval, _("while initializing krb5"));
104         exit(1);
105     }
106     parse_args(context, argc, argv);
107     get_tickets(context);
108 
109     database_fd = open_database(context, file, &database_size);
110     open_connection(context, replica_host, &fd);
111     kerberos_authenticate(context, &auth_context, fd, my_principal, &my_creds);
112     xmit_database(context, auth_context, my_creds, fd, database_fd,
113                   database_size);
114     update_last_prop_file(replica_host, file);
115     printf(_("Database propagation to %s: SUCCEEDED\n"), replica_host);
116     krb5_free_cred_contents(context, my_creds);
117     close_database(context, database_fd);
118     krb5_free_default_realm(context, def_realm);
119     exit(0);
120 }
121 
122 static void
parse_args(krb5_context context,int argc,char ** argv)123 parse_args(krb5_context context, int argc, char **argv)
124 {
125     int c;
126     krb5_error_code ret;
127 
128     progname = argv[0];
129     while ((c = getopt(argc, argv, "r:f:dP:s:")) != -1) {
130         switch (c) {
131         case 'r':
132             realm = optarg;
133             break;
134         case 'f':
135             file = optarg;
136             break;
137         case 'd':
138             debug++;
139             break;
140         case 'P':
141             port = optarg;
142             break;
143         case 's':
144             keytab_path = optarg;
145             break;
146         default:
147             usage();
148         }
149     }
150     if (argc - optind != 1)
151         usage();
152     replica_host = argv[optind];
153 
154     if (realm == NULL) {
155         ret = krb5_get_default_realm(context, &def_realm);
156         if (ret) {
157             com_err(progname, errno, _("while getting default realm"));
158             exit(1);
159         }
160         realm = def_realm;
161     }
162 }
163 
164 static void
get_tickets(krb5_context context)165 get_tickets(krb5_context context)
166 {
167     char *server;
168     krb5_error_code retval;
169     krb5_keytab keytab = NULL;
170     krb5_principal server_princ = NULL;
171 
172     /* Figure out what tickets we'll be using to send. */
173     retval = sn2princ_realm(context, NULL, KPROP_SERVICE_NAME, realm,
174                             &my_principal);
175     if (retval) {
176         com_err(progname, errno, _("while setting client principal name"));
177         exit(1);
178     }
179 
180     /* Construct the principal name for the replica host. */
181     memset(&creds, 0, sizeof(creds));
182     retval = sn2princ_realm(context, replica_host, KPROP_SERVICE_NAME, realm,
183                             &server_princ);
184     if (retval) {
185         com_err(progname, errno, _("while setting server principal name"));
186         exit(1);
187     }
188     retval = krb5_unparse_name_flags(context, server_princ,
189                                      KRB5_PRINCIPAL_UNPARSE_NO_REALM, &server);
190     if (retval) {
191         com_err(progname, retval, _("while unparsing server name"));
192         exit(1);
193     }
194 
195     if (keytab_path != NULL) {
196         retval = krb5_kt_resolve(context, keytab_path, &keytab);
197         if (retval) {
198             com_err(progname, retval, _("while resolving keytab"));
199             exit(1);
200         }
201     }
202 
203     retval = krb5_get_init_creds_keytab(context, &creds, my_principal, keytab,
204                                         0, server, NULL);
205     if (retval) {
206         com_err(progname, retval, _("while getting initial credentials\n"));
207         exit(1);
208     }
209 
210     if (keytab != NULL)
211         krb5_kt_close(context, keytab);
212     krb5_free_unparsed_name(context, server);
213     krb5_free_principal(context, server_princ);
214 }
215 
216 static void
open_connection(krb5_context context,char * host,int * fd_out)217 open_connection(krb5_context context, char *host, int *fd_out)
218 {
219     krb5_error_code retval;
220     krb5_address addr;
221     GETSOCKNAME_ARG3_TYPE socket_length;
222     struct addrinfo hints, *res, *answers;
223     struct sockaddr_storage my_sin;
224     int s, error;
225 
226     *fd_out = -1;
227     memset(&hints, 0, sizeof(hints));
228     hints.ai_family = PF_UNSPEC;
229     hints.ai_socktype = SOCK_STREAM;
230     hints.ai_flags = AI_ADDRCONFIG;
231     error = getaddrinfo(host, port, &hints, &answers);
232     if (error != 0) {
233         com_err(progname, 0, "%s: %s", host, gai_strerror(error));
234         exit(1);
235     }
236 
237     s = -1;
238     retval = EINVAL;
239     for (res = answers; res != NULL; res = res->ai_next) {
240         s = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
241         if (s < 0) {
242             com_err(progname, errno, _("while creating socket"));
243             exit(1);
244         }
245 
246         if (connect(s, res->ai_addr, res->ai_addrlen) < 0) {
247             retval = errno;
248             close(s);
249             s = -1;
250             continue;
251         }
252 
253         /* We successfully connect()ed */
254         *fd_out = s;
255 
256         break;
257     }
258 
259     freeaddrinfo(answers);
260 
261     if (s == -1) {
262         com_err(progname, retval, _("while connecting to server"));
263         exit(1);
264     }
265 
266     /* Set sender_addr. */
267     socket_length = sizeof(my_sin);
268     if (getsockname(s, (struct sockaddr *)&my_sin, &socket_length) < 0) {
269         com_err(progname, errno, _("while getting local socket address"));
270         exit(1);
271     }
272     if (k5_sockaddr_to_address(ss2sa(&my_sin), FALSE, &addr) != 0)
273         addr = k5_addr_directional_init;
274     retval = krb5_copy_addr(context, &addr, &sender_addr);
275     if (retval) {
276         com_err(progname, retval, _("while converting local address"));
277         exit(1);
278     }
279 }
280 
281 static void
kerberos_authenticate(krb5_context context,krb5_auth_context * auth_context,int fd,krb5_principal me,krb5_creds ** new_creds)282 kerberos_authenticate(krb5_context context, krb5_auth_context *auth_context,
283                       int fd, krb5_principal me, krb5_creds **new_creds)
284 {
285     krb5_error_code retval;
286     krb5_error *error = NULL;
287     krb5_ap_rep_enc_part *rep_result;
288 
289     retval = krb5_auth_con_init(context, auth_context);
290     if (retval)
291         exit(1);
292 
293     krb5_auth_con_setflags(context, *auth_context,
294                            KRB5_AUTH_CONTEXT_DO_SEQUENCE);
295 
296     retval = krb5_auth_con_setaddrs(context, *auth_context, sender_addr, NULL);
297     if (retval) {
298         com_err(progname, retval, _("in krb5_auth_con_setaddrs"));
299         exit(1);
300     }
301 
302     retval = krb5_sendauth(context, auth_context, &fd, kprop_version,
303                            me, creds.server, AP_OPTS_MUTUAL_REQUIRED, NULL,
304                            &creds, NULL, &error, &rep_result, new_creds);
305     if (retval) {
306         com_err(progname, retval, _("while authenticating to server"));
307         if (error != NULL) {
308             if (error->error == KRB_ERR_GENERIC) {
309                 if (error->text.data) {
310                     fprintf(stderr, _("Generic remote error: %s\n"),
311                             error->text.data);
312                 }
313             } else if (error->error) {
314                 com_err(progname,
315                         (krb5_error_code)error->error + ERROR_TABLE_BASE_krb5,
316                         _("signalled from server"));
317                 if (error->text.data) {
318                     fprintf(stderr, _("Error text from server: %s\n"),
319                             error->text.data);
320                 }
321             }
322             krb5_free_error(context, error);
323         }
324         exit(1);
325     }
326     krb5_free_ap_rep_enc_part(context, rep_result);
327 }
328 
329 /*
330  * Open the Kerberos database dump file.  Takes care of locking it
331  * and making sure that the .ok file is more recent that the database
332  * dump file itself.
333  *
334  * Returns the file descriptor of the database dump file.  Also fills
335  * in the size of the database file.
336  */
337 static int
open_database(krb5_context context,char * data_fn,off_t * size)338 open_database(krb5_context context, char *data_fn, off_t *size)
339 {
340     struct stat stbuf, stbuf_ok;
341     char *data_ok_fn;
342     int fd, err;
343 
344     dbpathname = strdup(data_fn);
345     if (dbpathname == NULL) {
346         com_err(progname, ENOMEM, _("allocating database file name '%s'"),
347                 data_fn);
348         exit(1);
349     }
350     fd = open(dbpathname, O_RDONLY);
351     if (fd < 0) {
352         com_err(progname, errno, _("while trying to open %s"), dbpathname);
353         exit(1);
354     }
355 
356     err = krb5_lock_file(context, fd,
357                          KRB5_LOCKMODE_SHARED | KRB5_LOCKMODE_DONTBLOCK);
358     if (err == EAGAIN || err == EWOULDBLOCK || errno == EACCES) {
359         com_err(progname, 0, _("database locked"));
360         exit(1);
361     } else if (err) {
362         com_err(progname, err, _("while trying to lock '%s'"), dbpathname);
363         exit(1);
364     }
365     if (fstat(fd, &stbuf)) {
366         com_err(progname, errno, _("while trying to stat %s"), data_fn);
367         exit(1);
368     }
369     if (asprintf(&data_ok_fn, "%s.dump_ok", data_fn) < 0) {
370         com_err(progname, ENOMEM, _("while trying to malloc data_ok_fn"));
371         exit(1);
372     }
373     if (stat(data_ok_fn, &stbuf_ok)) {
374         com_err(progname, errno, _("while trying to stat %s"), data_ok_fn);
375         free(data_ok_fn);
376         exit(1);
377     }
378     if (stbuf.st_mtime > stbuf_ok.st_mtime) {
379         com_err(progname, 0, _("'%s' more recent than '%s'."), data_fn,
380                 data_ok_fn);
381         exit(1);
382     }
383     free(data_ok_fn);
384     *size = stbuf.st_size;
385     return fd;
386 }
387 
388 static void
close_database(krb5_context context,int fd)389 close_database(krb5_context context, int fd)
390 {
391     int err;
392 
393     err = krb5_lock_file(context, fd, KRB5_LOCKMODE_UNLOCK);
394     if (err)
395         com_err(progname, err, _("while unlocking database '%s'"), dbpathname);
396     free(dbpathname);
397     close(fd);
398 }
399 
400 /*
401  * Now we send over the database.  We use the following protocol:
402  * Send over a KRB_SAFE message with the size.  Then we send over the
403  * database in blocks of KPROP_BLKSIZE, encrypted using KRB_PRIV.
404  * Then we expect to see a KRB_SAFE message with the size sent back.
405  *
406  * At any point in the protocol, we may send a KRB_ERROR message; this
407  * will abort the entire operation.
408  */
409 static void
xmit_database(krb5_context context,krb5_auth_context auth_context,krb5_creds * my_creds,int fd,int database_fd,off_t in_database_size)410 xmit_database(krb5_context context, krb5_auth_context auth_context,
411               krb5_creds *my_creds, int fd, int database_fd,
412               off_t in_database_size)
413 {
414     krb5_int32 n;
415     krb5_data inbuf, outbuf;
416     char buf[KPROP_BUFSIZ], dbsize_buf[KPROP_DBSIZE_MAX_BUFSIZ];
417     krb5_error_code retval;
418     krb5_error *error;
419     uint64_t database_size = in_database_size, send_size, sent_size;
420 
421     /* Send over the size. */
422     inbuf = make_data(dbsize_buf, sizeof(dbsize_buf));
423     encode_database_size(database_size, &inbuf);
424     /* KPROP_CKSUMTYPE */
425     retval = krb5_mk_safe(context, auth_context, &inbuf, &outbuf, NULL);
426     if (retval) {
427         com_err(progname, retval, _("while encoding database size"));
428         send_error(context, my_creds, fd, _("while encoding database size"),
429                    retval);
430         exit(1);
431     }
432 
433     retval = krb5_write_message(context, &fd, &outbuf);
434     if (retval) {
435         krb5_free_data_contents(context, &outbuf);
436         com_err(progname, retval, _("while sending database size"));
437         exit(1);
438     }
439     krb5_free_data_contents(context, &outbuf);
440 
441     /* Initialize the initial vector. */
442     retval = krb5_auth_con_initivector(context, auth_context);
443     if (retval) {
444         send_error(context, my_creds, fd,
445                    "failed while initializing i_vector", retval);
446         com_err(progname, retval, _("while allocating i_vector"));
447         exit(1);
448     }
449 
450     /* Send over the file, block by block. */
451     inbuf.data = buf;
452     sent_size = 0;
453     while ((n = read(database_fd, buf, sizeof(buf)))) {
454         inbuf.length = n;
455         retval = krb5_mk_priv(context, auth_context, &inbuf, &outbuf, NULL);
456         if (retval) {
457             snprintf(buf, sizeof(buf),
458                      "while encoding database block starting at %"PRIu64,
459                      sent_size);
460             com_err(progname, retval, "%s", buf);
461             send_error(context, my_creds, fd, buf, retval);
462             exit(1);
463         }
464 
465         retval = krb5_write_message(context, &fd, &outbuf);
466         if (retval) {
467             krb5_free_data_contents(context, &outbuf);
468             com_err(progname, retval,
469                     _("while sending database block starting at %"PRIu64),
470                     sent_size);
471             exit(1);
472         }
473         krb5_free_data_contents(context, &outbuf);
474         sent_size += n;
475         if (debug)
476             printf("%"PRIu64" bytes sent.\n", sent_size);
477     }
478     if (sent_size != database_size) {
479         com_err(progname, 0, _("Premature EOF found for database file!"));
480         send_error(context, my_creds, fd,
481                    "Premature EOF found for database file!",
482                    KRB5KRB_ERR_GENERIC);
483         exit(1);
484     }
485 
486     /*
487      * OK, we've sent the database; now let's wait for a success
488      * indication from the remote end.
489      */
490     retval = krb5_read_message(context, &fd, &inbuf);
491     if (retval) {
492         com_err(progname, retval, _("while reading response from server"));
493         exit(1);
494     }
495     /*
496      * If we got an error response back from the server, display
497      * the error message
498      */
499     if (krb5_is_krb_error(&inbuf)) {
500         retval = krb5_rd_error(context, &inbuf, &error);
501         if (retval) {
502             com_err(progname, retval,
503                     _("while decoding error response from server"));
504             exit(1);
505         }
506         if (error->error == KRB_ERR_GENERIC) {
507             if (error->text.data) {
508                 fprintf(stderr, _("Generic remote error: %s\n"),
509                         error->text.data);
510             }
511         } else if (error->error) {
512             com_err(progname,
513                     (krb5_error_code)error->error + ERROR_TABLE_BASE_krb5,
514                     _("signalled from server"));
515             if (error->text.data) {
516                 fprintf(stderr, _("Error text from server: %s\n"),
517                         error->text.data);
518             }
519         }
520         krb5_free_error(context, error);
521         exit(1);
522     }
523 
524     retval = krb5_rd_safe(context,auth_context,&inbuf,&outbuf,NULL);
525     if (retval) {
526         com_err(progname, retval,
527                 "while decoding final size packet from server");
528         exit(1);
529     }
530 
531     retval = decode_database_size(&outbuf, &send_size);
532     if (retval) {
533         com_err(progname, retval, _("malformed sent database size message"));
534         exit(1);
535     }
536     if (send_size != database_size) {
537         com_err(progname, 0, _("Kpropd sent database size %"PRIu64
538                                ", expecting %"PRIu64),
539                 send_size, database_size);
540         exit(1);
541     }
542     free(inbuf.data);
543     free(outbuf.data);
544 }
545 
546 static void
send_error(krb5_context context,krb5_creds * my_creds,int fd,char * err_text,krb5_error_code err_code)547 send_error(krb5_context context, krb5_creds *my_creds, int fd, char *err_text,
548            krb5_error_code err_code)
549 {
550     krb5_error error;
551     const char *text;
552     krb5_data outbuf;
553 
554     memset(&error, 0, sizeof(error));
555     krb5_us_timeofday(context, &error.ctime, &error.cusec);
556     error.server = my_creds->server;
557     error.client = my_principal;
558     error.error = err_code - ERROR_TABLE_BASE_krb5;
559     if (error.error > 127)
560         error.error = KRB_ERR_GENERIC;
561     text = (err_text != NULL) ? err_text : error_message(err_code);
562     error.text.length = strlen(text) + 1;
563     error.text.data = strdup(text);
564     if (error.text.data) {
565         if (!krb5_mk_error(context, &error, &outbuf)) {
566             (void)krb5_write_message(context, &fd, &outbuf);
567             krb5_free_data_contents(context, &outbuf);
568         }
569         free(error.text.data);
570     }
571 }
572 
573 static void
update_last_prop_file(char * hostname,char * file_name)574 update_last_prop_file(char *hostname, char *file_name)
575 {
576     char *file_last_prop;
577     int fd;
578     static char last_prop[] = ".last_prop";
579 
580     if (asprintf(&file_last_prop, "%s.%s%s", file_name, hostname,
581                  last_prop) < 0) {
582         com_err(progname, ENOMEM,
583                 _("while allocating filename for update_last_prop_file"));
584         return;
585     }
586     fd = THREEPARAMOPEN(file_last_prop, O_WRONLY | O_CREAT | O_TRUNC, 0600);
587     if (fd < 0) {
588         com_err(progname, errno, _("while creating 'last_prop' file, '%s'"),
589                 file_last_prop);
590         free(file_last_prop);
591         return;
592     }
593     write(fd, "", 1);
594     free(file_last_prop);
595     close(fd);
596 }
597