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