2 * Copyright (c) 1997 - 2008 Kungliga Tekniska Högskolan
3 * (Royal Institute of Technology, Stockholm, Sweden).
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
10 * 1. Redistributions of source code must retain the above copyright
11 * notice, this list of conditions and the following disclaimer.
13 * 2. Redistributions in binary form must reproduce the above copyright
14 * notice, this list of conditions and the following disclaimer in the
15 * documentation and/or other materials provided with the distribution.
17 * 3. Neither the name of the Institute nor the names of its contributors
18 * may be used to endorse or promote products derived from this software
19 * without specific prior written permission.
21 * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
22 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24 * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
25 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
27 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
28 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
30 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
38 static const char *config_name
= "ipropd-slave";
40 static krb5_log_facility
*log_facility
;
41 static char five_min
[] = "5 min";
42 static char *server_time_lost
= five_min
;
43 static int time_before_lost
;
44 const char *slave_str
= NULL
;
47 connect_to_master (krb5_context context
, const char *master
,
50 char port
[NI_MAXSERV
];
51 struct addrinfo
*ai
, *a
;
52 struct addrinfo hints
;
56 memset (&hints
, 0, sizeof(hints
));
57 hints
.ai_socktype
= SOCK_STREAM
;
59 if (port_str
== NULL
) {
60 snprintf(port
, sizeof(port
), "%u", IPROP_PORT
);
64 error
= getaddrinfo (master
, port_str
, &hints
, &ai
);
66 krb5_warnx(context
, "Failed to get address of to %s: %s",
67 master
, gai_strerror(error
));
71 for (a
= ai
; a
!= NULL
; a
= a
->ai_next
) {
72 char node
[NI_MAXHOST
];
73 error
= getnameinfo(a
->ai_addr
, a
->ai_addrlen
,
74 node
, sizeof(node
), NULL
, 0, NI_NUMERICHOST
);
76 strlcpy(node
, "[unknown-addr]", sizeof(node
));
78 s
= socket (a
->ai_family
, a
->ai_socktype
, a
->ai_protocol
);
81 if (connect (s
, a
->ai_addr
, a
->ai_addrlen
) < 0) {
82 krb5_warn(context
, errno
, "connection failed to %s[%s]",
87 krb5_warnx(context
, "connection successful "
88 "to master: %s[%s]", master
, node
);
100 get_creds(krb5_context context
, const char *keytab_str
,
101 krb5_ccache
*cache
, const char *serverhost
)
104 krb5_principal client
;
106 krb5_get_init_creds_opt
*init_opts
;
109 char keytab_buf
[256];
112 if (keytab_str
== NULL
) {
113 ret
= krb5_kt_default_name (context
, keytab_buf
, sizeof(keytab_buf
));
115 krb5_err (context
, 1, ret
, "krb5_kt_default_name");
116 keytab_str
= keytab_buf
;
119 ret
= krb5_kt_resolve(context
, keytab_str
, &keytab
);
121 krb5_err(context
, 1, ret
, "%s", keytab_str
);
124 ret
= krb5_sname_to_principal (context
, slave_str
, IPROP_NAME
,
125 KRB5_NT_SRV_HST
, &client
);
126 if (ret
) krb5_err(context
, 1, ret
, "krb5_sname_to_principal");
128 ret
= krb5_get_init_creds_opt_alloc(context
, &init_opts
);
129 if (ret
) krb5_err(context
, 1, ret
, "krb5_get_init_creds_opt_alloc");
131 aret
= asprintf (&server
, "%s/%s", IPROP_NAME
, serverhost
);
132 if (aret
== -1 || server
== NULL
)
133 krb5_errx (context
, 1, "malloc: no memory");
135 ret
= krb5_get_init_creds_keytab(context
, &creds
, client
, keytab
,
136 0, server
, init_opts
);
138 krb5_get_init_creds_opt_free(context
, init_opts
);
139 if(ret
) krb5_err(context
, 1, ret
, "krb5_get_init_creds");
141 ret
= krb5_kt_close(context
, keytab
);
142 if(ret
) krb5_err(context
, 1, ret
, "krb5_kt_close");
144 ret
= krb5_cc_new_unique(context
, krb5_cc_type_memory
, NULL
, cache
);
145 if(ret
) krb5_err(context
, 1, ret
, "krb5_cc_new_unique");
147 ret
= krb5_cc_initialize(context
, *cache
, client
);
148 if(ret
) krb5_err(context
, 1, ret
, "krb5_cc_initialize");
150 ret
= krb5_cc_store_cred(context
, *cache
, &creds
);
151 if(ret
) krb5_err(context
, 1, ret
, "krb5_cc_store_cred");
153 krb5_free_cred_contents(context
, &creds
);
154 krb5_free_principal(context
, client
);
157 static krb5_error_code
158 ihave (krb5_context context
, krb5_auth_context auth_context
,
159 int fd
, uint32_t version
)
166 sp
= krb5_storage_from_mem (buf
, 8);
167 krb5_store_int32 (sp
, I_HAVE
);
168 krb5_store_int32 (sp
, version
);
169 krb5_storage_free (sp
);
173 ret
= krb5_write_priv_message(context
, auth_context
, &fd
, &data
);
175 krb5_warn (context
, ret
, "krb5_write_message");
180 receive_loop (krb5_context context
,
182 kadm5_server_context
*server_context
)
191 * Seek to the current version of the local database.
194 int32_t len
, timestamp
, tmp
;
196 if(krb5_ret_int32 (sp
, &vers
) != 0)
198 krb5_ret_int32 (sp
, ×tamp
);
199 krb5_ret_int32 (sp
, &tmp
);
200 krb5_ret_int32 (sp
, &len
);
201 if ((uint32_t)vers
<= server_context
->log_context
.version
)
202 krb5_storage_seek(sp
, len
+ 8, SEEK_CUR
);
203 } while((uint32_t)vers
<= server_context
->log_context
.version
);
206 * Read up rest of the entires into the memory...
208 left
= krb5_storage_seek (sp
, -16, SEEK_CUR
);
209 right
= krb5_storage_seek (sp
, 0, SEEK_END
);
210 buf
= malloc (right
- left
);
211 if (buf
== NULL
&& (right
- left
) != 0)
212 krb5_errx (context
, 1, "malloc: no memory");
215 * ...and then write them out to the on-disk log.
217 krb5_storage_seek (sp
, left
, SEEK_SET
);
218 krb5_storage_read (sp
, buf
, right
- left
);
219 sret
= write (server_context
->log_context
.log_fd
, buf
, right
-left
);
220 if (sret
!= right
- left
)
221 krb5_err(context
, 1, errno
, "Failed to write log to disk");
222 ret
= fsync (server_context
->log_context
.log_fd
);
224 krb5_err(context
, 1, errno
, "Failed to sync log to disk");
228 * Go back to the startpoint and start to commit the entires to
231 krb5_storage_seek (sp
, left
, SEEK_SET
);
234 int32_t len
, len2
, timestamp
, tmp
;
238 if(krb5_ret_int32 (sp
, &vers
) != 0)
240 ret
= krb5_ret_int32 (sp
, ×tamp
);
241 if (ret
) krb5_errx(context
, 1, "entry %ld: too short", (long)vers
);
242 ret
= krb5_ret_int32 (sp
, &tmp
);
243 if (ret
) krb5_errx(context
, 1, "entry %ld: too short", (long)vers
);
245 ret
= krb5_ret_int32 (sp
, &len
);
246 if (ret
) krb5_errx(context
, 1, "entry %ld: too short", (long)vers
);
248 krb5_errx(context
, 1, "log is corrupted, "
249 "negative length of entry version %ld: %ld",
250 (long)vers
, (long)len
);
251 cur
= krb5_storage_seek(sp
, 0, SEEK_CUR
);
253 krb5_warnx (context
, "replaying entry %d", (int)vers
);
255 ret
= kadm5_log_replay (server_context
,
258 const char *s
= krb5_get_error_message(server_context
->context
, ret
);
260 "kadm5_log_replay: %ld. Lost entry entry, "
261 "Database out of sync ?: %s (%d)",
262 (long)vers
, s
? s
: "unknown error", ret
);
263 krb5_free_error_message(context
, s
);
268 * Make sure the krb5_log_replay does the right thing wrt
269 * reading out data from the sp.
271 cur2
= krb5_storage_seek(sp
, 0, SEEK_CUR
);
272 if (cur
+ len
!= cur2
)
273 krb5_errx(context
, 1,
274 "kadm5_log_reply version: %ld didn't read the whole entry",
278 if (krb5_ret_int32 (sp
, &len2
) != 0)
279 krb5_errx(context
, 1, "entry %ld: postamble too short", (long)vers
);
280 if(krb5_ret_int32 (sp
, &vers2
) != 0)
281 krb5_errx(context
, 1, "entry %ld: postamble too short", (long)vers
);
284 krb5_errx(context
, 1, "entry %ld: len != len2", (long)vers
);
286 krb5_errx(context
, 1, "entry %ld: vers != vers2", (long)vers
);
293 server_context
->log_context
.version
= vers
;
297 receive (krb5_context context
,
299 kadm5_server_context
*server_context
)
303 ret
= server_context
->db
->hdb_open(context
,
305 O_RDWR
| O_CREAT
, 0600);
307 krb5_err (context
, 1, ret
, "db->open");
309 receive_loop (context
, sp
, server_context
);
311 ret
= server_context
->db
->hdb_close (context
, server_context
->db
);
313 krb5_err (context
, 1, ret
, "db->close");
317 send_im_here (krb5_context context
, int fd
,
318 krb5_auth_context auth_context
)
324 ret
= krb5_data_alloc (&data
, 4);
326 krb5_err (context
, 1, ret
, "send_im_here");
328 sp
= krb5_storage_from_data (&data
);
330 krb5_errx (context
, 1, "krb5_storage_from_data");
331 krb5_store_int32(sp
, I_AM_HERE
);
332 krb5_storage_free(sp
);
334 ret
= krb5_write_priv_message(context
, auth_context
, &fd
, &data
);
335 krb5_data_free(&data
);
338 krb5_err (context
, 1, ret
, "krb5_write_priv_message");
342 reinit_log(krb5_context context
,
343 kadm5_server_context
*server_context
,
348 ret
= kadm5_log_reinit (server_context
);
350 krb5_err(context
, 1, ret
, "kadm5_log_reinit");
352 ret
= kadm5_log_set_version (server_context
, vno
- 1);
354 krb5_err (context
, 1, ret
, "kadm5_log_set_version");
356 ret
= kadm5_log_nop (server_context
);
358 krb5_err (context
, 1, ret
, "kadm5_log_nop");
362 static krb5_error_code
363 receive_everything (krb5_context context
, int fd
,
364 kadm5_server_context
*server_context
,
365 krb5_auth_context auth_context
)
376 krb5_warnx(context
, "receive complete database");
378 ret
= asprintf(&dbname
, "%s-NEW", server_context
->db
->hdb_name
);
380 krb5_err(context
, 1, ENOMEM
, "asprintf");
381 ret
= hdb_create(context
, &mydb
, dbname
);
383 krb5_err(context
,1, ret
, "hdb_create");
386 ret
= hdb_set_master_keyfile (context
,
387 mydb
, server_context
->config
.stash_file
);
389 krb5_err(context
,1, ret
, "hdb_set_master_keyfile");
391 /* I really want to use O_EXCL here, but given that I can't easily clean
392 up on error, I won't */
393 ret
= mydb
->hdb_open(context
, mydb
, O_RDWR
| O_CREAT
| O_TRUNC
, 0600);
395 krb5_err (context
, 1, ret
, "db->open");
398 krb5_data_zero(&data
);
400 ret
= krb5_read_priv_message(context
, auth_context
, &fd
, &data
);
403 krb5_warn (context
, ret
, "krb5_read_priv_message");
407 sp
= krb5_storage_from_data (&data
);
409 krb5_errx (context
, 1, "krb5_storage_from_data");
410 krb5_ret_int32 (sp
, &opcode
);
411 if (opcode
== ONE_PRINC
) {
415 krb5_storage_free(sp
);
417 fake_data
.data
= (char *)data
.data
+ 4;
418 fake_data
.length
= data
.length
- 4;
420 memset(&entry
, 0, sizeof(entry
));
422 ret
= hdb_value2entry (context
, &fake_data
, &entry
.entry
);
424 krb5_err (context
, 1, ret
, "hdb_value2entry");
425 ret
= mydb
->hdb_store(server_context
->context
,
429 krb5_err (context
, 1, ret
, "hdb_store");
431 hdb_free_entry (context
, &entry
);
432 krb5_data_free (&data
);
433 } else if (opcode
== NOW_YOU_HAVE
)
436 krb5_errx (context
, 1, "strange opcode %d", opcode
);
437 } while (opcode
== ONE_PRINC
);
439 if (opcode
!= NOW_YOU_HAVE
)
440 krb5_errx (context
, 1, "receive_everything: strange %d", opcode
);
442 krb5_ret_int32 (sp
, &vno
);
443 krb5_storage_free(sp
);
445 reinit_log(context
, server_context
, vno
);
447 ret
= mydb
->hdb_rename (context
, mydb
, server_context
->db
->hdb_name
);
449 krb5_err (context
, 1, ret
, "db->rename");
452 krb5_data_free (&data
);
454 ret
= mydb
->hdb_close (context
, mydb
);
456 krb5_err (context
, 1, ret
, "db->close");
458 ret
= mydb
->hdb_destroy (context
, mydb
);
460 krb5_err (context
, 1, ret
, "db->destroy");
462 krb5_warnx(context
, "receive complete database, version %ld", (long)vno
);
467 slave_status(krb5_context context
,
469 const char *status
, ...)
470 __attribute__ ((format (printf
, 3, 4)));
474 slave_status(krb5_context context
,
476 const char *fmt
, ...)
483 len
= vasprintf(&status
, fmt
, args
);
485 if (len
< 0 || status
== NULL
) {
489 krb5_warnx(context
, "slave status change: %s", status
);
491 rk_dumpdata(file
, status
, len
);
496 is_up_to_date(krb5_context context
, const char *file
,
497 kadm5_server_context
*server_context
)
501 ret
= krb5_format_time(context
, time(NULL
), buf
, sizeof(buf
), 1);
506 slave_status(context
, file
, "up-to-date with version: %lu at %s",
507 (unsigned long)server_context
->log_context
.version
, buf
);
510 static char *status_file
;
511 static char *config_file
;
513 static int version_flag
;
514 static int help_flag
;
515 static char *keytab_str
;
516 static char *port_str
;
517 #ifdef SUPPORT_DETACH
518 static int detach_from_console
= 0;
521 static struct getargs args
[] = {
522 { "config-file", 'c', arg_string
, &config_file
, NULL
, NULL
},
523 { "realm", 'r', arg_string
, &realm
, NULL
, NULL
},
524 { "keytab", 'k', arg_string
, &keytab_str
,
525 "keytab to get authentication from", "kspec" },
526 { "time-lost", 0, arg_string
, &server_time_lost
,
527 "time before server is considered lost", "time" },
528 { "status-file", 0, arg_string
, &status_file
,
529 "file to write out status into", "file" },
530 { "port", 0, arg_string
, &port_str
,
531 "port ipropd-slave will connect to", "port"},
532 #ifdef SUPPORT_DETACH
533 { "detach", 0, arg_flag
, &detach_from_console
,
534 "detach from console", NULL
},
536 { "hostname", 0, arg_string
, rk_UNCONST(&slave_str
),
537 "hostname of slave (if not same as hostname)", "hostname" },
538 { "version", 0, arg_flag
, &version_flag
, NULL
, NULL
},
539 { "help", 0, arg_flag
, &help_flag
, NULL
, NULL
}
542 static int num_args
= sizeof(args
) / sizeof(args
[0]);
547 arg_printusage(args
, num_args
, NULL
, "master");
552 main(int argc
, char **argv
)
555 krb5_context context
;
556 krb5_auth_context auth_context
;
558 kadm5_server_context
*server_context
;
559 kadm5_config_params conf
;
562 krb5_principal server
;
565 time_t reconnect_min
;
567 time_t reconnect_max
;
573 setprogname(argv
[0]);
575 if(getarg(args
, num_args
, argc
, argv
, &optidx
))
585 ret
= krb5_init_context(&context
);
587 errx (1, "krb5_init_context failed: %d", ret
);
591 if (config_file
== NULL
) {
592 if (asprintf(&config_file
, "%s/kdc.conf", hdb_db_dir(context
)) == -1
593 || config_file
== NULL
)
594 errx(1, "out of memory");
597 ret
= krb5_prepend_config_files_default(config_file
, &files
);
599 krb5_err(context
, 1, ret
, "getting configuration files");
601 ret
= krb5_set_config_files(context
, files
);
602 krb5_free_config_files(files
);
604 krb5_err(context
, 1, ret
, "reading configuration files");
614 if (status_file
== NULL
) {
615 if (asprintf(&status_file
, "%s/ipropd-slave-status", hdb_db_dir(context
)) < 0 || status_file
== NULL
)
616 krb5_errx(context
, 1, "can't allocate status file buffer");
619 #ifdef SUPPORT_DETACH
620 if (detach_from_console
){
621 int aret
= daemon(0, 0);
623 /* not much to do if detaching fails... */
624 krb5_err(context
, 1, aret
, "failed to daemon(3)ise");
629 krb5_openlog (context
, "ipropd-slave", &log_facility
);
630 krb5_set_warn_dest(context
, log_facility
);
632 slave_status(context
, status_file
, "bootstrapping");
634 ret
= krb5_kt_register(context
, &hdb_kt_ops
);
636 krb5_err(context
, 1, ret
, "krb5_kt_register");
638 time_before_lost
= parse_time (server_time_lost
, "s");
639 if (time_before_lost
< 0)
640 krb5_errx (context
, 1, "couldn't parse time: %s", server_time_lost
);
642 slave_status(context
, status_file
, "getting credentials from keytab/database");
644 memset(&conf
, 0, sizeof(conf
));
646 conf
.mask
|= KADM5_CONFIG_REALM
;
649 ret
= kadm5_init_with_password_ctx (context
,
656 krb5_err (context
, 1, ret
, "kadm5_init_with_password_ctx");
658 server_context
= (kadm5_server_context
*)kadm_handle
;
660 slave_status(context
, status_file
, "creating log file");
662 ret
= kadm5_log_init (server_context
);
664 krb5_err (context
, 1, ret
, "kadm5_log_init");
666 get_creds(context
, keytab_str
, &ccache
, master
);
668 ret
= krb5_sname_to_principal (context
, master
, IPROP_NAME
,
669 KRB5_NT_SRV_HST
, &server
);
671 krb5_err (context
, 1, ret
, "krb5_sname_to_principal");
676 krb5_appdefault_time(context
, config_name
, NULL
, "reconnect-min",
678 krb5_appdefault_time(context
, config_name
, NULL
, "reconnect-max",
679 300, &reconnect_max
);
680 krb5_appdefault_time(context
, config_name
, NULL
, "reconnect-backoff",
682 reconnect
= reconnect_min
;
684 slave_status(context
, status_file
, "ipropd-slave started");
688 int connected
= FALSE
;
691 elapsed
= now
- before
;
693 if (elapsed
< reconnect
) {
694 time_t left
= reconnect
- elapsed
;
695 krb5_warnx(context
, "sleeping %d seconds before "
696 "retrying to connect", (int)left
);
701 slave_status(context
, status_file
, "connecting to master: %s\n", master
);
703 master_fd
= connect_to_master (context
, master
, port_str
);
707 reconnect
= reconnect_min
;
710 krb5_auth_con_free(context
, auth_context
);
712 krb5_cc_destroy(context
, ccache
);
713 get_creds(context
, keytab_str
, &ccache
, master
);
715 ret
= krb5_sendauth (context
, &auth_context
, &master_fd
,
716 IPROP_VERSION
, NULL
, server
,
717 AP_OPTS_MUTUAL_REQUIRED
, NULL
, NULL
,
718 ccache
, NULL
, NULL
, NULL
);
720 krb5_warn (context
, ret
, "krb5_sendauth");
724 krb5_warnx(context
, "ipropd-slave started at version: %ld",
725 (long)server_context
->log_context
.version
);
727 ret
= ihave (context
, auth_context
, master_fd
,
728 server_context
->log_context
.version
);
734 slave_status(context
, status_file
, "connected to master, waiting instructions");
736 while (connected
&& !exit_flag
) {
743 #ifndef NO_LIMIT_FD_SETSIZE
744 if (master_fd
>= FD_SETSIZE
)
745 krb5_errx (context
, 1, "fd too large");
749 FD_SET(master_fd
, &readset
);
751 to
.tv_sec
= time_before_lost
;
754 ret
= select (master_fd
+ 1,
755 &readset
, NULL
, NULL
, &to
);
760 krb5_err (context
, 1, errno
, "select");
763 krb5_errx (context
, 1, "server didn't send a message "
764 "in %d seconds", time_before_lost
);
766 ret
= krb5_read_priv_message(context
, auth_context
, &master_fd
, &out
);
768 krb5_warn (context
, ret
, "krb5_read_priv_message");
773 sp
= krb5_storage_from_mem (out
.data
, out
.length
);
774 krb5_ret_int32 (sp
, &tmp
);
777 receive (context
, sp
, server_context
);
778 ret
= ihave (context
, auth_context
, master_fd
,
779 server_context
->log_context
.version
);
783 is_up_to_date(context
, status_file
, server_context
);
787 case TELL_YOU_EVERYTHING
:
788 ret
= receive_everything (context
, master_fd
, server_context
,
793 is_up_to_date(context
, status_file
, server_context
);
796 is_up_to_date(context
, status_file
, server_context
);
797 send_im_here (context
, master_fd
, auth_context
);
799 case YOU_HAVE_LAST_VERSION
:
800 is_up_to_date(context
, status_file
, server_context
);
807 krb5_warnx (context
, "Ignoring command %d", tmp
);
810 krb5_storage_free (sp
);
811 krb5_data_free (&out
);
815 slave_status(context
, status_file
, "disconnected from master");
817 if (connected
== FALSE
)
818 krb5_warnx (context
, "disconnected for server");
821 krb5_warnx (context
, "got an exit signal");
826 reconnect
+= backoff
;
827 if (reconnect
> reconnect_max
) {
828 slave_status(context
, status_file
, "disconnected from master for a long time");
829 reconnect
= reconnect_max
;
838 else if(exit_flag
== SIGXCPU
)
839 krb5_warnx(context
, "%s CPU time limit exceeded", getprogname());
841 else if(exit_flag
== SIGINT
|| exit_flag
== SIGTERM
)
842 krb5_warnx(context
, "%s terminated", getprogname());
844 krb5_warnx(context
, "%s unexpected exit reason: %ld",
845 getprogname(), (long)exit_flag
);