Mark up strings for translation
[krb5.git] / src / lib / krb5 / krb / chpw.c
1 /* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2 /*
3 ** set password functions added by Paul W. Nelson, Thursby Software Systems, Inc.
4 */
5 #include <string.h>
6
7 #include "k5-int.h"
8 #include "int-proto.h"
9 #include "auth_con.h"
10
11
12 krb5_error_code
13 krb5int_mk_chpw_req(krb5_context context,
14                     krb5_auth_context auth_context,
15                     krb5_data *ap_req,
16                     char *passwd,
17                     krb5_data *packet)
18 {
19     krb5_error_code ret = 0;
20     krb5_data clearpw;
21     krb5_data cipherpw;
22     krb5_replay_data replay;
23     char *ptr;
24
25     cipherpw.data = NULL;
26
27     if ((ret = krb5_auth_con_setflags(context, auth_context,
28                                       KRB5_AUTH_CONTEXT_DO_SEQUENCE)))
29         goto cleanup;
30
31     clearpw.length = strlen(passwd);
32     clearpw.data = passwd;
33
34     if ((ret = krb5_mk_priv(context, auth_context,
35                             &clearpw, &cipherpw, &replay)))
36         goto cleanup;
37
38     packet->length = 6 + ap_req->length + cipherpw.length;
39     packet->data = (char *) malloc(packet->length);
40     if (packet->data == NULL) {
41         ret = ENOMEM;
42         goto cleanup;
43     }
44     ptr = packet->data;
45
46     /* length */
47
48     store_16_be(packet->length, ptr);
49     ptr += 2;
50
51     /* version == 0x0001 big-endian */
52
53     *ptr++ = 0;
54     *ptr++ = 1;
55
56     /* ap_req length, big-endian */
57
58     store_16_be(ap_req->length, ptr);
59     ptr += 2;
60
61     /* ap-req data */
62
63     memcpy(ptr, ap_req->data, ap_req->length);
64     ptr += ap_req->length;
65
66     /* krb-priv of password */
67
68     memcpy(ptr, cipherpw.data, cipherpw.length);
69
70 cleanup:
71     if (cipherpw.data != NULL)  /* allocated by krb5_mk_priv */
72         free(cipherpw.data);
73
74     return(ret);
75 }
76
77 /* Decode error_packet as a KRB-ERROR message and retrieve its e-data into
78  * *edata_out. */
79 static krb5_error_code
80 get_error_edata(krb5_context context, const krb5_data *error_packet,
81                 krb5_data **edata_out)
82 {
83     krb5_error_code ret;
84     krb5_error *krberror = NULL;
85
86     *edata_out = NULL;
87
88     ret = krb5_rd_error(context, error_packet, &krberror);
89     if (ret)
90         return ret;
91
92     if (krberror->e_data.data == NULL) {
93         /* Return a krb5 error code based on the error number. */
94         ret = ERROR_TABLE_BASE_krb5 + (krb5_error_code)krberror->error;
95         goto cleanup;
96     }
97
98     ret = krb5_copy_data(context, &krberror->e_data, edata_out);
99
100 cleanup:
101     krb5_free_error(context, krberror);
102     return ret;
103 }
104
105 /* Decode a reply to produce the clear-text output. */
106 static krb5_error_code
107 get_clear_result(krb5_context context, krb5_auth_context auth_context,
108                  const krb5_data *packet, krb5_data **clear_out,
109                  krb5_boolean *is_error_out)
110 {
111     krb5_error_code ret;
112     char *ptr, *end = packet->data + packet->length;
113     unsigned int plen, vno, aplen;
114     krb5_data ap_rep, cipher, error;
115     krb5_ap_rep_enc_part *ap_rep_enc;
116     krb5_replay_data replay;
117     krb5_key send_subkey = NULL;
118     krb5_data clear = empty_data();
119
120     *clear_out = NULL;
121     *is_error_out = FALSE;
122
123     /* Check for an unframed KRB-ERROR (expected for RFC 3244 requests; also
124      * received from MS AD for version 1 requests). */
125     if (krb5_is_krb_error(packet)) {
126         *is_error_out = TRUE;
127         return get_error_edata(context, packet, clear_out);
128     }
129
130     if (packet->length < 6)
131         return KRB5KRB_AP_ERR_MODIFIED;
132
133     /* Decode and verify the length. */
134     ptr = packet->data;
135     plen = (*ptr++ & 0xff);
136     plen = (plen << 8) | (*ptr++ & 0xff);
137     if (plen != packet->length)
138         return KRB5KRB_AP_ERR_MODIFIED;
139
140     /* Decode and verify the version number. */
141     vno = (*ptr++ & 0xff);
142     vno = (vno << 8) | (*ptr++ & 0xff);
143     if (vno != 1 && vno != 0xff80)
144         return KRB5KDC_ERR_BAD_PVNO;
145
146     /* Decode and check the AP-REP length. */
147     aplen = (*ptr++ & 0xff);
148     aplen = (aplen << 8) | (*ptr++ & 0xff);
149     if (aplen > end - ptr)
150         return KRB5KRB_AP_ERR_MODIFIED;
151
152     /* A zero-length AP-REQ indicates a framed KRB-ERROR response.  (Expected
153      * for protocol version 1; specified but unusual for RFC 3244 requests.) */
154     if (aplen == 0) {
155         *is_error_out = TRUE;
156         error = make_data(ptr, end - ptr);
157         return get_error_edata(context, &error, clear_out);
158     }
159
160     /* We have an AP-REP.  Save send_subkey to later smash recv_subkey. */
161     ret = krb5_auth_con_getsendsubkey_k(context, auth_context, &send_subkey);
162     if (ret)
163         return ret;
164
165     /* Verify the AP-REP. */
166     ap_rep = make_data(ptr, aplen);
167     ptr += ap_rep.length;
168     ret = krb5_rd_rep(context, auth_context, &ap_rep, &ap_rep_enc);
169     if (ret)
170         goto cleanup;
171     krb5_free_ap_rep_enc_part(context, ap_rep_enc);
172
173     /* Smash recv_subkey to be send_subkey, per spec. */
174     ret = krb5_auth_con_setrecvsubkey_k(context, auth_context, send_subkey);
175     if (ret)
176         goto cleanup;
177
178     /* Extract and decrypt the result. */
179     cipher = make_data(ptr, end - ptr);
180     ret = krb5_rd_priv(context, auth_context, &cipher, &clear, &replay);
181     if (ret)
182         goto cleanup;
183
184     ret = krb5_copy_data(context, &clear, clear_out);
185     if (ret)
186         goto cleanup;
187     *is_error_out = FALSE;
188
189 cleanup:
190     krb5_k_free_key(context, send_subkey);
191     krb5_free_data_contents(context, &clear);
192     return ret;
193 }
194
195 krb5_error_code
196 krb5int_rd_chpw_rep(krb5_context context, krb5_auth_context auth_context,
197                     krb5_data *packet, int *result_code_out,
198                     krb5_data *result_data_out)
199 {
200     krb5_error_code ret;
201     krb5_data result_data, *clear = NULL;
202     krb5_boolean is_error;
203     char *ptr;
204     int result_code;
205
206     *result_code_out = 0;
207     *result_data_out = empty_data();
208
209     ret = get_clear_result(context, auth_context, packet, &clear, &is_error);
210     if (ret)
211         return ret;
212
213     if (clear->length < 2) {
214         ret = KRB5KRB_AP_ERR_MODIFIED;
215         goto cleanup;
216     }
217
218     /* Decode and check the result code. */
219     ptr = clear->data;
220     result_code = (*ptr++ & 0xff);
221     result_code = (result_code << 8) | (*ptr++ & 0xff);
222     if (result_code < KRB5_KPASSWD_SUCCESS ||
223         result_code > KRB5_KPASSWD_INITIAL_FLAG_NEEDED) {
224         ret = KRB5KRB_AP_ERR_MODIFIED;
225         goto cleanup;
226     }
227
228     /* Successful replies must not come from errors. */
229     if (is_error && result_code == KRB5_KPASSWD_SUCCESS) {
230         ret = KRB5KRB_AP_ERR_MODIFIED;
231         goto cleanup;
232     }
233
234     result_data = make_data(ptr, clear->data + clear->length - ptr);
235     ret = krb5int_copy_data_contents(context, &result_data, result_data_out);
236     if (ret)
237         goto cleanup;
238     *result_code_out = result_code;
239
240 cleanup:
241     krb5_free_data(context, clear);
242     return ret;
243 }
244
245 krb5_error_code KRB5_CALLCONV
246 krb5_chpw_result_code_string(krb5_context context, int result_code,
247                              char **code_string)
248 {
249     switch (result_code) {
250     case KRB5_KPASSWD_MALFORMED:
251         *code_string = _("Malformed request error");
252         break;
253     case KRB5_KPASSWD_HARDERROR:
254         *code_string = _("Server error");
255         break;
256     case KRB5_KPASSWD_AUTHERROR:
257         *code_string = _("Authentication error");
258         break;
259     case KRB5_KPASSWD_SOFTERROR:
260         *code_string = _("Password change rejected");
261         break;
262     case KRB5_KPASSWD_ACCESSDENIED:
263         *code_string = _("Access denied");
264         break;
265     case KRB5_KPASSWD_BAD_VERSION:
266         *code_string = _("Wrong protocol version");
267         break;
268     case KRB5_KPASSWD_INITIAL_FLAG_NEEDED:
269         *code_string = _("Initial password required");
270         break;
271     default:
272         *code_string = _("Password change failed");
273         break;
274     }
275
276     return 0;
277 }
278
279 krb5_error_code
280 krb5int_mk_setpw_req(krb5_context context,
281                      krb5_auth_context auth_context,
282                      krb5_data *ap_req,
283                      krb5_principal targprinc,
284                      char *passwd,
285                      krb5_data *packet)
286 {
287     krb5_error_code ret;
288     krb5_data   cipherpw;
289     krb5_data   *encoded_setpw;
290     struct krb5_setpw_req req;
291
292     char *ptr;
293
294     cipherpw.data = NULL;
295     cipherpw.length = 0;
296
297     if ((ret = krb5_auth_con_setflags(context, auth_context,
298                                       KRB5_AUTH_CONTEXT_DO_SEQUENCE)))
299         return(ret);
300
301     req.target = targprinc;
302     req.password.data = passwd;
303     req.password.length = strlen(passwd);
304     ret = encode_krb5_setpw_req(&req, &encoded_setpw);
305     if (ret) {
306         return ret;
307     }
308
309     if ((ret = krb5_mk_priv(context, auth_context, encoded_setpw, &cipherpw, NULL)) != 0) {
310         krb5_free_data(context, encoded_setpw);
311         return(ret);
312     }
313     krb5_free_data(context, encoded_setpw);
314
315
316     packet->length = 6 + ap_req->length + cipherpw.length;
317     packet->data = (char *) malloc(packet->length);
318     if (packet->data  == NULL) {
319         ret = ENOMEM;
320         goto cleanup;
321     }
322     ptr = packet->data;
323     /*
324     ** build the packet -
325     */
326     /* put in the length */
327     store_16_be(packet->length, ptr);
328     ptr += 2;
329     /* put in the version */
330     *ptr++ = (char)0xff;
331     *ptr++ = (char)0x80;
332     /* the ap_req length is big endian */
333     store_16_be(ap_req->length, ptr);
334     ptr += 2;
335     /* put in the request data */
336     memcpy(ptr, ap_req->data, ap_req->length);
337     ptr += ap_req->length;
338     /*
339     ** put in the "private" password data -
340     */
341     memcpy(ptr, cipherpw.data, cipherpw.length);
342     ret = 0;
343 cleanup:
344     if (cipherpw.data)
345         krb5_free_data_contents(context, &cipherpw);
346     if ((ret != 0) && packet->data) {
347         free(packet->data);
348         packet->data = NULL;
349     }
350     return ret;
351 }