1 /*
2  * Advanced Exchange Access (AXA) semantics for nmsg fields
3  *
4  *  Copyright (c) 2014-2017 by Farsight Security, Inc.
5  *
6  *  Licensed under the Apache License, Version 2.0 (the "License");
7  *  you may not use this file except in compliance with the License.
8  *  You may obtain a copy of the License at
9  *
10  *     http://www.apache.org/licenses/LICENSE-2.0
11  *
12  *  Unless required by applicable law or agreed to in writing, software
13  *  distributed under the License is distributed on an "AS IS" BASIS,
14  *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15  *  See the License for the specific language governing permissions and
16  *  limitations under the License.
17  */
18 
19 #include <axa/fields.h>
20 #include <config.h>
21 
22 #include <nmsg/base/defs.h>
23 #include <nmsg/base/encode.pb-c.h>
24 
25 #include <stdlib.h>
26 #include <errno.h>
27 #include <string.h>
28 
29 const axa_nmsg_field_t axa_null_field = {
30 	.idx = AXA_NMSG_IDX_RSVD,
31 	.class = {.idx = AXA_NMSG_IDX_NONE},
32 	.rtype = {.idx = AXA_NMSG_IDX_NONE},
33 	.owner = {.idx = AXA_NMSG_IDX_NONE},
34 	.enm = {.idx = AXA_NMSG_IDX_NONE},
35 };
36 
37 
38 /**
39  *  Vendor IDs and message types of messages that are worth decoding.
40  *  Each (vendor,message type) pair has a list of fields that
41  *  contain domains or IP addresses.
42  */
43 struct vm_entry {
44 	struct vm_entry *next;		/**< next vendor message */
45 	axa_nmsg_idx_t	vid;		/**< nmsg vendor ID */
46 	axa_nmsg_idx_t	msgtype;	/**< nmsg message type */
47 	struct nmsg_msgmod *mod;	/**< nmsg message module */
48 	axa_nmsg_field_t *fields;       /**< linked lisg of nmsg fields */
49 };
50 
51 /** Vendor ID Message Type hash table */
52 typedef struct {
53 	uint	    num_bins;		/**< number of bins in this hash */
54 	vm_entry_t  *bins[];		/**< the hash table itself */
55 } vm_hash_t;
56 static vm_hash_t *vm_hash_tbl;
57 
58 static inline vm_entry_t**
vm_hash_fnc(uint vid,uint msgtype)59 vm_hash_fnc(uint vid, uint msgtype)
60 {
61 	uint n;
62 
63 	n = (vid << 12) | msgtype;
64 	n %= vm_hash_tbl->num_bins;
65 	return (&vm_hash_tbl->bins[n]);
66 }
67 
68 static void
free_field(axa_nmsg_field_t * field)69 free_field(axa_nmsg_field_t *field)
70 {
71 	axa_nmsg_sf_t *sf;
72 
73 	while ((sf = field->sf) != NULL) {
74 		field->sf = sf->next;
75 		free(sf);
76 	}
77 	free(field);
78 }
79 
80 void
axa_unload_fields(void)81 axa_unload_fields(void)
82 {
83 	struct vm_entry *vm;
84 	axa_nmsg_field_t *field;
85 	uint n;
86 
87 	if (vm_hash_tbl == NULL)
88 		return;
89 	for (n = 0; n < vm_hash_tbl->num_bins; ++n) {
90 		while ((vm = vm_hash_tbl->bins[n]) != NULL) {
91 			vm_hash_tbl->bins[n] = vm->next;
92 			while ((field = vm->fields) != NULL) {
93 				vm->fields = field->next;
94 				free_field(field);
95 			}
96 			free(vm);
97 		}
98 	}
99 	free(vm_hash_tbl);
100 	vm_hash_tbl = NULL;
101 }
102 
103 /* Do we know a vendor ID and message type? */
104 const axa_nmsg_field_t *
axa_msg_fields(const nmsg_message_t msg)105 axa_msg_fields(const nmsg_message_t msg)
106 {
107 	axa_nmsg_idx_t vid, msgtype;
108 	const vm_entry_t  *e;
109 
110 	if (vm_hash_tbl == NULL)
111 		return (NULL);
112 
113 	vid = nmsg_message_get_vid(msg);
114 	msgtype = nmsg_message_get_msgtype(msg);
115 	for (e = *vm_hash_fnc(vid, msgtype); e != NULL; e = e->next) {
116 		if (e->vid == vid && e->msgtype == msgtype)
117 			return (e->fields);
118 	}
119 	return (NULL);
120 }
121 
122 static nmsg_message_t
message_init(struct nmsg_msgmod * mod,const char * fname,uint line_num,const char * fields_file)123 message_init(struct nmsg_msgmod *mod, const char *fname,
124 	     uint line_num, const char *fields_file)
125 {
126 	nmsg_message_t msg;
127 
128 	msg = nmsg_message_init(mod);
129 	if (msg == NULL)
130 		axa_error_msg("nmsg_message_init() failed for \"%s\""
131 			      " in line %d of \"%s\"",
132 			      fname, line_num, fields_file);
133 	return (msg);
134 }
135 
136 static bool
get_enum_value(struct nmsg_msgmod * mod,const char * fname,const char * enum_name,uint * valp,uint line_num,const char * fields_file)137 get_enum_value(struct nmsg_msgmod *mod, const char *fname,
138 	       const char *enum_name, uint *valp,
139 	       uint line_num, const char *fields_file)
140 {
141 	nmsg_message_t msg;
142 	nmsg_res res;
143 
144 	msg = message_init(mod, fname, line_num, fields_file);
145 	if (msg == NULL) {
146 		axa_error_msg("unrecognized %s enum field name \"%s\""
147 			      " in line %d of \"%s\"",
148 			      fname, enum_name, line_num, fields_file);
149 		return (false);
150 	}
151 
152 	res = nmsg_message_enum_name_to_value(msg, fname, enum_name, valp);
153 	nmsg_message_destroy(&msg);
154 
155 	if (res != nmsg_res_success) {
156 		axa_error_msg("unrecognized %s enum value \"%s\""
157 			      " in line %d of \"%s\"",
158 			      fname, enum_name, line_num, fields_file);
159 		return (false);
160 	}
161 	return (true);
162 }
163 
164 /* Get the nmsg index of a field by its name for a line in the fields file. */
165 static axa_nmsg_idx_t
get_field_idx(struct nmsg_msgmod * mod,const char * ftype,const char * fname,uint line_num,const char * fields_file)166 get_field_idx(struct nmsg_msgmod *mod,	/* This module */
167 	      const char *ftype,	/* our name for the field type */
168 	      const char *fname,	/* target field name */
169 	      uint line_num, const char *fields_file)
170 {
171 	uint idx;
172 	nmsg_message_t msg;
173 	nmsg_res res;
174 
175 	msg = message_init(mod, fname, line_num, fields_file);
176 	if (msg == NULL)
177 		return (AXA_NMSG_IDX_ERROR);
178 
179 	res = nmsg_message_get_field_idx(msg, fname, &idx);
180 	nmsg_message_destroy(&msg);
181 
182 	if (res != nmsg_res_success) {
183 		axa_error_msg("unrecognized %s%sfield name \"%s\""
184 			      " in line %d of \"%s\"",
185 			      ftype, ftype[0] != '\0' ? " " : "",
186 			      fname, line_num, fields_file);
187 		return (AXA_NMSG_IDX_ERROR);
188 	}
189 
190 	if (idx >= AXA_NMSG_IDX_RSVD) {
191 		axa_error_msg("%s%sfield name \"%s\"=%d and > AXA limit %d"
192 			      " in line %d of \"%s\"",
193 			      ftype, ftype[0] != '\0' ? " " : "",
194 			      fname, idx, AXA_NMSG_IDX_RSVD,
195 			      line_num, fields_file);
196 		return (AXA_NMSG_IDX_ERROR);
197 	}
198 
199 	return (idx);
200 }
201 
202 /* Get the nmsg index of a helper field and whether to use val_idx=0 */
203 static bool
get_help_idx(axa_nmsg_help_t * help,struct nmsg_msgmod * mod,const char * ftype,const char * fname,uint line_num,const char * fields_file)204 get_help_idx(axa_nmsg_help_t *help,	/* results here */
205 	     struct nmsg_msgmod *mod,	/* this module */
206 	     const char *ftype,		/* our name for the field type */
207 	     const char *fname,		/* helper nmsg field name */
208 	     uint line_num, const char *fields_file)
209 {
210 	help->idx = get_field_idx(mod, ftype, fname, line_num, fields_file);
211 	return (help->idx < AXA_NMSG_IDX_RSVD);
212 }
213 
214 /* Parse the content field of a fields file line to get a content type. */
215 static axa_fc_t
get_fc(const char * content,uint line_num,const char * fields_file)216 get_fc(const char *content, uint line_num, const char *fields_file)
217 {
218 	typedef struct {
219 		const char  *s;
220 		axa_fc_t    fc;
221 	} fc_tbl_t;
222 	static fc_tbl_t fc_tbl[] = {
223 		{"IP-dgram",	    AXA_FC_IP_DGRAM},
224 		{"IP",		    AXA_FC_IP},
225 		{"IP-ASCII",	    AXA_FC_IP_ASCII},
226 		{"domain",	    AXA_FC_DOM},
227 		{"domain-ASCII",    AXA_FC_DOM_ASCII},
228 		{"host",	    AXA_FC_HOST},
229 		{"rdata",	    AXA_FC_RDATA},
230 		{"dns",		    AXA_FC_DNS},
231 		{"json",	    AXA_FC_JSON},
232 	};
233 	const fc_tbl_t *tp;
234 
235 	/* This is done only at start-up, so do not worry about speed. */
236 	for (tp = fc_tbl; tp <= AXA_LAST(fc_tbl); ++tp) {
237 		if (strcasecmp(content, tp->s) == 0)
238 			return (tp->fc);
239 	}
240 
241 	if (content[0] == '\0') {
242 		axa_error_msg("missing field content"
243 			      " in line %d of \"%s\"",
244 			      line_num, fields_file);
245 	} else {
246 		axa_error_msg("unrecognized field content \"%s\""
247 			      " in line %d of \"%s\"",
248 			      content, line_num, fields_file);
249 	}
250 	return (AXA_FC_UNKNOWN);
251 }
252 
253 static char *
get_subsubval(char * subval)254 get_subsubval(char *subval)
255 {
256 	char *p;
257 
258 	p = strchr(subval, '=');
259 	if (p == NULL || p[1] == '\0')
260 		return (NULL);
261 	*p++ = '\0';
262 	return (p);
263 }
264 
265 /*
266  * Read the fields file to build the tables of known vendor IDs,
267  * message types, and fields.
268  */
269 void
axa_load_fields(const char * fields_file0)270 axa_load_fields(const char *fields_file0)
271 {
272 	char *fields_file;
273 	FILE *f;
274 	char *line_buf;
275 	size_t line_buf_size;
276 	uint line_num;
277 	const char *line;
278 	char *p;
279 	struct nmsg_msgmod *mod;
280 	vm_entry_t *vm_list, *vm, **vmp;
281 	axa_nmsg_field_t *field;
282 	axa_nmsg_sf_t *sf, *sf2;
283 	char fc[AXA_FIELD_NM_LEN];
284 	char subtype[AXA_FIELD_NM_LEN];
285 	char subval[AXA_FIELD_NM_LEN];
286 	uint vid, msgtype;
287 	uint num_vm, num_vm_bins;
288 	size_t len;
289 
290 	axa_unload_fields();
291 
292 	/*
293 	 * Use a specified file, or default to $AXACONF/fields,
294 	 * or $HOME/.axa/fields, or AXACONFDIR/fields.
295 	 */
296 	if (fields_file0 != NULL && *fields_file0 != '\0') {
297 		fields_file = axa_strdup(fields_file0);
298 		f = fopen(fields_file, "r");
299 	} else {
300 		f = NULL;
301 		p = getenv("AXACONF");
302 		if (p == NULL) {
303 			fields_file = NULL;
304 		} else {
305 			axa_asprintf(&fields_file, "%s/%s",
306 				     p, "fields");
307 			f = fopen(fields_file, "r");
308 		}
309 		if (f == NULL) {
310 			if (fields_file != NULL)
311 				free(fields_file);
312 			p = getenv("HOME");
313 			if (p == NULL) {
314 				fields_file = NULL;
315 			} else {
316 				axa_asprintf(&fields_file, "%s/%s", p, "fields");
317 				f = fopen(fields_file, "r");
318 			}
319 		}
320 		if (f == NULL) {
321 			if (fields_file != NULL)
322 				free(fields_file);
323 			fields_file = strdup(AXACONFDIR"/fields");
324 			f = fopen(fields_file, "r");
325 		}
326 	}
327 	if (f == NULL) {
328 		axa_error_msg("cannot open \"%s\": %s",
329 			      fields_file, strerror(errno));
330 		free(fields_file);
331 		return;
332 	}
333 
334 	line_buf = NULL;
335 	line_buf_size = 0;
336 
337 	vm_list = NULL;
338 	num_vm = 0;
339 	line_num = 0;
340 	field = NULL;
341 	for (;;) {
342 next_line:
343 		if (field != NULL) {
344 			free_field(field);
345 			field = NULL;
346 		}
347 
348 		line = axa_fgetln(f, fields_file, &line_num,
349 				  &line_buf, &line_buf_size);
350 		if (line == NULL)
351 			break;
352 
353 		field = AXA_SALLOC(axa_nmsg_field_t);
354 		*field = axa_null_field;
355 		field->line_num = line_num;
356 
357 		/* get the vendor name and message type from the line */
358 		if (0 > axa_get_token(field->vname, sizeof(field->vname),
359 				      &line, AXA_WHITESPACE)
360 		    || (vid = nmsg_msgmod_vname_to_vid(field->vname)) == 0) {
361 			axa_error_msg("unrecognized vendor \"%s\""
362 				      " in line %d of \"%s\"",
363 				      field->vname, line_num, fields_file);
364 			continue;
365 		}
366 		if (vid >= AXA_NMSG_IDX_RSVD) {
367 			axa_error_msg("vendor \"%s\" >= AXA limit %d"
368 				      " in line %d of \"%s\"",
369 				      field->vname, AXA_NMSG_IDX_RSVD,
370 				      line_num, fields_file);
371 			continue;
372 		}
373 
374 		if (*line == '\0') {
375 			axa_error_msg("missing message type and field name"
376 				      " in line %d of \"%s\"",
377 				      line_num, fields_file);
378 			continue;
379 		}
380 		if (0 > axa_get_token(field->mname, sizeof(field->mname),
381 				      &line, AXA_WHITESPACE)
382 		    || (msgtype = nmsg_msgmod_mname_to_msgtype(vid,
383 							field->mname)) == 0) {
384 			axa_error_msg("unrecognized message type \"%s\""
385 				      " in line %d of \"%s\"",
386 				      field->mname, line_num, fields_file);
387 			continue;
388 		}
389 		if (msgtype >= AXA_NMSG_IDX_RSVD) {
390 			axa_error_msg("message type \"%s\" >= AXA limit %d"
391 				      " in line %d of \"%s\"",
392 				      field->mname,
393 				      AXA_NMSG_IDX_RSVD, line_num, fields_file);
394 			continue;
395 		}
396 
397 		/* Search the list of known (vendor,message type) pairs.
398 		 * If this pair is new, get their libnmsg ordinals. */
399 		for (vm = vm_list; vm != NULL; vm = vm->next) {
400 			if (vm->vid == vid && vm->msgtype == msgtype)
401 				break;
402 		}
403 		if (vm != NULL) {
404 			mod = vm->mod;
405 		} else {
406 			mod = nmsg_msgmod_lookup_byname(field->vname,
407 							field->mname);
408 			if (mod == NULL) {
409 				axa_error_msg("cannot find module for vendor ID"
410 					      " \"%s\" and message type \"%s\""
411 					      " in line %d of \"%s\"",
412 					      field->vname, field->mname,
413 					      line_num, fields_file);
414 				continue;
415 			}
416 		}
417 
418 		/* Add the field specified by the rest of the line to the
419 		 * list of interesting fields of this (vendor, message type)
420 		 * pair.  Start by parsing the name of the field. */
421 		if (*line == '\0') {
422 			axa_error_msg("missing field name"
423 				      " in line %d of \"%s\"",
424 				      line_num, fields_file);
425 			continue;
426 		}
427 		if (0 > axa_get_token(field->name, sizeof(field->name),
428 				      &line, AXA_WHITESPACE)) {
429 			axa_error_msg("bad field name"
430 				      " in line %d of \"%s\"",
431 				      line_num, fields_file);
432 			continue;
433 		}
434 		field->idx = get_field_idx(mod, "", field->name,
435 					   line_num, fields_file);
436 		if (field->idx >= AXA_NMSG_IDX_RSVD)
437 			continue;
438 
439 		/* parse the content type */
440 		axa_get_token(fc, sizeof(fc), &line, AXA_WHITESPACE);
441 		field->fc = get_fc(fc, line_num, fields_file);
442 		if (field->fc == AXA_FC_UNKNOWN)
443 			continue;
444 
445 		while (*line != '\0') {
446 			/* get next optional "subtype=fname..."
447 			 * subtype is {rtype|class|...}
448 			 * subval is the name of the nmsg field with the
449 			 *	required subtype data. */
450 			if (0 > axa_get_token(subtype, sizeof(subtype),
451 					      &line, "=")
452 			    || strpbrk(subtype, AXA_WHITESPACE) != NULL) {
453 				axa_error_msg("unrecognized \"%s\""
454 					      " in line %d of \"%s\"",
455 					      subtype, line_num, fields_file);
456 				goto next_line;
457 			}
458 			if (0 > axa_get_token(subval, sizeof(subval),
459 					      &line, AXA_WHITESPACE)
460 			    || subval[0] == '\0') {
461 				axa_error_msg("unrecognized \"%s=%s\""
462 					      " in line %d of \"%s\"",
463 					      subtype, subval,
464 					      line_num, fields_file);
465 				goto next_line;
466 			}
467 
468 			if ((field->fc == AXA_FC_RDATA
469 			     || field->fc == AXA_FC_DOM)
470 			    && field->class.idx == AXA_NMSG_IDX_NONE
471 			    && strcasecmp(subtype, "class") == 0) {
472 				if (!get_help_idx(&field->class,
473 						  mod, subtype, subval,
474 						  line_num, fields_file))
475 					goto next_line;
476 
477 			} else if (field->fc == AXA_FC_RDATA
478 				   && field->rtype.idx == AXA_NMSG_IDX_NONE
479 				   && strcasecmp(subtype, "rtype") == 0) {
480 				if (!get_help_idx(&field->rtype,
481 						  mod, subtype, subval,
482 						  line_num, fields_file))
483 					goto next_line;
484 
485 			} else if (field->fc == AXA_FC_RDATA
486 				   && field->owner.idx == AXA_NMSG_IDX_NONE
487 				   && strcasecmp(subtype, "oname") == 0) {
488 				if (!get_help_idx(&field->owner,
489 						  mod, subtype, subval,
490 						  line_num, fields_file))
491 					goto next_line;
492 
493 			} else if (field->enm.idx == AXA_NMSG_IDX_NONE
494 				   && strcasecmp(subtype, "enum") == 0
495 				   && (p = get_subsubval(subval)) != '\0') {
496 				field->enm.idx = get_field_idx(mod,
497 							subtype, subval,
498 							line_num, fields_file);
499 				if (field->enm.idx >= AXA_NMSG_IDX_RSVD)
500 					goto next_line;
501 				if (!get_enum_value(mod, subval,
502 						    p, &field->enm_val,
503 						    line_num, fields_file))
504 					goto next_line;
505 
506 			} else if (field->fc == AXA_FC_JSON
507 				   && strcasecmp(subtype, "sfield") == 0
508 				   && (p = get_subsubval(subval)) != '\0') {
509 				sf = axa_zalloc(sizeof(axa_nmsg_sf_t)
510 						+strlen(subval)+1);
511 				sf->next = field->sf;
512 				field->sf = sf;
513 				sf->len = strlen(subval);
514 				memcpy(sf->name, subval, sf->len);
515 				sf->fc = get_fc(p, line_num, fields_file);
516 				switch (sf->fc) {
517 				case AXA_FC_IP_ASCII:
518 				case AXA_FC_DOM_ASCII:
519 				case AXA_FC_HOST:
520 					break;
521 				case AXA_FC_UNKNOWN:
522 				case AXA_FC_IP_DGRAM:
523 				case AXA_FC_IP:
524 				case AXA_FC_DOM:
525 				case AXA_FC_RDATA:
526 				case AXA_FC_DNS:
527 				case AXA_FC_JSON:
528 				default:
529 					goto next_line;
530 				}
531 				for (sf2 = sf->next;
532 				     sf2 != NULL;
533 				     sf2 = sf2->next) {
534 					if (strcmp(sf->name, sf2->name) == 0) {
535 					    axa_error_msg("duplicate sfield=%s"
536 							" in line %d of \"%s\"",
537 							sf->name,
538 							line_num, fields_file);
539 					    goto next_line;
540 					}
541 				}
542 			} else {
543 				axa_error_msg("unrecognized \"%s=%s\""
544 					      " in line %d of \"%s\"",
545 					      subtype, subval,
546 					      line_num, fields_file);
547 				goto next_line;
548 			}
549 		}
550 
551 		if (field->class.idx == AXA_NMSG_IDX_NONE
552 		    && (field->fc == AXA_FC_DOM
553 			|| field->fc == AXA_FC_RDATA)) {
554 			axa_error_msg("missing \"class=field\""
555 				      " in line %d of \"%s\"",
556 				      line_num, fields_file);
557 			continue;
558 		}
559 		if (field->rtype.idx == AXA_NMSG_IDX_NONE
560 		    && field->fc == AXA_FC_RDATA) {
561 			axa_error_msg("missing \"rtype=field\""
562 				      " in line %d of \"%s\"",
563 				      line_num, fields_file);
564 			continue;
565 		}
566 
567 		/* If we have previously seen this vendor and message type,
568 		 * ensure that we have not see this field. */
569 		if (vm != NULL) {
570 			const axa_nmsg_field_t *field2;
571 
572 			for (field2 = vm->fields;
573 			     field2 != NULL;
574 			     field2 = field2->next) {
575 				if (field2->idx == field->idx
576 				    && field->enm.idx == field2->enm.idx
577 				    && field->enm_val == field2->enm_val)
578 					break;
579 			}
580 			if (field2 != NULL) {
581 				axa_error_msg("duplicate vendor ID,"
582 					      " message type, and field name"
583 					      " in lines %d and %d of \"%s\"",
584 					      line_num, field2->line_num,
585 					      fields_file);
586 				continue;
587 			}
588 		} else {
589 			vm = AXA_SALLOC(vm_entry_t);
590 			vm->vid = vid;
591 			vm->msgtype = msgtype;
592 			vm->mod = mod;
593 			vm->next = vm_list;
594 			vm_list = vm;
595 			++num_vm;
596 		}
597 		field->next = vm->fields;
598 		field->vm = vm;
599 		vm->fields = field;
600 		field = NULL;
601 	}
602 	if (field != NULL) {
603 		free_field(field);
604 		field = NULL;
605 	}
606 	fclose(f);
607 	if (line_buf != NULL)
608 		free(line_buf);
609 
610 	if (num_vm == 0) {
611 		axa_error_msg("no fields defined in \"%s\"", fields_file);
612 		free(fields_file);
613 		return;
614 	}
615 
616 	/* Move the list of lists of fields into a hash table.
617 	 * Be generous because this hash table is small. */
618 	num_vm_bins = axa_hash_divisor(num_vm*2+20, false);
619 	len = sizeof(*vm_hash_tbl) + sizeof(vm_hash_tbl->bins[0])*num_vm_bins;
620 	vm_hash_tbl = axa_zalloc(len);
621 
622 	vm_hash_tbl->num_bins = num_vm_bins;
623 	while ((vm = vm_list) != NULL) {
624 		vm_list = vm->next;
625 		vmp = vm_hash_fnc(vm->vid, vm->msgtype);
626 		vm->next = *vmp;
627 		*vmp = vm;
628 	}
629 
630 	free(fields_file);
631 }
632 
633 /* Get the contents of a "helper" field for a fields file line */
634 bool
axa_get_helper(axa_emsg_t * emsg,const nmsg_message_t msg,const axa_nmsg_help_t * help,axa_nmsg_idx_t val_idx,void * val,size_t * val_len,size_t min_val_len,size_t max_val_len,axa_helper_cache_t * cache)635 axa_get_helper(axa_emsg_t *emsg, const nmsg_message_t msg,
636 	       const axa_nmsg_help_t *help, axa_nmsg_idx_t val_idx,
637 	       void *val, size_t *val_len,
638 	       size_t min_val_len, size_t max_val_len,
639 	       axa_helper_cache_t *cache)
640 {
641 	void *data;
642 	size_t data_len;
643 	uint cn;
644 	nmsg_res res;
645 
646 	if (help->idx >= AXA_NMSG_IDX_RSVD) {
647 		axa_pemsg(emsg, "invalid field index %#x", help->idx);
648 		return (false);
649 	}
650 
651 	/* Be fast about repeated fetches of the helper values */
652 	if (cache != NULL) {
653 		for (cn = 0; cn < cache->len; ++cn) {
654 			if (cache->e[cn].idx == help->idx
655 			    && cache->e[cn].val_idx == val_idx) {
656 				if (min_val_len == sizeof(cache->e[cn].val)
657 				    && max_val_len == sizeof(cache->e[cn].val)) {
658 					memcpy(val, &cache->e[cn].val,
659 					       min_val_len);
660 					if (val_len != NULL)
661 					    *val_len = sizeof(cache->e[cn].val);
662 					return (true);
663 				}
664 				break;
665 			}
666 		}
667 	}
668 
669 	res = nmsg_message_get_field_by_idx(msg, help->idx, val_idx,
670 					    &data, &data_len);
671 	if (res != nmsg_res_success) {
672 		axa_pemsg(emsg, "nmsg_message_get_field_by_idx(%s): %s",
673 			  axa_get_field_name(msg, help->idx),
674 			  nmsg_res_lookup(res));
675 		return (false);
676 	}
677 	if (data_len < min_val_len || data_len > max_val_len) {
678 		axa_pemsg(emsg, "%s size=%zd not >=%zd and <=%zd",
679 			  axa_get_field_name(msg, help->idx), data_len,
680 			  min_val_len, max_val_len);
681 		return (false);
682 	}
683 
684 	memcpy(val, data, data_len);
685 	if (val_len != NULL)
686 		*val_len = data_len;
687 
688 	if (cache != NULL && (cn = cache->len) < AXA_HELPER_CACHE_LEN
689 	    && min_val_len == data_len
690 	    && min_val_len == sizeof(cache->e[cn].val)
691 	    && max_val_len == sizeof(cache->e[cn].val)) {
692 		cache->e[cn].idx = help->idx;
693 		cache->e[cn].val_idx = val_idx;
694 		memcpy(&cache->e[cn].val, data, sizeof(cache->e[cn].val));
695 		++cache->len;
696 	}
697 
698 	return (true);
699 }
700 
701