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 
35 TAILQ_HEAD(attrs,attr);
36 
37 struct attr {
38 	uint8_t			 type;
39 	uint32_t		 vendor;
40 	uint32_t		 vtype;
41 	TAILQ_ENTRY(attr)	 next;
42 };
43 
44 struct 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 
52 static void	 module_standard_config_set(void *, const char *, int,
53 		    char * const *);
54 static void	 module_standard_reqdeco(void *, u_int, const u_char *, size_t);
55 static void	 module_standard_resdeco(void *, u_int, const u_char *, size_t,
56 		    const u_char *, size_t);
57 
58 int
59 main(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 
101 static void
102 module_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 */
190 static void
191 module_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 */
264 static void
265 module_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