1/*-
2 * Copyright (c) 2008 Doug Rabson
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 *    notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 *    notice, this list of conditions and the following disclaimer in the
12 *    documentation and/or other materials provided with the distribution.
13 *
14 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24 * SUCH DAMAGE.
25 *
26 *	$FreeBSD$
27 */
28
29#include <ctype.h>
30#include <stdio.h>
31#include <string.h>
32#include <stdlib.h>
33#include <errno.h>
34#include <sys/queue.h>
35#include <rpc/rpc.h>
36#include <rpc/rpcsec_gss.h>
37
38#include "rpcsec_gss_int.h"
39
40#ifndef _PATH_GSS_MECH
41#define _PATH_GSS_MECH	"/etc/gss/mech"
42#endif
43
44#ifndef _PATH_GSS_QOP
45#define _PATH_GSS_QOP	"/etc/gss/qop"
46#endif
47
48struct mech_info {
49	SLIST_ENTRY(mech_info) link;
50	char		*name;
51	gss_OID_desc	oid;
52	const char	**qops;
53	char		*lib;
54	char		*kobj;
55};
56SLIST_HEAD(mech_info_list, mech_info);
57
58static struct mech_info_list mechs = SLIST_HEAD_INITIALIZER(mechs);
59static const char **mech_names;
60
61struct qop_info {
62	SLIST_ENTRY(qop_info) link;
63	char		*name;
64	char*		mech;
65	u_int		qop;
66};
67SLIST_HEAD(qop_info_list, qop_info);
68
69static struct qop_info_list qops = SLIST_HEAD_INITIALIZER(qops);
70
71static int
72_rpc_gss_string_to_oid(const char* s, gss_OID oid)
73{
74	int			number_count, i, j;
75	int			byte_count;
76	const char		*p, *q;
77	char			*res;
78
79	/*
80	 * First figure out how many numbers in the oid, then
81	 * calculate the compiled oid size.
82	 */
83	number_count = 0;
84	for (p = s; p; p = q) {
85		q = strchr(p, '.');
86		if (q) q = q + 1;
87		number_count++;
88	}
89
90	/*
91	 * The first two numbers are in the first byte and each
92	 * subsequent number is encoded in a variable byte sequence.
93	 */
94	if (number_count < 2)
95		return (EINVAL);
96
97	/*
98	 * We do this in two passes. The first pass, we just figure
99	 * out the size. Second time around, we actually encode the
100	 * number.
101	 */
102	res = 0;
103	for (i = 0; i < 2; i++) {
104		byte_count = 0;
105		for (p = s, j = 0; p; p = q, j++) {
106			u_int number = 0;
107
108			/*
109			 * Find the end of this number.
110			 */
111			q = strchr(p, '.');
112			if (q) q = q + 1;
113
114			/*
115			 * Read the number of of the string. Don't
116			 * bother with anything except base ten.
117			 */
118			while (*p && *p != '.') {
119				number = 10 * number + (*p - '0');
120				p++;
121			}
122
123			/*
124			 * Encode the number. The first two numbers
125			 * are packed into the first byte. Subsequent
126			 * numbers are encoded in bytes seven bits at
127			 * a time with the last byte having the high
128			 * bit set.
129			 */
130			if (j == 0) {
131				if (res)
132					*res = number * 40;
133			} else if (j == 1) {
134				if (res) {
135					*res += number;
136					res++;
137				}
138				byte_count++;
139			} else if (j >= 2) {
140				/*
141				 * The number is encoded in seven bit chunks.
142				 */
143				u_int t;
144				int bytes;
145
146				bytes = 0;
147				for (t = number; t; t >>= 7)
148					bytes++;
149				if (bytes == 0) bytes = 1;
150				while (bytes) {
151					if (res) {
152						int bit = 7*(bytes-1);
153
154						*res = (number >> bit) & 0x7f;
155						if (bytes != 1)
156							*res |= 0x80;
157						res++;
158					}
159					byte_count++;
160					bytes--;
161				}
162			}
163		}
164		if (!res) {
165			res = malloc(byte_count);
166			if (!res)
167				return (ENOMEM);
168			oid->length = byte_count;
169			oid->elements = res;
170		}
171	}
172
173	return (0);
174}
175
176static void
177_rpc_gss_load_mech(void)
178{
179	FILE		*fp;
180	char		buf[256];
181	char		*p;
182	char		*name, *oid, *lib, *kobj;
183	struct mech_info *info;
184	int		count;
185	const char	**pp;
186
187	if (SLIST_FIRST(&mechs))
188		return;
189
190	fp = fopen(_PATH_GSS_MECH, "r");
191	if (!fp)
192		return;
193
194	count = 0;
195	while (fgets(buf, sizeof(buf), fp)) {
196		if (*buf == '#')
197			continue;
198		p = buf;
199		name = strsep(&p, "\t\n ");
200		if (p) while (isspace(*p)) p++;
201		oid = strsep(&p, "\t\n ");
202		if (p) while (isspace(*p)) p++;
203		lib = strsep(&p, "\t\n ");
204		if (p) while (isspace(*p)) p++;
205		kobj = strsep(&p, "\t\n ");
206		if (!name || !oid || !lib || !kobj)
207			continue;
208
209		info = malloc(sizeof(struct mech_info));
210		if (!info)
211			break;
212		if (_rpc_gss_string_to_oid(oid, &info->oid)) {
213			free(info);
214			continue;
215		}
216		info->name = strdup(name);
217		info->qops = NULL;
218		info->lib = strdup(lib);
219		info->kobj = strdup(kobj);
220		SLIST_INSERT_HEAD(&mechs, info, link);
221		count++;
222	}
223	fclose(fp);
224
225	mech_names = malloc((count + 1) * sizeof(char*));
226	pp = mech_names;
227	SLIST_FOREACH(info, &mechs, link) {
228		*pp++ = info->name;
229	}
230	*pp = NULL;
231}
232
233static void
234_rpc_gss_load_qop(void)
235{
236	FILE		*fp;
237	char		buf[256];
238	char		*p;
239	char		*name, *num, *mech;
240	struct mech_info *minfo;
241	struct qop_info *info;
242	int		count;
243	const char	**mech_qops;
244	const char	**pp;
245
246	if (SLIST_FIRST(&qops))
247		return;
248
249	fp = fopen(_PATH_GSS_QOP, "r");
250	if (!fp)
251		return;
252
253	while (fgets(buf, sizeof(buf), fp)) {
254		if (*buf == '#')
255			continue;
256		p = buf;
257		name = strsep(&p, "\t\n ");
258		if (p) while (isspace(*p)) p++;
259		num = strsep(&p, "\t\n ");
260		if (p) while (isspace(*p)) p++;
261		mech = strsep(&p, "\t\n ");
262		if (!name || !num || !mech)
263			continue;
264
265		info = malloc(sizeof(struct qop_info));
266		if (!info)
267			break;
268		info->name = strdup(name);
269		info->qop = strtoul(name, 0, 0);
270		info->mech = strdup(mech);
271		SLIST_INSERT_HEAD(&qops, info, link);
272	}
273	fclose(fp);
274
275	/*
276	 * Compile lists of qops for each mechanism.
277	 */
278	SLIST_FOREACH(minfo, &mechs, link) {
279		count = 0;
280		SLIST_FOREACH(info, &qops, link) {
281			if (strcmp(info->mech, minfo->name) == 0)
282				count++;
283		}
284		mech_qops = malloc((count + 1) * sizeof(char*));
285		pp = mech_qops;
286		SLIST_FOREACH(info, &qops, link) {
287			if (strcmp(info->mech, minfo->name) == 0)
288				*pp++ = info->name;
289		}
290		*pp = NULL;
291		minfo->qops = mech_qops;
292	}
293}
294
295bool_t
296rpc_gss_mech_to_oid(const char *mech, gss_OID *oid_ret)
297{
298	struct mech_info *info;
299
300	_rpc_gss_load_mech();
301	SLIST_FOREACH(info, &mechs, link) {
302		if (!strcmp(info->name, mech)) {
303			*oid_ret = &info->oid;
304			return (TRUE);
305		}
306	}
307	_rpc_gss_set_error(RPC_GSS_ER_SYSTEMERROR, ENOENT);
308	return (FALSE);
309}
310
311bool_t
312rpc_gss_oid_to_mech(gss_OID oid, const char **mech_ret)
313{
314	struct mech_info *info;
315
316	_rpc_gss_load_mech();
317	SLIST_FOREACH(info, &mechs, link) {
318		if (oid->length == info->oid.length
319		    && !memcmp(oid->elements, info->oid.elements,
320			oid->length)) {
321			*mech_ret = info->name;
322			return (TRUE);
323		}
324	}
325	_rpc_gss_set_error(RPC_GSS_ER_SYSTEMERROR, ENOENT);
326	return (FALSE);
327}
328
329bool_t
330rpc_gss_qop_to_num(const char *qop, const char *mech, u_int *num_ret)
331{
332	struct qop_info *info;
333
334	_rpc_gss_load_qop();
335	SLIST_FOREACH(info, &qops, link) {
336		if (strcmp(info->name, qop) == 0
337		    && strcmp(info->mech, mech) == 0) {
338			*num_ret = info->qop;
339			return (TRUE);
340		}
341	}
342	_rpc_gss_set_error(RPC_GSS_ER_SYSTEMERROR, ENOENT);
343	return (FALSE);
344}
345
346const char *
347_rpc_gss_num_to_qop(const char *mech, u_int num)
348{
349	struct qop_info *info;
350
351	if (num == GSS_C_QOP_DEFAULT)
352		return "default";
353
354	_rpc_gss_load_qop();
355	SLIST_FOREACH(info, &qops, link) {
356		if (info->qop == num && strcmp(info->mech, mech) == 0) {
357			return (info->name);
358		}
359	}
360	return (NULL);
361}
362
363const char **
364rpc_gss_get_mechanisms(void)
365{
366
367	_rpc_gss_load_mech();
368	return (mech_names);
369}
370
371const char **
372rpc_gss_get_mech_info(const char *mech, rpc_gss_service_t *service)
373{
374	struct mech_info *info;
375
376	_rpc_gss_load_mech();
377	_rpc_gss_load_qop();
378	SLIST_FOREACH(info, &mechs, link) {
379		if (!strcmp(mech, info->name)) {
380			/*
381			 * I'm not sure what to do with service
382			 * here. The Solaris manpages are not clear on
383			 * the subject and the OpenSolaris code just
384			 * sets it to rpc_gss_svc_privacy
385			 * unconditionally with a comment noting that
386			 * it is bogus.
387			 */
388			*service = rpc_gss_svc_privacy;
389			return info->qops;
390		}
391	}
392
393	_rpc_gss_set_error(RPC_GSS_ER_SYSTEMERROR, ENOENT);
394	return (NULL);
395}
396
397bool_t
398rpc_gss_get_versions(u_int *vers_hi, u_int *vers_lo)
399{
400
401	*vers_hi = 1;
402	*vers_lo = 1;
403	return (TRUE);
404}
405
406bool_t
407rpc_gss_is_installed(const char *mech)
408{
409	struct mech_info *info;
410
411	_rpc_gss_load_mech();
412	SLIST_FOREACH(info, &mechs, link)
413		if (!strcmp(mech, info->name))
414			return (TRUE);
415	return (FALSE);
416}
417
418