xref: /freebsd/crypto/heimdal/kpasswd/kpasswdd.c (revision 1e413cf93298b5b97441a21d9a50fdcd0ee9945e)
1 /*
2  * Copyright (c) 1997-2002 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 "kpasswd_locl.h"
35 RCSID("$Id: kpasswdd.c,v 1.54 2002/12/02 14:31:52 joda Exp $");
36 
37 #include <kadm5/admin.h>
38 #ifdef HAVE_SYS_UN_H
39 #include <sys/un.h>
40 #endif
41 #include <hdb.h>
42 #include <kadm5/private.h>
43 
44 static krb5_context context;
45 static krb5_log_facility *log_facility;
46 
47 static sig_atomic_t exit_flag = 0;
48 
49 static void
50 send_reply (int s,
51 	    struct sockaddr *sa,
52 	    int sa_size,
53 	    krb5_data *ap_rep,
54 	    krb5_data *rest)
55 {
56     struct msghdr msghdr;
57     struct iovec iov[3];
58     u_int16_t len, ap_rep_len;
59     u_char header[6];
60     u_char *p;
61 
62     if (ap_rep)
63 	ap_rep_len = ap_rep->length;
64     else
65 	ap_rep_len = 0;
66 
67     len = 6 + ap_rep_len + rest->length;
68     p = header;
69     *p++ = (len >> 8) & 0xFF;
70     *p++ = (len >> 0) & 0xFF;
71     *p++ = 0;
72     *p++ = 1;
73     *p++ = (ap_rep_len >> 8) & 0xFF;
74     *p++ = (ap_rep_len >> 0) & 0xFF;
75 
76     memset (&msghdr, 0, sizeof(msghdr));
77     msghdr.msg_name       = (void *)sa;
78     msghdr.msg_namelen    = sa_size;
79     msghdr.msg_iov        = iov;
80     msghdr.msg_iovlen     = sizeof(iov)/sizeof(*iov);
81 #if 0
82     msghdr.msg_control    = NULL;
83     msghdr.msg_controllen = 0;
84 #endif
85 
86     iov[0].iov_base       = (char *)header;
87     iov[0].iov_len        = 6;
88     if (ap_rep_len) {
89 	iov[1].iov_base   = ap_rep->data;
90 	iov[1].iov_len    = ap_rep->length;
91     } else {
92 	iov[1].iov_base   = NULL;
93 	iov[1].iov_len    = 0;
94     }
95     iov[2].iov_base       = rest->data;
96     iov[2].iov_len        = rest->length;
97 
98     if (sendmsg (s, &msghdr, 0) < 0)
99 	krb5_warn (context, errno, "sendmsg");
100 }
101 
102 static int
103 make_result (krb5_data *data,
104 	     u_int16_t result_code,
105 	     const char *expl)
106 {
107     krb5_data_zero (data);
108 
109     data->length = asprintf ((char **)&data->data,
110 			     "%c%c%s",
111 			     (result_code >> 8) & 0xFF,
112 			     result_code & 0xFF,
113 			     expl);
114 
115     if (data->data == NULL) {
116 	krb5_warnx (context, "Out of memory generating error reply");
117 	return 1;
118     }
119     return 0;
120 }
121 
122 static void
123 reply_error (krb5_principal server,
124 	     int s,
125 	     struct sockaddr *sa,
126 	     int sa_size,
127 	     krb5_error_code error_code,
128 	     u_int16_t result_code,
129 	     const char *expl)
130 {
131     krb5_error_code ret;
132     krb5_data error_data;
133     krb5_data e_data;
134 
135     if (make_result(&e_data, result_code, expl))
136 	return;
137 
138     ret = krb5_mk_error (context,
139 			 error_code,
140 			 NULL,
141 			 &e_data,
142 			 NULL,
143 			 server,
144 			 NULL,
145 			 NULL,
146 			 &error_data);
147     krb5_data_free (&e_data);
148     if (ret) {
149 	krb5_warn (context, ret, "Could not even generate error reply");
150 	return;
151     }
152     send_reply (s, sa, sa_size, NULL, &error_data);
153     krb5_data_free (&error_data);
154 }
155 
156 static void
157 reply_priv (krb5_auth_context auth_context,
158 	    int s,
159 	    struct sockaddr *sa,
160 	    int sa_size,
161 	    u_int16_t result_code,
162 	    const char *expl)
163 {
164     krb5_error_code ret;
165     krb5_data krb_priv_data;
166     krb5_data ap_rep_data;
167     krb5_data e_data;
168 
169     ret = krb5_mk_rep (context,
170 		       auth_context,
171 		       &ap_rep_data);
172     if (ret) {
173 	krb5_warn (context, ret, "Could not even generate error reply");
174 	return;
175     }
176 
177     if (make_result(&e_data, result_code, expl))
178 	return;
179 
180     ret = krb5_mk_priv (context,
181 			auth_context,
182 			&e_data,
183 			&krb_priv_data,
184 			NULL);
185     krb5_data_free (&e_data);
186     if (ret) {
187 	krb5_warn (context, ret, "Could not even generate error reply");
188 	return;
189     }
190     send_reply (s, sa, sa_size, &ap_rep_data, &krb_priv_data);
191     krb5_data_free (&ap_rep_data);
192     krb5_data_free (&krb_priv_data);
193 }
194 
195 /*
196  * Change the password for `principal', sending the reply back on `s'
197  * (`sa', `sa_size') to `pwd_data'.
198  */
199 
200 static void
201 change (krb5_auth_context auth_context,
202 	krb5_principal principal,
203 	int s,
204 	struct sockaddr *sa,
205 	int sa_size,
206 	krb5_data *pwd_data)
207 {
208     krb5_error_code ret;
209     char *client;
210     const char *pwd_reason;
211     kadm5_config_params conf;
212     void *kadm5_handle;
213     char *tmp;
214 
215     memset (&conf, 0, sizeof(conf));
216 
217     krb5_unparse_name (context, principal, &client);
218 
219     ret = kadm5_init_with_password_ctx(context,
220 				       client,
221 				       NULL,
222 				       KADM5_ADMIN_SERVICE,
223 				       &conf, 0, 0,
224 				       &kadm5_handle);
225     if (ret) {
226 	free (client);
227 	krb5_warn (context, ret, "kadm5_init_with_password_ctx");
228 	reply_priv (auth_context, s, sa, sa_size, 2,
229 		    "Internal error");
230 	return;
231     }
232 
233     krb5_warnx (context, "Changing password for %s", client);
234     free (client);
235 
236     pwd_reason = kadm5_check_password_quality (context, principal, pwd_data);
237     if (pwd_reason != NULL ) {
238 	krb5_warnx (context, "%s", pwd_reason);
239 	reply_priv (auth_context, s, sa, sa_size, 4, pwd_reason);
240 	kadm5_destroy (kadm5_handle);
241 	return;
242     }
243 
244     tmp = malloc (pwd_data->length + 1);
245     if (tmp == NULL) {
246 	krb5_warnx (context, "malloc: out of memory");
247 	reply_priv (auth_context, s, sa, sa_size, 2,
248 		    "Internal error");
249 	goto out;
250     }
251     memcpy (tmp, pwd_data->data, pwd_data->length);
252     tmp[pwd_data->length] = '\0';
253 
254     ret = kadm5_s_chpass_principal_cond (kadm5_handle, principal, tmp);
255     memset (tmp, 0, pwd_data->length);
256     free (tmp);
257     if (ret) {
258 	krb5_warn (context, ret, "kadm5_s_chpass_principal_cond");
259 	reply_priv (auth_context, s, sa, sa_size, 2,
260 		    "Internal error");
261 	goto out;
262     }
263     reply_priv (auth_context, s, sa, sa_size, 0, "Password changed");
264 out:
265     kadm5_destroy (kadm5_handle);
266 }
267 
268 static int
269 verify (krb5_auth_context *auth_context,
270 	krb5_principal server,
271 	krb5_keytab keytab,
272 	krb5_ticket **ticket,
273 	krb5_data *out_data,
274 	int s,
275 	struct sockaddr *sa,
276 	int sa_size,
277 	u_char *msg,
278 	size_t len)
279 {
280     krb5_error_code ret;
281     u_int16_t pkt_len, pkt_ver, ap_req_len;
282     krb5_data ap_req_data;
283     krb5_data krb_priv_data;
284 
285     pkt_len = (msg[0] << 8) | (msg[1]);
286     pkt_ver = (msg[2] << 8) | (msg[3]);
287     ap_req_len = (msg[4] << 8) | (msg[5]);
288     if (pkt_len != len) {
289 	krb5_warnx (context, "Strange len: %ld != %ld",
290 		    (long)pkt_len, (long)len);
291 	reply_error (server, s, sa, sa_size, 0, 1, "Bad request");
292 	return 1;
293     }
294     if (pkt_ver != 0x0001) {
295 	krb5_warnx (context, "Bad version (%d)", pkt_ver);
296 	reply_error (server, s, sa, sa_size, 0, 1, "Wrong program version");
297 	return 1;
298     }
299 
300     ap_req_data.data   = msg + 6;
301     ap_req_data.length = ap_req_len;
302 
303     ret = krb5_rd_req (context,
304 		       auth_context,
305 		       &ap_req_data,
306 		       server,
307 		       keytab,
308 		       NULL,
309 		       ticket);
310     if (ret) {
311 	if(ret == KRB5_KT_NOTFOUND) {
312 	    char *name;
313 	    krb5_unparse_name(context, server, &name);
314 	    krb5_warnx (context, "krb5_rd_req: %s (%s)",
315 			krb5_get_err_text(context, ret), name);
316 	    free(name);
317 	} else
318 	    krb5_warn (context, ret, "krb5_rd_req");
319 	reply_error (server, s, sa, sa_size, ret, 3, "Authentication failed");
320 	return 1;
321     }
322 
323     if (!(*ticket)->ticket.flags.initial) {
324 	krb5_warnx (context, "initial flag not set");
325 	reply_error (server, s, sa, sa_size, ret, 1,
326 		     "Bad request");
327 	goto out;
328     }
329     krb_priv_data.data   = msg + 6 + ap_req_len;
330     krb_priv_data.length = len - 6 - ap_req_len;
331 
332     ret = krb5_rd_priv (context,
333 			*auth_context,
334 			&krb_priv_data,
335 			out_data,
336 			NULL);
337 
338     if (ret) {
339 	krb5_warn (context, ret, "krb5_rd_priv");
340 	reply_error (server, s, sa, sa_size, ret, 3, "Bad request");
341 	goto out;
342     }
343     return 0;
344 out:
345     krb5_free_ticket (context, *ticket);
346     return 1;
347 }
348 
349 static void
350 process (krb5_principal server,
351 	 krb5_keytab keytab,
352 	 int s,
353 	 krb5_address *this_addr,
354 	 struct sockaddr *sa,
355 	 int sa_size,
356 	 u_char *msg,
357 	 int len)
358 {
359     krb5_error_code ret;
360     krb5_auth_context auth_context = NULL;
361     krb5_data out_data;
362     krb5_ticket *ticket;
363     krb5_address other_addr;
364 
365     krb5_data_zero (&out_data);
366 
367     ret = krb5_auth_con_init (context, &auth_context);
368     if (ret) {
369 	krb5_warn (context, ret, "krb5_auth_con_init");
370 	return;
371     }
372 
373     krb5_auth_con_setflags (context, auth_context,
374 			    KRB5_AUTH_CONTEXT_DO_SEQUENCE);
375 
376     ret = krb5_sockaddr2address (context, sa, &other_addr);
377     if (ret) {
378 	krb5_warn (context, ret, "krb5_sockaddr2address");
379 	goto out;
380     }
381 
382     ret = krb5_auth_con_setaddrs (context,
383 				  auth_context,
384 				  this_addr,
385 				  &other_addr);
386     krb5_free_address (context, &other_addr);
387     if (ret) {
388 	krb5_warn (context, ret, "krb5_auth_con_setaddr");
389 	goto out;
390     }
391 
392     if (verify (&auth_context, server, keytab, &ticket, &out_data,
393 		s, sa, sa_size, msg, len) == 0) {
394 	change (auth_context,
395 		ticket->client,
396 		s,
397 		sa, sa_size,
398 		&out_data);
399 	memset (out_data.data, 0, out_data.length);
400 	krb5_free_ticket (context, ticket);
401 	free (ticket);
402     }
403 
404 out:
405     krb5_data_free (&out_data);
406     krb5_auth_con_free (context, auth_context);
407 }
408 
409 static int
410 doit (krb5_keytab keytab, int port)
411 {
412     krb5_error_code ret;
413     krb5_principal server;
414     int *sockets;
415     int maxfd;
416     char *realm;
417     krb5_addresses addrs;
418     unsigned n, i;
419     fd_set real_fdset;
420     struct sockaddr_storage __ss;
421     struct sockaddr *sa = (struct sockaddr *)&__ss;
422 
423     ret = krb5_get_default_realm (context, &realm);
424     if (ret)
425 	krb5_err (context, 1, ret, "krb5_get_default_realm");
426 
427     ret = krb5_build_principal (context,
428 				&server,
429 				strlen(realm),
430 				realm,
431 				"kadmin",
432 				"changepw",
433 				NULL);
434     if (ret)
435 	krb5_err (context, 1, ret, "krb5_build_principal");
436 
437     free (realm);
438 
439     ret = krb5_get_all_server_addrs (context, &addrs);
440     if (ret)
441 	krb5_err (context, 1, ret, "krb5_get_all_server_addrs");
442 
443     n = addrs.len;
444 
445     sockets = malloc (n * sizeof(*sockets));
446     if (sockets == NULL)
447 	krb5_errx (context, 1, "out of memory");
448     maxfd = -1;
449     FD_ZERO(&real_fdset);
450     for (i = 0; i < n; ++i) {
451 	int sa_size = sizeof(__ss);
452 
453 	krb5_addr2sockaddr (context, &addrs.val[i], sa, &sa_size, port);
454 
455 	sockets[i] = socket (sa->sa_family, SOCK_DGRAM, 0);
456 	if (sockets[i] < 0)
457 	    krb5_err (context, 1, errno, "socket");
458 	if (bind (sockets[i], sa, sa_size) < 0) {
459 	    char str[128];
460 	    size_t len;
461 	    int save_errno = errno;
462 
463 	    ret = krb5_print_address (&addrs.val[i], str, sizeof(str), &len);
464 	    if (ret)
465 		strlcpy(str, "unknown address", sizeof(str));
466 	    krb5_warn (context, save_errno, "bind(%s)", str);
467 	    continue;
468 	}
469 	maxfd = max (maxfd, sockets[i]);
470 	if (maxfd >= FD_SETSIZE)
471 	    krb5_errx (context, 1, "fd too large");
472 	FD_SET(sockets[i], &real_fdset);
473     }
474     if (maxfd == -1)
475 	krb5_errx (context, 1, "No sockets!");
476 
477     while(exit_flag == 0) {
478 	int ret;
479 	fd_set fdset = real_fdset;
480 
481 	ret = select (maxfd + 1, &fdset, NULL, NULL, NULL);
482 	if (ret < 0) {
483 	    if (errno == EINTR)
484 		continue;
485 	    else
486 		krb5_err (context, 1, errno, "select");
487 	}
488 	for (i = 0; i < n; ++i)
489 	    if (FD_ISSET(sockets[i], &fdset)) {
490 		u_char buf[BUFSIZ];
491 		socklen_t addrlen = sizeof(__ss);
492 
493 		ret = recvfrom (sockets[i], buf, sizeof(buf), 0,
494 				sa, &addrlen);
495 		if (ret < 0) {
496 		    if(errno == EINTR)
497 			break;
498 		    else
499 			krb5_err (context, 1, errno, "recvfrom");
500 		}
501 
502 		process (server, keytab, sockets[i],
503 			 &addrs.val[i],
504 			 sa, addrlen,
505 			 buf, ret);
506 	    }
507     }
508     krb5_free_addresses (context, &addrs);
509     krb5_free_principal (context, server);
510     krb5_free_context (context);
511     return 0;
512 }
513 
514 static RETSIGTYPE
515 sigterm(int sig)
516 {
517     exit_flag = 1;
518 }
519 
520 const char *check_library  = NULL;
521 const char *check_function = NULL;
522 char *keytab_str = "HDB:";
523 char *realm_str;
524 int version_flag;
525 int help_flag;
526 char *port_str;
527 
528 struct getargs args[] = {
529 #ifdef HAVE_DLOPEN
530     { "check-library", 0, arg_string, &check_library,
531       "library to load password check function from", "library" },
532     { "check-function", 0, arg_string, &check_function,
533       "password check function to load", "function" },
534 #endif
535     { "keytab", 'k', arg_string, &keytab_str,
536       "keytab to get authentication key from", "kspec" },
537     { "realm", 'r', arg_string, &realm_str, "default realm", "realm" },
538     { "port",  'p', arg_string, &port_str, "port" },
539     { "version", 0, arg_flag, &version_flag },
540     { "help", 0, arg_flag, &help_flag }
541 };
542 int num_args = sizeof(args) / sizeof(args[0]);
543 
544 int
545 main (int argc, char **argv)
546 {
547     int optind;
548     krb5_keytab keytab;
549     krb5_error_code ret;
550     int port;
551 
552     optind = krb5_program_setup(&context, argc, argv, args, num_args, NULL);
553 
554     if(help_flag)
555 	krb5_std_usage(0, args, num_args);
556     if(version_flag) {
557 	print_version(NULL);
558 	exit(0);
559     }
560 
561     if(realm_str)
562 	krb5_set_default_realm(context, realm_str);
563 
564     krb5_openlog (context, "kpasswdd", &log_facility);
565     krb5_set_warn_dest(context, log_facility);
566 
567     if (port_str != NULL) {
568 	struct servent *s = roken_getservbyname (port_str, "udp");
569 
570 	if (s != NULL)
571 	    port = s->s_port;
572 	else {
573 	    char *ptr;
574 
575 	    port = strtol (port_str, &ptr, 10);
576 	    if (port == 0 && ptr == port_str)
577 		krb5_errx (context, 1, "bad port `%s'", port_str);
578 	    port = htons(port);
579 	}
580     } else
581 	port = krb5_getportbyname (context, "kpasswd", "udp", KPASSWD_PORT);
582 
583     ret = krb5_kt_register(context, &hdb_kt_ops);
584     if(ret)
585 	krb5_err(context, 1, ret, "krb5_kt_register");
586 
587     ret = krb5_kt_resolve(context, keytab_str, &keytab);
588     if(ret)
589 	krb5_err(context, 1, ret, "%s", keytab_str);
590 
591     kadm5_setup_passwd_quality_check (context, check_library, check_function);
592 
593 #ifdef HAVE_SIGACTION
594     {
595 	struct sigaction sa;
596 
597 	sa.sa_flags = 0;
598 	sa.sa_handler = sigterm;
599 	sigemptyset(&sa.sa_mask);
600 
601 	sigaction(SIGINT,  &sa, NULL);
602 	sigaction(SIGTERM, &sa, NULL);
603     }
604 #else
605     signal(SIGINT,  sigterm);
606     signal(SIGTERM, sigterm);
607 #endif
608 
609     pidfile(NULL);
610 
611     return doit (keytab, port);
612 }
613