Add clock skew handling to our kerberos code. This allows us to cope with
[Samba.git] / source / libads / krb5_setpw.c
bloba49b6cbe3b0ee8a01ed6ec30286544d0e175921c
1 /*
2 Unix SMB/CIFS implementation.
3 krb5 set password implementation
4 Copyright (C) Andrew Tridgell 2001
5 Copyright (C) Remus Koos 2001 (remuskoos@yahoo.com)
7 This program is free software; you can redistribute it and/or modify
8 it under the terms of the GNU General Public License as published by
9 the Free Software Foundation; either version 2 of the License, or
10 (at your option) any later version.
12 This program is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 GNU General Public License for more details.
17 You should have received a copy of the GNU General Public License
18 along with this program; if not, write to the Free Software
19 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
22 #include "includes.h"
24 #ifdef HAVE_KRB5
26 #define DEFAULT_KPASSWD_PORT 464
27 #define KRB5_KPASSWD_VERS_CHANGEPW 1
28 #define KRB5_KPASSWD_VERS_SETPW 0xff80
29 #define KRB5_KPASSWD_ACCESSDENIED 5
30 #define KRB5_KPASSWD_BAD_VERSION 6
32 /* This implements the Kerb password change protocol as specifed in
33 * kerb-chg-password-02.txt
35 static DATA_BLOB encode_krb5_setpw(const char *principal, const char *password)
37 char* princ_part1 = NULL;
38 char* princ_part2 = NULL;
39 char* realm = NULL;
40 char* c;
41 char* princ;
43 ASN1_DATA req;
44 DATA_BLOB ret;
47 princ = strdup(principal);
49 if ((c = strchr(princ, '/')) == NULL) {
50 c = princ;
51 } else {
52 *c = '\0';
53 c++;
54 princ_part1 = princ;
57 princ_part2 = c;
59 if ((c = strchr(c, '@')) != NULL) {
60 *c = '\0';
61 c++;
62 realm = c;
65 memset(&req, 0, sizeof(req));
67 asn1_push_tag(&req, ASN1_SEQUENCE(0));
68 asn1_push_tag(&req, ASN1_CONTEXT(0));
69 asn1_write_OctetString(&req, password, strlen(password));
70 asn1_pop_tag(&req);
72 asn1_push_tag(&req, ASN1_CONTEXT(1));
73 asn1_push_tag(&req, ASN1_SEQUENCE(0));
75 asn1_push_tag(&req, ASN1_CONTEXT(0));
76 asn1_write_Integer(&req, 1);
77 asn1_pop_tag(&req);
79 asn1_push_tag(&req, ASN1_CONTEXT(1));
80 asn1_push_tag(&req, ASN1_SEQUENCE(0));
82 if (princ_part1)
83 asn1_write_GeneralString(&req, princ_part1);
85 asn1_write_GeneralString(&req, princ_part2);
86 asn1_pop_tag(&req);
87 asn1_pop_tag(&req);
88 asn1_pop_tag(&req);
89 asn1_pop_tag(&req);
91 asn1_push_tag(&req, ASN1_CONTEXT(2));
92 asn1_write_GeneralString(&req, realm);
93 asn1_pop_tag(&req);
94 asn1_pop_tag(&req);
96 ret = data_blob(req.data, req.length);
97 asn1_free(&req);
99 free(princ);
101 return ret;
104 static krb5_error_code build_setpw_request(krb5_context context,
105 krb5_auth_context auth_context,
106 krb5_data *ap_req,
107 const char *princ,
108 const char *passwd,
109 krb5_data *packet)
111 krb5_error_code ret;
112 krb5_data cipherpw;
113 krb5_data encoded_setpw;
114 krb5_replay_data replay;
115 char *p;
116 DATA_BLOB setpw;
118 ret = krb5_auth_con_setflags(context,
119 auth_context,KRB5_AUTH_CONTEXT_DO_SEQUENCE);
120 if (ret) {
121 DEBUG(1,("krb5_auth_con_setflags failed (%s)\n",
122 error_message(ret)));
123 return ret;
126 setpw = encode_krb5_setpw(princ, passwd);
128 encoded_setpw.data = setpw.data;
129 encoded_setpw.length = setpw.length;
131 ret = krb5_mk_priv(context, auth_context,
132 &encoded_setpw, &cipherpw, &replay);
134 data_blob_free(&setpw); /*from 'encode_krb5_setpw(...)' */
136 if (ret) {
137 DEBUG(1,("krb5_mk_priv failed (%s)\n", error_message(ret)));
138 return ret;
141 packet->data = (char *)malloc(ap_req->length + cipherpw.length + 6);
143 /* see the RFC for details */
144 p = packet->data + 2;
145 RSSVAL(p, 0, 0xff80); p += 2;
146 RSSVAL(p, 0, ap_req->length); p += 2;
147 memcpy(p, ap_req->data, ap_req->length); p += ap_req->length;
148 memcpy(p, cipherpw.data, cipherpw.length); p += cipherpw.length;
149 packet->length = PTR_DIFF(p,packet->data);
150 RSSVAL(packet->data, 0, packet->length);
152 free(cipherpw.data); /* from 'krb5_mk_priv(...)' */
154 return 0;
157 static krb5_error_code parse_setpw_reply(krb5_context context,
158 krb5_auth_context auth_context,
159 krb5_data *packet)
161 krb5_data ap_rep;
162 char *p;
163 int vnum, ret, res_code;
164 krb5_data cipherresult;
165 krb5_data clearresult;
166 krb5_ap_rep_enc_part *ap_rep_enc;
167 krb5_replay_data replay;
169 if (packet->length < 4) {
170 return KRB5KRB_AP_ERR_MODIFIED;
173 p = packet->data;
175 if (packet->data[0] == 0x7e || packet->data[0] == 0x5e) {
176 /* it's an error packet. We should parse it ... */
177 DEBUG(1,("Got error packet 0x%x from kpasswd server\n",
178 packet->data[0]));
179 return KRB5KRB_AP_ERR_MODIFIED;
182 if (RSVAL(p, 0) != packet->length) {
183 DEBUG(1,("Bad packet length (%d/%d) from kpasswd server\n",
184 RSVAL(p, 0), packet->length));
185 return KRB5KRB_AP_ERR_MODIFIED;
188 p += 2;
190 vnum = RSVAL(p, 0); p += 2;
192 if (vnum != KRB5_KPASSWD_VERS_SETPW && vnum != KRB5_KPASSWD_VERS_CHANGEPW) {
193 DEBUG(1,("Bad vnum (%d) from kpasswd server\n", vnum));
194 return KRB5KDC_ERR_BAD_PVNO;
197 ap_rep.length = RSVAL(p, 0); p += 2;
199 if (p + ap_rep.length >= packet->data + packet->length) {
200 DEBUG(1,("ptr beyond end of packet from kpasswd server\n"));
201 return KRB5KRB_AP_ERR_MODIFIED;
204 if (ap_rep.length == 0) {
205 DEBUG(1,("got unencrypted setpw result?!\n"));
206 return KRB5KRB_AP_ERR_MODIFIED;
209 /* verify ap_rep */
210 ap_rep.data = p;
211 p += ap_rep.length;
213 ret = krb5_rd_rep(context, auth_context, &ap_rep, &ap_rep_enc);
214 if (ret) {
215 DEBUG(1,("failed to rd setpw reply (%s)\n", error_message(ret)));
216 return KRB5KRB_AP_ERR_MODIFIED;
219 krb5_free_ap_rep_enc_part(context, ap_rep_enc);
221 cipherresult.data = p;
222 cipherresult.length = (packet->data + packet->length) - p;
224 ret = krb5_rd_priv(context, auth_context, &cipherresult, &clearresult,
225 &replay);
226 if (ret) {
227 DEBUG(1,("failed to decrypt setpw reply (%s)\n", error_message(ret)));
228 return KRB5KRB_AP_ERR_MODIFIED;
231 if (clearresult.length < 2) {
232 free(clearresult.data);
233 ret = KRB5KRB_AP_ERR_MODIFIED;
234 return KRB5KRB_AP_ERR_MODIFIED;
237 p = clearresult.data;
239 res_code = RSVAL(p, 0);
241 free(clearresult.data);
243 if ((res_code < KRB5_KPASSWD_SUCCESS) ||
244 (res_code >= KRB5_KPASSWD_ACCESSDENIED)) {
245 return KRB5KRB_AP_ERR_MODIFIED;
248 return 0;
251 ADS_STATUS krb5_set_password(const char *kdc_host, const char *princ, const char *newpw,
252 int time_offset)
254 krb5_context context;
255 krb5_auth_context auth_context = NULL;
256 krb5_principal principal;
257 char *princ_name;
258 char *realm;
259 krb5_creds creds, *credsp;
260 krb5_ccache ccache;
261 krb5_data ap_req, chpw_req, chpw_rep;
262 int ret, sock, addr_len;
263 struct sockaddr remote_addr, local_addr;
264 krb5_address local_kaddr, remote_kaddr;
266 ret = krb5_init_context(&context);
267 if (ret) {
268 DEBUG(1,("Failed to init krb5 context (%s)\n", error_message(ret)));
269 return ADS_ERROR_KRB5(ret);
272 if (time_offset != 0) {
273 krb5_set_real_time(context, time(NULL) + time_offset, 0);
276 ret = krb5_cc_default(context, &ccache);
277 if (ret) {
278 krb5_free_context(context);
279 DEBUG(1,("Failed to get default creds (%s)\n", error_message(ret)));
280 return ADS_ERROR_KRB5(ret);
283 ZERO_STRUCT(creds);
285 realm = strchr(princ, '@');
286 realm++;
288 asprintf(&princ_name, "kadmin/changepw@%s", realm);
289 ret = krb5_parse_name(context, princ_name, &creds.server);
290 if (ret) {
291 krb5_free_context(context);
292 DEBUG(1,("Failed to parse kadmin/changepw (%s)\n", error_message(ret)));
293 return ADS_ERROR_KRB5(ret);
295 free(princ_name);
297 /* parse the principal we got as a function argument */
298 ret = krb5_parse_name(context, princ, &principal);
299 if (ret) {
300 krb5_free_context(context);
301 DEBUG(1,("Failed to parse %s (%s)\n", princ_name, error_message(ret)));
302 return ADS_ERROR_KRB5(ret);
305 krb5_princ_set_realm(context, creds.server,
306 krb5_princ_realm(context, principal));
308 ret = krb5_cc_get_principal(context, ccache, &creds.client);
309 if (ret) {
310 krb5_free_principal(context, principal);
311 krb5_free_context(context);
312 DEBUG(1,("Failed to get principal from ccache (%s)\n",
313 error_message(ret)));
314 return ADS_ERROR_KRB5(ret);
317 ret = krb5_get_credentials(context, 0, ccache, &creds, &credsp);
318 if (ret) {
319 krb5_free_principal(context, creds.client);
320 krb5_free_principal(context, principal);
321 krb5_free_context(context);
322 DEBUG(1,("krb5_get_credentials failed (%s)\n", error_message(ret)));
323 return ADS_ERROR_KRB5(ret);
326 /* we might have to call krb5_free_creds(...) from now on ... */
327 ret = krb5_mk_req_extended(context, &auth_context, AP_OPTS_USE_SUBKEY,
328 NULL, credsp, &ap_req);
329 if (ret) {
330 krb5_free_creds(context, credsp);
331 krb5_free_principal(context, creds.client);
332 krb5_free_principal(context, principal);
333 krb5_free_context(context);
334 DEBUG(1,("krb5_mk_req_extended failed (%s)\n", error_message(ret)));
335 return ADS_ERROR_KRB5(ret);
338 sock = open_udp_socket(kdc_host, DEFAULT_KPASSWD_PORT);
339 if (sock == -1) {
340 int rc = errno;
341 free(ap_req.data);
342 krb5_free_creds(context, credsp);
343 krb5_free_principal(context, creds.client);
344 krb5_free_principal(context, principal);
345 krb5_free_context(context);
346 DEBUG(1,("failed to open kpasswd socket to %s (%s)\n",
347 kdc_host, strerror(errno)));
348 return ADS_ERROR_SYSTEM(rc);
351 addr_len = sizeof(remote_addr);
352 getpeername(sock, &remote_addr, &addr_len);
353 addr_len = sizeof(local_addr);
354 getsockname(sock, &local_addr, &addr_len);
356 remote_kaddr.addrtype = ADDRTYPE_INET;
357 remote_kaddr.length = sizeof(((struct sockaddr_in *)&remote_addr)->sin_addr);
358 remote_kaddr.contents = (char *)&(((struct sockaddr_in *)&remote_addr)->sin_addr);
359 local_kaddr.addrtype = ADDRTYPE_INET;
360 local_kaddr.length = sizeof(((struct sockaddr_in *)&local_addr)->sin_addr);
361 local_kaddr.contents = (char *)&(((struct sockaddr_in *)&local_addr)->sin_addr);
363 ret = krb5_auth_con_setaddrs(context, auth_context, &local_kaddr, NULL);
364 if (ret) {
365 close(sock);
366 free(ap_req.data);
367 krb5_free_creds(context, credsp);
368 krb5_free_principal(context, creds.client);
369 krb5_free_principal(context, principal);
370 krb5_free_context(context);
371 DEBUG(1,("krb5_auth_con_setaddrs failed (%s)\n", error_message(ret)));
372 return ADS_ERROR_KRB5(ret);
375 ret = build_setpw_request(context, auth_context, &ap_req,
376 princ, newpw, &chpw_req);
377 if (ret) {
378 close(sock);
379 free(ap_req.data);
380 krb5_free_creds(context, credsp);
381 krb5_free_principal(context, creds.client);
382 krb5_free_principal(context, principal);
383 krb5_free_context(context);
384 DEBUG(1,("build_setpw_request failed (%s)\n", error_message(ret)));
385 return ADS_ERROR_KRB5(ret);
388 if (write(sock, chpw_req.data, chpw_req.length) != chpw_req.length) {
389 close(sock);
390 free(chpw_req.data);
391 free(ap_req.data);
392 krb5_free_creds(context, credsp);
393 krb5_free_principal(context, creds.client);
394 krb5_free_principal(context, principal);
395 krb5_free_context(context);
396 DEBUG(1,("send of chpw failed (%s)\n", strerror(errno)));
397 return ADS_ERROR_SYSTEM(errno);
400 free(chpw_req.data);
402 chpw_rep.length = 1500;
403 chpw_rep.data = (char *) malloc(chpw_rep.length);
405 ret = read(sock, chpw_rep.data, chpw_rep.length);
406 if (ret < 0) {
407 close(sock);
408 free(chpw_rep.data);
409 free(ap_req.data);
410 krb5_free_creds(context, credsp);
411 krb5_free_principal(context, creds.client);
412 krb5_free_principal(context, principal);
413 krb5_free_context(context);
414 DEBUG(1,("recv of chpw reply failed (%s)\n", strerror(errno)));
415 return ADS_ERROR_SYSTEM(errno);
418 close(sock);
419 chpw_rep.length = ret;
421 ret = krb5_auth_con_setaddrs(context, auth_context, NULL,&remote_kaddr);
422 if (ret) {
423 free(chpw_rep.data);
424 free(ap_req.data);
425 krb5_free_creds(context, credsp);
426 krb5_free_principal(context, creds.client);
427 krb5_free_principal(context, principal);
428 krb5_free_context(context);
429 DEBUG(1,("krb5_auth_con_setaddrs on reply failed (%s)\n",
430 error_message(ret)));
431 return ADS_ERROR_KRB5(ret);
434 ret = parse_setpw_reply(context, auth_context, &chpw_rep);
435 free(chpw_rep.data);
437 if (ret) {
438 free(ap_req.data);
439 krb5_free_creds(context, credsp);
440 krb5_free_principal(context, creds.client);
441 krb5_free_principal(context, principal);
442 krb5_free_context(context);
443 DEBUG(1,("parse_setpw_reply failed (%s)\n",
444 error_message(ret)));
445 return ADS_ERROR_KRB5(ret);
448 free(ap_req.data);
449 krb5_free_creds(context, credsp);
450 krb5_free_principal(context, creds.client);
451 krb5_free_principal(context, principal);
452 krb5_free_context(context);
454 return ADS_SUCCESS;
458 ADS_STATUS kerberos_set_password(const char *kpasswd_server,
459 const char *auth_principal, const char *auth_password,
460 const char *target_principal, const char *new_password,
461 int time_offset)
463 int ret;
465 if ((ret = kerberos_kinit_password(auth_principal, auth_password, time_offset))) {
466 DEBUG(1,("Failed kinit for principal %s (%s)\n", auth_principal, error_message(ret)));
467 return ADS_ERROR_KRB5(ret);
470 return krb5_set_password(kpasswd_server, target_principal, new_password, time_offset);
474 #endif