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