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