1/*******************************************************************************
2 * *****************************************************************************
3 * *
4 * * saslcache.c
5 * *
6 * * Description:  A small utility that can attach to saslauthd's shared
7 * *               memory region and display/dump information in the cache.
8 * *
9 * * Copyright (C) 2003 Jeremy Rumpf
10 * *
11 * * Redistribution and use in source and binary forms, with or without
12 * * modification, are permitted provided that the following conditions
13 * * are met:
14 * *
15 * * 1. Redistributions of source code must retain the above copyright
16 * *    notice, this list of conditions and the following disclaimer.
17 * *
18 * * 2. Redistributions in binary form must reproduce the above copyright
19 * *    notice, this list of conditions and the following disclaimer in the
20 * *    documentation and/or other materials provided with the distribution.
21 * *
22 * * THIS SOFTWARE IS PROVIDED ``AS IS''. ANY EXPRESS OR IMPLIED WARRANTIES,
23 * * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
24 * * IN NO EVENT SHALL JEREMY RUMPF OR ANY CONTRIBUTER TO THIS SOFTWARE BE
25 * * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
26 * * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
27 * * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
28 * * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
29 * * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
30 * * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
31 * * THE POSSIBILITY OF SUCH DAMAGE
32 * *
33 * * Jeremy Rumpf
34 * * jrumpf@heavyload.net
35 * *
36 * ******************************************************************************
37 ********************************************************************************/
38
39/****************************************
40* * includes
41*****************************************/
42#include <sys/types.h>
43#include <sys/stat.h>
44#include <sys/fcntl.h>
45#include <sys/mman.h>
46#include <errno.h>
47#include <unistd.h>
48#include <stdio.h>
49#include <string.h>
50#include <time.h>
51
52#include "cache.h"
53
54
55/****************************************
56* * declarations/protos
57*****************************************/
58void	show_usage(void);
59void	dump_cache_stats(void);
60void	dump_cache_users(void);
61char	*make_time(time_t);
62
63/****************************************
64* * module globals
65*****************************************/
66static  void            *shm_base = NULL;
67static  struct bucket   *table = NULL;
68static  struct stats    *table_stats = NULL;
69
70/****************************************
71*****************************************/
72
73
74/*************************************************************
75* * Main
76**************************************************************/
77int main(int argc, char **argv) {
78
79	int		option;
80	int		dump_user_info = 0;
81	int		dump_stat_info = 0;
82	char   		*file = NULL;
83	int		file_fd;
84	int		shmid = 0;
85	char		shmid_buff[256];
86	char		cache_magic[64];
87	struct stat 	stat_buff;
88
89	while ((option = getopt(argc, argv, "dm:s")) != -1) {
90		switch(option) {
91
92			case 'd':
93				dump_user_info = 1;
94				break;
95
96			case 's':
97				dump_stat_info = 1;
98				break;
99
100			case 'm':
101				file = strdup(optarg);
102				break;
103
104			default:
105				show_usage();
106		}
107	}
108
109	if (file == NULL)
110		file = PATH_SASLAUTHD_RUNDIR "/cache.mmap";
111
112	if (stat(file, &stat_buff) == -1) {
113		fprintf(stderr, "could not stat mmap file: %s\n", file);
114		fprintf(stderr, "stat: %s\n", strerror(errno));
115		exit(1);
116	}
117
118	if ((file_fd = open(file, O_RDONLY)) < 0) {
119		fprintf(stderr, "could not open mmap file: %s\n", file);
120		fprintf(stderr, "open: %s\n", strerror(errno));
121		fprintf(stderr, "perhaps saslcache -m <path>\n");
122		exit(1);
123	}
124
125	if ((shm_base = mmap(NULL, stat_buff.st_size, PROT_READ, MAP_SHARED, file_fd, 0))== (void *)-1) {
126		fprintf(stderr, "could not mmap shared memory file: %s\n", file);
127		fprintf(stderr, "mmap: %s\n", strerror(errno));
128		exit(1);
129	}
130
131	memcpy(cache_magic, shm_base, 64);
132	cache_magic[63] = '\0';
133
134	if (strcmp(cache_magic, CACHE_CACHE_MAGIC) != 0) {
135		fprintf(stderr, "mmap file [%s] is not a valid saslauthd cache\n", file);
136		exit(1);
137	}
138
139	table_stats = shm_base + 64;
140	(char *)table = (char *)table_stats + 128;
141
142	if (dump_stat_info == 0 && dump_user_info == 0)
143		dump_stat_info = 1;
144
145	if (dump_stat_info)
146		dump_cache_stats();
147
148	if (dump_user_info)
149		dump_cache_users();
150
151	exit(0);
152}
153
154
155/****************************************************
156* * Dump a delimited record for each item in the
157* * cache to stdout.
158****************************************************/
159void dump_cache_users(void) {
160
161	unsigned int		x;
162	struct bucket		*ref_bucket;
163        time_t			epoch_to;
164
165	epoch_to = time(NULL) - table_stats->timeout;
166
167	fprintf(stdout, "\"user\",\"realm\",\"service\",\"created\",\"created_localtime\"\n");
168
169	for (x = 0; x < (table_stats->table_size * table_stats->max_buckets_per); x++) {
170
171		ref_bucket = table + x;
172
173		if (ref_bucket->created > epoch_to && *(ref_bucket->creds) != '\0') {
174			fprintf(stderr, "\"%s\",", ref_bucket->creds + ref_bucket->user_offt);
175			fprintf(stderr, "\"%s\",", ref_bucket->creds + ref_bucket->realm_offt);
176			fprintf(stderr, "\"%s\",", ref_bucket->creds + ref_bucket->service_offt);
177			fprintf(stderr, "\"%lu\",", ref_bucket->created);
178			fprintf(stderr, "\"%s\"\n", make_time(ref_bucket->created));
179		}
180	}
181}
182
183/****************************************************
184* * Dump some usage statistics about the cred cache.
185* * (clean this up someday)
186****************************************************/
187void dump_cache_stats(void) {
188
189        unsigned int		x, y, z;
190        float			a;
191        unsigned int		max_chain_length = 0;
192        unsigned int		min_chain_length = 0;
193        unsigned int		buckets_in_use = 0;
194        unsigned int		slots_in_use = 0;
195        unsigned int		slots_max_chain = 0;
196        unsigned int		slots_min_chain = 0;
197        time_t			epoch_to;
198
199
200	min_chain_length = table_stats->max_buckets_per;
201	epoch_to = time(NULL) - table_stats->timeout;
202
203	for (x = 0; x < table_stats->table_size; x++) {
204
205		z = 0;
206
207		for (y = (x * table_stats->max_buckets_per); y < ((x + 1) * table_stats->max_buckets_per); y++) {
208			if (table[y].created > epoch_to) {
209				buckets_in_use++;
210				z++;
211			}
212		}
213
214		if (z == min_chain_length)
215			slots_min_chain++;
216
217		if (z == max_chain_length)
218			slots_max_chain++;
219
220		if (z > 0)
221			slots_in_use++;
222
223		if (z > max_chain_length) {
224			max_chain_length = z;
225			slots_max_chain = 1;
226		}
227
228		if (z < min_chain_length) {
229			min_chain_length = z;
230			slots_min_chain = 1;
231		}
232	}
233
234	fprintf(stdout, "----------------------------------------\n");
235	fprintf(stdout, "Saslauthd Cache Detail:\n");
236	fprintf(stdout, "\n");
237	fprintf(stdout, "  timeout (seconds)           :  %d\n", table_stats->timeout);
238	fprintf(stdout, "  total slots allocated       :  %d\n", table_stats->table_size);
239	fprintf(stdout, "  slots in use                :  %d\n", slots_in_use);
240	fprintf(stdout, "  total buckets               :  %d\n", (table_stats->max_buckets_per * table_stats->table_size));
241	fprintf(stdout, "  buckets per slot            :  %d\n", table_stats->max_buckets_per);
242	fprintf(stdout, "  buckets in use              :  %d\n", buckets_in_use);
243	fprintf(stdout, "  hash table size (bytes)     :  %d\n", table_stats->bytes);
244	fprintf(stdout, "  bucket size (bytes)         :  %d\n", table_stats->sizeof_bucket);
245	fprintf(stdout, "  minimum slot allocation     :  %d\n", min_chain_length);
246	fprintf(stdout, "  maximum slot allocation     :  %d\n", max_chain_length);
247	fprintf(stdout, "  slots at maximum allocation :  %d\n", slots_max_chain);
248	fprintf(stdout, "  slots at minimum allocation :  %d\n", slots_min_chain);
249
250	if (table_stats->table_size == 0)
251		a = 0;
252	else
253		a = slots_in_use / (float)table_stats->table_size;
254
255	fprintf(stdout, "  overall hash table load     :  %0.2f\n", a);
256	fprintf(stdout, "\n");
257	fprintf(stdout, "  hits*                       :  %d\n", table_stats->hits);
258	fprintf(stdout, "  misses*                     :  %d\n", table_stats->misses);
259	fprintf(stdout, "  total lookup attempts*      :  %d\n", table_stats->attempts);
260
261	if (table_stats->attempts == 0)
262		a = 0;
263	else
264		a = (table_stats->hits / (float)table_stats->attempts) * 100;
265
266	fprintf(stdout, "  hit ratio*                  :  %0.2f\n", a);
267	fprintf(stdout, "  flock failures*             :  %d\n", table_stats->lock_failures);
268	fprintf(stdout, "----------------------------------------\n");
269	fprintf(stdout, "* May not be completely accurate\n");
270	fprintf(stdout, "----------------------------------------\n\n");
271}
272
273
274/**************************************************
275* * Create a human readable time representation
276****************************************************/
277char	*make_time(time_t epoch) {
278
279	static char	created_str[128];
280	struct	tm	*tm_st = NULL;
281
282
283	tm_st = localtime(&epoch);
284
285	if (tm_st == NULL)
286		return "unknown";
287
288	strftime(created_str, 127, "%c", tm_st);
289	created_str[127] = '\0';
290
291	return created_str;
292}
293
294
295/**************************************************
296* * Dump out the usage information and exit
297****************************************************/
298void show_usage(void) {
299
300    fprintf(stderr, "usage: saslcache [options]\n\n");
301    fprintf(stderr, "option information:\n");
302    fprintf(stderr, "  -d             Dumps a csv list of information in the cache.\n");
303    fprintf(stderr, "  -f             Purges all entries from the cache.\n");
304    fprintf(stderr, "  -m <path>      Alternate path to the cache.mmap file.\n");
305    fprintf(stderr, "                 Defaults to: %s\n", PATH_SASLAUTHD_RUNDIR "/cache.mmap");
306    fprintf(stderr, "  -s             Dumps general statistic information about the cache.\n");
307    fprintf(stderr, "\n");
308    fprintf(stderr, "  All data is delivered to stdout.\n");
309
310    exit(1);
311
312}
313
314