xref: /freebsd/crypto/heimdal/lib/kadm5/ipropd_master.c (revision 4137ff4cc173ea2e05227027e1c9e0ea42bcc0dc)
1 /*
2  * Copyright (c) 1997 - 2001 Kungliga Tekniska H�gskolan
3  * (Royal Institute of Technology, Stockholm, Sweden).
4  * All rights reserved.
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions
8  * are met:
9  *
10  * 1. Redistributions of source code must retain the above copyright
11  *    notice, this list of conditions and the following disclaimer.
12  *
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.
16  *
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.
20  *
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
31  * SUCH DAMAGE.
32  */
33 
34 #include "iprop.h"
35 
36 RCSID("$Id: ipropd_master.c,v 1.24 2001/09/03 05:54:18 assar Exp $");
37 
38 static krb5_log_facility *log_facility;
39 
40 static int
41 make_signal_socket (krb5_context context)
42 {
43     struct sockaddr_un addr;
44     int fd;
45 
46     fd = socket (AF_UNIX, SOCK_DGRAM, 0);
47     if (fd < 0)
48 	krb5_err (context, 1, errno, "socket AF_UNIX");
49     memset (&addr, 0, sizeof(addr));
50     addr.sun_family = AF_UNIX;
51     strlcpy (addr.sun_path, KADM5_LOG_SIGNAL, sizeof(addr.sun_path));
52     unlink (addr.sun_path);
53     if (bind (fd, (struct sockaddr *)&addr, sizeof(addr)) < 0)
54 	krb5_err (context, 1, errno, "bind %s", addr.sun_path);
55     return fd;
56 }
57 
58 static int
59 make_listen_socket (krb5_context context)
60 {
61     int fd;
62     int one = 1;
63     struct sockaddr_in addr;
64 
65     fd = socket (AF_INET, SOCK_STREAM, 0);
66     if (fd < 0)
67 	krb5_err (context, 1, errno, "socket AF_INET");
68     setsockopt (fd, SOL_SOCKET, SO_REUSEADDR, (void *)&one, sizeof(one));
69     memset (&addr, 0, sizeof(addr));
70     addr.sin_family = AF_INET;
71     addr.sin_port   = krb5_getportbyname (context,
72 					  IPROP_SERVICE, "tcp", IPROP_PORT);
73     if(bind(fd, (struct sockaddr *)&addr, sizeof(addr)) < 0)
74 	krb5_err (context, 1, errno, "bind");
75     if (listen(fd, SOMAXCONN) < 0)
76 	krb5_err (context, 1, errno, "listen");
77     return fd;
78 }
79 
80 struct slave {
81     int fd;
82     struct sockaddr_in addr;
83     char *name;
84     krb5_auth_context ac;
85     u_int32_t version;
86     struct slave *next;
87 };
88 
89 typedef struct slave slave;
90 
91 static int
92 check_acl (krb5_context context, const char *name)
93 {
94     FILE *fp;
95     char buf[256];
96     int ret = 1;
97 
98     fp = fopen (KADM5_SLAVE_ACL, "r");
99     if (fp == NULL)
100 	return 1;
101     while (fgets(buf, sizeof(buf), fp) != NULL) {
102 	if (buf[strlen(buf) - 1 ] == '\n')
103 	    buf[strlen(buf) - 1 ] = '\0';
104 	if (strcmp (buf, name) == 0) {
105 	    ret = 0;
106 	    break;
107 	}
108     }
109     fclose (fp);
110     return ret;
111 }
112 
113 static void
114 add_slave (krb5_context context, krb5_keytab keytab, slave **root, int fd)
115 {
116     krb5_principal server;
117     krb5_error_code ret;
118     slave *s;
119     socklen_t addr_len;
120     krb5_ticket *ticket = NULL;
121     char hostname[128];
122 
123     s = malloc(sizeof(*s));
124     if (s == NULL) {
125 	krb5_warnx (context, "add_slave: no memory");
126 	return;
127     }
128     s->name = NULL;
129     s->ac = NULL;
130 
131     addr_len = sizeof(s->addr);
132     s->fd = accept (fd, (struct sockaddr *)&s->addr, &addr_len);
133     if (s->fd < 0) {
134 	krb5_warn (context, errno, "accept");
135 	goto error;
136     }
137     gethostname(hostname, sizeof(hostname));
138     ret = krb5_sname_to_principal (context, hostname, IPROP_NAME,
139 				   KRB5_NT_SRV_HST, &server);
140     if (ret) {
141 	krb5_warn (context, ret, "krb5_sname_to_principal");
142 	goto error;
143     }
144 
145     ret = krb5_recvauth (context, &s->ac, &s->fd,
146 			 IPROP_VERSION, server, 0, keytab, &ticket);
147     krb5_free_principal (context, server);
148     if (ret) {
149 	krb5_warn (context, ret, "krb5_recvauth");
150 	goto error;
151     }
152     ret = krb5_unparse_name (context, ticket->client, &s->name);
153     if (ret) {
154 	krb5_warn (context, ret, "krb5_unparse_name");
155 	goto error;
156     }
157     if (check_acl (context, s->name)) {
158 	krb5_warnx (context, "%s not in acl", s->name);
159 	goto error;
160     }
161     krb5_free_ticket (context, ticket);
162     krb5_warnx (context, "connection from %s", s->name);
163 
164     s->version = 0;
165     s->next = *root;
166     *root = s;
167     return;
168 error:
169     if (s->name)
170 	free (s->name);
171     if (s->ac)
172 	krb5_auth_con_free(context, s->ac);
173     if (ticket)
174     krb5_free_ticket (context, ticket);
175     close (s->fd);
176     free(s);
177 }
178 
179 static void
180 remove_slave (krb5_context context, slave *s, slave **root)
181 {
182     slave **p;
183 
184     close (s->fd);
185     free (s->name);
186     krb5_auth_con_free (context, s->ac);
187 
188     for (p = root; *p; p = &(*p)->next)
189 	if (*p == s) {
190 	    *p = s->next;
191 	    break;
192 	}
193     free (s);
194 }
195 
196 struct prop_context {
197     krb5_auth_context auth_context;
198     int fd;
199 };
200 
201 static int
202 prop_one (krb5_context context, HDB *db, hdb_entry *entry, void *v)
203 {
204     krb5_error_code ret;
205     krb5_data data;
206     struct slave *slave = (struct slave *)v;
207 
208     ret = hdb_entry2value (context, entry, &data);
209     if (ret)
210 	return ret;
211     ret = krb5_data_realloc (&data, data.length + 4);
212     if (ret) {
213 	krb5_data_free (&data);
214 	return ret;
215     }
216     memmove ((char *)data.data + 4, data.data, data.length - 4);
217     _krb5_put_int (data.data, ONE_PRINC, 4);
218 
219     ret = krb5_write_priv_message (context, slave->ac, &slave->fd, &data);
220     krb5_data_free (&data);
221     return ret;
222 }
223 
224 static int
225 send_complete (krb5_context context, slave *s,
226 	       const char *database, u_int32_t current_version)
227 {
228     krb5_error_code ret;
229     HDB *db;
230     krb5_data data;
231     char buf[8];
232 
233     ret = hdb_create (context, &db, database);
234     if (ret)
235 	krb5_err (context, 1, ret, "hdb_create: %s", database);
236     ret = db->open (context, db, O_RDONLY, 0);
237     if (ret)
238 	krb5_err (context, 1, ret, "db->open");
239 
240     _krb5_put_int(buf, TELL_YOU_EVERYTHING, 4);
241 
242     data.data   = buf;
243     data.length = 4;
244 
245     ret = krb5_write_priv_message(context, s->ac, &s->fd, &data);
246 
247     if (ret)
248 	krb5_err (context, 1, ret, "krb5_write_priv_message");
249 
250     ret = hdb_foreach (context, db, 0, prop_one, s);
251     if (ret)
252 	krb5_err (context, 1, ret, "hdb_foreach");
253 
254     _krb5_put_int (buf, NOW_YOU_HAVE, 4);
255     _krb5_put_int (buf + 4, current_version, 4);
256     data.length = 8;
257 
258     ret = krb5_write_priv_message(context, s->ac, &s->fd, &data);
259 
260     if (ret)
261 	krb5_err (context, 1, ret, "krb5_write_priv_message");
262 
263     return 0;
264 }
265 
266 static int
267 send_diffs (krb5_context context, slave *s, int log_fd,
268 	    const char *database, u_int32_t current_version)
269 {
270     krb5_storage *sp;
271     u_int32_t ver;
272     time_t timestamp;
273     enum kadm_ops op;
274     u_int32_t len;
275     off_t right, left;
276     krb5_data data;
277     int ret = 0;
278 
279     if (s->version == current_version)
280 	return 0;
281 
282     sp = kadm5_log_goto_end (log_fd);
283     right = sp->seek(sp, 0, SEEK_CUR);
284     for (;;) {
285 	if (kadm5_log_previous (sp, &ver, &timestamp, &op, &len))
286 	    abort ();
287 	left = sp->seek(sp, -16, SEEK_CUR);
288 	if (ver == s->version)
289 	    return 0;
290 	if (ver == s->version + 1)
291 	    break;
292 	if (left == 0)
293 	    return send_complete (context, s, database, current_version);
294     }
295     krb5_data_alloc (&data, right - left + 4);
296     sp->fetch (sp, (char *)data.data + 4, data.length - 4);
297     krb5_storage_free(sp);
298 
299     _krb5_put_int(data.data, FOR_YOU, 4);
300 
301     ret = krb5_write_priv_message(context, s->ac, &s->fd, &data);
302 
303     if (ret) {
304 	krb5_warn (context, ret, "krb5_write_priv_message");
305 	return 1;
306     }
307     return 0;
308 }
309 
310 static int
311 process_msg (krb5_context context, slave *s, int log_fd,
312 	     const char *database, u_int32_t current_version)
313 {
314     int ret = 0;
315     krb5_data out;
316     krb5_storage *sp;
317     int32_t tmp;
318 
319     ret = krb5_read_priv_message(context, s->ac, &s->fd, &out);
320     if(ret) {
321 	krb5_warn (context, ret, "error reading message from %s", s->name);
322 	return 1;
323     }
324 
325     sp = krb5_storage_from_mem (out.data, out.length);
326     krb5_ret_int32 (sp, &tmp);
327     switch (tmp) {
328     case I_HAVE :
329 	krb5_ret_int32 (sp, &tmp);
330 	s->version = tmp;
331 	ret = send_diffs (context, s, log_fd, database, current_version);
332 	break;
333     case FOR_YOU :
334     default :
335 	krb5_warnx (context, "Ignoring command %d", tmp);
336 	break;
337     }
338 
339     krb5_data_free (&out);
340     return ret;
341 }
342 
343 static char *realm;
344 static int version_flag;
345 static int help_flag;
346 static char *keytab_str = "HDB:";
347 static char *database;
348 
349 static struct getargs args[] = {
350     { "realm", 'r', arg_string, &realm },
351     { "keytab", 'k', arg_string, &keytab_str,
352       "keytab to get authentication from", "kspec" },
353     { "database", 'd', arg_string, &database, "database", "file"},
354     { "version", 0, arg_flag, &version_flag },
355     { "help", 0, arg_flag, &help_flag }
356 };
357 static int num_args = sizeof(args) / sizeof(args[0]);
358 
359 int
360 main(int argc, char **argv)
361 {
362     krb5_error_code ret;
363     krb5_context context;
364     void *kadm_handle;
365     kadm5_server_context *server_context;
366     kadm5_config_params conf;
367     int signal_fd, listen_fd;
368     int log_fd;
369     slave *slaves = NULL;
370     u_int32_t current_version, old_version = 0;
371     krb5_keytab keytab;
372     int optind;
373 
374     optind = krb5_program_setup(&context, argc, argv, args, num_args, NULL);
375 
376     if(help_flag)
377 	krb5_std_usage(0, args, num_args);
378     if(version_flag) {
379 	print_version(NULL);
380 	exit(0);
381     }
382 
383     pidfile (NULL);
384     krb5_openlog (context, "ipropd-master", &log_facility);
385     krb5_set_warn_dest(context, log_facility);
386 
387     ret = krb5_kt_register(context, &hdb_kt_ops);
388     if(ret)
389 	krb5_err(context, 1, ret, "krb5_kt_register");
390 
391     ret = krb5_kt_resolve(context, keytab_str, &keytab);
392     if(ret)
393 	krb5_err(context, 1, ret, "krb5_kt_resolve: %s", keytab_str);
394 
395     memset(&conf, 0, sizeof(conf));
396     if(realm) {
397 	conf.mask |= KADM5_CONFIG_REALM;
398 	conf.realm = realm;
399     }
400     ret = kadm5_init_with_skey_ctx (context,
401 				    KADM5_ADMIN_SERVICE,
402 				    NULL,
403 				    KADM5_ADMIN_SERVICE,
404 				    &conf, 0, 0,
405 				    &kadm_handle);
406     if (ret)
407 	krb5_err (context, 1, ret, "kadm5_init_with_password_ctx");
408 
409     server_context = (kadm5_server_context *)kadm_handle;
410 
411     log_fd = open (server_context->log_context.log_file, O_RDONLY, 0);
412     if (log_fd < 0)
413 	krb5_err (context, 1, errno, "open %s",
414 		  server_context->log_context.log_file);
415 
416     signal_fd = make_signal_socket (context);
417     listen_fd = make_listen_socket (context);
418 
419     signal (SIGPIPE, SIG_IGN);
420 
421     for (;;) {
422 	slave *p;
423 	fd_set readset;
424 	int max_fd = 0;
425 	struct timeval to = {30, 0};
426 	u_int32_t vers;
427 
428 	if (signal_fd >= FD_SETSIZE || listen_fd >= FD_SETSIZE)
429 	    krb5_errx (context, 1, "fd too large");
430 
431 	FD_ZERO(&readset);
432 	FD_SET(signal_fd, &readset);
433 	max_fd = max(max_fd, signal_fd);
434 	FD_SET(listen_fd, &readset);
435 	max_fd = max(max_fd, listen_fd);
436 
437 	for (p = slaves; p != NULL; p = p->next) {
438 	    FD_SET(p->fd, &readset);
439 	    max_fd = max(max_fd, p->fd);
440 	}
441 
442 	ret = select (max_fd + 1,
443 		      &readset, NULL, NULL, &to);
444 	if (ret < 0) {
445 	    if (errno == EINTR)
446 		continue;
447 	    else
448 		krb5_err (context, 1, errno, "select");
449 	}
450 
451 	if (ret == 0) {
452 	    old_version = current_version;
453 	    kadm5_log_get_version_fd (log_fd, &current_version);
454 
455 	    if (current_version > old_version)
456 		for (p = slaves; p != NULL; p = p->next)
457 		    send_diffs (context, p, log_fd, database, current_version);
458 	}
459 
460 	if (ret && FD_ISSET(signal_fd, &readset)) {
461 	    struct sockaddr_un peer_addr;
462 	    socklen_t peer_len = sizeof(peer_addr);
463 
464 	    if(recvfrom(signal_fd, (void *)&vers, sizeof(vers), 0,
465 			(struct sockaddr *)&peer_addr, &peer_len) < 0) {
466 		krb5_warn (context, errno, "recvfrom");
467 		continue;
468 	    }
469 	    --ret;
470 	    old_version = current_version;
471 	    kadm5_log_get_version_fd (log_fd, &current_version);
472 	    for (p = slaves; p != NULL; p = p->next)
473 		send_diffs (context, p, log_fd, database, current_version);
474 	}
475 
476 	for(p = slaves; p != NULL; p = p->next)
477 	    if (FD_ISSET(p->fd, &readset)) {
478 		--ret;
479 		if(process_msg (context, p, log_fd, database, current_version))
480 		    remove_slave (context, p, &slaves);
481 	    }
482 
483 	if (ret && FD_ISSET(listen_fd, &readset)) {
484 	    add_slave (context, keytab, &slaves, listen_fd);
485 	    --ret;
486 	}
487 
488     }
489 
490     return 0;
491 }
492