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