cond otp
[heimdal.git] / kpasswd / kpasswdd.c
blobcb72904fd884e789545711090e942690cae4358d
1 /*
2 * Copyright (c) 1997 Kungliga Tekniska Högskolan
3 * (Royal Institute of Technology, Stockholm, Sweden).
4 * All rights reserved.
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
8 * are met:
10 * 1. Redistributions of source code must retain the above copyright
11 * notice, this list of conditions and the following disclaimer.
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.
17 * 3. All advertising materials mentioning features or use of this software
18 * must display the following acknowledgement:
19 * This product includes software developed by Kungliga Tekniska
20 * Högskolan and its contributors.
22 * 4. Neither the name of the Institute nor the names of its contributors
23 * may be used to endorse or promote products derived from this software
24 * without specific prior written permission.
26 * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
27 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
28 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
29 * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
30 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
31 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
32 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
33 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
34 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
35 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
36 * SUCH DAMAGE.
39 #include "kpasswd_locl.h"
40 #include <hdb.h>
41 RCSID("$Id$");
43 static krb5_context context;
44 static krb5_log_facility *log_facility;
46 static sig_atomic_t exit_flag = 0;
48 static krb5_data msater_key;
49 static int master_key_set = 0;
51 #define KPASSWDD_LOG_ERR 0
52 #define KPASSWDD_LOG_INFO 1
54 static void
55 syslog_and_die (const char *m, ...)
57 va_list args;
59 va_start(args, m);
60 krb5_vlog (context, log_facility, KPASSWDD_LOG_ERR, m, args);
61 va_end(args);
62 exit (1);
65 static char *database = HDB_DEFAULT_DB;
66 static HDB *db;
68 static void
69 send_reply (int s,
70 struct sockaddr *sa,
71 int sa_size,
72 krb5_data *ap_rep,
73 krb5_data *rest)
75 struct msghdr msghdr;
76 struct iovec iov[3];
77 u_int16_t len, ap_rep_len;
78 u_char header[6];
79 u_char *p;
81 if (ap_rep)
82 ap_rep_len = ap_rep->length;
83 else
84 ap_rep_len = 0;
86 len = 6 + ap_rep_len + rest->length;
87 p = header;
88 *p++ = (len >> 8) & 0xFF;
89 *p++ = (len >> 0) & 0xFF;
90 *p++ = 0;
91 *p++ = 1;
92 *p++ = (ap_rep_len >> 8) & 0xFF;
93 *p++ = (ap_rep_len >> 0) & 0xFF;
95 memset (&msghdr, 0, sizeof(msghdr));
96 msghdr.msg_name = (void *)sa;
97 msghdr.msg_namelen = sa_size;
98 msghdr.msg_iov = iov;
99 msghdr.msg_iovlen = sizeof(iov)/sizeof(*iov);
100 #if 0
101 msghdr.msg_control = NULL;
102 msghdr.msg_controllen = 0;
103 #endif
105 iov[0].iov_base = (char *)header;
106 iov[0].iov_len = 6;
107 if (ap_rep_len) {
108 iov[1].iov_base = ap_rep->data;
109 iov[1].iov_len = ap_rep->length;
110 } else {
111 iov[1].iov_base = NULL;
112 iov[1].iov_len = 0;
114 iov[2].iov_base = rest->data;
115 iov[2].iov_len = rest->length;
117 if (sendmsg (s, &msghdr, 0) < 0)
118 krb5_log (context, log_facility, KPASSWDD_LOG_ERR,
119 "sendmsg: %s",
120 strerror(errno));
123 static int
124 make_result (krb5_data *data,
125 u_int16_t result_code,
126 const char *expl)
128 krb5_data_zero (data);
130 data->length = asprintf ((char **)&data->data,
131 "%c%c%s",
132 (result_code >> 8) & 0xFF,
133 result_code & 0xFF,
134 expl);
136 if (data->data == NULL) {
137 krb5_log (context, log_facility, KPASSWDD_LOG_ERR,
138 "Out of memory generating error reply");
139 return 1;
141 return 0;
144 static void
145 reply_error (krb5_principal server,
146 int s,
147 struct sockaddr *sa,
148 int sa_size,
149 krb5_error_code error_code,
150 u_int16_t result_code,
151 const char *expl)
153 krb5_error_code ret;
154 krb5_data error_data;
155 krb5_data e_data;
157 if (make_result(&e_data, result_code, expl))
158 return;
160 ret = krb5_mk_error (context,
161 error_code,
162 NULL,
163 &e_data,
164 NULL,
165 server,
167 &error_data);
168 krb5_data_free (&e_data);
169 if (ret) {
170 krb5_log (context, log_facility, KPASSWDD_LOG_ERR,
171 "Could not even generate error reply: %s",
172 krb5_get_err_text (context, ret));
173 return;
175 send_reply (s, sa, sa_size, NULL, &error_data);
176 krb5_data_free (&error_data);
179 static void
180 reply_priv (krb5_auth_context auth_context,
181 int s,
182 struct sockaddr *sa,
183 int sa_size,
184 u_int16_t result_code,
185 const char *expl)
187 krb5_error_code ret;
188 krb5_data krb_priv_data;
189 krb5_data ap_rep_data;
190 krb5_data e_data;
192 ret = krb5_mk_rep (context,
193 &auth_context,
194 &ap_rep_data);
195 if (ret) {
196 krb5_log (context, log_facility, KPASSWDD_LOG_ERR,
197 "Could not even generate error reply: %s",
198 krb5_get_err_text (context, ret));
199 return;
202 if (make_result(&e_data, result_code, expl))
203 return;
205 ret = krb5_mk_priv (context,
206 auth_context,
207 &e_data,
208 &krb_priv_data,
209 NULL);
210 krb5_data_free (&e_data);
211 if (ret) {
212 krb5_log (context, log_facility, KPASSWDD_LOG_ERR,
213 "Could not even generate error reply: %s",
214 krb5_get_err_text (context, ret));
215 return;
217 send_reply (s, sa, sa_size, &ap_rep_data, &krb_priv_data);
218 krb5_data_free (&ap_rep_data);
219 krb5_data_free (&krb_priv_data);
222 static char *
223 passwd_quality_check (krb5_data *pwd)
225 if (pwd->length < 6)
226 return "Password too short";
227 else
228 return NULL;
231 static void
232 change (krb5_auth_context auth_context,
233 krb5_principal principal,
234 int s,
235 struct sockaddr *sa,
236 int sa_size,
237 krb5_data *pwd_data)
239 krb5_error_code ret;
240 char *c;
241 hdb_entry ent;
242 krb5_data salt;
243 krb5_keyblock new_keyblock, *old_keyblock;
244 char *pwd_reason;
246 krb5_unparse_name (context, principal, &c);
248 krb5_log (context, log_facility, KPASSWDD_LOG_INFO,
249 "Changing password for %s", c);
250 free (c);
252 pwd_reason = passwd_quality_check (pwd_data);
253 if (pwd_reason != NULL ) {
254 krb5_log (context, log_facility,
255 KPASSWDD_LOG_ERR, pwd_reason);
256 reply_priv (auth_context, s, sa, sa_size, 4, pwd_reason);
257 return;
260 ret = db->open(context, db, O_RDWR, 0600);
261 if (ret) {
262 krb5_log (context, log_facility, KPASSWDD_LOG_ERR,
263 "hdb_open: %s", krb5_get_err_text(context, ret));
264 reply_priv (auth_context, s, sa, sa_size, 2, "hdb_open failed");
265 return;
268 krb5_copy_principal (context, principal, &ent.principal);
270 ret = db->fetch (context, db, &ent);
272 switch (ret) {
273 case HDB_ERR_NOENTRY:
274 krb5_log (context, log_facility, KPASSWDD_LOG_ERR,
275 "not found in database");
276 reply_priv (auth_context, s, sa, sa_size, 2,
277 "entry not found in database");
278 goto out;
279 case 0:
280 break;
281 default :
282 krb5_log (context, log_facility, KPASSWDD_LOG_ERR,
283 "dbfetch: %s", krb5_get_err_text(context, ret));
284 reply_priv (auth_context, s, sa, sa_size, 2,
285 "db_fetch failed");
286 goto out;
290 * Compare with the first key to see if it already has been
291 * changed. If it hasn't, store the new key in the database and
292 * string2key all the rest of them.
295 krb5_data_zero (&salt);
296 krb5_get_salt (principal, &salt);
297 memset (&new_keyblock, 0, sizeof(new_keyblock));
298 old_keyblock = &ent.keys.val[0].key;
299 krb5_string_to_key_data (pwd_data, &salt, old_keyblock->keytype, /* XXX */
300 &new_keyblock);
302 if (new_keyblock.keytype == old_keyblock->keytype
303 && new_keyblock.keyvalue.length == old_keyblock->keyvalue.length
304 && memcmp (new_keyblock.keyvalue.data,
305 old_keyblock->keyvalue.data,
306 new_keyblock.keyvalue.length) == 0) {
307 ret = 0;
308 } else {
309 Event *e;
310 int i;
312 free_EncryptionKey (old_keyblock);
313 memset (old_keyblock, 0, sizeof(*old_keyblock));
314 old_keyblock->keytype = new_keyblock.keytype;
315 krb5_data_copy (&old_keyblock->keyvalue,
316 new_keyblock.keyvalue.data,
317 new_keyblock.keyvalue.length);
319 for(i = 1; i < ent.keys.len; ++i) {
320 free_Key (&ent.keys.val[i]);
321 krb5_string_to_key_data (pwd_data,
322 &salt,
323 ent.keys.val[i].key.keytype,
324 &ent.keys.val[i].key);
327 ent.kvno++;
328 e = malloc(sizeof(*e));
329 e->time = time(NULL);
330 krb5_copy_principal (context, principal, &e->principal);
331 if (ent.modified_by) {
332 free_Event (ent.modified_by);
333 free (ent.modified_by);
335 ent.modified_by = e;
336 if (ent.pw_end)
337 *ent.pw_end = e->time + 3600; /* XXX - Change here! */
338 ret = db->store (context, db, 1, &ent);
340 krb5_data_free (&salt);
341 krb5_free_keyblock (context, &new_keyblock);
343 if (ret) {
344 krb5_log (context, log_facility, KPASSWDD_LOG_ERR,
345 "dbstore: %s", krb5_get_err_text (context, ret));
346 reply_priv (auth_context, s, sa, sa_size, 2,
347 "db_store failed");
348 } else {
349 reply_priv (auth_context, s, sa, sa_size, 0, "password changed");
351 out:
352 hdb_free_entry (context, &ent);
353 db->close (context, db);
356 static int
357 verify (krb5_auth_context *auth_context,
358 krb5_principal server,
359 krb5_ticket **ticket,
360 krb5_data *out_data,
361 int s,
362 struct sockaddr *sa,
363 int sa_size,
364 u_char *msg,
365 size_t len)
367 krb5_error_code ret;
368 u_int16_t pkt_len, pkt_ver, ap_req_len;
369 krb5_data ap_req_data;
370 krb5_data krb_priv_data;
372 pkt_len = (msg[0] << 8) | (msg[1]);
373 pkt_ver = (msg[2] << 8) | (msg[3]);
374 ap_req_len = (msg[4] << 8) | (msg[5]);
375 if (pkt_len != len) {
376 krb5_log (context, log_facility, KPASSWDD_LOG_ERR,
377 "Strange len: %d != %d", pkt_len, len);
378 reply_error (server, s, sa, sa_size, 0, 1, "bad length");
379 return 1;
381 if (pkt_ver != 0x0001) {
382 krb5_log (context, log_facility, KPASSWDD_LOG_ERR,
383 "Bad version (%d)", pkt_ver);
384 reply_error (server, s, sa, sa_size, 0, 1, "bad version");
385 return 1;
388 ap_req_data.data = msg + 6;
389 ap_req_data.length = ap_req_len;
391 ret = krb5_rd_req (context,
392 auth_context,
393 &ap_req_data,
394 server,
395 NULL,
396 NULL,
397 ticket);
398 if (ret) {
399 krb5_log (context, log_facility, KPASSWDD_LOG_ERR, "krb5_rd_req: %s",
400 krb5_get_err_text(context, ret));
401 reply_error (server, s, sa, sa_size, ret, 3, "rd_req failed");
402 return 1;
405 if (!(*ticket)->ticket.flags.initial) {
406 krb5_log (context, log_facility, KPASSWDD_LOG_ERR,
407 "initial flag not set");
408 reply_error (server, s, sa, sa_size, ret, 1,
409 "initial flag not set");
410 goto out;
412 krb_priv_data.data = msg + 6 + ap_req_len;
413 krb_priv_data.length = len - 6 - ap_req_len;
415 ret = krb5_rd_priv (context,
416 *auth_context,
417 &krb_priv_data,
418 out_data,
419 NULL);
421 if (ret) {
422 krb5_log (context, log_facility, KPASSWDD_LOG_ERR, "krb5_rd_priv: %s",
423 krb5_get_err_text(context, ret));
424 reply_error (server, s, sa, sa_size, ret, 3, "rd_priv failed");
425 goto out;
427 return 0;
428 out:
429 krb5_free_ticket (context, *ticket);
430 return 1;
433 static void
434 process (krb5_principal server,
435 int s,
436 krb5_address *this_addr,
437 struct sockaddr *sa,
438 int sa_size,
439 u_char *msg,
440 int len)
442 krb5_error_code ret;
443 krb5_auth_context auth_context = NULL;
444 krb5_data out_data;
445 krb5_ticket *ticket;
446 krb5_address other_addr;
448 krb5_data_zero (&out_data);
450 ret = krb5_auth_con_init (context, &auth_context);
451 if (ret) {
452 krb5_log (context, log_facility, KPASSWDD_LOG_ERR,
453 "krb5_auth_con_init: %s",
454 krb5_get_err_text(context, ret));
455 return;
458 krb5_auth_con_setflags (context, auth_context,
459 KRB5_AUTH_CONTEXT_DO_SEQUENCE);
461 ret = krb5_sockaddr2address (sa, &other_addr);
462 if (ret) {
463 krb5_log (context, log_facility, KPASSWDD_LOG_ERR,
464 "krb5_sockaddr2address: %s",
465 krb5_get_err_text(context, ret));
466 goto out;
469 ret = krb5_auth_con_setaddrs (context,
470 auth_context,
471 this_addr,
472 &other_addr);
473 krb5_free_address (context, &other_addr);
474 if (ret) {
475 krb5_log (context, log_facility, KPASSWDD_LOG_ERR,
476 "krb5_auth_con_setaddr: %s",
477 krb5_get_err_text(context, ret));
478 goto out;
481 if (verify (&auth_context, server, &ticket, &out_data,
482 s, sa, sa_size, msg, len) == 0) {
483 change (auth_context,
484 ticket->client,
486 sa, sa_size,
487 &out_data);
488 krb5_free_ticket (context, ticket);
489 free (ticket);
492 out:
493 krb5_data_free (&out_data);
494 krb5_auth_con_free (context, auth_context);
497 static int
498 doit (int port)
500 krb5_error_code ret;
501 krb5_principal server;
502 int *sockets;
503 int maxfd;
504 char *realm;
505 krb5_addresses addrs;
506 unsigned n, i;
507 fd_set real_fdset;
508 char *sa_buf;
509 int sa_max_size;
510 struct sockaddr *sa;
512 sa_max_size = krb5_max_sockaddr_size ();
513 sa_buf = malloc (sa_max_size);
514 if (sa_buf == NULL)
515 syslog_and_die ("out of memory");
516 sa = (struct sockaddr *)sa_buf;
518 ret = krb5_get_default_realm (context, &realm);
519 if (ret)
520 syslog_and_die ("krb5_get_default_realm: %s",
521 krb5_get_err_text(context, ret));
523 ret = krb5_build_principal (context,
524 &server,
525 strlen(realm),
526 realm,
527 "kadmin",
528 "changepw",
529 NULL);
530 if (ret)
531 syslog_and_die ("krb5_build_principal_ext: %s",
532 krb5_get_err_text(context, ret));
534 free (realm);
536 ret = krb5_get_all_client_addrs (&addrs);
537 if (ret)
538 syslog_and_die ("krb5_get_all_clients_addrs: %s",
539 krb5_get_err_text(context, ret));
541 n = addrs.len;
543 sockets = malloc (n * sizeof(*sockets));
544 maxfd = 0;
545 FD_ZERO(&real_fdset);
546 for (i = 0; i < n; ++i) {
547 int sa_size;
549 krb5_addr2sockaddr (&addrs.val[i], sa, &sa_size, port);
551 sockets[i] = socket (sa->sa_family, SOCK_DGRAM, 0);
552 if (sockets[i] < 0)
553 syslog_and_die ("socket: %m");
555 if (bind (sockets[i], sa, sa_size) < 0)
556 syslog_and_die ("bind: %m");
557 maxfd = max (maxfd, sockets[i]);
558 FD_SET(sockets[i], &real_fdset);
561 while(exit_flag == 0) {
562 int ret;
563 struct fd_set fdset = real_fdset;
565 ret = select (maxfd + 1, &fdset, NULL, NULL, NULL);
566 if (ret < 0)
567 if (errno == EINTR)
568 continue;
569 else
570 syslog_and_die ("select: %m");
571 for (i = 0; i < n; ++i)
572 if (FD_ISSET(sockets[i], &fdset)) {
573 u_char buf[BUFSIZ];
574 int addrlen = sa_max_size;
576 ret = recvfrom (sockets[i], buf, sizeof(buf), 0,
577 sa, &addrlen);
578 if (ret < 0)
579 if(errno == EINTR)
580 break;
581 else
582 syslog_and_die ("recvfrom: %m");
584 process (server, sockets[i],
585 &addrs.val[i],
586 sa, addrlen,
587 buf, ret);
590 krb5_free_addresses (context, &addrs);
591 krb5_free_principal (context, server);
592 krb5_free_context (context);
593 free (sa_buf);
594 return 0;
597 static RETSIGTYPE
598 sigterm(int sig)
600 exit_flag = 1;
604 main (int argc, char **argv)
606 krb5_error_code ret;
607 char *keyfile = NULL;
609 krb5_init_context (&context);
611 set_progname (argv[0]);
612 krb5_openlog (context, "kpasswdd", &log_facility);
614 ret = hdb_create (context, &db, database);
615 if (ret)
616 syslog_and_die ("Failed to open database %s: %s",
617 database, krb5_get_err_text(context, ret));
618 ret = hdb_set_master_key(context, db, keyfile);
619 if (ret)
620 syslog_and_die ("Failed to set master key: %s",
621 krb5_get_err_text(context, ret));
623 #ifdef HAVE_SIGACTION
625 struct sigaction sa;
627 sa.sa_flags = 0;
628 sa.sa_handler = sigterm;
629 sigemptyset(&sa.sa_mask);
631 sigaction(SIGINT, &sa, NULL);
633 #else
634 signal(SIGINT, sigterm);
635 #endif
637 return doit (krb5_getportbyname (context, "kpasswd", "udp", KPASSWD_PORT));