xref: /illumos-gate/usr/src/lib/krb5/kadm5/clnt/chpw.c (revision 7c478bd9)
1 /*
2  * Copyright 2004 Sun Microsystems, Inc.  All rights reserved.
3  * Use is subject to license terms.
4  */
5 
6 #pragma ident	"%Z%%M%	%I%	%E% SMI"
7 
8 #include <string.h>
9 
10 #include <k5-int.h>
11 #include <kadm5/admin.h>
12 #include <client_internal.h>
13 #include <auth_con.h>
14 #include <locale.h>
15 
16 /*
17  * krb5_mk_chpw_req
18  *
19  * Generate a CHANGEPW request packet to send to a
20  * password server.
21  * The format of the packet used here is defined in the
22  * Marc Horowitz Password change protocol document (1998)
23  * (expired).
24  * It is also defined in the latest kerberos passwd set/change
25  * protocol IETF draft document by UMich, Cisco, and MS.
26  */
27 krb5_error_code KRB5_CALLCONV
28 krb5_mk_chpw_req(context, auth_context, ap_req, passwd, packet)
29 krb5_context context;
30 krb5_auth_context auth_context;
31 krb5_data *ap_req;
32 char *passwd;
33 krb5_data *packet;
34 {
35 	krb5_error_code ret = 0;
36 	krb5_data clearpw;
37 	krb5_data cipherpw;
38 	krb5_replay_data replay;
39 	char *ptr;
40 
41 	cipherpw.data = NULL;
42 
43 	if (ret = krb5_auth_con_setflags(context, auth_context,
44 					KRB5_AUTH_CONTEXT_DO_SEQUENCE))
45 		goto cleanup;
46 
47 	clearpw.length = strlen(passwd);
48 	clearpw.data = passwd;
49 
50 	if (ret = krb5_mk_priv(context, auth_context,
51 			    &clearpw, &cipherpw, &replay))
52 		goto cleanup;
53 
54 	packet->length = 6 + ap_req->length + cipherpw.length;
55 	packet->data = (char *)malloc(packet->length);
56 	if (packet->data == NULL)
57 	{
58 		ret = ENOMEM;
59 		goto cleanup;
60 	}
61 	ptr = packet->data;
62 
63 	/* length */
64 	*ptr++ = (packet->length>>8) & 0xff;
65 	*ptr++ = packet->length & 0xff;
66 
67 	/*
68 	 * version == 0x0001 big-endian
69 	 * NOTE: when MS and MIT start supporting the latest
70 	 *	version of the passwd change protocol (v2),
71 	 *	this value will change to 2.
72 	 */
73 	*ptr++ = 0;
74 	*ptr++ = 1;
75 
76 	/* ap_req length, big-endian */
77 	*ptr++ = (ap_req->length>>8) & 0xff;
78 	*ptr++ = ap_req->length & 0xff;
79 
80 	/* ap-req data */
81 	memcpy(ptr, ap_req->data, ap_req->length);
82 	ptr += ap_req->length;
83 
84 	/* krb-priv of password */
85 	memcpy(ptr, cipherpw.data, cipherpw.length);
86 
87 cleanup:
88 	if (cipherpw.data != NULL)  /* allocated by krb5_mk_priv */
89 		free(cipherpw.data);
90 
91 	return (ret);
92 }
93 
94 /*
95  * krb5_rd_chpw_rep
96  *
97  * Decode and parse the reply from the CHANGEPW request.
98  */
99 krb5_error_code KRB5_CALLCONV
100 krb5_rd_chpw_rep(context, auth_context, packet, result_code, result_data)
101 krb5_context context;
102 krb5_auth_context auth_context;
103 krb5_data *packet;
104 int *result_code;
105 krb5_data *result_data;
106 {
107 	char *ptr;
108 	int plen, vno;
109 	krb5_data ap_rep;
110 	krb5_ap_rep_enc_part *ap_rep_enc;
111 	krb5_error_code ret;
112 	krb5_data cipherresult;
113 	krb5_data clearresult;
114 	krb5_error *krberror;
115 	krb5_replay_data replay;
116 	krb5_keyblock *tmp;
117 	int local_result_code;
118 
119 	if (packet->length < 4)
120 		/*
121 		 * either this, or the server is printing bad messages,
122 		 * or the caller passed in garbage
123 		 */
124 		return (KRB5KRB_AP_ERR_MODIFIED);
125 
126 	ptr = packet->data;
127 
128 	/* verify length */
129 	plen = (*ptr++ & 0xff);
130 	plen = (plen<<8) | (*ptr++ & 0xff);
131 
132 	if (plen != packet->length)
133 		return (KRB5KRB_AP_ERR_MODIFIED);
134 
135 	/* verify version number */
136 	vno = (*ptr++ & 0xff);
137 	vno = (vno<<8) | (*ptr++ & 0xff);
138 
139 	/*
140 	 * when the servers update to v2 of the protocol,
141 	 * "2" will be a valid version number here
142 	 */
143 	if (vno != 1 && vno != 2)
144 		return (KRB5KDC_ERR_BAD_PVNO);
145 
146 	/* read, check ap-rep length */
147 	ap_rep.length = (*ptr++ & 0xff);
148 	ap_rep.length = (ap_rep.length<<8) | (*ptr++ & 0xff);
149 
150 	if (ptr + ap_rep.length >= packet->data + packet->length)
151 		return (KRB5KRB_AP_ERR_MODIFIED);
152 
153 	if (ap_rep.length) {
154 		/* verify ap_rep */
155 		ap_rep.data = ptr;
156 		ptr += ap_rep.length;
157 
158 		if (ret = krb5_rd_rep(context, auth_context, &ap_rep,
159 				    &ap_rep_enc))
160 			return (ret);
161 
162 		krb5_free_ap_rep_enc_part(context, ap_rep_enc);
163 
164 		/* extract and decrypt the result */
165 		cipherresult.data = ptr;
166 		cipherresult.length = (packet->data + packet->length) - ptr;
167 
168 		/*
169 		 * XXX there's no api to do this right. The problem is that
170 		 * if there's a remote subkey, it will be used.  This is
171 		 * not what the spec requires
172 		 */
173 		tmp = auth_context->recv_subkey;
174 		auth_context->recv_subkey = NULL;
175 
176 		ret = krb5_rd_priv(context, auth_context, &cipherresult,
177 				&clearresult, &replay);
178 
179 		auth_context->recv_subkey = tmp;
180 
181 		if (ret)
182 			return (ret);
183 	} else {
184 		cipherresult.data = ptr;
185 		cipherresult.length = (packet->data + packet->length) - ptr;
186 
187 		if (ret = krb5_rd_error(context, &cipherresult, &krberror))
188 			return (ret);
189 
190 		clearresult = krberror->e_data;
191 	}
192 
193 	if (clearresult.length < 2) {
194 		ret = KRB5KRB_AP_ERR_MODIFIED;
195 		goto cleanup;
196 	}
197 
198 	ptr = clearresult.data;
199 
200 	local_result_code = (*ptr++ & 0xff);
201 	local_result_code = (local_result_code<<8) | (*ptr++ & 0xff);
202 
203 	if (result_code)
204 		*result_code = local_result_code;
205 
206 	/*
207 	 * Make sure the result code is in range for this
208 	 * protocol.
209 	 */
210 	if ((local_result_code < KRB5_KPASSWD_SUCCESS) ||
211 	    (local_result_code > KRB5_KPASSWD_ETYPE_NOSUPP)) {
212 		ret = KRB5KRB_AP_ERR_MODIFIED;
213 		goto cleanup;
214 	}
215 
216 
217 	/* all success replies should be authenticated/encrypted */
218 	if ((ap_rep.length == 0) &&
219 	    (local_result_code == KRB5_KPASSWD_SUCCESS)) {
220 		ret = KRB5KRB_AP_ERR_MODIFIED;
221 		goto cleanup;
222 	}
223 
224 	result_data->length = (clearresult.data + clearresult.length) - ptr;
225 
226 	if (result_data->length) {
227 		result_data->data = (char *)malloc(result_data->length);
228 		if (result_data->data == NULL) {
229 			ret = ENOMEM;
230 			goto cleanup;
231 		}
232 		memcpy(result_data->data, ptr, result_data->length);
233 	} else {
234 		result_data->data = NULL;
235 	}
236 
237 	ret = 0;
238 
239 cleanup:
240 	if (ap_rep.length) {
241 		krb5_xfree(clearresult.data);
242 	} else {
243 		krb5_free_error(context, krberror);
244 	}
245 
246 	return (ret);
247 }
248