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