1 /* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2 /* kdc/dispatch.c - Dispatch an incoming packet */
3 /*
4  * Copyright 1990, 2009 by the Massachusetts Institute of Technology.
5  *
6  * Export of this software from the United States of America may
7  *   require a specific license from the United States Government.
8  *   It is the responsibility of any person or organization contemplating
9  *   export to obtain such a license before exporting.
10  *
11  * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
12  * distribute this software and its documentation for any purpose and
13  * without fee is hereby granted, provided that the above copyright
14  * notice appear in all copies and that both that copyright notice and
15  * this permission notice appear in supporting documentation, and that
16  * the name of M.I.T. not be used in advertising or publicity pertaining
17  * to distribution of the software without specific, written prior
18  * permission.  Furthermore if you modify this software you must label
19  * your software as modified software and not distribute it in such a
20  * fashion that it might be confused with the original M.I.T. software.
21  * M.I.T. makes no representations about the suitability of
22  * this software for any purpose.  It is provided "as is" without express
23  * or implied warranty.
24  */
25 
26 #include "k5-int.h"
27 #include <syslog.h>
28 #include "kdc_util.h"
29 #include "extern.h"
30 #include "adm_proto.h"
31 #include "realm_data.h"
32 #include <netinet/in.h>
33 #include <arpa/inet.h>
34 #include <string.h>
35 
36 static krb5_int32 last_usec = 0, last_os_random = 0;
37 
38 static krb5_error_code make_too_big_error(kdc_realm_t *kdc_active_realm,
39                                           krb5_data **out);
40 
41 struct dispatch_state {
42     loop_respond_fn respond;
43     void *arg;
44     krb5_data *request;
45     int is_tcp;
46     kdc_realm_t *active_realm;
47     krb5_context kdc_err_context;
48 };
49 
50 static void
finish_dispatch(struct dispatch_state * state,krb5_error_code code,krb5_data * response)51 finish_dispatch(struct dispatch_state *state, krb5_error_code code,
52                 krb5_data *response)
53 {
54     loop_respond_fn oldrespond = state->respond;
55     void *oldarg = state->arg;
56     kdc_realm_t *kdc_active_realm = state->active_realm;
57 
58     if (state->is_tcp == 0 && response &&
59         response->length > (unsigned int)max_dgram_reply_size) {
60         krb5_free_data(kdc_context, response);
61         response = NULL;
62         code = make_too_big_error(kdc_active_realm, &response);
63         if (code)
64             krb5_klog_syslog(LOG_ERR, "error constructing "
65                              "KRB_ERR_RESPONSE_TOO_BIG error: %s",
66                              error_message(code));
67     }
68 
69     free(state);
70     (*oldrespond)(oldarg, code, response);
71 }
72 
73 static void
finish_dispatch_cache(void * arg,krb5_error_code code,krb5_data * response)74 finish_dispatch_cache(void *arg, krb5_error_code code, krb5_data *response)
75 {
76     struct dispatch_state *state = arg;
77     krb5_context kdc_err_context = state->kdc_err_context;
78 
79 #ifndef NOCACHE
80     /* Remove the null cache entry unless we actually want to discard this
81      * request. */
82     if (code != KRB5KDC_ERR_DISCARD)
83         kdc_remove_lookaside(kdc_err_context, state->request);
84 
85     /* Put the response into the lookaside buffer (if we produced one). */
86     if (code == 0 && response != NULL)
87         kdc_insert_lookaside(kdc_err_context, state->request, response);
88 #endif
89 
90     finish_dispatch(state, code, response);
91 }
92 
93 static void
reseed_random(krb5_context kdc_err_context)94 reseed_random(krb5_context kdc_err_context)
95 {
96     krb5_error_code retval;
97     krb5_timestamp now;
98     krb5_int32 now_usec, usec_difference;
99     krb5_data data;
100 
101     retval = krb5_crypto_us_timeofday(&now, &now_usec);
102     if (retval == 0) {
103         usec_difference = now_usec - last_usec;
104         if (last_os_random == 0)
105             last_os_random = now;
106         /* Grab random data from OS every hour*/
107         if (ts_delta(now, last_os_random) >= 60 * 60) {
108             krb5_c_random_os_entropy(kdc_err_context, 0, NULL);
109             last_os_random = now;
110         }
111 
112         data.length = sizeof(krb5_int32);
113         data.data = (void *)&usec_difference;
114 
115         krb5_c_random_add_entropy(kdc_err_context,
116                                   KRB5_C_RANDSOURCE_TIMING, &data);
117         last_usec = now_usec;
118     }
119 }
120 
121 void
dispatch(void * cb,const krb5_fulladdr * local_addr,const krb5_fulladdr * remote_addr,krb5_data * pkt,int is_tcp,verto_ctx * vctx,loop_respond_fn respond,void * arg)122 dispatch(void *cb, const krb5_fulladdr *local_addr,
123          const krb5_fulladdr *remote_addr, krb5_data *pkt, int is_tcp,
124          verto_ctx *vctx, loop_respond_fn respond, void *arg)
125 {
126     krb5_error_code retval;
127     krb5_kdc_req *req = NULL;
128     krb5_data *response = NULL;
129     struct dispatch_state *state;
130     struct server_handle *handle = cb;
131     krb5_context kdc_err_context = handle->kdc_err_context;
132 
133     state = k5alloc(sizeof(*state), &retval);
134     if (state == NULL) {
135         (*respond)(arg, retval, NULL);
136         return;
137     }
138     state->respond = respond;
139     state->arg = arg;
140     state->request = pkt;
141     state->is_tcp = is_tcp;
142     state->kdc_err_context = kdc_err_context;
143 
144     /* decode incoming packet, and dispatch */
145 
146 #ifndef NOCACHE
147     /* try the replay lookaside buffer */
148     if (kdc_check_lookaside(kdc_err_context, pkt, &response)) {
149         /* a hit! */
150         const char *name = 0;
151         char buf[46];
152 
153         name = inet_ntop(ADDRTYPE2FAMILY(remote_addr->address->addrtype),
154                          remote_addr->address->contents, buf, sizeof(buf));
155         if (name == 0)
156             name = "[unknown address type]";
157         if (response)
158             krb5_klog_syslog(LOG_INFO,
159                              "DISPATCH: repeated (retransmitted?) request "
160                              "from %s, resending previous response", name);
161         else
162             krb5_klog_syslog(LOG_INFO,
163                              "DISPATCH: repeated (retransmitted?) request "
164                              "from %s during request processing, dropping "
165                              "repeated request", name);
166 
167         finish_dispatch(state, response ? 0 : KRB5KDC_ERR_DISCARD, response);
168         return;
169     }
170 
171     /* Insert a NULL entry into the lookaside to indicate that this request
172      * is currently being processed. */
173     kdc_insert_lookaside(kdc_err_context, pkt, NULL);
174 #endif
175     reseed_random(kdc_err_context);
176 
177     /* try TGS_REQ first; they are more common! */
178 
179     if (krb5_is_tgs_req(pkt))
180         retval = decode_krb5_tgs_req(pkt, &req);
181     else if (krb5_is_as_req(pkt))
182         retval = decode_krb5_as_req(pkt, &req);
183     else
184         retval = KRB5KRB_AP_ERR_MSG_TYPE;
185     if (retval)
186         goto done;
187 
188     state->active_realm = setup_server_realm(handle, req->server);
189     if (state->active_realm == NULL) {
190         retval = KRB5KDC_ERR_WRONG_REALM;
191         goto done;
192     }
193 
194     if (krb5_is_tgs_req(pkt)) {
195         /* process_tgs_req frees the request */
196         retval = process_tgs_req(req, pkt, remote_addr, state->active_realm,
197                                  &response);
198         req = NULL;
199     } else if (krb5_is_as_req(pkt)) {
200         /* process_as_req frees the request and calls finish_dispatch_cache. */
201         process_as_req(req, pkt, local_addr, remote_addr, state->active_realm,
202                        vctx, finish_dispatch_cache, state);
203         return;
204     }
205 
206 done:
207     krb5_free_kdc_req(kdc_err_context, req);
208     finish_dispatch_cache(state, retval, response);
209 }
210 
211 static krb5_error_code
make_too_big_error(kdc_realm_t * kdc_active_realm,krb5_data ** out)212 make_too_big_error(kdc_realm_t *kdc_active_realm, krb5_data **out)
213 {
214     krb5_error errpkt;
215     krb5_error_code retval;
216     krb5_data *scratch;
217 
218     *out = NULL;
219     memset(&errpkt, 0, sizeof(errpkt));
220 
221     retval = krb5_us_timeofday(kdc_context, &errpkt.stime, &errpkt.susec);
222     if (retval)
223         return retval;
224     errpkt.error = KRB_ERR_RESPONSE_TOO_BIG;
225     errpkt.server = tgs_server;
226     errpkt.client = NULL;
227     errpkt.text.length = 0;
228     errpkt.text.data = 0;
229     errpkt.e_data.length = 0;
230     errpkt.e_data.data = 0;
231     scratch = malloc(sizeof(*scratch));
232     if (scratch == NULL)
233         return ENOMEM;
234     retval = krb5_mk_error(kdc_context, &errpkt, scratch);
235     if (retval) {
236         free(scratch);
237         return retval;
238     }
239 
240     *out = scratch;
241     return 0;
242 }
243 
get_context(void * handle)244 krb5_context get_context(void *handle)
245 {
246     struct server_handle *sh = handle;
247 
248     return sh->kdc_err_context;
249 }
250