2 * Copyright (c) 1997 Kungliga Tekniska Högskolan
3 * (Royal Institute of Technology, Stockholm, Sweden).
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
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
39 #include "kpasswd_locl.h"
43 static krb5_context context
;
44 static krb5_log_facility
*log_facility
;
46 static sig_atomic_t exit_flag
= 0;
48 #define KPASSWDD_LOG_ERR 0
49 #define KPASSWDD_LOG_INFO 1
52 syslog_and_die (const char *m
, ...)
57 krb5_vlog (context
, log_facility
, KPASSWDD_LOG_ERR
, m
, args
);
62 static char *database
= HDB_DEFAULT_DB
;
74 u_int16_t len
, ap_rep_len
;
79 ap_rep_len
= ap_rep
->length
;
83 len
= 6 + ap_rep_len
+ rest
->length
;
85 *p
++ = (len
>> 8) & 0xFF;
86 *p
++ = (len
>> 0) & 0xFF;
89 *p
++ = (ap_rep_len
>> 8) & 0xFF;
90 *p
++ = (ap_rep_len
>> 0) & 0xFF;
92 memset (&msghdr
, 0, sizeof(msghdr
));
93 msghdr
.msg_name
= (void *)sa
;
94 msghdr
.msg_namelen
= sa_size
;
96 msghdr
.msg_iovlen
= sizeof(iov
)/sizeof(*iov
);
98 msghdr
.msg_control
= NULL
;
99 msghdr
.msg_controllen
= 0;
102 iov
[0].iov_base
= (char *)header
;
105 iov
[1].iov_base
= ap_rep
->data
;
106 iov
[1].iov_len
= ap_rep
->length
;
108 iov
[1].iov_base
= NULL
;
111 iov
[2].iov_base
= rest
->data
;
112 iov
[2].iov_len
= rest
->length
;
114 if (sendmsg (s
, &msghdr
, 0) < 0)
115 krb5_log (context
, log_facility
, KPASSWDD_LOG_ERR
,
121 make_result (krb5_data
*data
,
122 u_int16_t result_code
,
125 krb5_data_zero (data
);
127 data
->length
= asprintf ((char **)&data
->data
,
129 (result_code
>> 8) & 0xFF,
133 if (data
->data
== NULL
) {
134 krb5_log (context
, log_facility
, KPASSWDD_LOG_ERR
,
135 "Out of memory generating error reply");
142 reply_error (krb5_principal server
,
146 krb5_error_code error_code
,
147 u_int16_t result_code
,
151 krb5_data error_data
;
154 if (make_result(&e_data
, result_code
, expl
))
157 ret
= krb5_mk_error (context
,
165 krb5_data_free (&e_data
);
167 krb5_log (context
, log_facility
, KPASSWDD_LOG_ERR
,
168 "Could not even generate error reply: %s",
169 krb5_get_err_text (context
, ret
));
172 send_reply (s
, sa
, sa_size
, NULL
, &error_data
);
173 krb5_data_free (&error_data
);
177 reply_priv (krb5_auth_context auth_context
,
181 u_int16_t result_code
,
185 krb5_data krb_priv_data
;
186 krb5_data ap_rep_data
;
189 ret
= krb5_mk_rep (context
,
193 krb5_log (context
, log_facility
, KPASSWDD_LOG_ERR
,
194 "Could not even generate error reply: %s",
195 krb5_get_err_text (context
, ret
));
199 if (make_result(&e_data
, result_code
, expl
))
202 ret
= krb5_mk_priv (context
,
207 krb5_data_free (&e_data
);
209 krb5_log (context
, log_facility
, KPASSWDD_LOG_ERR
,
210 "Could not even generate error reply: %s",
211 krb5_get_err_text (context
, ret
));
214 send_reply (s
, sa
, sa_size
, &ap_rep_data
, &krb_priv_data
);
215 krb5_data_free (&ap_rep_data
);
216 krb5_data_free (&krb_priv_data
);
220 passwd_quality_check (krb5_data
*pwd
)
223 return "Password too short";
229 change (krb5_auth_context auth_context
,
230 krb5_principal principal
,
240 krb5_keyblock new_keyblock
, *old_keyblock
;
243 krb5_unparse_name (context
, principal
, &c
);
245 krb5_log (context
, log_facility
, KPASSWDD_LOG_INFO
,
246 "Changing password for %s", c
);
249 pwd_reason
= passwd_quality_check (pwd_data
);
250 if (pwd_reason
!= NULL
) {
251 krb5_log (context
, log_facility
,
252 KPASSWDD_LOG_ERR
, pwd_reason
);
253 reply_priv (auth_context
, s
, sa
, sa_size
, 4, pwd_reason
);
257 ret
= db
->open(context
, db
, O_RDWR
, 0600);
259 krb5_log (context
, log_facility
, KPASSWDD_LOG_ERR
,
260 "hdb_open: %s", krb5_get_err_text(context
, ret
));
261 reply_priv (auth_context
, s
, sa
, sa_size
, 2, "hdb_open failed");
265 krb5_copy_principal (context
, principal
, &ent
.principal
);
267 ret
= db
->fetch (context
, db
, &ent
);
270 case HDB_ERR_NOENTRY
:
271 krb5_log (context
, log_facility
, KPASSWDD_LOG_ERR
,
272 "not found in database");
273 reply_priv (auth_context
, s
, sa
, sa_size
, 2,
274 "entry not found in database");
279 krb5_log (context
, log_facility
, KPASSWDD_LOG_ERR
,
280 "dbfetch: %s", krb5_get_err_text(context
, ret
));
281 reply_priv (auth_context
, s
, sa
, sa_size
, 2,
287 * Compare with the first key to see if it already has been
288 * changed. If it hasn't, store the new key in the database and
289 * string2key all the rest of them.
292 krb5_data_zero (&salt
);
293 krb5_get_salt (principal
, &salt
);
294 memset (&new_keyblock
, 0, sizeof(new_keyblock
));
295 old_keyblock
= &ent
.keys
.val
[0].key
;
296 krb5_string_to_key_data (pwd_data
, &salt
,
297 old_keyblock
->keytype
, /* XXX */
300 if (new_keyblock
.keytype
== old_keyblock
->keytype
301 && new_keyblock
.keyvalue
.length
== old_keyblock
->keyvalue
.length
302 && memcmp (new_keyblock
.keyvalue
.data
,
303 old_keyblock
->keyvalue
.data
,
304 new_keyblock
.keyvalue
.length
) == 0) {
310 free_EncryptionKey (old_keyblock
);
311 memset (old_keyblock
, 0, sizeof(*old_keyblock
));
312 old_keyblock
->keytype
= new_keyblock
.keytype
;
313 krb5_data_copy (&old_keyblock
->keyvalue
,
314 new_keyblock
.keyvalue
.data
,
315 new_keyblock
.keyvalue
.length
);
317 for(i
= 1; i
< ent
.keys
.len
; ++i
) {
318 free_Key (&ent
.keys
.val
[i
]);
319 krb5_string_to_key_data (pwd_data
,
321 ent
.keys
.val
[i
].key
.keytype
,
322 &ent
.keys
.val
[i
].key
);
326 e
= malloc(sizeof(*e
));
327 e
->time
= time(NULL
);
328 krb5_copy_principal (context
, principal
, &e
->principal
);
329 if (ent
.modified_by
) {
330 free_Event (ent
.modified_by
);
331 free (ent
.modified_by
);
335 int t
= krb5_config_get_time(context
->cf
,
337 "pw_expiration", NULL
);
339 *ent
.pw_end
= e
->time
+ t
;
345 ret
= db
->store (context
, db
, 1, &ent
);
347 krb5_data_free (&salt
);
348 krb5_free_keyblock_contents (context
, &new_keyblock
);
351 krb5_log (context
, log_facility
, KPASSWDD_LOG_ERR
,
352 "dbstore: %s", krb5_get_err_text (context
, ret
));
353 reply_priv (auth_context
, s
, sa
, sa_size
, 2,
356 reply_priv (auth_context
, s
, sa
, sa_size
, 0, "password changed");
359 hdb_free_entry (context
, &ent
);
360 db
->close (context
, db
);
364 verify (krb5_auth_context
*auth_context
,
365 krb5_principal server
,
366 krb5_ticket
**ticket
,
375 u_int16_t pkt_len
, pkt_ver
, ap_req_len
;
376 krb5_data ap_req_data
;
377 krb5_data krb_priv_data
;
379 pkt_len
= (msg
[0] << 8) | (msg
[1]);
380 pkt_ver
= (msg
[2] << 8) | (msg
[3]);
381 ap_req_len
= (msg
[4] << 8) | (msg
[5]);
382 if (pkt_len
!= len
) {
383 krb5_log (context
, log_facility
, KPASSWDD_LOG_ERR
,
384 "Strange len: %d != %d", pkt_len
, len
);
385 reply_error (server
, s
, sa
, sa_size
, 0, 1, "bad length");
388 if (pkt_ver
!= 0x0001) {
389 krb5_log (context
, log_facility
, KPASSWDD_LOG_ERR
,
390 "Bad version (%d)", pkt_ver
);
391 reply_error (server
, s
, sa
, sa_size
, 0, 1, "bad version");
395 ap_req_data
.data
= msg
+ 6;
396 ap_req_data
.length
= ap_req_len
;
398 ret
= krb5_rd_req (context
,
406 krb5_log (context
, log_facility
, KPASSWDD_LOG_ERR
, "krb5_rd_req: %s",
407 krb5_get_err_text(context
, ret
));
408 reply_error (server
, s
, sa
, sa_size
, ret
, 3, "rd_req failed");
412 if (!(*ticket
)->ticket
.flags
.initial
) {
413 krb5_log (context
, log_facility
, KPASSWDD_LOG_ERR
,
414 "initial flag not set");
415 reply_error (server
, s
, sa
, sa_size
, ret
, 1,
416 "initial flag not set");
419 krb_priv_data
.data
= msg
+ 6 + ap_req_len
;
420 krb_priv_data
.length
= len
- 6 - ap_req_len
;
422 ret
= krb5_rd_priv (context
,
429 krb5_log (context
, log_facility
, KPASSWDD_LOG_ERR
, "krb5_rd_priv: %s",
430 krb5_get_err_text(context
, ret
));
431 reply_error (server
, s
, sa
, sa_size
, ret
, 3, "rd_priv failed");
436 krb5_free_ticket (context
, *ticket
);
441 process (krb5_principal server
,
443 krb5_address
*this_addr
,
450 krb5_auth_context auth_context
= NULL
;
453 krb5_address other_addr
;
455 krb5_data_zero (&out_data
);
457 ret
= krb5_auth_con_init (context
, &auth_context
);
459 krb5_log (context
, log_facility
, KPASSWDD_LOG_ERR
,
460 "krb5_auth_con_init: %s",
461 krb5_get_err_text(context
, ret
));
465 krb5_auth_con_setflags (context
, auth_context
,
466 KRB5_AUTH_CONTEXT_DO_SEQUENCE
);
468 ret
= krb5_sockaddr2address (sa
, &other_addr
);
470 krb5_log (context
, log_facility
, KPASSWDD_LOG_ERR
,
471 "krb5_sockaddr2address: %s",
472 krb5_get_err_text(context
, ret
));
476 ret
= krb5_auth_con_setaddrs (context
,
480 krb5_free_address (context
, &other_addr
);
482 krb5_log (context
, log_facility
, KPASSWDD_LOG_ERR
,
483 "krb5_auth_con_setaddr: %s",
484 krb5_get_err_text(context
, ret
));
488 if (verify (&auth_context
, server
, &ticket
, &out_data
,
489 s
, sa
, sa_size
, msg
, len
) == 0) {
490 change (auth_context
,
495 krb5_free_ticket (context
, ticket
);
500 krb5_data_free (&out_data
);
501 krb5_auth_con_free (context
, auth_context
);
508 krb5_principal server
;
512 krb5_addresses addrs
;
519 sa_max_size
= krb5_max_sockaddr_size ();
520 sa_buf
= malloc (sa_max_size
);
522 syslog_and_die ("out of memory");
523 sa
= (struct sockaddr
*)sa_buf
;
525 ret
= krb5_get_default_realm (context
, &realm
);
527 syslog_and_die ("krb5_get_default_realm: %s",
528 krb5_get_err_text(context
, ret
));
530 ret
= krb5_build_principal (context
,
538 syslog_and_die ("krb5_build_principal_ext: %s",
539 krb5_get_err_text(context
, ret
));
543 ret
= krb5_get_all_client_addrs (&addrs
);
545 syslog_and_die ("krb5_get_all_clients_addrs: %s",
546 krb5_get_err_text(context
, ret
));
550 sockets
= malloc (n
* sizeof(*sockets
));
552 FD_ZERO(&real_fdset
);
553 for (i
= 0; i
< n
; ++i
) {
556 krb5_addr2sockaddr (&addrs
.val
[i
], sa
, &sa_size
, port
);
558 sockets
[i
] = socket (sa
->sa_family
, SOCK_DGRAM
, 0);
560 syslog_and_die ("socket: %m");
562 if (bind (sockets
[i
], sa
, sa_size
) < 0)
563 syslog_and_die ("bind: %m");
564 maxfd
= max (maxfd
, sockets
[i
]);
565 FD_SET(sockets
[i
], &real_fdset
);
568 while(exit_flag
== 0) {
570 struct fd_set fdset
= real_fdset
;
572 ret
= select (maxfd
+ 1, &fdset
, NULL
, NULL
, NULL
);
577 syslog_and_die ("select: %m");
578 for (i
= 0; i
< n
; ++i
)
579 if (FD_ISSET(sockets
[i
], &fdset
)) {
581 int addrlen
= sa_max_size
;
583 ret
= recvfrom (sockets
[i
], buf
, sizeof(buf
), 0,
589 syslog_and_die ("recvfrom: %m");
591 process (server
, sockets
[i
],
597 krb5_free_addresses (context
, &addrs
);
598 krb5_free_principal (context
, server
);
599 krb5_free_context (context
);
611 main (int argc
, char **argv
)
614 char *keyfile
= NULL
;
616 krb5_init_context (&context
);
618 set_progname (argv
[0]);
619 krb5_openlog (context
, "kpasswdd", &log_facility
);
621 ret
= hdb_create (context
, &db
, database
);
623 syslog_and_die ("Failed to open database %s: %s",
624 database
, krb5_get_err_text(context
, ret
));
625 ret
= hdb_set_master_key(context
, db
, keyfile
);
627 syslog_and_die ("Failed to set master key: %s",
628 krb5_get_err_text(context
, ret
));
630 #ifdef HAVE_SIGACTION
635 sa
.sa_handler
= sigterm
;
636 sigemptyset(&sa
.sa_mask
);
638 sigaction(SIGINT
, &sa
, NULL
);
641 signal(SIGINT
, sigterm
);
644 return doit (krb5_getportbyname (context
, "kpasswd", "udp", KPASSWD_PORT
));