xref: /freebsd/crypto/krb5/src/kdc/replay.c (revision 7f2fe78b9dd5f51c821d771b63d2e096f6fd49e9)
1*7f2fe78bSCy Schubert /* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2*7f2fe78bSCy Schubert /* kdc/replay.c - Replay lookaside cache for the KDC, to avoid extra work */
3*7f2fe78bSCy Schubert /*
4*7f2fe78bSCy Schubert  * Copyright 1991 by the Massachusetts Institute of Technology.
5*7f2fe78bSCy Schubert  * All Rights Reserved.
6*7f2fe78bSCy Schubert  *
7*7f2fe78bSCy Schubert  * Export of this software from the United States of America may
8*7f2fe78bSCy Schubert  *   require a specific license from the United States Government.
9*7f2fe78bSCy Schubert  *   It is the responsibility of any person or organization contemplating
10*7f2fe78bSCy Schubert  *   export to obtain such a license before exporting.
11*7f2fe78bSCy Schubert  *
12*7f2fe78bSCy Schubert  * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
13*7f2fe78bSCy Schubert  * distribute this software and its documentation for any purpose and
14*7f2fe78bSCy Schubert  * without fee is hereby granted, provided that the above copyright
15*7f2fe78bSCy Schubert  * notice appear in all copies and that both that copyright notice and
16*7f2fe78bSCy Schubert  * this permission notice appear in supporting documentation, and that
17*7f2fe78bSCy Schubert  * the name of M.I.T. not be used in advertising or publicity pertaining
18*7f2fe78bSCy Schubert  * to distribution of the software without specific, written prior
19*7f2fe78bSCy Schubert  * permission.  Furthermore if you modify this software you must label
20*7f2fe78bSCy Schubert  * your software as modified software and not distribute it in such a
21*7f2fe78bSCy Schubert  * fashion that it might be confused with the original M.I.T. software.
22*7f2fe78bSCy Schubert  * M.I.T. makes no representations about the suitability of
23*7f2fe78bSCy Schubert  * this software for any purpose.  It is provided "as is" without express
24*7f2fe78bSCy Schubert  * or implied warranty.
25*7f2fe78bSCy Schubert  */
26*7f2fe78bSCy Schubert 
27*7f2fe78bSCy Schubert #include "k5-int.h"
28*7f2fe78bSCy Schubert #include "k5-queue.h"
29*7f2fe78bSCy Schubert #include "k5-hashtab.h"
30*7f2fe78bSCy Schubert #include "kdc_util.h"
31*7f2fe78bSCy Schubert #include "extern.h"
32*7f2fe78bSCy Schubert 
33*7f2fe78bSCy Schubert #ifndef NOCACHE
34*7f2fe78bSCy Schubert 
35*7f2fe78bSCy Schubert struct entry {
36*7f2fe78bSCy Schubert     K5_TAILQ_ENTRY(entry) links;
37*7f2fe78bSCy Schubert     int num_hits;
38*7f2fe78bSCy Schubert     krb5_timestamp timein;
39*7f2fe78bSCy Schubert     krb5_data req_packet;
40*7f2fe78bSCy Schubert     krb5_data reply_packet;
41*7f2fe78bSCy Schubert };
42*7f2fe78bSCy Schubert 
43*7f2fe78bSCy Schubert #ifndef LOOKASIDE_MAX_SIZE
44*7f2fe78bSCy Schubert #define LOOKASIDE_MAX_SIZE (10 * 1024 * 1024)
45*7f2fe78bSCy Schubert #endif
46*7f2fe78bSCy Schubert 
47*7f2fe78bSCy Schubert K5_LIST_HEAD(entry_list, entry);
48*7f2fe78bSCy Schubert K5_TAILQ_HEAD(entry_queue, entry);
49*7f2fe78bSCy Schubert 
50*7f2fe78bSCy Schubert static struct k5_hashtab *hash_table;
51*7f2fe78bSCy Schubert static struct entry_queue expiration_queue;
52*7f2fe78bSCy Schubert 
53*7f2fe78bSCy Schubert static int hits = 0;
54*7f2fe78bSCy Schubert static int calls = 0;
55*7f2fe78bSCy Schubert static int max_hits_per_entry = 0;
56*7f2fe78bSCy Schubert static int num_entries = 0;
57*7f2fe78bSCy Schubert static size_t total_size = 0;
58*7f2fe78bSCy Schubert 
59*7f2fe78bSCy Schubert #define STALE_TIME      (2*60)            /* two minutes */
60*7f2fe78bSCy Schubert #define STALE(ptr, now) (ts_after(now, ts_incr((ptr)->timein, STALE_TIME)))
61*7f2fe78bSCy Schubert 
62*7f2fe78bSCy Schubert /* Return the rough memory footprint of an entry containing req and rep. */
63*7f2fe78bSCy Schubert static size_t
entry_size(const krb5_data * req,const krb5_data * rep)64*7f2fe78bSCy Schubert entry_size(const krb5_data *req, const krb5_data *rep)
65*7f2fe78bSCy Schubert {
66*7f2fe78bSCy Schubert     return sizeof(struct entry) + req->length +
67*7f2fe78bSCy Schubert         ((rep == NULL) ? 0 : rep->length);
68*7f2fe78bSCy Schubert }
69*7f2fe78bSCy Schubert 
70*7f2fe78bSCy Schubert /* Insert an entry into the cache. */
71*7f2fe78bSCy Schubert static struct entry *
insert_entry(krb5_context context,krb5_data * req,krb5_data * rep,krb5_timestamp time)72*7f2fe78bSCy Schubert insert_entry(krb5_context context, krb5_data *req, krb5_data *rep,
73*7f2fe78bSCy Schubert              krb5_timestamp time)
74*7f2fe78bSCy Schubert {
75*7f2fe78bSCy Schubert     krb5_error_code ret;
76*7f2fe78bSCy Schubert     struct entry *entry;
77*7f2fe78bSCy Schubert     size_t esize = entry_size(req, rep);
78*7f2fe78bSCy Schubert 
79*7f2fe78bSCy Schubert     entry = calloc(1, sizeof(*entry));
80*7f2fe78bSCy Schubert     if (entry == NULL)
81*7f2fe78bSCy Schubert         goto error;
82*7f2fe78bSCy Schubert     entry->timein = time;
83*7f2fe78bSCy Schubert 
84*7f2fe78bSCy Schubert     ret = krb5int_copy_data_contents(context, req, &entry->req_packet);
85*7f2fe78bSCy Schubert     if (ret)
86*7f2fe78bSCy Schubert         goto error;
87*7f2fe78bSCy Schubert 
88*7f2fe78bSCy Schubert     if (rep != NULL) {
89*7f2fe78bSCy Schubert         ret = krb5int_copy_data_contents(context, rep, &entry->reply_packet);
90*7f2fe78bSCy Schubert         if (ret)
91*7f2fe78bSCy Schubert             goto error;
92*7f2fe78bSCy Schubert     }
93*7f2fe78bSCy Schubert 
94*7f2fe78bSCy Schubert     ret = k5_hashtab_add(hash_table, entry->req_packet.data,
95*7f2fe78bSCy Schubert                          entry->req_packet.length, entry);
96*7f2fe78bSCy Schubert     if (ret)
97*7f2fe78bSCy Schubert         goto error;
98*7f2fe78bSCy Schubert     K5_TAILQ_INSERT_TAIL(&expiration_queue, entry, links);
99*7f2fe78bSCy Schubert     num_entries++;
100*7f2fe78bSCy Schubert     total_size += esize;
101*7f2fe78bSCy Schubert 
102*7f2fe78bSCy Schubert     return entry;
103*7f2fe78bSCy Schubert 
104*7f2fe78bSCy Schubert error:
105*7f2fe78bSCy Schubert     if (entry != NULL) {
106*7f2fe78bSCy Schubert         krb5_free_data_contents(context, &entry->req_packet);
107*7f2fe78bSCy Schubert         krb5_free_data_contents(context, &entry->reply_packet);
108*7f2fe78bSCy Schubert         free(entry);
109*7f2fe78bSCy Schubert     }
110*7f2fe78bSCy Schubert     return NULL;
111*7f2fe78bSCy Schubert }
112*7f2fe78bSCy Schubert 
113*7f2fe78bSCy Schubert 
114*7f2fe78bSCy Schubert /* Remove entry from its hash bucket and the expiration queue, and free it. */
115*7f2fe78bSCy Schubert static void
discard_entry(krb5_context context,struct entry * entry)116*7f2fe78bSCy Schubert discard_entry(krb5_context context, struct entry *entry)
117*7f2fe78bSCy Schubert {
118*7f2fe78bSCy Schubert     total_size -= entry_size(&entry->req_packet, &entry->reply_packet);
119*7f2fe78bSCy Schubert     num_entries--;
120*7f2fe78bSCy Schubert     k5_hashtab_remove(hash_table, entry->req_packet.data,
121*7f2fe78bSCy Schubert                       entry->req_packet.length);
122*7f2fe78bSCy Schubert     K5_TAILQ_REMOVE(&expiration_queue, entry, links);
123*7f2fe78bSCy Schubert     krb5_free_data_contents(context, &entry->req_packet);
124*7f2fe78bSCy Schubert     krb5_free_data_contents(context, &entry->reply_packet);
125*7f2fe78bSCy Schubert     free(entry);
126*7f2fe78bSCy Schubert }
127*7f2fe78bSCy Schubert 
128*7f2fe78bSCy Schubert /* Initialize the lookaside cache structures and randomize the hash seed. */
129*7f2fe78bSCy Schubert krb5_error_code
kdc_init_lookaside(krb5_context context)130*7f2fe78bSCy Schubert kdc_init_lookaside(krb5_context context)
131*7f2fe78bSCy Schubert {
132*7f2fe78bSCy Schubert     krb5_error_code ret;
133*7f2fe78bSCy Schubert     uint8_t seed[K5_HASH_SEED_LEN];
134*7f2fe78bSCy Schubert     krb5_data d = make_data(seed, sizeof(seed));
135*7f2fe78bSCy Schubert 
136*7f2fe78bSCy Schubert     ret = krb5_c_random_make_octets(context, &d);
137*7f2fe78bSCy Schubert     if (ret)
138*7f2fe78bSCy Schubert         return ret;
139*7f2fe78bSCy Schubert     ret = k5_hashtab_create(seed, 8192, &hash_table);
140*7f2fe78bSCy Schubert     if (ret)
141*7f2fe78bSCy Schubert         return ret;
142*7f2fe78bSCy Schubert     K5_TAILQ_INIT(&expiration_queue);
143*7f2fe78bSCy Schubert     return 0;
144*7f2fe78bSCy Schubert }
145*7f2fe78bSCy Schubert 
146*7f2fe78bSCy Schubert /* Remove the lookaside cache entry for a packet. */
147*7f2fe78bSCy Schubert void
kdc_remove_lookaside(krb5_context kcontext,krb5_data * req_packet)148*7f2fe78bSCy Schubert kdc_remove_lookaside(krb5_context kcontext, krb5_data *req_packet)
149*7f2fe78bSCy Schubert {
150*7f2fe78bSCy Schubert     struct entry *e;
151*7f2fe78bSCy Schubert 
152*7f2fe78bSCy Schubert     e = k5_hashtab_get(hash_table, req_packet->data, req_packet->length);
153*7f2fe78bSCy Schubert     if (e != NULL)
154*7f2fe78bSCy Schubert         discard_entry(kcontext, e);
155*7f2fe78bSCy Schubert }
156*7f2fe78bSCy Schubert 
157*7f2fe78bSCy Schubert /*
158*7f2fe78bSCy Schubert  * Return true and fill in reply_packet_out if req_packet is in the lookaside
159*7f2fe78bSCy Schubert  * cache; otherwise return false.
160*7f2fe78bSCy Schubert  *
161*7f2fe78bSCy Schubert  * If the request was inserted with a NULL reply_packet to indicate that a
162*7f2fe78bSCy Schubert  * request is still being processed, then return TRUE with reply_packet_out set
163*7f2fe78bSCy Schubert  * to NULL.
164*7f2fe78bSCy Schubert  */
165*7f2fe78bSCy Schubert krb5_boolean
kdc_check_lookaside(krb5_context kcontext,krb5_data * req_packet,krb5_data ** reply_packet_out)166*7f2fe78bSCy Schubert kdc_check_lookaside(krb5_context kcontext, krb5_data *req_packet,
167*7f2fe78bSCy Schubert                     krb5_data **reply_packet_out)
168*7f2fe78bSCy Schubert {
169*7f2fe78bSCy Schubert     struct entry *e;
170*7f2fe78bSCy Schubert 
171*7f2fe78bSCy Schubert     *reply_packet_out = NULL;
172*7f2fe78bSCy Schubert     calls++;
173*7f2fe78bSCy Schubert 
174*7f2fe78bSCy Schubert     e = k5_hashtab_get(hash_table, req_packet->data, req_packet->length);
175*7f2fe78bSCy Schubert     if (e == NULL)
176*7f2fe78bSCy Schubert         return FALSE;
177*7f2fe78bSCy Schubert 
178*7f2fe78bSCy Schubert     e->num_hits++;
179*7f2fe78bSCy Schubert     hits++;
180*7f2fe78bSCy Schubert 
181*7f2fe78bSCy Schubert     /* Leave *reply_packet_out as NULL for an in-progress entry. */
182*7f2fe78bSCy Schubert     if (e->reply_packet.length == 0)
183*7f2fe78bSCy Schubert         return TRUE;
184*7f2fe78bSCy Schubert 
185*7f2fe78bSCy Schubert     return (krb5_copy_data(kcontext, &e->reply_packet,
186*7f2fe78bSCy Schubert                            reply_packet_out) == 0);
187*7f2fe78bSCy Schubert }
188*7f2fe78bSCy Schubert 
189*7f2fe78bSCy Schubert /*
190*7f2fe78bSCy Schubert  * Insert a request and reply into the lookaside cache.  Assumes it's not
191*7f2fe78bSCy Schubert  * already there, and can fail silently on memory exhaustion.  Also discard old
192*7f2fe78bSCy Schubert  * entries in the cache.
193*7f2fe78bSCy Schubert  *
194*7f2fe78bSCy Schubert  * The reply_packet may be NULL to indicate a request that is still processing.
195*7f2fe78bSCy Schubert  */
196*7f2fe78bSCy Schubert void
kdc_insert_lookaside(krb5_context kcontext,krb5_data * req_packet,krb5_data * reply_packet)197*7f2fe78bSCy Schubert kdc_insert_lookaside(krb5_context kcontext, krb5_data *req_packet,
198*7f2fe78bSCy Schubert                      krb5_data *reply_packet)
199*7f2fe78bSCy Schubert {
200*7f2fe78bSCy Schubert     struct entry *e, *next;
201*7f2fe78bSCy Schubert     krb5_timestamp timenow;
202*7f2fe78bSCy Schubert     size_t esize = entry_size(req_packet, reply_packet);
203*7f2fe78bSCy Schubert 
204*7f2fe78bSCy Schubert     if (krb5_timeofday(kcontext, &timenow))
205*7f2fe78bSCy Schubert         return;
206*7f2fe78bSCy Schubert 
207*7f2fe78bSCy Schubert     /* Purge stale entries and limit the total size of the entries. */
208*7f2fe78bSCy Schubert     K5_TAILQ_FOREACH_SAFE(e, &expiration_queue, links, next) {
209*7f2fe78bSCy Schubert         if (!STALE(e, timenow) && total_size + esize <= LOOKASIDE_MAX_SIZE)
210*7f2fe78bSCy Schubert             break;
211*7f2fe78bSCy Schubert         max_hits_per_entry = max(max_hits_per_entry, e->num_hits);
212*7f2fe78bSCy Schubert         discard_entry(kcontext, e);
213*7f2fe78bSCy Schubert     }
214*7f2fe78bSCy Schubert 
215*7f2fe78bSCy Schubert     insert_entry(kcontext, req_packet, reply_packet, timenow);
216*7f2fe78bSCy Schubert     return;
217*7f2fe78bSCy Schubert }
218*7f2fe78bSCy Schubert 
219*7f2fe78bSCy Schubert /* Free all entries in the lookaside cache. */
220*7f2fe78bSCy Schubert void
kdc_free_lookaside(krb5_context kcontext)221*7f2fe78bSCy Schubert kdc_free_lookaside(krb5_context kcontext)
222*7f2fe78bSCy Schubert {
223*7f2fe78bSCy Schubert     struct entry *e, *next;
224*7f2fe78bSCy Schubert 
225*7f2fe78bSCy Schubert     K5_TAILQ_FOREACH_SAFE(e, &expiration_queue, links, next) {
226*7f2fe78bSCy Schubert         discard_entry(kcontext, e);
227*7f2fe78bSCy Schubert     }
228*7f2fe78bSCy Schubert     k5_hashtab_free(hash_table);
229*7f2fe78bSCy Schubert }
230*7f2fe78bSCy Schubert 
231*7f2fe78bSCy Schubert #endif /* NOCACHE */
232