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];
111 if (keytab_str
== NULL
) {
112 ret
= krb5_kt_default_name (context
, keytab_buf
, sizeof(keytab_buf
));
114 krb5_err (context
, 1, ret
, "krb5_kt_default_name");
115 keytab_str
= keytab_buf
;
118 ret
= krb5_kt_resolve(context
, keytab_str
, &keytab
);
120 krb5_err(context
, 1, ret
, "%s", keytab_str
);
123 ret
= krb5_sname_to_principal (context
, slave_str
, IPROP_NAME
,
124 KRB5_NT_SRV_HST
, &client
);
125 if (ret
) krb5_err(context
, 1, ret
, "krb5_sname_to_principal");
127 ret
= krb5_get_init_creds_opt_alloc(context
, &init_opts
);
128 if (ret
) krb5_err(context
, 1, ret
, "krb5_get_init_creds_opt_alloc");
130 asprintf (&server
, "%s/%s", IPROP_NAME
, serverhost
);
132 krb5_errx (context
, 1, "malloc: no memory");
134 ret
= krb5_get_init_creds_keytab(context
, &creds
, client
, keytab
,
135 0, server
, init_opts
);
137 krb5_get_init_creds_opt_free(context
, init_opts
);
138 if(ret
) krb5_err(context
, 1, ret
, "krb5_get_init_creds");
140 ret
= krb5_kt_close(context
, keytab
);
141 if(ret
) krb5_err(context
, 1, ret
, "krb5_kt_close");
143 ret
= krb5_cc_new_unique(context
, krb5_cc_type_memory
, NULL
, cache
);
144 if(ret
) krb5_err(context
, 1, ret
, "krb5_cc_new_unique");
146 ret
= krb5_cc_initialize(context
, *cache
, client
);
147 if(ret
) krb5_err(context
, 1, ret
, "krb5_cc_initialize");
149 ret
= krb5_cc_store_cred(context
, *cache
, &creds
);
150 if(ret
) krb5_err(context
, 1, ret
, "krb5_cc_store_cred");
152 krb5_free_cred_contents(context
, &creds
);
153 krb5_free_principal(context
, client
);
156 static krb5_error_code
157 ihave (krb5_context context
, krb5_auth_context auth_context
,
158 int fd
, uint32_t version
)
165 sp
= krb5_storage_from_mem (buf
, 8);
166 krb5_store_int32 (sp
, I_HAVE
);
167 krb5_store_int32 (sp
, version
);
168 krb5_storage_free (sp
);
172 ret
= krb5_write_priv_message(context
, auth_context
, &fd
, &data
);
174 krb5_warn (context
, ret
, "krb5_write_message");
179 receive_loop (krb5_context context
,
181 kadm5_server_context
*server_context
)
190 * Seek to the current version of the local database.
193 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
);
201 krb5_ret_int32 (sp
, &len
);
202 if ((uint32_t)vers
<= server_context
->log_context
.version
)
203 krb5_storage_seek(sp
, len
+ 8, SEEK_CUR
);
204 } while((uint32_t)vers
<= server_context
->log_context
.version
);
207 * Read up rest of the entires into the memory...
209 left
= krb5_storage_seek (sp
, -16, SEEK_CUR
);
210 right
= krb5_storage_seek (sp
, 0, SEEK_END
);
211 buf
= malloc (right
- left
);
212 if (buf
== NULL
&& (right
- left
) != 0)
213 krb5_errx (context
, 1, "malloc: no memory");
216 * ...and then write them out to the on-disk log.
218 krb5_storage_seek (sp
, left
, SEEK_SET
);
219 krb5_storage_read (sp
, buf
, right
- left
);
220 sret
= write (server_context
->log_context
.log_fd
, buf
, right
-left
);
221 if (sret
!= right
- left
)
222 krb5_err(context
, 1, errno
, "Failed to write log to disk");
223 ret
= fsync (server_context
->log_context
.log_fd
);
225 krb5_err(context
, 1, errno
, "Failed to sync log to disk");
229 * Go back to the startpoint and start to commit the entires to
232 krb5_storage_seek (sp
, left
, SEEK_SET
);
235 int32_t len
, len2
, timestamp
, tmp
;
239 if(krb5_ret_int32 (sp
, &vers
) != 0)
241 ret
= krb5_ret_int32 (sp
, ×tamp
);
242 if (ret
) krb5_errx(context
, 1, "entry %ld: too short", (long)vers
);
243 ret
= krb5_ret_int32 (sp
, &tmp
);
244 if (ret
) krb5_errx(context
, 1, "entry %ld: too short", (long)vers
);
246 ret
= krb5_ret_int32 (sp
, &len
);
247 if (ret
) krb5_errx(context
, 1, "entry %ld: too short", (long)vers
);
249 krb5_errx(context
, 1, "log is corrupted, "
250 "negative length of entry version %ld: %ld",
251 (long)vers
, (long)len
);
252 cur
= krb5_storage_seek(sp
, 0, SEEK_CUR
);
254 krb5_warnx (context
, "replaying entry %d", (int)vers
);
256 ret
= kadm5_log_replay (server_context
,
259 const char *s
= krb5_get_error_message(server_context
->context
, ret
);
261 "kadm5_log_replay: %ld. Lost entry entry, "
262 "Database out of sync ?: %s (%d)",
263 (long)vers
, s
? s
: "unknown error", ret
);
264 krb5_free_error_message(context
, s
);
269 * Make sure the krb5_log_replay does the right thing wrt
270 * reading out data from the sp.
272 cur2
= krb5_storage_seek(sp
, 0, SEEK_CUR
);
273 if (cur
+ len
!= cur2
)
274 krb5_errx(context
, 1,
275 "kadm5_log_reply version: %ld didn't read the whole entry",
279 if (krb5_ret_int32 (sp
, &len2
) != 0)
280 krb5_errx(context
, 1, "entry %ld: postamble too short", (long)vers
);
281 if(krb5_ret_int32 (sp
, &vers2
) != 0)
282 krb5_errx(context
, 1, "entry %ld: postamble too short", (long)vers
);
285 krb5_errx(context
, 1, "entry %ld: len != len2", (long)vers
);
287 krb5_errx(context
, 1, "entry %ld: vers != vers2", (long)vers
);
294 server_context
->log_context
.version
= vers
;
298 receive (krb5_context context
,
300 kadm5_server_context
*server_context
)
304 ret
= server_context
->db
->hdb_open(context
,
306 O_RDWR
| O_CREAT
, 0600);
308 krb5_err (context
, 1, ret
, "db->open");
310 receive_loop (context
, sp
, server_context
);
312 ret
= server_context
->db
->hdb_close (context
, server_context
->db
);
314 krb5_err (context
, 1, ret
, "db->close");
318 send_im_here (krb5_context context
, int fd
,
319 krb5_auth_context auth_context
)
325 ret
= krb5_data_alloc (&data
, 4);
327 krb5_err (context
, 1, ret
, "send_im_here");
329 sp
= krb5_storage_from_data (&data
);
331 krb5_errx (context
, 1, "krb5_storage_from_data");
332 krb5_store_int32(sp
, I_AM_HERE
);
333 krb5_storage_free(sp
);
335 ret
= krb5_write_priv_message(context
, auth_context
, &fd
, &data
);
336 krb5_data_free(&data
);
339 krb5_err (context
, 1, ret
, "krb5_write_priv_message");
342 static krb5_error_code
343 receive_everything (krb5_context context
, int fd
,
344 kadm5_server_context
*server_context
,
345 krb5_auth_context auth_context
)
356 krb5_warnx(context
, "receive complete database");
358 asprintf(&dbname
, "%s-NEW", server_context
->db
->hdb_name
);
359 ret
= hdb_create(context
, &mydb
, dbname
);
361 krb5_err(context
,1, ret
, "hdb_create");
364 ret
= hdb_set_master_keyfile (context
,
365 mydb
, server_context
->config
.stash_file
);
367 krb5_err(context
,1, ret
, "hdb_set_master_keyfile");
369 /* I really want to use O_EXCL here, but given that I can't easily clean
370 up on error, I won't */
371 ret
= mydb
->hdb_open(context
, mydb
, O_RDWR
| O_CREAT
| O_TRUNC
, 0600);
373 krb5_err (context
, 1, ret
, "db->open");
377 ret
= krb5_read_priv_message(context
, auth_context
, &fd
, &data
);
380 krb5_warn (context
, ret
, "krb5_read_priv_message");
384 sp
= krb5_storage_from_data (&data
);
386 krb5_errx (context
, 1, "krb5_storage_from_data");
387 krb5_ret_int32 (sp
, &opcode
);
388 if (opcode
== ONE_PRINC
) {
392 krb5_storage_free(sp
);
394 fake_data
.data
= (char *)data
.data
+ 4;
395 fake_data
.length
= data
.length
- 4;
397 memset(&entry
, 0, sizeof(entry
));
399 ret
= hdb_value2entry (context
, &fake_data
, &entry
.entry
);
401 krb5_err (context
, 1, ret
, "hdb_value2entry");
402 ret
= mydb
->hdb_store(server_context
->context
,
406 krb5_err (context
, 1, ret
, "hdb_store");
408 hdb_free_entry (context
, &entry
);
409 krb5_data_free (&data
);
410 } else if (opcode
== NOW_YOU_HAVE
)
413 krb5_errx (context
, 1, "strange opcode %d", opcode
);
414 } while (opcode
== ONE_PRINC
);
416 if (opcode
!= NOW_YOU_HAVE
)
417 krb5_errx (context
, 1, "receive_everything: strange %d", opcode
);
419 krb5_ret_int32 (sp
, &vno
);
420 krb5_storage_free(sp
);
422 ret
= kadm5_log_reinit (server_context
);
424 krb5_err(context
, 1, ret
, "kadm5_log_reinit");
426 ret
= kadm5_log_set_version (server_context
, vno
- 1);
428 krb5_err (context
, 1, ret
, "kadm5_log_set_version");
430 ret
= kadm5_log_nop (server_context
);
432 krb5_err (context
, 1, ret
, "kadm5_log_nop");
434 ret
= mydb
->hdb_rename (context
, mydb
, server_context
->db
->hdb_name
);
436 krb5_err (context
, 1, ret
, "db->rename");
439 krb5_data_free (&data
);
441 ret
= mydb
->hdb_close (context
, mydb
);
443 krb5_err (context
, 1, ret
, "db->close");
445 ret
= mydb
->hdb_destroy (context
, mydb
);
447 krb5_err (context
, 1, ret
, "db->destroy");
449 krb5_warnx(context
, "receive complete database, version %ld", (long)vno
);
453 static char *config_file
;
455 static int version_flag
;
456 static int help_flag
;
457 static char *keytab_str
;
458 static char *port_str
;
459 #ifdef SUPPORT_DETACH
460 static int detach_from_console
= 0;
463 static struct getargs args
[] = {
464 { "config-file", 'c', arg_string
, &config_file
, NULL
, NULL
},
465 { "realm", 'r', arg_string
, &realm
, NULL
, NULL
},
466 { "keytab", 'k', arg_string
, &keytab_str
,
467 "keytab to get authentication from", "kspec" },
468 { "time-lost", 0, arg_string
, &server_time_lost
,
469 "time before server is considered lost", "time" },
470 { "port", 0, arg_string
, &port_str
,
471 "port ipropd-slave will connect to", "port"},
472 #ifdef SUPPORT_DETACH
473 { "detach", 0, arg_flag
, &detach_from_console
,
474 "detach from console", NULL
},
476 { "hostname", 0, arg_string
, rk_UNCONST(&slave_str
),
477 "hostname of slave (if not same as hostname)", "hostname" },
478 { "version", 0, arg_flag
, &version_flag
, NULL
, NULL
},
479 { "help", 0, arg_flag
, &help_flag
, NULL
, NULL
}
482 static int num_args
= sizeof(args
) / sizeof(args
[0]);
487 arg_printusage(args
, num_args
, NULL
, "master");
492 main(int argc
, char **argv
)
495 krb5_context context
;
496 krb5_auth_context auth_context
;
498 kadm5_server_context
*server_context
;
499 kadm5_config_params conf
;
502 krb5_principal server
;
505 time_t reconnect_min
;
507 time_t reconnect_max
;
513 setprogname(argv
[0]);
515 if(getarg(args
, num_args
, argc
, argv
, &optidx
))
525 ret
= krb5_init_context(&context
);
527 errx (1, "krb5_init_context failed: %d", ret
);
531 if (config_file
== NULL
) {
532 if (asprintf(&config_file
, "%s/kdc.conf", hdb_db_dir(context
)) == -1
533 || config_file
== NULL
)
534 errx(1, "out of memory");
537 ret
= krb5_prepend_config_files_default(config_file
, &files
);
539 krb5_err(context
, 1, ret
, "getting configuration files");
541 ret
= krb5_set_config_files(context
, files
);
542 krb5_free_config_files(files
);
544 krb5_err(context
, 1, ret
, "reading configuration files");
554 #ifdef SUPPORT_DETACH
555 if (detach_from_console
)
559 krb5_openlog (context
, "ipropd-slave", &log_facility
);
560 krb5_set_warn_dest(context
, log_facility
);
562 ret
= krb5_kt_register(context
, &hdb_kt_ops
);
564 krb5_err(context
, 1, ret
, "krb5_kt_register");
566 time_before_lost
= parse_time (server_time_lost
, "s");
567 if (time_before_lost
< 0)
568 krb5_errx (context
, 1, "couldn't parse time: %s", server_time_lost
);
570 memset(&conf
, 0, sizeof(conf
));
572 conf
.mask
|= KADM5_CONFIG_REALM
;
575 ret
= kadm5_init_with_password_ctx (context
,
582 krb5_err (context
, 1, ret
, "kadm5_init_with_password_ctx");
584 server_context
= (kadm5_server_context
*)kadm_handle
;
586 ret
= kadm5_log_init (server_context
);
588 krb5_err (context
, 1, ret
, "kadm5_log_init");
590 get_creds(context
, keytab_str
, &ccache
, master
);
592 ret
= krb5_sname_to_principal (context
, master
, IPROP_NAME
,
593 KRB5_NT_SRV_HST
, &server
);
595 krb5_err (context
, 1, ret
, "krb5_sname_to_principal");
600 krb5_appdefault_time(context
, config_name
, NULL
, "reconnect-min",
602 krb5_appdefault_time(context
, config_name
, NULL
, "reconnect-max",
603 300, &reconnect_max
);
604 krb5_appdefault_time(context
, config_name
, NULL
, "reconnect-backoff",
606 reconnect
= reconnect_min
;
610 int connected
= FALSE
;
613 elapsed
= now
- before
;
615 if (elapsed
< reconnect
) {
616 time_t left
= reconnect
- elapsed
;
617 krb5_warnx(context
, "sleeping %d seconds before "
618 "retrying to connect", (int)left
);
623 master_fd
= connect_to_master (context
, master
, port_str
);
627 reconnect
= reconnect_min
;
630 krb5_auth_con_free(context
, auth_context
);
632 krb5_cc_destroy(context
, ccache
);
633 get_creds(context
, keytab_str
, &ccache
, master
);
635 ret
= krb5_sendauth (context
, &auth_context
, &master_fd
,
636 IPROP_VERSION
, NULL
, server
,
637 AP_OPTS_MUTUAL_REQUIRED
, NULL
, NULL
,
638 ccache
, NULL
, NULL
, NULL
);
640 krb5_warn (context
, ret
, "krb5_sendauth");
644 krb5_warnx(context
, "ipropd-slave started at version: %ld",
645 (long)server_context
->log_context
.version
);
647 ret
= ihave (context
, auth_context
, master_fd
,
648 server_context
->log_context
.version
);
654 while (connected
&& !exit_flag
) {
661 #ifndef NO_LIMIT_FD_SETSIZE
662 if (master_fd
>= FD_SETSIZE
)
663 krb5_errx (context
, 1, "fd too large");
667 FD_SET(master_fd
, &readset
);
669 to
.tv_sec
= time_before_lost
;
672 ret
= select (master_fd
+ 1,
673 &readset
, NULL
, NULL
, &to
);
678 krb5_err (context
, 1, errno
, "select");
681 krb5_errx (context
, 1, "server didn't send a message "
682 "in %d seconds", time_before_lost
);
684 ret
= krb5_read_priv_message(context
, auth_context
, &master_fd
, &out
);
686 krb5_warn (context
, ret
, "krb5_read_priv_message");
691 sp
= krb5_storage_from_mem (out
.data
, out
.length
);
692 krb5_ret_int32 (sp
, &tmp
);
695 receive (context
, sp
, server_context
);
696 ret
= ihave (context
, auth_context
, master_fd
,
697 server_context
->log_context
.version
);
701 case TELL_YOU_EVERYTHING
:
702 ret
= receive_everything (context
, master_fd
, server_context
,
708 send_im_here (context
, master_fd
, auth_context
);
715 krb5_warnx (context
, "Ignoring command %d", tmp
);
718 krb5_storage_free (sp
);
719 krb5_data_free (&out
);
723 if (connected
== FALSE
)
724 krb5_warnx (context
, "disconnected for server");
726 krb5_warnx (context
, "got an exit signal");
731 reconnect
+= backoff
;
732 if (reconnect
> reconnect_max
)
733 reconnect
= reconnect_max
;
738 else if(exit_flag
== SIGXCPU
)
739 krb5_warnx(context
, "%s CPU time limit exceeded", getprogname());
741 else if(exit_flag
== SIGINT
|| exit_flag
== SIGTERM
)
742 krb5_warnx(context
, "%s terminated", getprogname());
744 krb5_warnx(context
, "%s unexpected exit reason: %ld",
745 getprogname(), (long)exit_flag
);