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 *server_time_lost
= "5 min";
42 static int time_before_lost
;
43 const char *slave_str
= NULL
;
46 connect_to_master (krb5_context context
, const char *master
,
49 char port
[NI_MAXSERV
];
50 struct addrinfo
*ai
, *a
;
51 struct addrinfo hints
;
55 memset (&hints
, 0, sizeof(hints
));
56 hints
.ai_socktype
= SOCK_STREAM
;
58 if (port_str
== NULL
) {
59 snprintf(port
, sizeof(port
), "%u", IPROP_PORT
);
63 error
= getaddrinfo (master
, port_str
, &hints
, &ai
);
65 krb5_warnx(context
, "Failed to get address of to %s: %s",
66 master
, gai_strerror(error
));
70 for (a
= ai
; a
!= NULL
; a
= a
->ai_next
) {
71 char node
[NI_MAXHOST
];
72 error
= getnameinfo(a
->ai_addr
, a
->ai_addrlen
,
73 node
, sizeof(node
), NULL
, 0, NI_NUMERICHOST
);
75 strlcpy(node
, "[unknown-addr]", sizeof(node
));
77 s
= socket (a
->ai_family
, a
->ai_socktype
, a
->ai_protocol
);
80 if (connect (s
, a
->ai_addr
, a
->ai_addrlen
) < 0) {
81 krb5_warn(context
, errno
, "connection failed to %s[%s]",
86 krb5_warnx(context
, "connection successful "
87 "to master: %s[%s]", master
, node
);
99 get_creds(krb5_context context
, const char *keytab_str
,
100 krb5_ccache
*cache
, const char *serverhost
)
103 krb5_principal client
;
105 krb5_get_init_creds_opt
*init_opts
;
108 char keytab_buf
[256];
110 if (keytab_str
== NULL
) {
111 ret
= krb5_kt_default_name (context
, keytab_buf
, sizeof(keytab_buf
));
113 krb5_err (context
, 1, ret
, "krb5_kt_default_name");
114 keytab_str
= keytab_buf
;
117 ret
= krb5_kt_resolve(context
, keytab_str
, &keytab
);
119 krb5_err(context
, 1, ret
, "%s", keytab_str
);
122 ret
= krb5_sname_to_principal (context
, slave_str
, IPROP_NAME
,
123 KRB5_NT_SRV_HST
, &client
);
124 if (ret
) krb5_err(context
, 1, ret
, "krb5_sname_to_principal");
126 ret
= krb5_get_init_creds_opt_alloc(context
, &init_opts
);
127 if (ret
) krb5_err(context
, 1, ret
, "krb5_get_init_creds_opt_alloc");
129 asprintf (&server
, "%s/%s", IPROP_NAME
, serverhost
);
131 krb5_errx (context
, 1, "malloc: no memory");
133 ret
= krb5_get_init_creds_keytab(context
, &creds
, client
, keytab
,
134 0, server
, init_opts
);
136 krb5_get_init_creds_opt_free(context
, init_opts
);
137 if(ret
) krb5_err(context
, 1, ret
, "krb5_get_init_creds");
139 ret
= krb5_kt_close(context
, keytab
);
140 if(ret
) krb5_err(context
, 1, ret
, "krb5_kt_close");
142 ret
= krb5_cc_new_unique(context
, krb5_cc_type_memory
, NULL
, cache
);
143 if(ret
) krb5_err(context
, 1, ret
, "krb5_cc_new_unique");
145 ret
= krb5_cc_initialize(context
, *cache
, client
);
146 if(ret
) krb5_err(context
, 1, ret
, "krb5_cc_initialize");
148 ret
= krb5_cc_store_cred(context
, *cache
, &creds
);
149 if(ret
) krb5_err(context
, 1, ret
, "krb5_cc_store_cred");
151 krb5_free_cred_contents(context
, &creds
);
152 krb5_free_principal(context
, client
);
155 static krb5_error_code
156 ihave (krb5_context context
, krb5_auth_context auth_context
,
157 int fd
, uint32_t version
)
164 sp
= krb5_storage_from_mem (buf
, 8);
165 krb5_store_int32 (sp
, I_HAVE
);
166 krb5_store_int32 (sp
, version
);
167 krb5_storage_free (sp
);
171 ret
= krb5_write_priv_message(context
, auth_context
, &fd
, &data
);
173 krb5_warn (context
, ret
, "krb5_write_message");
178 receive_loop (krb5_context context
,
180 kadm5_server_context
*server_context
)
189 * Seek to the current version of the local database.
192 int32_t len
, timestamp
, tmp
;
195 if(krb5_ret_int32 (sp
, &vers
) != 0)
197 krb5_ret_int32 (sp
, ×tamp
);
198 krb5_ret_int32 (sp
, &tmp
);
200 krb5_ret_int32 (sp
, &len
);
201 if (vers
<= server_context
->log_context
.version
)
202 krb5_storage_seek(sp
, len
+ 8, SEEK_CUR
);
203 } while(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");
341 static krb5_error_code
342 receive_everything (krb5_context context
, int fd
,
343 kadm5_server_context
*server_context
,
344 krb5_auth_context auth_context
)
355 krb5_warnx(context
, "receive complete database");
357 asprintf(&dbname
, "%s-NEW", server_context
->db
->hdb_name
);
358 ret
= hdb_create(context
, &mydb
, dbname
);
360 krb5_err(context
,1, ret
, "hdb_create");
363 ret
= hdb_set_master_keyfile (context
,
364 mydb
, server_context
->config
.stash_file
);
366 krb5_err(context
,1, ret
, "hdb_set_master_keyfile");
368 /* I really want to use O_EXCL here, but given that I can't easily clean
369 up on error, I won't */
370 ret
= mydb
->hdb_open(context
, mydb
, O_RDWR
| O_CREAT
| O_TRUNC
, 0600);
372 krb5_err (context
, 1, ret
, "db->open");
376 ret
= krb5_read_priv_message(context
, auth_context
, &fd
, &data
);
379 krb5_warn (context
, ret
, "krb5_read_priv_message");
383 sp
= krb5_storage_from_data (&data
);
385 krb5_errx (context
, 1, "krb5_storage_from_data");
386 krb5_ret_int32 (sp
, &opcode
);
387 if (opcode
== ONE_PRINC
) {
391 krb5_storage_free(sp
);
393 fake_data
.data
= (char *)data
.data
+ 4;
394 fake_data
.length
= data
.length
- 4;
396 memset(&entry
, 0, sizeof(entry
));
398 ret
= hdb_value2entry (context
, &fake_data
, &entry
.entry
);
400 krb5_err (context
, 1, ret
, "hdb_value2entry");
401 ret
= mydb
->hdb_store(server_context
->context
,
405 krb5_err (context
, 1, ret
, "hdb_store");
407 hdb_free_entry (context
, &entry
);
408 krb5_data_free (&data
);
409 } else if (opcode
== NOW_YOU_HAVE
)
412 krb5_errx (context
, 1, "strange opcode %d", opcode
);
413 } while (opcode
== ONE_PRINC
);
415 if (opcode
!= NOW_YOU_HAVE
)
416 krb5_errx (context
, 1, "receive_everything: strange %d", opcode
);
418 krb5_ret_int32 (sp
, &vno
);
419 krb5_storage_free(sp
);
421 ret
= kadm5_log_reinit (server_context
);
423 krb5_err(context
, 1, ret
, "kadm5_log_reinit");
425 ret
= kadm5_log_set_version (server_context
, vno
- 1);
427 krb5_err (context
, 1, ret
, "kadm5_log_set_version");
429 ret
= kadm5_log_nop (server_context
);
431 krb5_err (context
, 1, ret
, "kadm5_log_nop");
433 ret
= mydb
->hdb_rename (context
, mydb
, server_context
->db
->hdb_name
);
435 krb5_err (context
, 1, ret
, "db->rename");
438 krb5_data_free (&data
);
440 ret
= mydb
->hdb_close (context
, mydb
);
442 krb5_err (context
, 1, ret
, "db->close");
444 ret
= mydb
->hdb_destroy (context
, mydb
);
446 krb5_err (context
, 1, ret
, "db->destroy");
448 krb5_warnx(context
, "receive complete database, version %ld", (long)vno
);
452 static char *config_file
;
454 static int version_flag
;
455 static int help_flag
;
456 static char *keytab_str
;
457 static char *port_str
;
458 #ifdef SUPPORT_DETACH
459 static int detach_from_console
= 0;
462 static struct getargs args
[] = {
463 { "config-file", 'c', arg_string
, &config_file
},
464 { "realm", 'r', arg_string
, &realm
},
465 { "keytab", 'k', arg_string
, &keytab_str
,
466 "keytab to get authentication from", "kspec" },
467 { "time-lost", 0, arg_string
, &server_time_lost
,
468 "time before server is considered lost", "time" },
469 { "port", 0, arg_string
, &port_str
,
470 "port ipropd-slave will connect to", "port"},
471 #ifdef SUPPORT_DETACH
472 { "detach", 0, arg_flag
, &detach_from_console
,
473 "detach from console" },
475 { "hostname", 0, arg_string
, &slave_str
,
476 "hostname of slave (if not same as hostname)", "hostname" },
477 { "version", 0, arg_flag
, &version_flag
},
478 { "help", 0, arg_flag
, &help_flag
}
481 static int num_args
= sizeof(args
) / sizeof(args
[0]);
486 arg_printusage(args
, num_args
, NULL
, "master");
491 main(int argc
, char **argv
)
494 krb5_context context
;
495 krb5_auth_context auth_context
;
497 kadm5_server_context
*server_context
;
498 kadm5_config_params conf
;
501 krb5_principal server
;
504 time_t reconnect_min
;
506 time_t reconnect_max
;
512 setprogname(argv
[0]);
514 if(getarg(args
, num_args
, argc
, argv
, &optidx
))
524 ret
= krb5_init_context(&context
);
526 errx (1, "krb5_init_context failed: %d", ret
);
530 if (config_file
== NULL
) {
531 asprintf(&config_file
, "%s/kdc.conf", hdb_db_dir(context
));
532 if (config_file
== NULL
)
533 errx(1, "out of memory");
536 ret
= krb5_prepend_config_files_default(config_file
, &files
);
538 krb5_err(context
, 1, ret
, "getting configuration files");
540 ret
= krb5_set_config_files(context
, files
);
541 krb5_free_config_files(files
);
543 krb5_err(context
, 1, ret
, "reading configuration files");
553 #ifdef SUPPORT_DETACH
554 if (detach_from_console
)
558 krb5_openlog (context
, "ipropd-slave", &log_facility
);
559 krb5_set_warn_dest(context
, log_facility
);
561 ret
= krb5_kt_register(context
, &hdb_kt_ops
);
563 krb5_err(context
, 1, ret
, "krb5_kt_register");
565 time_before_lost
= parse_time (server_time_lost
, "s");
566 if (time_before_lost
< 0)
567 krb5_errx (context
, 1, "couldn't parse time: %s", server_time_lost
);
569 memset(&conf
, 0, sizeof(conf
));
571 conf
.mask
|= KADM5_CONFIG_REALM
;
574 ret
= kadm5_init_with_password_ctx (context
,
581 krb5_err (context
, 1, ret
, "kadm5_init_with_password_ctx");
583 server_context
= (kadm5_server_context
*)kadm_handle
;
585 ret
= kadm5_log_init (server_context
);
587 krb5_err (context
, 1, ret
, "kadm5_log_init");
589 get_creds(context
, keytab_str
, &ccache
, master
);
591 ret
= krb5_sname_to_principal (context
, master
, IPROP_NAME
,
592 KRB5_NT_SRV_HST
, &server
);
594 krb5_err (context
, 1, ret
, "krb5_sname_to_principal");
599 krb5_appdefault_time(context
, config_name
, NULL
, "reconnect-min",
601 krb5_appdefault_time(context
, config_name
, NULL
, "reconnect-max",
602 300, &reconnect_max
);
603 krb5_appdefault_time(context
, config_name
, NULL
, "reconnect-backoff",
605 reconnect
= reconnect_min
;
609 int connected
= FALSE
;
612 elapsed
= now
- before
;
614 if (elapsed
< reconnect
) {
615 time_t left
= reconnect
- elapsed
;
616 krb5_warnx(context
, "sleeping %d seconds before "
617 "retrying to connect", (int)left
);
622 master_fd
= connect_to_master (context
, master
, port_str
);
626 reconnect
= reconnect_min
;
629 krb5_auth_con_free(context
, auth_context
);
631 krb5_cc_destroy(context
, ccache
);
632 get_creds(context
, keytab_str
, &ccache
, master
);
634 ret
= krb5_sendauth (context
, &auth_context
, &master_fd
,
635 IPROP_VERSION
, NULL
, server
,
636 AP_OPTS_MUTUAL_REQUIRED
, NULL
, NULL
,
637 ccache
, NULL
, NULL
, NULL
);
639 krb5_warn (context
, ret
, "krb5_sendauth");
643 krb5_warnx(context
, "ipropd-slave started at version: %ld",
644 (long)server_context
->log_context
.version
);
646 ret
= ihave (context
, auth_context
, master_fd
,
647 server_context
->log_context
.version
);
653 while (connected
&& !exit_flag
) {
660 if (master_fd
>= FD_SETSIZE
)
661 krb5_errx (context
, 1, "fd too large");
664 FD_SET(master_fd
, &readset
);
666 to
.tv_sec
= time_before_lost
;
669 ret
= select (master_fd
+ 1,
670 &readset
, NULL
, NULL
, &to
);
675 krb5_err (context
, 1, errno
, "select");
678 krb5_errx (context
, 1, "server didn't send a message "
679 "in %d seconds", time_before_lost
);
681 ret
= krb5_read_priv_message(context
, auth_context
, &master_fd
, &out
);
683 krb5_warn (context
, ret
, "krb5_read_priv_message");
688 sp
= krb5_storage_from_mem (out
.data
, out
.length
);
689 krb5_ret_int32 (sp
, &tmp
);
692 receive (context
, sp
, server_context
);
693 ret
= ihave (context
, auth_context
, master_fd
,
694 server_context
->log_context
.version
);
698 case TELL_YOU_EVERYTHING
:
699 ret
= receive_everything (context
, master_fd
, server_context
,
705 send_im_here (context
, master_fd
, auth_context
);
712 krb5_warnx (context
, "Ignoring command %d", tmp
);
715 krb5_storage_free (sp
);
716 krb5_data_free (&out
);
720 if (connected
== FALSE
)
721 krb5_warnx (context
, "disconnected for server");
723 krb5_warnx (context
, "got an exit signal");
728 reconnect
+= backoff
;
729 if (reconnect
> reconnect_max
)
730 reconnect
= reconnect_max
;
733 if(exit_flag
== SIGXCPU
)
734 krb5_warnx(context
, "%s CPU time limit exceeded", getprogname());
735 else if(exit_flag
== SIGINT
|| exit_flag
== SIGTERM
)
736 krb5_warnx(context
, "%s terminated", getprogname());
738 krb5_warnx(context
, "%s unexpected exit reason: %ld",
739 getprogname(), (long)exit_flag
);