Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/freebsd-src
Path: blob/main/crypto/krb5/src/kprop/kprop.c
34878 views
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
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
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
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
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
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
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
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
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
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
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
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
598