1 /*	$OpenBSD: radiusd_standard.c,v 1.1 2023/09/08 05:56:22 yasuoka 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 
57 int
58 main(int argc, char *argv[])
59 {
60 	struct module_standard module_standard;
61 	struct module_handlers handlers = {
62 		.config_set = module_standard_config_set,
63 		.request_decoration = module_standard_reqdeco,
64 		.response_decoration = module_standard_resdeco
65 	};
66 	struct attr		*attr;
67 
68 	memset(&module_standard, 0, sizeof(module_standard));
69 	TAILQ_INIT(&module_standard.remove_reqattrs);
70 	TAILQ_INIT(&module_standard.remove_resattrs);
71 
72 	if ((module_standard.base = module_create(
73 	    STDIN_FILENO, &module_standard, &handlers)) == NULL)
74 		err(1, "Could not create a module instance");
75 
76 	module_drop_privilege(module_standard.base);
77 	if (pledge("stdio", NULL) == -1)
78 		err(1, "pledge");
79 
80 	module_load(module_standard.base);
81 
82 	openlog(NULL, LOG_PID, LOG_DAEMON);
83 
84 	while (module_run(module_standard.base) == 0)
85 		;
86 
87 	module_destroy(module_standard.base);
88 	while ((attr = TAILQ_FIRST(&module_standard.remove_reqattrs)) != NULL) {
89 		TAILQ_REMOVE(&module_standard.remove_reqattrs, attr, next);
90 		freezero(attr, sizeof(struct attr));
91 	}
92 	while ((attr = TAILQ_FIRST(&module_standard.remove_resattrs)) != NULL) {
93 		TAILQ_REMOVE(&module_standard.remove_resattrs, attr, next);
94 		freezero(attr, sizeof(struct attr));
95 	}
96 
97 	exit(EXIT_SUCCESS);
98 }
99 
100 static void
101 module_standard_config_set(void *ctx, const char *name, int argc,
102     char * const * argv)
103 {
104 	struct module_standard	*module = ctx;
105 	struct attr		*attr;
106 	const char		*errmsg = "none";
107 	const char		*errstr;
108 
109 	if (strcmp(name, "strip-atmark-realm") == 0) {
110 		SYNTAX_ASSERT(argc == 1,
111 		    "`strip-atmark-realm' must have only one argment");
112 		if (strcmp(argv[0], "true") == 0)
113 			module->strip_atmark_realm = true;
114 		else if (strcmp(argv[0], "false") == 0)
115 			module->strip_atmark_realm = false;
116 		else
117 			SYNTAX_ASSERT(0,
118 			    "`strip-atmark-realm' must `true' or `false'");
119 	} else if (strcmp(name, "strip-nt-domain") == 0) {
120 		SYNTAX_ASSERT(argc == 1,
121 		    "`strip-nt-domain' must have only one argment");
122 		if (strcmp(argv[0], "true") == 0)
123 			module->strip_nt_domain = true;
124 		else if (strcmp(argv[0], "false") == 0)
125 			module->strip_nt_domain = false;
126 		else
127 			SYNTAX_ASSERT(0,
128 			    "`strip-nt-domain' must `true' or `false'");
129 	} else if (strcmp(name, "remove-request-attribute") == 0 ||
130 	    strcmp(name, "remove-response-attribute") == 0) {
131 		struct attrs		*attrs;
132 
133 		if (strcmp(name, "remove-request-attribute") == 0) {
134 			SYNTAX_ASSERT(argc == 1 || argc == 2,
135 			    "`remove-request-attribute' must have one or two "
136 			    "argment");
137 			attrs = &module->remove_reqattrs;
138 		} else {
139 			SYNTAX_ASSERT(argc == 1 || argc == 2,
140 			    "`remove-response-attribute' must have one or two "
141 			    "argment");
142 			attrs = &module->remove_resattrs;
143 		}
144 		if ((attr = calloc(1, sizeof(struct attr))) == NULL) {
145 			module_send_message(module->base, IMSG_NG,
146 			    "Out of memory: %s", strerror(errno));
147 		}
148 		if (argc == 1) {
149 			attr->type = strtonum(argv[0], 0, 255, &errstr);
150 			if (errstr == NULL &&
151 			    attr->type != RADIUS_TYPE_VENDOR_SPECIFIC) {
152 				TAILQ_INSERT_TAIL(attrs, attr, next);
153 				attr = NULL;
154 			}
155 		} else {
156 			attr->type = RADIUS_TYPE_VENDOR_SPECIFIC;
157 			attr->vendor = strtonum(argv[0], 0, UINT32_MAX,
158 			    &errstr);
159 			if (errstr == NULL)
160 				attr->vtype = strtonum(argv[1], 0, 255,
161 				    &errstr);
162 			if (errstr == NULL) {
163 				TAILQ_INSERT_TAIL(attrs, attr, next);
164 				attr = NULL;
165 			}
166 		}
167 		freezero(attr, sizeof(struct attr));
168 		if (strcmp(name, "remove-request-attribute") == 0)
169 			SYNTAX_ASSERT(attr == NULL,
170 			    "wrong number for `remove-request-attribute`");
171 		else
172 			SYNTAX_ASSERT(attr == NULL,
173 			    "wrong number for `remove-response-attribute`");
174 	} else if (strncmp(name, "_", 1) == 0)
175 		/* nothing */; /* ignore all internal messages */
176 	else {
177 		module_send_message(module->base, IMSG_NG,
178 		    "Unknown config parameter name `%s'", name);
179 		return;
180 	}
181 	module_send_message(module->base, IMSG_OK, NULL);
182 	return;
183 
184  syntax_error:
185 	module_send_message(module->base, IMSG_NG, "%s", errmsg);
186 }
187 
188 /* request message decoration */
189 static void
190 module_standard_reqdeco(void *ctx, u_int q_id, const u_char *pkt, size_t pktlen)
191 {
192 	struct module_standard	*module = ctx;
193 	RADIUS_PACKET		*radpkt = NULL;
194 	int			 changed = 0;
195 	char			*ch, *username, buf[256];
196 	struct attr		*attr;
197 
198 	if (module->strip_atmark_realm || module->strip_nt_domain) {
199 		if ((radpkt = radius_convert_packet(pkt, pktlen)) == NULL) {
200 			syslog(LOG_ERR,
201 			    "%s: radius_convert_packet() failed: %m", __func__);
202 			module_stop(module->base);
203 			return;
204 		}
205 
206 		username = buf;
207 		if (radius_get_string_attr(radpkt, RADIUS_TYPE_USER_NAME,
208 		    username, sizeof(buf)) != 0) {
209 			syslog(LOG_WARNING,
210 			    "standard: q=%u could not get User-Name attribute",
211 			    q_id);
212 			goto skip;
213 		}
214 
215 		if (module->strip_atmark_realm &&
216 		    (ch = strrchr(username, '@')) != NULL) {
217 			*ch = '\0';
218 			changed++;
219 		}
220 		if (module->strip_nt_domain &&
221 		    (ch = strchr(username, '\\')) != NULL) {
222 			username = ch + 1;
223 			changed++;
224 		}
225 		if (changed > 0) {
226 			radius_del_attr_all(radpkt, RADIUS_TYPE_USER_NAME);
227 			radius_put_string_attr(radpkt,
228 			    RADIUS_TYPE_USER_NAME, username);
229 		}
230 	}
231  skip:
232 	TAILQ_FOREACH(attr, &module->remove_reqattrs, next) {
233 		if (radpkt == NULL &&
234 		    (radpkt = radius_convert_packet(pkt, pktlen)) == NULL) {
235 			syslog(LOG_ERR,
236 			    "%s: radius_convert_packet() failed: %m", __func__);
237 			module_stop(module->base);
238 			return;
239 		}
240 		if (attr->type != RADIUS_TYPE_VENDOR_SPECIFIC)
241 			radius_del_attr_all(radpkt, attr->type);
242 		else
243 			radius_del_vs_attr_all(radpkt, attr->vendor,
244 			    attr->vtype);
245 	}
246 	if (radpkt == NULL) {
247 		pkt = NULL;
248 		pktlen = 0;
249 	} else {
250 		pkt = radius_get_data(radpkt);
251 		pktlen = radius_get_length(radpkt);
252 	}
253 	if (module_reqdeco_done(module->base, q_id, pkt, pktlen) == -1) {
254 		syslog(LOG_ERR, "%s: module_reqdeco_done() failed: %m",
255 		    __func__);
256 		module_stop(module->base);
257 	}
258 	if (radpkt != NULL)
259 		radius_delete_packet(radpkt);
260 }
261 
262 /* response message decoration */
263 static void
264 module_standard_resdeco(void *ctx, u_int q_id, const u_char *pkt, size_t pktlen)
265 {
266 	struct module_standard	*module = ctx;
267 	RADIUS_PACKET		*radpkt = NULL;
268 	struct attr		*attr;
269 
270 	TAILQ_FOREACH(attr, &module->remove_reqattrs, next) {
271 		if (radpkt == NULL &&
272 		    (radpkt = radius_convert_packet(pkt, pktlen)) == NULL) {
273 			syslog(LOG_ERR,
274 			    "%s: radius_convert_packet() failed: %m", __func__);
275 			module_stop(module->base);
276 			return;
277 		}
278 		if (attr->type != RADIUS_TYPE_VENDOR_SPECIFIC)
279 			radius_del_attr_all(radpkt, attr->type);
280 		else
281 			radius_del_vs_attr_all(radpkt, attr->vendor,
282 			    attr->vtype);
283 	}
284 	if (radpkt == NULL) {
285 		pkt = NULL;
286 		pktlen = 0;
287 	} else {
288 		pkt = radius_get_data(radpkt);
289 		pktlen = radius_get_length(radpkt);
290 	}
291 	if (module_resdeco_done(module->base, q_id, pkt, pktlen) == -1) {
292 		syslog(LOG_ERR, "%s: module_resdeco_done() failed: %m",
293 		    __func__);
294 		module_stop(module->base);
295 	}
296 	if (radpkt != NULL)
297 		radius_delete_packet(radpkt);
298 }
299