3 THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
4 ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED
5 TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
8 Copyright (C) 1999 Microsoft Corporation. All rights reserved.
16 Set a user's password using the
17 Kerberos Change Password Protocol (I-D) variant for Windows 2000
21 * lib/krb5/os/changepw.c
23 * Copyright 1990 by the Massachusetts Institute of Technology.
24 * All Rights Reserved.
26 * Export of this software from the United States of America may
27 * require a specific license from the United States Government.
28 * It is the responsibility of any person or organization contemplating
29 * export to obtain such a license before exporting.
31 * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
32 * distribute this software and its documentation for any purpose and
33 * without fee is hereby granted, provided that the above copyright
34 * notice appear in all copies and that both that copyright notice and
35 * this permission notice appear in supporting documentation, and that
36 * the name of M.I.T. not be used in advertising or publicity pertaining
37 * to distribution of the software without specific, written prior
38 * permission. M.I.T. makes no representations about the suitability of
39 * this software for any purpose. It is provided "as is" without express
40 * or implied warranty.
56 #include <sys/socket.h>
58 #include <sys/select.h>
64 #include <sys/timeb.h>
70 #ifndef krb5_is_krb_error
71 #define krb5_is_krb_error(dat)\
72 ((dat) && (dat)->length && ((dat)->data[0] == 0x7e ||\
73 (dat)->data[0] == 0x5e))
77 #if defined(_WIN32) && !defined(__CYGWIN32__)
79 #define ECONNABORTED WSAECONNABORTED
82 #define ECONNREFUSED WSAECONNREFUSED
85 #define EHOSTUNREACH WSAEHOSTUNREACH
87 #endif /* _WIN32 && !__CYGWIN32__ */
89 static const char rcsid[] = "$Id$";
91 static int frequency[26][26] =
92 { {4, 20, 28, 52, 2, 11, 28, 4, 32, 4, 6, 62, 23, 167, 2, 14, 0, 83, 76,
93 127, 7, 25, 8, 1, 9, 1}, /* aa - az */
94 {13, 0, 0, 0, 55, 0, 0, 0, 8, 2, 0, 22, 0, 0, 11, 0, 0, 15, 4, 2, 13, 0,
95 0, 0, 15, 0}, /* ba - bz */
96 {32, 0, 7, 1, 69, 0, 0, 33, 17, 0, 10, 9, 1, 0, 50, 3, 0, 10, 0, 28, 11,
97 0, 0, 0, 3, 0}, /* ca - cz */
98 {40, 16, 9, 5, 65, 18, 3, 9, 56, 0, 1, 4, 15, 6, 16, 4, 0, 21, 18, 53,
99 19, 5, 15, 0, 3, 0}, /* da - dz */
100 {84, 20, 55, 125, 51, 40, 19, 16, 50, 1, 4, 55, 54, 146, 35, 37, 6, 191,
101 149, 65, 9, 26, 21, 12, 5, 0}, /* ea - ez */
102 {19, 3, 5, 1, 19, 21, 1, 3, 30, 2, 0, 11, 1, 0, 51, 0, 0, 26, 8, 47, 6,
103 3, 3, 0, 2, 0}, /* fa - fz */
104 {20, 4, 3, 2, 35, 1, 3, 15, 18, 0, 0, 5, 1, 4, 21, 1, 1, 20, 9, 21, 9,
105 0, 5, 0, 1, 0}, /* ga - gz */
106 {101, 1, 3, 0, 270, 5, 1, 6, 57, 0, 0, 0, 3, 2, 44, 1, 0, 3, 10, 18, 6,
107 0, 5, 0, 3, 0}, /* ha - hz */
108 {40, 7, 51, 23, 25, 9, 11, 3, 0, 0, 2, 38, 25, 202, 56, 12, 1, 46, 79,
109 117, 1, 22, 0, 4, 0, 3}, /* ia - iz */
110 {3, 0, 0, 0, 5, 0, 0, 0, 1, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0, 3, 0, 0, 0,
112 {1, 0, 0, 0, 11, 0, 0, 0, 13, 0, 0, 0, 0, 2, 0, 0, 0, 0, 6, 2, 1, 0, 2,
113 0, 1, 0}, /* ka - kz */
114 {44, 2, 5, 12, 62, 7, 5, 2, 42, 1, 1, 53, 2, 2, 25, 1, 1, 2, 16, 23, 9,
115 0, 1, 0, 33, 0}, /* la - lz */
116 {52, 14, 1, 0, 64, 0, 0, 3, 37, 0, 0, 0, 7, 1, 17, 18, 1, 2, 12, 3, 8,
117 0, 1, 0, 2, 0}, /* ma - mz */
118 {42, 10, 47, 122, 63, 19, 106, 12, 30, 1, 6, 6, 9, 7, 54, 7, 1, 7, 44,
119 124, 6, 1, 15, 0, 12, 0}, /* na - nz */
120 {7, 12, 14, 17, 5, 95, 3, 5, 14, 0, 0, 19, 41, 134, 13, 23, 0, 91, 23,
121 42, 55, 16, 28, 0, 4, 1}, /* oa - oz */
122 {19, 1, 0, 0, 37, 0, 0, 4, 8, 0, 0, 15, 1, 0, 27, 9, 0, 33, 14, 7, 6, 0,
123 0, 0, 0, 0}, /* pa - pz */
124 {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 17, 0, 0,
125 0, 0, 0}, /* qa - qz */
126 {83, 8, 16, 23, 169, 4, 8, 8, 77, 1, 10, 5, 26, 16, 60, 4, 0, 24, 37,
127 55, 6, 11, 4, 0, 28, 0}, /* ra - rz */
128 {65, 9, 17, 9, 73, 13, 1, 47, 75, 3, 0, 7, 11, 12, 56, 17, 6, 9, 48,
129 116, 35, 1, 28, 0, 4, 0}, /* sa - sz */
130 {57, 22, 3, 1, 76, 5, 2, 330, 126, 1, 0, 14, 10, 6, 79, 7, 0, 49, 50,
131 56, 21, 2, 27, 0, 24, 0}, /* ta - tz */
132 {11, 5, 9, 6, 9, 1, 6, 0, 9, 0, 1, 19, 5, 31, 1, 15, 0, 47, 39, 31, 0,
133 3, 0, 0, 0, 0}, /* ua - uz */
134 {7, 0, 0, 0, 72, 0, 0, 0, 28, 0, 0, 0, 0, 0, 5, 0, 0, 0, 0, 0, 0, 0, 0,
135 0, 3, 0}, /* va - vz */
136 {36, 1, 1, 0, 38, 0, 0, 33, 36, 0, 0, 4, 1, 8, 15, 0, 0, 0, 4, 2, 0, 0,
137 1, 0, 0, 0}, /* wa - wz */
138 {1, 0, 2, 0, 0, 1, 0, 0, 3, 0, 0, 0, 0, 0, 1, 5, 0, 0, 0, 3, 0, 0, 1, 0,
140 {14, 5, 4, 2, 7, 12, 12, 6, 10, 0, 0, 3, 7, 5, 17, 3, 0, 4, 16, 30, 0,
141 0, 5, 0, 0, 0}, /* ya - yz */
142 {1, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
143 0, 0}}; /* za - zz */
146 * This MUST be equal to the sum of the equivalent rows above.
149 static int row_sums[26] =
150 {796, 160, 284, 401, 1276, 262, 199, 539, 777,
151 16, 39, 351, 243, 751, 662, 181, 17, 683,
152 662, 968, 248, 115, 180, 17, 162, 5};
155 * Frequencies of starting characters
158 static int start_freq [26] =
159 {1299, 425, 725, 271, 375, 470, 93, 223, 1009,
160 24, 20, 355, 379, 319, 823, 618, 21, 317,
161 962, 1991, 271, 104, 516, 6, 16, 14};
164 * This MUST be equal to the sum of all elements in the above array.
166 static int total_sum = 11646;
169 void generate_password(char *password);
170 int set_password(char *user, char *domain);
171 krb5_error_code encode_krb5_setpw
172 PROTOTYPE((const krb5_setpw *rep, krb5_data ** code));
174 krb5_locate_kpasswd(krb5_context context, const krb5_data *realm,
175 struct sockaddr **addr_pp, int *naddrs);
177 krb5_error_code krb5_mk_setpw_req(krb5_context context, krb5_auth_context auth_context,
178 krb5_data *ap_req, krb5_principal targprinc,
179 char *passwd, krb5_data *packet)
184 krb5_data *encoded_setpw;
185 krb5_replay_data replay;
187 register int count = 2;
189 memset (&setpw, 0, sizeof(krb5_setpw));
190 if (ret = krb5_auth_con_setflags(context, auth_context,
191 KRB5_AUTH_CONTEXT_DO_SEQUENCE))
193 setpw.targprinc = targprinc;
194 setpw.newpasswd.length = strlen(passwd);
195 setpw.newpasswd.data = passwd;
196 if ((ret = encode_krb5_setpw(&setpw, &encoded_setpw)))
198 if (ret = krb5_mk_priv(context, auth_context,
199 encoded_setpw, &cipherpw, &replay))
201 packet->length = 6 + ap_req->length + cipherpw.length;
202 packet->data = (char *) malloc(packet->length);
205 *ptr++ = (packet->length>>8) & 0xff;
206 *ptr++ = packet->length & 0xff;
210 /* ap_req length, big-endian */
211 *ptr++ = (ap_req->length>>8) & 0xff;
212 *ptr++ = ap_req->length & 0xff;
214 memcpy(ptr, ap_req->data, ap_req->length);
215 ptr += ap_req->length;
216 /* krb-priv of password */
217 memcpy(ptr, cipherpw.data, cipherpw.length);
221 krb5_error_code krb5_rd_setpw_rep(krb5_context context, krb5_auth_context auth_context,
222 krb5_data *packet, int *result_code,
223 krb5_data *result_data)
230 krb5_data cipherresult;
231 krb5_data clearresult;
232 krb5_error *krberror;
233 krb5_replay_data replay;
235 krb5_ap_rep_enc_part *ap_rep_enc;
237 if (packet->length < 4)
238 return(KRB5KRB_AP_ERR_MODIFIED);
240 if (krb5_is_krb_error(packet))
242 ret = decode_krb5_error(packet, &krberror);
245 ret = krberror->error;
246 krb5_free_error(context, krberror);
250 plen = (*ptr++ & 0xff);
251 plen = (plen<<8) | (*ptr++ & 0xff);
252 if (plen != packet->length)
253 return(KRB5KRB_AP_ERR_MODIFIED);
254 vno = (*ptr++ & 0xff);
255 vno = (vno<<8) | (*ptr++ & 0xff);
256 if (vno != KRB5_KPASSWD_VERS_SETPW && vno != KRB5_KPASSWD_VERS_CHANGEPW)
257 return(KRB5KDC_ERR_BAD_PVNO);
258 /* read, check ap-rep length */
259 ap_rep.length = (*ptr++ & 0xff);
260 ap_rep.length = (ap_rep.length<<8) | (*ptr++ & 0xff);
261 if (ptr + ap_rep.length >= packet->data + packet->length)
262 return(KRB5KRB_AP_ERR_MODIFIED);
267 ptr += ap_rep.length;
268 if (ret = krb5_rd_rep(context, auth_context, &ap_rep, &ap_rep_enc))
270 krb5_free_ap_rep_enc_part(context, ap_rep_enc);
271 /* extract and decrypt the result */
272 cipherresult.data = ptr;
273 cipherresult.length = (packet->data + packet->length) - ptr;
274 /* XXX there's no api to do this right. The problem is that
275 if there's a remote subkey, it will be used. This is
276 not what the spec requires */
277 tmp = auth_context->remote_subkey;
278 auth_context->remote_subkey = NULL;
279 ret = krb5_rd_priv(context, auth_context, &cipherresult, &clearresult,
281 auth_context->remote_subkey = tmp;
287 cipherresult.data = ptr;
288 cipherresult.length = (packet->data + packet->length) - ptr;
290 if (ret = krb5_rd_error(context, &cipherresult, &krberror))
293 clearresult = krberror->e_data;
295 if (clearresult.length < 2)
297 ret = KRB5KRB_AP_ERR_MODIFIED;
300 ptr = clearresult.data;
301 *result_code = (*ptr++ & 0xff);
302 *result_code = (*result_code<<8) | (*ptr++ & 0xff);
303 if ((*result_code < KRB5_KPASSWD_SUCCESS) ||
304 (*result_code > KRB5_KPASSWD_ACCESSDENIED))
306 ret = KRB5KRB_AP_ERR_MODIFIED;
309 /* all success replies should be authenticated/encrypted */
310 if ((ap_rep.length == 0) && (*result_code == KRB5_KPASSWD_SUCCESS))
312 ret = KRB5KRB_AP_ERR_MODIFIED;
315 result_data->length = (clearresult.data + clearresult.length) - ptr;
316 if (result_data->length)
318 result_data->data = (char *) malloc(result_data->length);
319 memcpy(result_data->data, ptr, result_data->length);
322 result_data->data = NULL;
326 free(clearresult.data);
328 krb5_free_error(context, krberror);
332 krb5_error_code krb5_set_password(krb5_context context, krb5_ccache ccache,
333 char *newpw, char *user, char *domain,
336 krb5_auth_context auth_context;
340 krb5_data result_string;
341 krb5_address local_kaddr;
342 krb5_address remote_kaddr;
345 krb5_error_code code;
348 struct sockaddr *addr_p;
349 struct sockaddr local_addr;
350 struct sockaddr remote_addr;
351 struct sockaddr tmp_addr;
358 int local_result_code;
361 krb5_principal targprinc;
362 struct timeval TimeVal;
368 memset(&local_addr, 0, sizeof(local_addr));
369 memset(&local_kaddr, 0, sizeof(local_kaddr));
370 memset(&result_string, 0, sizeof(result_string));
371 memset(&remote_kaddr, 0, sizeof(remote_kaddr));
372 memset(&chpw_req, 0, sizeof(krb5_data));
373 memset(&chpw_rep, 0, sizeof(krb5_data));
374 memset(&ap_req, 0, sizeof(krb5_data));
376 memset(&creds, 0, sizeof(creds));
377 memset(userrealm, '\0', sizeof(userrealm));
379 for (i = 0; i < (int)strlen(domain); i++)
380 userrealm[i] = toupper(domain[i]);
382 sprintf(temp, "%s@%s", user, userrealm);
383 krb5_parse_name(context, temp, &targprinc);
385 sprintf(temp, "%s@%s", "kadmin/changepw", userrealm);
386 if (code = krb5_parse_name(context, temp, &creds.server))
389 if (code = krb5_cc_get_principal(context, ccache, &creds.client))
391 if (code = krb5_get_credentials(context, 0, ccache, &creds, &credsp))
393 if (code = krb5_mk_req_extended(context, &auth_context, AP_OPTS_USE_SUBKEY,
394 NULL, credsp, &ap_req))
396 if (code = krb5_locate_kpasswd(context, &targprinc->realm, &addr_p, &out))
399 { /* Couldn't resolve any KPASSWD names */
404 /* this is really obscure. s1 is used for all communications. it
405 is left unconnected in case the server is multihomed and routes
406 are asymmetric. s2 is connected to resolve routes and get
407 addresses. this is the *only* way to get proper addresses for
408 multihomed hosts if routing is asymmetric.
410 A related problem in the server, but not the client, is that
411 many os's have no way to disconnect a connected udp socket, so
412 the s2 socket needs to be closed and recreated for each
413 request. The s1 socket must not be closed, or else queued
414 requests will be lost.
416 A "naive" client implementation (one socket, no connect,
417 hostname resolution to get the local ip addr) will work and
418 interoperate if the client is single-homed. */
420 if ((s1 = socket(AF_INET, SOCK_DGRAM, 0)) == INVALID_SOCKET)
425 if ((s2 = socket(AF_INET, SOCK_DGRAM, 0)) == INVALID_SOCKET)
432 for (i=0; i<out; i++)
434 if (connect(s2, &addr_p[i], sizeof(addr_p[i])) == SOCKET_ERROR)
437 addrlen = sizeof(local_addr);
438 if (getsockname(s2, &local_addr, &addrlen) < 0)
440 if (((struct sockaddr_in *)&local_addr)->sin_addr.s_addr != 0)
442 local_kaddr.addrtype = ADDRTYPE_INET;
444 sizeof(((struct sockaddr_in *) &local_addr)->sin_addr);
445 local_kaddr.contents =
446 (char *) &(((struct sockaddr_in *) &local_addr)->sin_addr);
450 krb5_address **addrs;
451 krb5_os_localaddr(context, &addrs);
452 local_kaddr.magic = addrs[0]->magic;
453 local_kaddr.addrtype = addrs[0]->addrtype;
454 local_kaddr.length = addrs[0]->length;
455 local_kaddr.contents = calloc(1, addrs[0]->length);
456 memcpy(local_kaddr.contents, addrs[0]->contents, addrs[0]->length);
457 krb5_free_addresses(context, addrs);
460 addrlen = sizeof(remote_addr);
461 if (getpeername(s2, &remote_addr, &addrlen) < 0)
463 remote_kaddr.addrtype = ADDRTYPE_INET;
464 remote_kaddr.length =
465 sizeof(((struct sockaddr_in *) &remote_addr)->sin_addr);
466 remote_kaddr.contents =
467 (char *) &(((struct sockaddr_in *) &remote_addr)->sin_addr);
468 /* mk_priv requires that the local address be set.
469 getsockname is used for this. rd_priv requires that the
470 remote address be set. recvfrom is used for this. If
471 rd_priv is given a local address, and the message has the
472 recipient addr in it, this will be checked. However, there
473 is simply no way to know ahead of time what address the
474 message will be delivered *to*. Therefore, it is important
475 that either no recipient address is in the messages when
476 mk_priv is called, or that no local address is passed to
477 rd_priv. Both is a better idea, and I have done that. In
478 summary, when mk_priv is called, *only* a local address is
479 specified. when rd_priv is called, *only* a remote address
480 is specified. Are we having fun yet? */
481 if (code = krb5_auth_con_setaddrs(context, auth_context, &local_kaddr, NULL))
483 if (code = krb5_mk_setpw_req(context, auth_context, &ap_req,
484 targprinc, newpw, &chpw_req))
486 if ((cc = sendto(s1, chpw_req.data, chpw_req.length, 0,
487 (struct sockaddr *) &addr_p[i],
488 sizeof(addr_p[i]))) != chpw_req.length)
489 continue; /* try the next addr */
491 krb5_free_data_contents(context, &chpw_req);
495 chpw_rep.length = 1500;
496 chpw_rep.data = (char *) calloc(1, chpw_rep.length);
497 /* XXX need a timeout/retry loop here */
498 /* "recv" would be good enough here... except that Windows/NT
499 commits the atrocity of returning -1 to indicate failure,
500 but leaving errno set to 0.
502 "recvfrom(...,NULL,NULL)" would seem to be a good enough
503 alternative, and it works on NT, but it doesn't work on
504 SunOS 4.1.4 or Irix 5.3. Thus we must actually accept the
505 value and discard it. */
506 tmp_len = sizeof(tmp_addr);
511 FD_SET(s1, &readfds);
512 code = select(1, &readfds, NULL, NULL, &TimeVal);
514 if ((code == 0) || (code == SOCKET_ERROR))
520 if ((s1 = socket(AF_INET, SOCK_DGRAM, 0)) == INVALID_SOCKET)
522 if ((s2 = socket(AF_INET, SOCK_DGRAM, 0)) == INVALID_SOCKET)
531 if ((cc = recvfrom(s1, chpw_rep.data, chpw_rep.length, 0, &tmp_addr, &tmp_len)) < 0)
536 chpw_rep.length = cc;
537 if (code = krb5_auth_con_setaddrs(context, auth_context, NULL,
542 local_result_code = 0;
543 code = krb5_rd_setpw_rep(context, auth_context, &chpw_rep,
544 &local_result_code, &result_string);
547 *result_code = local_result_code;
549 krb5_free_data_contents(context, &chpw_req);
561 if (auth_context != NULL)
562 krb5_auth_con_free(context, auth_context);
563 if (ap_req.data != NULL)
565 krb5_free_data_contents(context, &ap_req);
569 krb5_free_cred_contents(context, &creds);
571 krb5_free_creds(context, credsp);
572 if (targprinc != NULL)
573 krb5_free_principal(context, targprinc);
577 int set_password(char *user, char *domain)
579 krb5_context context;
582 krb5_error_code retval;
583 char pw[PW_LENGTH+1];
585 if (retval = krb5_init_context(&context))
587 if (retval = krb5_cc_default(context, &ccache))
590 memset(pw, '\0', sizeof(pw));
591 generate_password(pw);
592 retval = krb5_set_password(context, ccache, pw, user, domain, &res_code);
594 krb5_cc_close(context, ccache);
595 krb5_free_context(context);
599 void generate_password(char *password)
610 for (line = 22; line; --line)
612 for (word = 7; word; --word)
614 position = myrandom()%total_sum;
615 for(row_position = 0, j = 0; position >= row_position; row_position += start_freq[j], j++)
617 *(pwp = password) = j + 'a' - 1;
618 for (nchars = PW_LENGTH-1; nchars; --nchars)
622 position = myrandom()%row_sums[i];
623 for (row_position = 0, j = 0; position >= row_position; row_position += frequency[i][j], j++)
639 struct _timeb timebuffer;
650 srand(timebuffer.time ^ timebuffer.millitm ^ pid);
652 gettimeofday(&tv, (struct timezone *) NULL);
653 srandom(tv.tv_sec ^ tv.tv_usec ^ pid);