1/*	$OpenBSD: radiusd_standard.c,v 1.5 2024/04/23 13:34:51 jsg Exp $	*/
2
3/*
4 * Copyright (c) 2013, 2023 Internet Initiative Japan Inc.
5 *
6 * Permission to use, copy, modify, and distribute this software for any
7 * purpose with or without fee is hereby granted, provided that the above
8 * copyright notice and this permission notice appear in all copies.
9 *
10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17 */
18#include <sys/types.h>
19#include <sys/queue.h>
20
21#include <err.h>
22#include <errno.h>
23#include <radius.h>
24#include <stdbool.h>
25#include <stdint.h>
26#include <stdio.h>
27#include <stdlib.h>
28#include <string.h>
29#include <syslog.h>
30#include <unistd.h>
31
32#include "radiusd.h"
33#include "radiusd_module.h"
34
35TAILQ_HEAD(attrs,attr);
36
37struct attr {
38	uint8_t			 type;
39	uint32_t		 vendor;
40	uint32_t		 vtype;
41	TAILQ_ENTRY(attr)	 next;
42};
43
44struct module_standard {
45	struct module_base	*base;
46	bool			 strip_atmark_realm;
47	bool			 strip_nt_domain;
48	struct attrs		 remove_reqattrs;
49	struct attrs		 remove_resattrs;
50};
51
52static void	 module_standard_config_set(void *, const char *, int,
53		    char * const *);
54static void	 module_standard_reqdeco(void *, u_int, const u_char *, size_t);
55static void	 module_standard_resdeco(void *, u_int, const u_char *, size_t,
56		    const u_char *, size_t);
57
58int
59main(int argc, char *argv[])
60{
61	struct module_standard module_standard;
62	struct module_handlers handlers = {
63		.config_set = module_standard_config_set,
64		.request_decoration = module_standard_reqdeco,
65		.response_decoration = module_standard_resdeco
66	};
67	struct attr		*attr;
68
69	memset(&module_standard, 0, sizeof(module_standard));
70	TAILQ_INIT(&module_standard.remove_reqattrs);
71	TAILQ_INIT(&module_standard.remove_resattrs);
72
73	if ((module_standard.base = module_create(
74	    STDIN_FILENO, &module_standard, &handlers)) == NULL)
75		err(1, "Could not create a module instance");
76
77	module_drop_privilege(module_standard.base, 0);
78	if (pledge("stdio", NULL) == -1)
79		err(1, "pledge");
80
81	module_load(module_standard.base);
82
83	openlog(NULL, LOG_PID, LOG_DAEMON);
84
85	while (module_run(module_standard.base) == 0)
86		;
87
88	module_destroy(module_standard.base);
89	while ((attr = TAILQ_FIRST(&module_standard.remove_reqattrs)) != NULL) {
90		TAILQ_REMOVE(&module_standard.remove_reqattrs, attr, next);
91		freezero(attr, sizeof(struct attr));
92	}
93	while ((attr = TAILQ_FIRST(&module_standard.remove_resattrs)) != NULL) {
94		TAILQ_REMOVE(&module_standard.remove_resattrs, attr, next);
95		freezero(attr, sizeof(struct attr));
96	}
97
98	exit(EXIT_SUCCESS);
99}
100
101static void
102module_standard_config_set(void *ctx, const char *name, int argc,
103    char * const * argv)
104{
105	struct module_standard	*module = ctx;
106	struct attr		*attr;
107	const char		*errmsg = "none";
108	const char		*errstr;
109
110	if (strcmp(name, "strip-atmark-realm") == 0) {
111		SYNTAX_ASSERT(argc == 1,
112		    "`strip-atmark-realm' must have only one argment");
113		if (strcmp(argv[0], "true") == 0)
114			module->strip_atmark_realm = true;
115		else if (strcmp(argv[0], "false") == 0)
116			module->strip_atmark_realm = false;
117		else
118			SYNTAX_ASSERT(0,
119			    "`strip-atmark-realm' must `true' or `false'");
120	} else if (strcmp(name, "strip-nt-domain") == 0) {
121		SYNTAX_ASSERT(argc == 1,
122		    "`strip-nt-domain' must have only one argment");
123		if (strcmp(argv[0], "true") == 0)
124			module->strip_nt_domain = true;
125		else if (strcmp(argv[0], "false") == 0)
126			module->strip_nt_domain = false;
127		else
128			SYNTAX_ASSERT(0,
129			    "`strip-nt-domain' must `true' or `false'");
130	} else if (strcmp(name, "remove-request-attribute") == 0 ||
131	    strcmp(name, "remove-response-attribute") == 0) {
132		struct attrs		*attrs;
133
134		if (strcmp(name, "remove-request-attribute") == 0) {
135			SYNTAX_ASSERT(argc == 1 || argc == 2,
136			    "`remove-request-attribute' must have one or two "
137			    "argment");
138			attrs = &module->remove_reqattrs;
139		} else {
140			SYNTAX_ASSERT(argc == 1 || argc == 2,
141			    "`remove-response-attribute' must have one or two "
142			    "argment");
143			attrs = &module->remove_resattrs;
144		}
145		if ((attr = calloc(1, sizeof(struct attr))) == NULL) {
146			module_send_message(module->base, IMSG_NG,
147			    "Out of memory: %s", strerror(errno));
148		}
149		if (argc == 1) {
150			attr->type = strtonum(argv[0], 0, 255, &errstr);
151			if (errstr == NULL &&
152			    attr->type != RADIUS_TYPE_VENDOR_SPECIFIC) {
153				TAILQ_INSERT_TAIL(attrs, attr, next);
154				attr = NULL;
155			}
156		} else {
157			attr->type = RADIUS_TYPE_VENDOR_SPECIFIC;
158			attr->vendor = strtonum(argv[0], 0, UINT32_MAX,
159			    &errstr);
160			if (errstr == NULL)
161				attr->vtype = strtonum(argv[1], 0, 255,
162				    &errstr);
163			if (errstr == NULL) {
164				TAILQ_INSERT_TAIL(attrs, attr, next);
165				attr = NULL;
166			}
167		}
168		freezero(attr, sizeof(struct attr));
169		if (strcmp(name, "remove-request-attribute") == 0)
170			SYNTAX_ASSERT(attr == NULL,
171			    "wrong number for `remove-request-attribute`");
172		else
173			SYNTAX_ASSERT(attr == NULL,
174			    "wrong number for `remove-response-attribute`");
175	} else if (strncmp(name, "_", 1) == 0)
176		/* nothing */; /* ignore all internal messages */
177	else {
178		module_send_message(module->base, IMSG_NG,
179		    "Unknown config parameter name `%s'", name);
180		return;
181	}
182	module_send_message(module->base, IMSG_OK, NULL);
183	return;
184
185 syntax_error:
186	module_send_message(module->base, IMSG_NG, "%s", errmsg);
187}
188
189/* request message decoration */
190static void
191module_standard_reqdeco(void *ctx, u_int q_id, const u_char *pkt, size_t pktlen)
192{
193	struct module_standard	*module = ctx;
194	RADIUS_PACKET		*radpkt = NULL;
195	int			 changed = 0;
196	char			*ch, *username, buf[256];
197	struct attr		*attr;
198
199	if (module->strip_atmark_realm || module->strip_nt_domain) {
200		if ((radpkt = radius_convert_packet(pkt, pktlen)) == NULL) {
201			syslog(LOG_ERR,
202			    "%s: radius_convert_packet() failed: %m", __func__);
203			module_stop(module->base);
204			return;
205		}
206
207		username = buf;
208		if (radius_get_string_attr(radpkt, RADIUS_TYPE_USER_NAME,
209		    username, sizeof(buf)) != 0) {
210			syslog(LOG_WARNING,
211			    "standard: q=%u could not get User-Name attribute",
212			    q_id);
213			goto skip;
214		}
215
216		if (module->strip_atmark_realm &&
217		    (ch = strrchr(username, '@')) != NULL) {
218			*ch = '\0';
219			changed++;
220		}
221		if (module->strip_nt_domain &&
222		    (ch = strchr(username, '\\')) != NULL) {
223			username = ch + 1;
224			changed++;
225		}
226		if (changed > 0) {
227			radius_del_attr_all(radpkt, RADIUS_TYPE_USER_NAME);
228			radius_put_string_attr(radpkt,
229			    RADIUS_TYPE_USER_NAME, username);
230		}
231	}
232 skip:
233	TAILQ_FOREACH(attr, &module->remove_reqattrs, next) {
234		if (radpkt == NULL &&
235		    (radpkt = radius_convert_packet(pkt, pktlen)) == NULL) {
236			syslog(LOG_ERR,
237			    "%s: radius_convert_packet() failed: %m", __func__);
238			module_stop(module->base);
239			return;
240		}
241		if (attr->type != RADIUS_TYPE_VENDOR_SPECIFIC)
242			radius_del_attr_all(radpkt, attr->type);
243		else
244			radius_del_vs_attr_all(radpkt, attr->vendor,
245			    attr->vtype);
246	}
247	if (radpkt == NULL) {
248		pkt = NULL;
249		pktlen = 0;
250	} else {
251		pkt = radius_get_data(radpkt);
252		pktlen = radius_get_length(radpkt);
253	}
254	if (module_reqdeco_done(module->base, q_id, pkt, pktlen) == -1) {
255		syslog(LOG_ERR, "%s: module_reqdeco_done() failed: %m",
256		    __func__);
257		module_stop(module->base);
258	}
259	if (radpkt != NULL)
260		radius_delete_packet(radpkt);
261}
262
263/* response message decoration */
264static void
265module_standard_resdeco(void *ctx, u_int q_id, const u_char *req, size_t reqlen,
266    const u_char *res, size_t reslen)
267{
268	struct module_standard	*module = ctx;
269	RADIUS_PACKET		*radres = NULL;
270	struct attr		*attr;
271
272	TAILQ_FOREACH(attr, &module->remove_resattrs, next) {
273		if (radres == NULL &&
274		    (radres = radius_convert_packet(res, reslen)) == NULL) {
275			syslog(LOG_ERR,
276			    "%s: radius_convert_packet() failed: %m", __func__);
277			module_stop(module->base);
278			return;
279		}
280		if (attr->type != RADIUS_TYPE_VENDOR_SPECIFIC)
281			radius_del_attr_all(radres, attr->type);
282		else
283			radius_del_vs_attr_all(radres, attr->vendor,
284			    attr->vtype);
285	}
286	if (radres == NULL) {
287		res = NULL;
288		reslen = 0;
289	} else {
290		res = radius_get_data(radres);
291		reslen = radius_get_length(radres);
292	}
293	if (module_resdeco_done(module->base, q_id, res, reslen) == -1) {
294		syslog(LOG_ERR, "%s: module_resdeco_done() failed: %m",
295		    __func__);
296		module_stop(module->base);
297	}
298	if (radres != NULL)
299		radius_delete_packet(radres);
300}
301