1 /*
2  * lib/krb5/os/changepw.c
3  *
4  * Copyright 1990,1999,2001 by the Massachusetts Institute of Technology.
5  * All Rights Reserved.
6  *
7  * Export of this software from the United States of America may
8  *   require a specific license from the United States Government.
9  *   It is the responsibility of any person or organization contemplating
10  *   export to obtain such a license before exporting.
11  *
12  * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
13  * distribute this software and its documentation for any purpose and
14  * without fee is hereby granted, provided that the above copyright
15  * notice appear in all copies and that both that copyright notice and
16  * this permission notice appear in supporting documentation, and that
17  * the name of M.I.T. not be used in advertising or publicity pertaining
18  * to distribution of the software without specific, written prior
19  * permission.  Furthermore if you modify this software you must label
20  * your software as modified software and not distribute it in such a
21  * fashion that it might be confused with the original M.I.T. software.
22  * M.I.T. makes no representations about the suitability of
23  * this software for any purpose.  It is provided "as is" without express
24  * or implied warranty.
25  *
26  */
27 /*
28  * krb5_set_password - Implements set password per RFC 3244
29  * Added by Paul W. Nelson, Thursby Software Systems, Inc.
30  * Modified by Todd Stecher, Isilon Systems, to use krb1.4 socket infrastructure
31  */
32 
33 #include "fake-addrinfo.h"
34 #include "k5-int.h"
35 #include "os-proto.h"
36 #include "cm.h"
37 
38 #include <stdio.h>
39 #include <errno.h>
40 
41 #ifndef GETSOCKNAME_ARG3_TYPE
42 #define GETSOCKNAME_ARG3_TYPE int
43 #endif
44 
45 struct sendto_callback_context {
46     krb5_context 	context;
47     krb5_auth_context 	auth_context;
48     krb5_principal 	set_password_for;
49     char 		*newpw;
50     krb5_data 		ap_req;
51 };
52 
53 
54 /*
55  * Wrapper function for the two backends
56  */
57 
58 static krb5_error_code
krb5_locate_kpasswd(krb5_context context,const krb5_data * realm,struct addrlist * addrlist,krb5_boolean useTcp)59 krb5_locate_kpasswd(krb5_context context, const krb5_data *realm,
60 		    struct addrlist *addrlist, krb5_boolean useTcp)
61 {
62     krb5_error_code code;
63     int sockType = (useTcp ? SOCK_STREAM : SOCK_DGRAM);
64 
65     code = krb5int_locate_server (context, realm, addrlist,
66 				  locate_service_kpasswd, sockType, 0);
67 
68     if (code == KRB5_REALM_CANT_RESOLVE || code == KRB5_REALM_UNKNOWN) {
69 	code = krb5int_locate_server (context, realm, addrlist,
70 				      locate_service_kadmin, SOCK_STREAM, 0);
71 	if (!code) {
72 	    /* Success with admin_server but now we need to change the
73 	       port number to use DEFAULT_KPASSWD_PORT and the socktype.  */
74 	    int i;
75 	    for (i=0; i<addrlist->naddrs; i++) {
76 		struct addrinfo *a = addrlist->addrs[i].ai;
77 		if (a->ai_family == AF_INET)
78 		    sa2sin (a->ai_addr)->sin_port = htons(DEFAULT_KPASSWD_PORT);
79 		if (sockType != SOCK_STREAM)
80 		    a->ai_socktype = sockType;
81 	    }
82 	}
83     }
84     return (code);
85 }
86 
87 
88 /**
89  * This routine is used for a callback in sendto_kdc.c code. Simply
90  * put, we need the client addr to build the krb_priv portion of the
91  * password request.
92  */
93 
94 
kpasswd_sendto_msg_cleanup(void * callback_context,krb5_data * message)95 static void kpasswd_sendto_msg_cleanup (void* callback_context, krb5_data* message)
96 {
97     struct sendto_callback_context *ctx = callback_context;
98     krb5_free_data_contents(ctx->context, message);
99 }
100 
101 
kpasswd_sendto_msg_callback(struct conn_state * conn,void * callback_context,krb5_data * message)102 static int kpasswd_sendto_msg_callback(struct conn_state *conn, void *callback_context, krb5_data* message)
103 {
104     krb5_error_code 			code = 0;
105     struct sockaddr_storage 		local_addr;
106     krb5_address 			local_kaddr;
107     struct sendto_callback_context	*ctx = callback_context;
108     GETSOCKNAME_ARG3_TYPE 		addrlen;
109     krb5_data				output;
110 
111     memset (message, 0, sizeof(krb5_data));
112 
113     /*
114      * We need the local addr from the connection socket
115      */
116     addrlen = sizeof(local_addr);
117 
118     if (getsockname(conn->fd, ss2sa(&local_addr), &addrlen) < 0) {
119 	code = SOCKET_ERRNO;
120 	goto cleanup;
121     }
122 
123     /* some brain-dead OS's don't return useful information from
124      * the getsockname call.  Namely, windows and solaris.  */
125 
126     if (ss2sin(&local_addr)->sin_addr.s_addr != 0) {
127 	local_kaddr.addrtype = ADDRTYPE_INET;
128 	local_kaddr.length = sizeof(ss2sin(&local_addr)->sin_addr);
129 	local_kaddr.contents = (krb5_octet *) &ss2sin(&local_addr)->sin_addr;
130     } else {
131 	krb5_address **addrs;
132 
133 	code = krb5_os_localaddr(ctx->context, &addrs);
134 	if (code)
135 	    goto cleanup;
136 
137 	local_kaddr.magic = addrs[0]->magic;
138 	local_kaddr.addrtype = addrs[0]->addrtype;
139 	local_kaddr.length = addrs[0]->length;
140 	local_kaddr.contents = malloc(addrs[0]->length);
141 	if (local_kaddr.contents == NULL && addrs[0]->length != 0) {
142 	    code = errno;
143 	    krb5_free_addresses(ctx->context, addrs);
144 	    goto cleanup;
145 	}
146 	memcpy(local_kaddr.contents, addrs[0]->contents, addrs[0]->length);
147 
148 	krb5_free_addresses(ctx->context, addrs);
149     }
150 
151 
152     /*
153      * TBD:  Does this tamper w/ the auth context in such a way
154      * to break us?  Yes - provide 1 per conn-state / host...
155      */
156 
157 
158     if ((code = krb5_auth_con_setaddrs(ctx->context, ctx->auth_context,
159 				       &local_kaddr, NULL)))
160 	goto cleanup;
161 
162     if (ctx->set_password_for)
163 	code = krb5int_mk_setpw_req(ctx->context,
164 				    ctx->auth_context,
165 				    &ctx->ap_req,
166 				    ctx->set_password_for,
167 				    ctx->newpw,
168 				    &output);
169     else
170 	code = krb5int_mk_chpw_req(ctx->context,
171 				   ctx->auth_context,
172 				   &ctx->ap_req,
173 				   ctx->newpw,
174 				   &output);
175     if (code)
176 	goto cleanup;
177 
178     message->length = output.length;
179     message->data = output.data;
180 
181 cleanup:
182     return code;
183 }
184 
185 
186 /*
187 ** The logic for setting and changing a password is mostly the same
188 ** krb5_change_set_password handles both cases
189 **	if set_password_for is NULL, then a password change is performed,
190 **  otherwise, the password is set for the principal indicated in set_password_for
191 */
192 krb5_error_code KRB5_CALLCONV
krb5_change_set_password(krb5_context context,krb5_creds * creds,char * newpw,krb5_principal set_password_for,int * result_code,krb5_data * result_code_string,krb5_data * result_string)193 krb5_change_set_password(krb5_context context, krb5_creds *creds, char *newpw,
194 			 krb5_principal set_password_for,
195 			 int *result_code, krb5_data *result_code_string,
196 			 krb5_data *result_string)
197 {
198     krb5_data 			chpw_rep;
199     krb5_address 		remote_kaddr;
200     krb5_boolean		useTcp = 0;
201     GETSOCKNAME_ARG3_TYPE 	addrlen;
202     krb5_error_code 		code = 0;
203     char 			*code_string;
204     int				local_result_code;
205 
206     struct sendto_callback_context  callback_ctx;
207     struct sendto_callback_info	callback_info;
208     struct sockaddr_storage	remote_addr;
209     struct addrlist 		al = ADDRLIST_INIT;
210 
211     memset( &callback_ctx, 0, sizeof(struct sendto_callback_context));
212     callback_ctx.context = context;
213     callback_ctx.newpw = newpw;
214     callback_ctx.set_password_for = set_password_for;
215 
216     if ((code = krb5_auth_con_init(callback_ctx.context,
217 				   &callback_ctx.auth_context)))
218 	goto cleanup;
219 
220     if ((code = krb5_mk_req_extended(callback_ctx.context,
221 				     &callback_ctx.auth_context,
222 				     AP_OPTS_USE_SUBKEY,
223 				     NULL,
224 				     creds,
225 				     &callback_ctx.ap_req)))
226 	goto cleanup;
227 
228     do {
229 	if ((code = krb5_locate_kpasswd(callback_ctx.context,
230 					krb5_princ_realm(callback_ctx.context,
231 							 creds->server),
232 					&al, useTcp)))
233 	    break;
234 
235 	addrlen = sizeof(remote_addr);
236 
237 	callback_info.context = (void*) &callback_ctx;
238 	callback_info.pfn_callback = kpasswd_sendto_msg_callback;
239 	callback_info.pfn_cleanup = kpasswd_sendto_msg_cleanup;
240 
241 	if ((code = krb5int_sendto(callback_ctx.context,
242 				   NULL,
243 				   &al,
244 				   &callback_info,
245 				   &chpw_rep,
246 				   NULL,
247 				   NULL,
248 				   ss2sa(&remote_addr),
249                                    &addrlen,
250 				   NULL,
251 				   NULL,
252 				   NULL
253 		 ))) {
254 
255 	    /*
256 	     * Here we may want to switch to TCP on some errors.
257 	     * right?
258 	     */
259 	    break;
260 	}
261 
262 	remote_kaddr.addrtype = ADDRTYPE_INET;
263 	remote_kaddr.length = sizeof(ss2sin(&remote_addr)->sin_addr);
264 	remote_kaddr.contents = (krb5_octet *) &ss2sin(&remote_addr)->sin_addr;
265 
266 	if ((code = krb5_auth_con_setaddrs(callback_ctx.context,
267 					   callback_ctx.auth_context,
268 					   NULL,
269 					   &remote_kaddr)))
270 	    break;
271 
272 	if (set_password_for)
273 	    code = krb5int_rd_setpw_rep(callback_ctx.context,
274 					callback_ctx.auth_context,
275 					&chpw_rep,
276 					&local_result_code,
277 					result_string);
278 	else
279 	    code = krb5int_rd_chpw_rep(callback_ctx.context,
280 				       callback_ctx.auth_context,
281 				       &chpw_rep,
282 				       &local_result_code,
283 				       result_string);
284 
285 	if (code) {
286 	    if (code == KRB5KRB_ERR_RESPONSE_TOO_BIG && !useTcp ) {
287 		krb5int_free_addrlist (&al);
288 		useTcp = 1;
289 		continue;
290 	    }
291 
292 	    break;
293 	}
294 
295 	if (result_code)
296 	    *result_code = local_result_code;
297 
298 	if (result_code_string) {
299 	    if (set_password_for)
300 		code = krb5int_setpw_result_code_string(callback_ctx.context,
301 							local_result_code,
302 							(const char **)&code_string);
303 	    else
304 		code = krb5_chpw_result_code_string(callback_ctx.context,
305 						    local_result_code,
306 						    &code_string);
307 	    if(code)
308 		goto cleanup;
309 
310 	    result_code_string->length = strlen(code_string);
311 	    result_code_string->data = malloc(result_code_string->length);
312 	    if (result_code_string->data == NULL) {
313 		code = ENOMEM;
314 		goto cleanup;
315 	    }
316 	    strncpy(result_code_string->data, code_string, result_code_string->length);
317 	}
318 
319 	if (code == KRB5KRB_ERR_RESPONSE_TOO_BIG && !useTcp ) {
320 	    krb5int_free_addrlist (&al);
321 	    useTcp = 1;
322         } else {
323 	    break;
324 	}
325     } while (TRUE);
326 
327 cleanup:
328     if (callback_ctx.auth_context != NULL)
329 	krb5_auth_con_free(callback_ctx.context, callback_ctx.auth_context);
330 
331     krb5int_free_addrlist (&al);
332     krb5_free_data_contents(callback_ctx.context, &callback_ctx.ap_req);
333 
334     return(code);
335 }
336 
337 krb5_error_code KRB5_CALLCONV
krb5_change_password(krb5_context context,krb5_creds * creds,char * newpw,int * result_code,krb5_data * result_code_string,krb5_data * result_string)338 krb5_change_password(krb5_context context, krb5_creds *creds, char *newpw, int *result_code, krb5_data *result_code_string, krb5_data *result_string)
339 {
340 	return krb5_change_set_password(
341 		context, creds, newpw, NULL, result_code, result_code_string, result_string );
342 }
343 
344 /*
345  * krb5_set_password - Implements set password per RFC 3244
346  *
347  */
348 
349 krb5_error_code KRB5_CALLCONV
krb5_set_password(krb5_context context,krb5_creds * creds,char * newpw,krb5_principal change_password_for,int * result_code,krb5_data * result_code_string,krb5_data * result_string)350 krb5_set_password(
351 	krb5_context context,
352 	krb5_creds *creds,
353 	char *newpw,
354 	krb5_principal change_password_for,
355 	int *result_code, krb5_data *result_code_string, krb5_data *result_string
356 	)
357 {
358 	return krb5_change_set_password(
359 		context, creds, newpw, change_password_for, result_code, result_code_string, result_string );
360 }
361 
362 krb5_error_code KRB5_CALLCONV
krb5_set_password_using_ccache(krb5_context context,krb5_ccache ccache,char * newpw,krb5_principal change_password_for,int * result_code,krb5_data * result_code_string,krb5_data * result_string)363 krb5_set_password_using_ccache(
364 	krb5_context context,
365 	krb5_ccache ccache,
366 	char *newpw,
367 	krb5_principal change_password_for,
368 	int *result_code, krb5_data *result_code_string, krb5_data *result_string
369 	)
370 {
371     krb5_creds		creds;
372     krb5_creds		*credsp;
373     krb5_error_code	code;
374 
375     /*
376     ** get the proper creds for use with krb5_set_password -
377     */
378     memset (&creds, 0, sizeof(creds));
379     /*
380     ** first get the principal for the password service -
381     */
382     code = krb5_cc_get_principal (context, ccache, &creds.client);
383     if (!code) {
384 	code = krb5_build_principal(context, &creds.server,
385 				    krb5_princ_realm(context, change_password_for)->length,
386 				    krb5_princ_realm(context, change_password_for)->data,
387 				    "kadmin", "changepw", NULL);
388 	if (!code) {
389 	    code = krb5_get_credentials(context, 0, ccache, &creds, &credsp);
390 	    if (!code) {
391 		code = krb5_set_password(context, credsp, newpw, change_password_for,
392 					 result_code, result_code_string,
393 					 result_string);
394 		krb5_free_creds(context, credsp);
395 	    }
396 	}
397 	krb5_free_cred_contents(context, &creds);
398     }
399     return code;
400 }
401