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()83 static void usage()
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 GETSOCKNAME_ARG3_TYPE socket_length;
221 struct addrinfo hints, *res, *answers;
222 struct sockaddr *sa;
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 sa = (struct sockaddr *)&my_sin;
273 if (sockaddr2krbaddr(context, sa->sa_family, sa, &sender_addr) != 0) {
274 com_err(progname, errno, _("while converting local address"));
275 exit(1);
276 }
277 }
278
279 static void
kerberos_authenticate(krb5_context context,krb5_auth_context * auth_context,int fd,krb5_principal me,krb5_creds ** new_creds)280 kerberos_authenticate(krb5_context context, krb5_auth_context *auth_context,
281 int fd, krb5_principal me, krb5_creds **new_creds)
282 {
283 krb5_error_code retval;
284 krb5_error *error = NULL;
285 krb5_ap_rep_enc_part *rep_result;
286
287 retval = krb5_auth_con_init(context, auth_context);
288 if (retval)
289 exit(1);
290
291 krb5_auth_con_setflags(context, *auth_context,
292 KRB5_AUTH_CONTEXT_DO_SEQUENCE);
293
294 retval = krb5_auth_con_setaddrs(context, *auth_context, sender_addr, NULL);
295 if (retval) {
296 com_err(progname, retval, _("in krb5_auth_con_setaddrs"));
297 exit(1);
298 }
299
300 retval = krb5_sendauth(context, auth_context, &fd, kprop_version,
301 me, creds.server, AP_OPTS_MUTUAL_REQUIRED, NULL,
302 &creds, NULL, &error, &rep_result, new_creds);
303 if (retval) {
304 com_err(progname, retval, _("while authenticating to server"));
305 if (error != NULL) {
306 if (error->error == KRB_ERR_GENERIC) {
307 if (error->text.data) {
308 fprintf(stderr, _("Generic remote error: %s\n"),
309 error->text.data);
310 }
311 } else if (error->error) {
312 com_err(progname,
313 (krb5_error_code)error->error + ERROR_TABLE_BASE_krb5,
314 _("signalled from server"));
315 if (error->text.data) {
316 fprintf(stderr, _("Error text from server: %s\n"),
317 error->text.data);
318 }
319 }
320 krb5_free_error(context, error);
321 }
322 exit(1);
323 }
324 krb5_free_ap_rep_enc_part(context, rep_result);
325 }
326
327 /*
328 * Open the Kerberos database dump file. Takes care of locking it
329 * and making sure that the .ok file is more recent that the database
330 * dump file itself.
331 *
332 * Returns the file descriptor of the database dump file. Also fills
333 * in the size of the database file.
334 */
335 static int
open_database(krb5_context context,char * data_fn,off_t * size)336 open_database(krb5_context context, char *data_fn, off_t *size)
337 {
338 struct stat stbuf, stbuf_ok;
339 char *data_ok_fn;
340 int fd, err;
341
342 dbpathname = strdup(data_fn);
343 if (dbpathname == NULL) {
344 com_err(progname, ENOMEM, _("allocating database file name '%s'"),
345 data_fn);
346 exit(1);
347 }
348 fd = open(dbpathname, O_RDONLY);
349 if (fd < 0) {
350 com_err(progname, errno, _("while trying to open %s"), dbpathname);
351 exit(1);
352 }
353
354 err = krb5_lock_file(context, fd,
355 KRB5_LOCKMODE_SHARED | KRB5_LOCKMODE_DONTBLOCK);
356 if (err == EAGAIN || err == EWOULDBLOCK || errno == EACCES) {
357 com_err(progname, 0, _("database locked"));
358 exit(1);
359 } else if (err) {
360 com_err(progname, err, _("while trying to lock '%s'"), dbpathname);
361 exit(1);
362 }
363 if (fstat(fd, &stbuf)) {
364 com_err(progname, errno, _("while trying to stat %s"), data_fn);
365 exit(1);
366 }
367 if (asprintf(&data_ok_fn, "%s.dump_ok", data_fn) < 0) {
368 com_err(progname, ENOMEM, _("while trying to malloc data_ok_fn"));
369 exit(1);
370 }
371 if (stat(data_ok_fn, &stbuf_ok)) {
372 com_err(progname, errno, _("while trying to stat %s"), data_ok_fn);
373 free(data_ok_fn);
374 exit(1);
375 }
376 if (stbuf.st_mtime > stbuf_ok.st_mtime) {
377 com_err(progname, 0, _("'%s' more recent than '%s'."), data_fn,
378 data_ok_fn);
379 exit(1);
380 }
381 free(data_ok_fn);
382 *size = stbuf.st_size;
383 return fd;
384 }
385
386 static void
close_database(krb5_context context,int fd)387 close_database(krb5_context context, int fd)
388 {
389 int err;
390
391 err = krb5_lock_file(context, fd, KRB5_LOCKMODE_UNLOCK);
392 if (err)
393 com_err(progname, err, _("while unlocking database '%s'"), dbpathname);
394 free(dbpathname);
395 close(fd);
396 }
397
398 /*
399 * Now we send over the database. We use the following protocol:
400 * Send over a KRB_SAFE message with the size. Then we send over the
401 * database in blocks of KPROP_BLKSIZE, encrypted using KRB_PRIV.
402 * Then we expect to see a KRB_SAFE message with the size sent back.
403 *
404 * At any point in the protocol, we may send a KRB_ERROR message; this
405 * will abort the entire operation.
406 */
407 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)408 xmit_database(krb5_context context, krb5_auth_context auth_context,
409 krb5_creds *my_creds, int fd, int database_fd,
410 off_t in_database_size)
411 {
412 krb5_int32 n;
413 krb5_data inbuf, outbuf;
414 char buf[KPROP_BUFSIZ], dbsize_buf[KPROP_DBSIZE_MAX_BUFSIZ];
415 krb5_error_code retval;
416 krb5_error *error;
417 uint64_t database_size = in_database_size, send_size, sent_size;
418
419 /* Send over the size. */
420 inbuf = make_data(dbsize_buf, sizeof(dbsize_buf));
421 encode_database_size(database_size, &inbuf);
422 /* KPROP_CKSUMTYPE */
423 retval = krb5_mk_safe(context, auth_context, &inbuf, &outbuf, NULL);
424 if (retval) {
425 com_err(progname, retval, _("while encoding database size"));
426 send_error(context, my_creds, fd, _("while encoding database size"),
427 retval);
428 exit(1);
429 }
430
431 retval = krb5_write_message(context, &fd, &outbuf);
432 if (retval) {
433 krb5_free_data_contents(context, &outbuf);
434 com_err(progname, retval, _("while sending database size"));
435 exit(1);
436 }
437 krb5_free_data_contents(context, &outbuf);
438
439 /* Initialize the initial vector. */
440 retval = krb5_auth_con_initivector(context, auth_context);
441 if (retval) {
442 send_error(context, my_creds, fd,
443 "failed while initializing i_vector", retval);
444 com_err(progname, retval, _("while allocating i_vector"));
445 exit(1);
446 }
447
448 /* Send over the file, block by block. */
449 inbuf.data = buf;
450 sent_size = 0;
451 while ((n = read(database_fd, buf, sizeof(buf)))) {
452 inbuf.length = n;
453 retval = krb5_mk_priv(context, auth_context, &inbuf, &outbuf, NULL);
454 if (retval) {
455 snprintf(buf, sizeof(buf),
456 "while encoding database block starting at %"PRIu64,
457 sent_size);
458 com_err(progname, retval, "%s", buf);
459 send_error(context, my_creds, fd, buf, retval);
460 exit(1);
461 }
462
463 retval = krb5_write_message(context, &fd, &outbuf);
464 if (retval) {
465 krb5_free_data_contents(context, &outbuf);
466 com_err(progname, retval,
467 _("while sending database block starting at %"PRIu64),
468 sent_size);
469 exit(1);
470 }
471 krb5_free_data_contents(context, &outbuf);
472 sent_size += n;
473 if (debug)
474 printf("%"PRIu64" bytes sent.\n", sent_size);
475 }
476 if (sent_size != database_size) {
477 com_err(progname, 0, _("Premature EOF found for database file!"));
478 send_error(context, my_creds, fd,
479 "Premature EOF found for database file!",
480 KRB5KRB_ERR_GENERIC);
481 exit(1);
482 }
483
484 /*
485 * OK, we've sent the database; now let's wait for a success
486 * indication from the remote end.
487 */
488 retval = krb5_read_message(context, &fd, &inbuf);
489 if (retval) {
490 com_err(progname, retval, _("while reading response from server"));
491 exit(1);
492 }
493 /*
494 * If we got an error response back from the server, display
495 * the error message
496 */
497 if (krb5_is_krb_error(&inbuf)) {
498 retval = krb5_rd_error(context, &inbuf, &error);
499 if (retval) {
500 com_err(progname, retval,
501 _("while decoding error response from server"));
502 exit(1);
503 }
504 if (error->error == KRB_ERR_GENERIC) {
505 if (error->text.data) {
506 fprintf(stderr, _("Generic remote error: %s\n"),
507 error->text.data);
508 }
509 } else if (error->error) {
510 com_err(progname,
511 (krb5_error_code)error->error + ERROR_TABLE_BASE_krb5,
512 _("signalled from server"));
513 if (error->text.data) {
514 fprintf(stderr, _("Error text from server: %s\n"),
515 error->text.data);
516 }
517 }
518 krb5_free_error(context, error);
519 exit(1);
520 }
521
522 retval = krb5_rd_safe(context,auth_context,&inbuf,&outbuf,NULL);
523 if (retval) {
524 com_err(progname, retval,
525 "while decoding final size packet from server");
526 exit(1);
527 }
528
529 retval = decode_database_size(&outbuf, &send_size);
530 if (retval) {
531 com_err(progname, retval, _("malformed sent database size message"));
532 exit(1);
533 }
534 if (send_size != database_size) {
535 com_err(progname, 0, _("Kpropd sent database size %"PRIu64
536 ", expecting %"PRIu64),
537 send_size, database_size);
538 exit(1);
539 }
540 free(inbuf.data);
541 free(outbuf.data);
542 }
543
544 static void
send_error(krb5_context context,krb5_creds * my_creds,int fd,char * err_text,krb5_error_code err_code)545 send_error(krb5_context context, krb5_creds *my_creds, int fd, char *err_text,
546 krb5_error_code err_code)
547 {
548 krb5_error error;
549 const char *text;
550 krb5_data outbuf;
551
552 memset(&error, 0, sizeof(error));
553 krb5_us_timeofday(context, &error.ctime, &error.cusec);
554 error.server = my_creds->server;
555 error.client = my_principal;
556 error.error = err_code - ERROR_TABLE_BASE_krb5;
557 if (error.error > 127)
558 error.error = KRB_ERR_GENERIC;
559 text = (err_text != NULL) ? err_text : error_message(err_code);
560 error.text.length = strlen(text) + 1;
561 error.text.data = strdup(text);
562 if (error.text.data) {
563 if (!krb5_mk_error(context, &error, &outbuf)) {
564 (void)krb5_write_message(context, &fd, &outbuf);
565 krb5_free_data_contents(context, &outbuf);
566 }
567 free(error.text.data);
568 }
569 }
570
571 static void
update_last_prop_file(char * hostname,char * file_name)572 update_last_prop_file(char *hostname, char *file_name)
573 {
574 char *file_last_prop;
575 int fd;
576 static char last_prop[] = ".last_prop";
577
578 if (asprintf(&file_last_prop, "%s.%s%s", file_name, hostname,
579 last_prop) < 0) {
580 com_err(progname, ENOMEM,
581 _("while allocating filename for update_last_prop_file"));
582 return;
583 }
584 fd = THREEPARAMOPEN(file_last_prop, O_WRONLY | O_CREAT | O_TRUNC, 0600);
585 if (fd < 0) {
586 com_err(progname, errno, _("while creating 'last_prop' file, '%s'"),
587 file_last_prop);
588 free(file_last_prop);
589 return;
590 }
591 write(fd, "", 1);
592 free(file_last_prop);
593 close(fd);
594 }
595