1 /*
2  *   This program is is free software; you can redistribute it and/or modify
3  *   it under the terms of the GNU General Public License as published by
4  *   the Free Software Foundation; either version 2 of the License, or (at
5  *   your option) any later version.
6  *
7  *   This program is distributed in the hope that it will be useful,
8  *   but WITHOUT ANY WARRANTY; without even the implied warranty of
9  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
10  *   GNU General Public License for more details.
11  *
12  *   You should have received a copy of the GNU General Public License
13  *   along with this program; if not, write to the Free Software
14  *   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
15  */
16 
17 /**
18  * $Id: 93979818bae5e8643673d9809a65dca3ff10b68f $
19  * @file rlm_unpack.c
20  * @brief Unpack binary data
21  *
22  * @copyright 2014 The FreeRADIUS server project
23  * @copyright 2014 Alan DeKok <aland@freeradius.org>
24  */
25 RCSID("$Id: 93979818bae5e8643673d9809a65dca3ff10b68f $")
26 
27 #include <freeradius-devel/radiusd.h>
28 #include <freeradius-devel/modules.h>
29 #include <ctype.h>
30 
31 #define PW_CAST_BASE (1850)
32 
33 #define GOTO_ERROR do { REDEBUG("Unexpected text at '%s'", p); goto error;} while (0)
34 
35 /** Unpack data
36  *
37  *  Example: %{unpack:&Class 0 integer}
38  *
39  *  Expands Class, treating octet at offset 0 (bytes 0-3) as an "integer".
40  */
unpack_xlat(UNUSED void * instance,REQUEST * request,char const * fmt,char * out,size_t outlen)41 static ssize_t unpack_xlat(UNUSED void *instance, REQUEST *request, char const *fmt,
42 			   char *out, size_t outlen)
43 {
44 	char *data_name, *data_size, *data_type;
45 	char *p;
46 	size_t len, input_len;
47 	int offset;
48 	PW_TYPE type;
49 	DICT_ATTR const *da;
50 	VALUE_PAIR *vp, *cast;
51 	uint8_t const *input;
52 	char buffer[256];
53 	uint8_t blob[256];
54 
55 	/*
56 	 *	FIXME: copy only the fields here, as we parse them.
57 	 */
58 	strlcpy(buffer, fmt, sizeof(buffer));
59 
60 	p = buffer;
61 	while (isspace((int) *p)) p++; /* skip leading spaces */
62 
63 	data_name = p;
64 
65 	while (*p && !isspace((int) *p)) p++;
66 
67 	if (!*p) {
68 	error:
69 		REDEBUG("Format string should be '<data> <offset> <type>' e.g. '&Class 1 integer'");
70 	nothing:
71 		*out = '\0';
72 		return -1;
73 	}
74 
75 	while (isspace((int) *p)) *(p++) = '\0';
76 	if (!*p) GOTO_ERROR;
77 
78 	data_size = p;
79 
80 	while (*p && !isspace((int) *p)) p++;
81 	if (!*p) GOTO_ERROR;
82 
83 	while (isspace((int) *p)) *(p++) = '\0';
84 	if (!*p) GOTO_ERROR;
85 
86 	data_type = p;
87 
88 	while (*p && !isspace((int) *p)) p++;
89 	if (*p) GOTO_ERROR;	/* anything after the type is an error */
90 
91 	/*
92 	 *	Attribute reference
93 	 */
94 	if (*data_name == '&') {
95 		if (radius_get_vp(&vp, request, data_name) < 0) goto nothing;
96 
97 		if ((vp->da->type != PW_TYPE_OCTETS) &&
98 		    (vp->da->type != PW_TYPE_STRING)) {
99 			REDEBUG("unpack requires the input attribute to be 'string' or 'octets'");
100 			goto nothing;
101 		}
102 		input = vp->vp_octets;
103 		input_len = vp->vp_length;
104 
105 	} else if ((data_name[0] == '0') && (data_name[1] == 'x')) {
106 		/*
107 		 *	Hex data.
108 		 */
109 		len = strlen(data_name + 2);
110 		if ((len & 0x01) != 0) {
111 			RDEBUG("Invalid hex string in '%s'", data_name);
112 			goto nothing;
113 		}
114 		input = blob;
115 		input_len = fr_hex2bin(blob, sizeof(blob), data_name + 2, len);
116 		vp = NULL;
117 
118 	} else {
119 		GOTO_ERROR;
120 	}
121 
122 	offset = (int) strtoul(data_size, &p, 10);
123 	if (*p) {
124 		REDEBUG("unpack requires a decimal number, not '%s'", data_size);
125 		goto nothing;
126 	}
127 
128 	if ((size_t) offset >= input_len) {
129 		REDEBUG("Offset is larger then the input.");
130 		goto nothing;
131 	}
132 
133 	/*
134 	 *	Allow for string(4) or octets(4), which says "take 4
135 	 *	bytes from the thing.
136 	 */
137 	p = strchr(data_type, '(');
138 	if (p) {
139 		char *end;
140 		unsigned long to_copy;
141 
142 		*p = '\0';
143 
144 		/*
145 		 *	Allow the caller to say "get me everything
146 		 *	else"
147 		 */
148 		if (p[1] == ')') {
149 			to_copy = input_len - offset;
150 			end = p + 1;
151 
152 		} else {
153 			to_copy = strtoul(p + 1, &end, 10);
154 		}
155 		if (to_copy > input_len) {
156 			REDEBUG("Invalid length at '%s'", p + 1);
157 			goto nothing;
158 		}
159 
160 		if ((end[0] != ')') || (end[1] != '\0')) {
161 			REDEBUG("Invalid ending at '%s'", end);
162 			goto nothing;
163 		}
164 
165 		type = fr_str2int(dict_attr_types, data_type, PW_TYPE_INVALID);
166 		if (type == PW_TYPE_INVALID) {
167 			REDEBUG("Invalid data type '%s'", data_type);
168 			goto nothing;
169 		}
170 
171 		if ((type != PW_TYPE_OCTETS) && (type != PW_TYPE_STRING)) {
172 			REDEBUG("Cannot take substring of data type '%s'", data_type);
173 			goto nothing;
174 		}
175 
176 		if (input_len < (offset + to_copy)) {
177 			REDEBUG("Insufficient data to unpack '%s' from '%s'", data_type, data_name);
178 			goto nothing;
179 		}
180 
181 		/*
182 		 *	Just copy the string over.
183 		 */
184 		if (type == PW_TYPE_STRING) {
185 			if (outlen <= to_copy) {
186 				REDEBUG("Insufficient buffer space to unpack data");
187 				goto nothing;
188 			}
189 
190 			memcpy(out, input + offset, to_copy);
191 			out[to_copy] = '\0';
192 			return to_copy;
193 		}
194 
195 		/*
196 		 *	We hex encode octets.
197 		 */
198 		if (outlen <= (to_copy * 2)) {
199 			REDEBUG("Insufficient buffer space to unpack data");
200 			goto nothing;
201 		}
202 
203 		return fr_bin2hex(out, input + offset, to_copy);
204 	}
205 
206 	type = fr_str2int(dict_attr_types, data_type, PW_TYPE_INVALID);
207 	if (type == PW_TYPE_INVALID) {
208 		REDEBUG("Invalid data type '%s'", data_type);
209 		goto nothing;
210 	}
211 
212 	/*
213 	 *	Output must be a non-zero limited size.
214 	 */
215 	if ((dict_attr_sizes[type][0] == 0) ||
216 	    (dict_attr_sizes[type][0] != dict_attr_sizes[type][1])) {
217 		REDEBUG("unpack requires fixed-size output type, not '%s'", data_type);
218 		goto nothing;
219 	}
220 
221 	if (input_len < (offset + dict_attr_sizes[type][0])) {
222 		REDEBUG("Insufficient data to unpack '%s' from '%s'", data_type, data_name);
223 		goto nothing;
224 	}
225 
226 	da = dict_attrbyvalue(PW_CAST_BASE + type, 0);
227 	if (!da) {
228 		REDEBUG("Cannot decode type '%s'", data_type);
229 		goto nothing;
230 	}
231 
232 	cast = fr_pair_afrom_da(request, da);
233 	if (!cast) goto nothing;
234 
235 	memcpy(&(cast->data), input + offset, dict_attr_sizes[type][0]);
236 	cast->vp_length = dict_attr_sizes[type][0];
237 
238 	/*
239 	 *	Hacks
240 	 */
241 	switch (type) {
242 	case PW_TYPE_SIGNED:
243 	case PW_TYPE_INTEGER:
244 	case PW_TYPE_DATE:
245 		cast->vp_integer = ntohl(cast->vp_integer);
246 		break;
247 
248 	case PW_TYPE_SHORT:
249 		cast->vp_short = ((input[offset] << 8) | input[offset + 1]);
250 		break;
251 
252 	case PW_TYPE_INTEGER64:
253 		cast->vp_integer64 = ntohll(cast->vp_integer64);
254 		break;
255 
256 	default:
257 		break;
258 	}
259 
260 	len = vp_prints_value(out, outlen, cast, 0);
261 	talloc_free(cast);
262 
263 	if (is_truncated(len, outlen)) {
264 		REDEBUG("Insufficient buffer space to unpack data");
265 		goto nothing;
266 	}
267 
268 	return len;
269 }
270 
271 /** Return a substring from a starting character for a given length
272  *
273  * Example: "%{substring:foobar 2 3}" == "oba"
274  * Example: "%{substring:foobar -3 2}" == "ba"
275  * Example: "%{substring:foobar 1 -1}" == "ooba"
276  *
277  * Syntax: "%{substring:<string|attribute> <start> <len>}"
278  */
substring_xlat(UNUSED void * instance,REQUEST * request,char const * fmt,char * out,size_t outlen)279 static ssize_t substring_xlat(UNUSED void *instance, REQUEST *request,
280 			    char const *fmt, char *out, size_t outlen)
281 {
282 	ssize_t slen;
283 	long start, len;
284 	char const *p = fmt;
285 	char *end, *buffer;
286 
287 	/*
288 	 *  Trim whitespace
289 	 */
290 	while (isspace(*p) && p++);
291 
292 	/*
293 	 * Find numeric parameters at the end.
294 	 * Start with final space in fmt
295 	 */
296 	end = strrchr(p, ' ');
297 	if (!end) {
298 	arg_error:
299 		REDEBUG("substring needs exactly three arguments: &ref <start> <len>");
300 		return -1;
301 	}
302 	if (end == fmt) goto arg_error;
303 
304 	/*
305 	 * Walk back for previous space
306 	 */
307 	end--;
308 	while ((end >= p) && (*end != ' ') && end--);
309 	if (*end != ' ') goto arg_error;
310 	/*
311 	 * Store the total length of fmt up to the parameters including
312 	 * leading whitespace - if we're given a plain string we need the
313 	 * whole thing
314 	 */
315 	slen = end - fmt;
316 
317 	end++;
318 	start = strtol(end, &end, 10);
319 	end++;
320 	len = strtol(end, &end, 10);
321 
322 	/*
323 	 * Check for an attribute
324 	 */
325 	if (*p == '&') {
326 		vp_tmpl_t vpt;
327 		slen = tmpl_from_attr_substr(&vpt, p, REQUEST_CURRENT, PAIR_LIST_REQUEST, false, false);
328 		if (slen <= 0) {
329 			REDEBUG("%s", fr_strerror());
330 			return -1;
331 		}
332 
333 		slen = tmpl_aexpand(NULL, &buffer, request, &vpt, NULL, NULL);
334 		if (slen < 0) {
335 			talloc_free(buffer);
336 			REDEBUG("Unable to expand substring value");
337 			return -1;
338 		}
339 
340 	} else {
341 		/*
342 		 * Input is a string, copy it to the workspace
343 		 */
344 		buffer = talloc_array(NULL, char, slen + 1);
345 		strncpy(buffer, fmt, slen);
346 		buffer[slen] = '\0';
347 	}
348 	/*
349 	 * Negative start counts in from the end of the string,
350 	 * calculate the actual start position
351 	 */
352 	if (start < 0) {
353 		if ((0 - start) > slen) {
354 			start = 0;
355 		} else {
356 			start = slen + start;
357 		}
358 	}
359 
360 	if (start > slen) {
361 		*out = '\0';
362 		talloc_free(buffer);
363 		WARN("Start position %li is after end of string length of %li", start, slen);
364 		return 0;
365 	}
366 
367 	/*
368 	 * Negative length drops characters from the end of the string,
369 	 * calculate the actual length
370 	 */
371 	if (len < 0) len = slen - start + len;
372 
373 	if (len < 0) {
374 		WARN("String length of %li too short for substring parameters", slen);
375 		len = 0;
376 	}
377 
378 	/*
379 	 * Reduce length to match available string length
380 	 */
381 	if (len > (slen - start)) len = slen - start;
382 
383 	/*
384 	 * Reduce length to "out" capacity
385 	 */
386 	if (len > (long) outlen) len = outlen;
387 
388 	strncpy(out, buffer + start, len);
389 	out[len] = '\0';
390 	talloc_free(buffer);
391 	return len;
392 }
393 
394 /*
395  *	Register the xlats
396  */
mod_bootstrap(CONF_SECTION * conf,void * instance)397 static int mod_bootstrap(CONF_SECTION *conf, void *instance)
398 {
399 	if (cf_section_name2(conf)) return 0;
400 
401 	xlat_register("unpack", unpack_xlat, NULL, instance);
402 	xlat_register("substring", substring_xlat, NULL, instance);
403 
404 	return 0;
405 }
406 
407 /*
408  *	The module name should be the only globally exported symbol.
409  *	That is, everything else should be 'static'.
410  *
411  *	If the module needs to temporarily modify it's instantiation
412  *	data, the type should be changed to RLM_TYPE_THREAD_UNSAFE.
413  *	The server will then take care of ensuring that the module
414  *	is single-threaded.
415  */
416 extern module_t rlm_unpack;
417 module_t rlm_unpack = {
418 	.magic		= RLM_MODULE_INIT,
419 	.name		= "unpack",
420 	.type		= RLM_TYPE_THREAD_SAFE,
421 	.bootstrap	= mod_bootstrap
422 };
423