xref: /freebsd/crypto/krb5/src/kadmin/server/schpw.c (revision f1c4c3daccbaf3820f0e2224de53df12fc952fcc)
1 /* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2 #include "k5-int.h"
3 #include <kadm5/admin.h>
4 #include <syslog.h>
5 #include <adm_proto.h>  /* krb5_klog_syslog */
6 #include <stdio.h>
7 #include <errno.h>
8 
9 #include "kadm5/server_internal.h" /* XXX for kadm5_server_handle_t */
10 
11 #include "misc.h"
12 
13 #ifndef GETSOCKNAME_ARG3_TYPE
14 #define GETSOCKNAME_ARG3_TYPE int
15 #endif
16 
17 #define RFC3244_VERSION 0xff80
18 
19 static krb5_error_code
process_chpw_request(krb5_context context,void * server_handle,char * realm,krb5_keytab keytab,const struct sockaddr * local_addr,const struct sockaddr * remote_addr,krb5_data * req,krb5_data * rep)20 process_chpw_request(krb5_context context, void *server_handle, char *realm,
21                      krb5_keytab keytab, const struct sockaddr *local_addr,
22                      const struct sockaddr *remote_addr, krb5_data *req,
23                      krb5_data *rep)
24 {
25     krb5_error_code ret;
26     char *ptr;
27     unsigned int plen, vno;
28     krb5_data ap_req, ap_rep = empty_data();
29     krb5_data cipher = empty_data(), clear = empty_data();
30     krb5_auth_context auth_context = NULL;
31     krb5_principal changepw = NULL;
32     krb5_principal client, target = NULL;
33     krb5_ticket *ticket = NULL;
34     krb5_replay_data replay;
35     krb5_error krberror;
36     krb5_address laddr;
37     int numresult;
38     char strresult[1024];
39     char *clientstr = NULL, *targetstr = NULL;
40     const char *errmsg = NULL;
41     size_t clen;
42     char *cdots;
43     char addrbuf[128];
44 
45     *rep = empty_data();
46 
47     if (req->length < 4) {
48         /* either this, or the server is printing bad messages,
49            or the caller passed in garbage */
50         ret = KRB5KRB_AP_ERR_MODIFIED;
51         numresult = KRB5_KPASSWD_MALFORMED;
52         strlcpy(strresult, "Request was truncated", sizeof(strresult));
53         goto bailout;
54     }
55 
56     ptr = req->data;
57 
58     /* verify length */
59 
60     plen = (*ptr++ & 0xff);
61     plen = (plen<<8) | (*ptr++ & 0xff);
62 
63     if (plen != req->length) {
64         ret = KRB5KRB_AP_ERR_MODIFIED;
65         numresult = KRB5_KPASSWD_MALFORMED;
66         strlcpy(strresult, "Request length was inconsistent",
67                 sizeof(strresult));
68         goto bailout;
69     }
70 
71     /* verify version number */
72 
73     vno = (*ptr++ & 0xff) ;
74     vno = (vno<<8) | (*ptr++ & 0xff);
75 
76     if (vno != 1 && vno != RFC3244_VERSION) {
77         ret = KRB5KDC_ERR_BAD_PVNO;
78         numresult = KRB5_KPASSWD_BAD_VERSION;
79         snprintf(strresult, sizeof(strresult),
80                  "Request contained unknown protocol version number %d", vno);
81         goto bailout;
82     }
83 
84     /* read, check ap-req length */
85 
86     ap_req.length = (*ptr++ & 0xff);
87     ap_req.length = (ap_req.length<<8) | (*ptr++ & 0xff);
88 
89     if (ptr + ap_req.length >= req->data + req->length) {
90         ret = KRB5KRB_AP_ERR_MODIFIED;
91         numresult = KRB5_KPASSWD_MALFORMED;
92         strlcpy(strresult, "Request was truncated in AP-REQ",
93                 sizeof(strresult));
94         goto bailout;
95     }
96 
97     /* verify ap_req */
98 
99     ap_req.data = ptr;
100     ptr += ap_req.length;
101 
102     ret = krb5_auth_con_init(context, &auth_context);
103     if (ret) {
104         numresult = KRB5_KPASSWD_HARDERROR;
105         strlcpy(strresult, "Failed initializing auth context",
106                 sizeof(strresult));
107         goto chpwfail;
108     }
109 
110     ret = krb5_auth_con_setflags(context, auth_context,
111                                  KRB5_AUTH_CONTEXT_DO_SEQUENCE);
112     if (ret) {
113         numresult = KRB5_KPASSWD_HARDERROR;
114         strlcpy(strresult, "Failed initializing auth context",
115                 sizeof(strresult));
116         goto chpwfail;
117     }
118 
119     ret = krb5_build_principal(context, &changepw, strlen(realm), realm,
120                                "kadmin", "changepw", NULL);
121     if (ret) {
122         numresult = KRB5_KPASSWD_HARDERROR;
123         strlcpy(strresult, "Failed building kadmin/changepw principal",
124                 sizeof(strresult));
125         goto chpwfail;
126     }
127 
128     ret = krb5_rd_req(context, &auth_context, &ap_req, changepw, keytab,
129                       NULL, &ticket);
130 
131     if (ret) {
132         numresult = KRB5_KPASSWD_AUTHERROR;
133         strlcpy(strresult, "Failed reading application request",
134                 sizeof(strresult));
135         goto chpwfail;
136     }
137 
138     /* construct the ap-rep */
139 
140     ret = krb5_mk_rep(context, auth_context, &ap_rep);
141     if (ret) {
142         numresult = KRB5_KPASSWD_AUTHERROR;
143         strlcpy(strresult, "Failed replying to application request",
144                 sizeof(strresult));
145         goto chpwfail;
146     }
147 
148     /* decrypt the ChangePasswdData */
149 
150     cipher.length = (req->data + req->length) - ptr;
151     cipher.data = ptr;
152 
153     /*
154      * Don't set a remote address in auth_context before calling krb5_rd_priv,
155      * so that we can work against clients behind a NAT.  Reflection attacks
156      * aren't a concern since we use sequence numbers and since our requests
157      * don't look anything like our responses.  Also don't set a local address,
158      * since we don't know what interface the request was received on.
159      */
160 
161     ret = krb5_rd_priv(context, auth_context, &cipher, &clear, &replay);
162     if (ret) {
163         numresult = KRB5_KPASSWD_HARDERROR;
164         strlcpy(strresult, "Failed decrypting request", sizeof(strresult));
165         goto chpwfail;
166     }
167 
168     client = ticket->enc_part2->client;
169 
170     /* decode ChangePasswdData for setpw requests */
171     if (vno == RFC3244_VERSION) {
172         krb5_data *clear_data;
173 
174         ret = decode_krb5_setpw_req(&clear, &clear_data, &target);
175         if (ret != 0) {
176             numresult = KRB5_KPASSWD_MALFORMED;
177             strlcpy(strresult, "Failed decoding ChangePasswdData",
178                     sizeof(strresult));
179             goto chpwfail;
180         }
181 
182         zapfree(clear.data, clear.length);
183 
184         clear = *clear_data;
185         free(clear_data);
186 
187         if (target != NULL) {
188             ret = krb5_unparse_name(context, target, &targetstr);
189             if (ret != 0) {
190                 numresult = KRB5_KPASSWD_HARDERROR;
191                 strlcpy(strresult, "Failed unparsing target name for log",
192                         sizeof(strresult));
193                 goto chpwfail;
194             }
195         }
196     }
197 
198     ret = krb5_unparse_name(context, client, &clientstr);
199     if (ret) {
200         numresult = KRB5_KPASSWD_HARDERROR;
201         strlcpy(strresult, "Failed unparsing client name for log",
202                 sizeof(strresult));
203         goto chpwfail;
204     }
205 
206     /* change the password */
207 
208     ptr = k5memdup0(clear.data, clear.length, &ret);
209     ret = schpw_util_wrapper(server_handle, client, target,
210                              (ticket->enc_part2->flags & TKT_FLG_INITIAL) != 0,
211                              ptr, NULL, strresult, sizeof(strresult));
212     if (ret)
213         errmsg = krb5_get_error_message(context, ret);
214 
215     /* zap the password */
216     zapfree(clear.data, clear.length);
217     zapfree(ptr, clear.length);
218     clear = empty_data();
219 
220     clen = strlen(clientstr);
221     trunc_name(&clen, &cdots);
222 
223     k5_print_addr(remote_addr, addrbuf, sizeof(addrbuf));
224 
225     if (vno == RFC3244_VERSION) {
226         size_t tlen;
227         char *tdots;
228         const char *targetp;
229 
230         if (target == NULL) {
231             tlen = clen;
232             tdots = cdots;
233             targetp = targetstr;
234         } else {
235             tlen = strlen(targetstr);
236             trunc_name(&tlen, &tdots);
237             targetp = clientstr;
238         }
239 
240         krb5_klog_syslog(LOG_NOTICE, _("setpw request from %s by %.*s%s for "
241                                        "%.*s%s: %s"), addrbuf, (int) clen,
242                          clientstr, cdots, (int) tlen, targetp, tdots,
243                          errmsg ? errmsg : "success");
244     } else {
245         krb5_klog_syslog(LOG_NOTICE, _("chpw request from %s for %.*s%s: %s"),
246                          addrbuf, (int) clen, clientstr, cdots,
247                          errmsg ? errmsg : "success");
248     }
249     switch (ret) {
250     case KADM5_AUTH_CHANGEPW:
251         numresult = KRB5_KPASSWD_ACCESSDENIED;
252         break;
253     case KADM5_AUTH_INITIAL:
254         numresult = KRB5_KPASSWD_INITIAL_FLAG_NEEDED;
255         break;
256     case KADM5_PASS_Q_TOOSHORT:
257     case KADM5_PASS_REUSE:
258     case KADM5_PASS_Q_CLASS:
259     case KADM5_PASS_Q_DICT:
260     case KADM5_PASS_Q_GENERIC:
261     case KADM5_PASS_TOOSOON:
262         numresult = KRB5_KPASSWD_SOFTERROR;
263         break;
264     case 0:
265         numresult = KRB5_KPASSWD_SUCCESS;
266         strlcpy(strresult, "", sizeof(strresult));
267         break;
268     default:
269         numresult = KRB5_KPASSWD_HARDERROR;
270         break;
271     }
272 
273 chpwfail:
274 
275     ret = alloc_data(&clear, 2 + strlen(strresult));
276     if (ret)
277         goto bailout;
278 
279     ptr = clear.data;
280 
281     *ptr++ = (numresult>>8) & 0xff;
282     *ptr++ = numresult & 0xff;
283 
284     memcpy(ptr, strresult, strlen(strresult));
285 
286     cipher = empty_data();
287 
288     if (ap_rep.length) {
289         if (k5_sockaddr_to_address(local_addr, FALSE, &laddr) != 0)
290             laddr = k5_addr_directional_accept;
291         ret = krb5_auth_con_setaddrs(context, auth_context, &laddr, NULL);
292         if (ret) {
293             numresult = KRB5_KPASSWD_HARDERROR;
294             strlcpy(strresult,
295                     "Failed storing client and server internet addresses",
296                     sizeof(strresult));
297         } else {
298             ret = krb5_mk_priv(context, auth_context, &clear, &cipher,
299                                &replay);
300             if (ret) {
301                 numresult = KRB5_KPASSWD_HARDERROR;
302                 strlcpy(strresult, "Failed encrypting reply",
303                         sizeof(strresult));
304             }
305         }
306     }
307 
308     /* if no KRB-PRIV was constructed, then we need a KRB-ERROR.
309        if this fails, just bail.  there's nothing else we can do. */
310 
311     if (cipher.length == 0) {
312         /* clear out ap_rep now, so that it won't be inserted in the
313            reply */
314 
315         if (ap_rep.length) {
316             free(ap_rep.data);
317             ap_rep = empty_data();
318         }
319 
320         krberror.ctime = 0;
321         krberror.cusec = 0;
322         krberror.susec = 0;
323         ret = krb5_timeofday(context, &krberror.stime);
324         if (ret)
325             goto bailout;
326 
327         /* this is really icky.  but it's what all the other callers
328            to mk_error do. */
329         krberror.error = ret;
330         krberror.error -= ERROR_TABLE_BASE_krb5;
331         if (krberror.error > KRB_ERR_MAX)
332             krberror.error = KRB_ERR_GENERIC;
333 
334         krberror.client = NULL;
335 
336         ret = krb5_build_principal(context, &krberror.server,
337                                    strlen(realm), realm,
338                                    "kadmin", "changepw", NULL);
339         if (ret)
340             goto bailout;
341         krberror.text.length = 0;
342         krberror.e_data = clear;
343 
344         ret = krb5_mk_error(context, &krberror, &cipher);
345 
346         krb5_free_principal(context, krberror.server);
347 
348         if (ret)
349             goto bailout;
350     }
351 
352     /* construct the reply */
353 
354     ret = alloc_data(rep, 6 + ap_rep.length + cipher.length);
355     if (ret)
356         goto bailout;
357     ptr = rep->data;
358 
359     /* length */
360 
361     *ptr++ = (rep->length>>8) & 0xff;
362     *ptr++ = rep->length & 0xff;
363 
364     /* version == 0x0001 big-endian */
365 
366     *ptr++ = 0;
367     *ptr++ = 1;
368 
369     /* ap_rep length, big-endian */
370 
371     *ptr++ = (ap_rep.length>>8) & 0xff;
372     *ptr++ = ap_rep.length & 0xff;
373 
374     /* ap-rep data */
375 
376     if (ap_rep.length) {
377         memcpy(ptr, ap_rep.data, ap_rep.length);
378         ptr += ap_rep.length;
379     }
380 
381     /* krb-priv or krb-error */
382 
383     memcpy(ptr, cipher.data, cipher.length);
384 
385 bailout:
386     krb5_auth_con_free(context, auth_context);
387     krb5_free_principal(context, changepw);
388     krb5_free_ticket(context, ticket);
389     free(ap_rep.data);
390     free(clear.data);
391     free(cipher.data);
392     krb5_free_principal(context, target);
393     krb5_free_unparsed_name(context, targetstr);
394     krb5_free_unparsed_name(context, clientstr);
395     krb5_free_error_message(context, errmsg);
396     return ret;
397 }
398 
399 /* Dispatch routine for set/change password */
400 void
dispatch(void * handle,const struct sockaddr * local_addr,const struct sockaddr * remote_addr,krb5_data * request,int is_tcp,verto_ctx * vctx,loop_respond_fn respond,void * arg)401 dispatch(void *handle, const struct sockaddr *local_addr,
402          const struct sockaddr *remote_addr, krb5_data *request, int is_tcp,
403          verto_ctx *vctx, loop_respond_fn respond, void *arg)
404 {
405     krb5_error_code ret;
406     krb5_keytab kt = NULL;
407     kadm5_server_handle_t server_handle = *(void **)handle;
408     krb5_data *response = NULL;
409     const char *emsg;
410 
411     ret = krb5_kt_resolve(server_handle->context, "KDB:", &kt);
412     if (ret != 0) {
413         emsg = krb5_get_error_message(server_handle->context, ret);
414         krb5_klog_syslog(LOG_ERR, _("chpw: Couldn't open admin keytab %s"),
415                          emsg);
416         krb5_free_error_message(server_handle->context, emsg);
417         goto egress;
418     }
419 
420     response = k5alloc(sizeof(krb5_data), &ret);
421     if (response == NULL)
422         goto egress;
423 
424     ret = process_chpw_request(server_handle->context,
425                                server_handle,
426                                server_handle->params.realm,
427                                kt,
428                                local_addr,
429                                remote_addr,
430                                request,
431                                response);
432 egress:
433     if (ret)
434         krb5_free_data(server_handle->context, response);
435     krb5_kt_close(server_handle->context, kt);
436     (*respond)(arg, ret, ret == 0 ? response : NULL);
437 }
438