/*
* METAOPS - Provides operations to be performed on data blocks
* according to metadata definitions.
*
* Author:
* Emile van Bergen, emile@evbergen.xs4all.nl
*
* Permission to redistribute an original or modified version of this program
* in source, intermediate or object code form is hereby granted exclusively
* under the terms of the GNU General Public License, version 2. Please see the
* file COPYING for details, or refer to http://www.gnu.org/copyleft/gpl.html.
*
* History:
* 2001/03/30 - EvB - Created
* 2001/04/17 - EvB - Changed decode not to add items in reverse order
* 2001/04/24 - EvB - Added buildtree / encode
* 2001/05/08 - EvB - Added integer value and temporary flag to META_AV
* 2001/05/10 - EvB - Merged string length and integer value (always fixed
* from dictionary)
* 2001/05/16 - EvB - Added printav / printavlist debugging functions here
* 2001/06/04 - EvB - Made decode create a double linked list instead of single
* 2001/06/25 - EvB - Added meta_avtomsg
* 2001/09/11 - EvB - Added meta_addav here; used by job_new, chan_handle_read
* and vm_run (we should, at least).
* 2001/09/14 - EvB - Added ACL support to meta_avtomsg here after some doubt.
* 2001/12/27 - EvB - Rewrote chan_*msgtoav into meta_*msgtoav here.
* 2002/02/04 - EvB - Fixed meta_decode to allow zero-sized encapsulating
* attributes
* - Added more diagnostics output to meta_decode
* - Made negative len_adj values for zero-sized length fields
* to signify a (skip) length relative to the enclosing
* block. Useful for decoding relative-sized fixed fields
* without accompanying length fields.
* - Meta_decode searches the dictionary 1. for an attribute
* that has the right ID and vendor PEC, 2. for an attribute
* that has the right ID and vendor=any, 3. for an attribute
* that has the right vendor and ID=any, 4. for an attribute
* that has vendor=any and ID=any. Step 3 used to be left
* out.
* 2002/03/01 - EvB - Made buildtree even more gross by having it delete
* items with noenc=1 from its source list. Removes memory
* leak though. Cleanup still needed.
* 2002/03/17 - EvB - Removed that hack; noenc=1 items are just left on the
* source list now.
* 2002/04/03 - EvB - Good cleanup of buildtree(). It also leaves the source
* list intact now, so that you can re-use parts of it.
* 2002/12/12 - EvB - Fixed incorrect search for attributes from any vendor
* instead of attributes with vendor nr. META_ORD_ERR if
* no attribute with the correct vendor is found in
* meta_decode.
* 2005/06/01 - EvB - Made buildtree set a pointer to the copied pair in the
* source pair, so that you can find the destination pair
* after calling buildtree.
* - Made meta_encode set 'encp' in source tree pair to
* designate the location of the value in the encoded packet.
* 2005/07/06 - EvB - Made buildtree not set a pointer to the tree item in the
* source pair's 'sub' member, but in a separate 'treeitem'
*/
char metaops_id[] = "METAOPS - Copyright (C) 2001 Emile van Bergen.";
/*
* INCLUDES & DEFINES
*/
#include <unistd.h> /* For the write() in printav */
#include <stdlib.h>
#include <string.h>
#include <misc.h> /* For hex() */
#include <metaops.h>
#include <debug.h>
/* Maximum stack depth for build tree */
#define BT_STKDEPTH 6
/*
* FUNCTIONS
*/
/*
* Helper macros and functions for meta_decode
*/
/* Return the attribute or vendor field */
#define getatrnr(s, d) (getord((char *)(d) + (s)->atr_ofs, (s)->atr_size))
#define getvndnr(s, d) (getord((char *)(d) + (s)->vnd_ofs, (s)->vnd_size))
/* The so-called skip length is the length needed to get from offset 0 in this
attribute to the next attribute. The value length is the length of the
value itself.
The skip length can be obtained as follows: if the item has a length field,
(detected by len_size being non-zero), take its value and add the adjustment
given by len_adj, which can be positive, negative or zero.
If there is no length field, and len_adj is zero or less, take the size
of the enclosing block and add len_adj. If there is no length field but
len_adj is positive, then skiplen is specified by the absolute value of
len_adj.
The value length is calculated like this: if val_size is zero or less, take
skiplen and add val_size. If val_size is positive, then the value length
is defined by the absolute value of val_size.
This scheme allows for any combination of absolute or relative
specifications for the offset of the next attribute and for the size of the
value itself. */
#define getskiplen(i, d, bl) \
((i)->len_adj + ( (i)->len_size ? \
getord((char *)(d) + (i)->len_ofs, (i)->len_size) : \
( (i)->len_adj <= 0 ? \
(bl) : 0)))
#define getvallen(i, sl) \
((i)->val_size + ( (i)->val_size <= 0 ? \
(sl) : 0))
/* Post-mortem decoding diagnostics */
static void debug_fixed(META_ITEM *i, char *blk, META_ORD blksize)
{
if (i->len_ofs + i->len_size > blksize) {
msg(F_RECV, L_DEBUG, "meta_decode: ERROR: Length field for "
"%s/%ld/%s at 0x%lx, length %ld not "
"within block, length 0x%lx!\n",
i->spc->name, i->vnd, i->name,
i->len_ofs, i->len_size, blksize);
}
else {
msg(F_RECV, L_DEBUG, "meta_decode: ERROR: Field %s/%ld/%s at "
"0x%lx, length %ld not within block, "
"length 0x%lx!\n",
i->spc->name, i->vnd, i->name,
i->val_ofs, getvallen(i, getskiplen(i, blk, blksize)),
blksize);
}
}
static void debug_attr(META_SPC *s, META_ITEM *i, char *blk, char *p,
META_ORD blksize, META_ORD defvnd)
{
META_ORD atr, vnd, skiplen, vallen;
if (p > blk + blksize) {
msg(F_RECV, L_DEBUG, "meta_decode: ERROR: Arrived at 0x%lx "
"in space %s which is beyond 0x%lx!\n",
p - blk, s->name, blksize);
return;
}
if (p + s->atr_ofs + s->atr_size > blk + blksize) {
msg(F_RECV, L_DEBUG, "meta_decode: ERROR: Attribute field in "
"space %s at 0x%lx, length %ld not "
"within area, length 0x%lx!\n",
s->name, p - blk + s->atr_ofs, s->atr_size, blksize);
return;
}
atr = getatrnr(s, p);
if (p + s->vnd_ofs + s->vnd_size > blk + blksize) {
msg(F_RECV, L_DEBUG, "meta_decode: ERROR: Vendor field in "
"space %s at 0x%lx, length %ld not "
"within area, length 0x%lx!\n",
s->name, p - blk + s->vnd_ofs, s->vnd_size, blksize);
return;
}
vnd = s->vnd_size ? getvndnr(s, p) : defvnd;
if (!i) {
msg(F_RECV, L_DEBUG, "meta_decode: ERROR: Space %s has no attr"
"ibute that matches id %ld, vendor %ld!\n",
s->name, atr, vnd);
return;
}
skiplen = getskiplen(i, p, blksize);
vallen = getvallen(i, skiplen);
if (skiplen < 1 || vallen < 0) {
msg(F_RECV, L_DEBUG, "meta_decode: ERROR: Invalid skip length "
"(%ld) and/or value length (%ld) for "
"attribute %s/%ld/%s at 0x%lx!\n",
skiplen, vallen, s->name, vnd, i->name, p-blk+i->val_ofs);
return;
}
msg(F_RECV, L_DEBUG, "meta_decode: ERROR: Value for attribute "
"%s/%ld/%s at 0x%lx, length %ld "
"not within area, length 0x%lx!\n",
s->name, vnd, i->name, p - blk + i->val_ofs, vallen, blksize);
}
/* Decodes a data block, forming a flat list of AV items. */
META_AV *meta_decode(META *m, META_SPC *s, META_ORD defvnd,
char *blk, META_ORD blksize, META_AV **rettail)
{
META_ITEM *i;
META_ORD skiplen, vallen;
META_ORD atrnr, vndnr;
META_AV *av, *avlist, *tail, *newtail;
char *p;
/* Check arguments */
if (!m || !s || !blk) {
msg(F_MISC, L_ERR, "meta_decode: BUG: invalid argument!\n");
return 0;
}
tail = avlist = 0;
/* Check if this is an A/V or fixed field space */
if (s->atr_size == 0) {
/* Test here if the space doesn't have any items and tell about
it. Otherwise we'd also return 0 but without telling why. */
if (!s->items) {
msg(F_RECV, L_ERR, "meta_decode: ERROR: Space %s "
"does not contain any fields!\n",
s->name);
return 0;
}
/* Fixed fields - loop through the items. */
for(i = s->items;
i && i->len_ofs + i->len_size <= blksize &&
i->val_ofs + (vallen = getvallen(i, getskiplen(i, blk, blksize))) <= blksize;
i = i->next) {
/* Add the data, unless we shouldn't decode it */
if (!i->nodec) {
av = (META_AV *)malloc(sizeof(META_AV));
if (!av) { meta_freeavlist(avlist); return 0; }
memset(av, 0, sizeof(META_AV));
av->i = i;
if (MT_ISORD(i->val_type))
av->l = getord(blk+i->val_ofs, vallen);
else av->l = vallen, av->p = blk+i->val_ofs;
if (!avlist) avlist = av;
if (tail) tail->next = av;
av->prev = tail;
tail = av;
}
/* Recurse into a subspace, if any */
if (i->subspace && vallen) {
av = meta_decode(m, i->subspace, defvnd,
blk + i->val_ofs, vallen,
&newtail);
if (!av) { meta_freeavlist(avlist); return 0; }
if (!avlist) avlist = av;
if (tail) tail->next = av;
av->prev = tail;
tail = newtail;
}
}
/* If we terminated prematurely, show error and return */
if (i) { debug_fixed(i, blk, blksize); meta_freeavlist(avlist);
return 0; }
/* Return the head of the created list */
if (rettail) *rettail = tail;
return avlist;
}
/* Prevent a nonsense warning - the compiler doesn't see that
the p += skiplen statement will only be executed if skiplen
actally got assigned to. Oh well. */
skiplen = 0;
/* Attribute space. You've just got to love C's logical operators... */
for(p = blk, i = 0;
p < blk + blksize &&
p + s->atr_ofs + s->atr_size <= blk + blksize &&
p + s->vnd_ofs + s->vnd_size <= blk + blksize &&
((i = meta_getitembynr(m, s, (atrnr = getatrnr(s, p)),
(vndnr = (s->vnd_size ? getvndnr(s, p) : defvnd)))) ||
(i = meta_getitembynr(m, s, atrnr, META_ORD_ERR)) ||
(i = meta_getitembynr(m, s, META_ORD_ERR, vndnr)) ||
(i = meta_getitembynr(m, s, META_ORD_ERR, META_ORD_ERR))) &&
p + i->val_ofs + ((skiplen = getskiplen(i, p, blksize)), (vallen = getvallen(i, skiplen))) <= blk + blksize &&
skiplen > 0 && vallen >= 0;
p += skiplen, i = 0) {
/* Add the data, unless we shouldn't decode it */
if (!i->nodec) {
av = (META_AV *)malloc(sizeof(META_AV));
if (!av) { meta_freeavlist(avlist); return 0; }
memset(av, 0, sizeof(META_AV));
av->i = i;
if (MT_ISORD(i->val_type))
av->l = getord(p + i->val_ofs, vallen);
else av->l = vallen, av->p = p + i->val_ofs;
if (!avlist) avlist = av;
if (tail) tail->next = av;
av->prev = tail;
tail = av;
}
/* Recurse into a subspace, if any */
if (i->subspace && vallen) {
av = meta_decode(m, i->subspace, vndnr,
p + i->val_ofs, vallen, &newtail);
if (!av) { meta_freeavlist(avlist); return 0; }
if (!avlist) avlist = av;
if (tail) tail->next = av;
av->prev = tail;
tail = newtail;
}
}
/* If the loop terminated prematurely, show and return 0 to signal it */
if (p != blk + blksize) {
debug_attr(s, i, blk, p, blksize, defvnd);
meta_freeavlist(avlist);
return 0;
}
/* Return the (tail and) head of the created list */
if (rettail) *rettail = tail;
return avlist;
}
/* Encodes a AV tree created by buildtree, according to applicable metadata */
META_ORD meta_encode(META_SPC *s, char *blk, META_ORD blksize, META_AV *avlist,
META_ORD *retvnd)
{
META_ITEM *i;
META_AV *av;
META_ORD vndnr, pos, skiplen, retlen;
if (!blk || blksize <= 0 || !avlist) {
msg(F_MISC, L_ERR, "meta_encode: BUG: invalid argument!\n");
return -1;
}
/* Set the vendor number used when the space has a vendor field but
the item's vendor number is 'any', to the one inherited from the
caller; in most cases this same function. */
vndnr = retvnd ? *retvnd : 0;
for(av = avlist, pos = retlen = 0;
av && (i = av->i) && i->spc == s;
av = av->next) {
/* Skip deleted items. Don't skip items with 'noenc' specified,
they're only skipped in buildtree so they can still be used
as encapsulation items. */
if (av->flags & AV_DEL) continue;
/* Check if the vendor and attribute fields fit */
if (pos + s->atr_ofs + s->atr_size > blksize ||
pos + s->vnd_ofs + s->vnd_size > blksize) {
msg(F_SEND, L_ERR, "meta_encode: ERROR: attribute "
"field (at %d, size %d) and/or "
"vendor field (at %d, size %d), "
"do not fit in remaining output "
"block, size %d!\n",
pos + s->atr_ofs, s->atr_size, pos + s->vnd_ofs,
s->vnd_size, blksize);
return -1;
}
/* Determine skiplen and put value */
if (av->sub) {
/* The value is a list of items again, so recurse */
skiplen = meta_encode(i->subspace,
blk+pos+i->val_ofs,
blksize - (pos + i->val_ofs),
av->sub, &vndnr) - i->val_size;
}
else {
/* The value is a real item */
/* Record value's location in encoded packet */
av->encp = blk + pos + i->val_ofs;
if (MT_ISORD(i->val_type)) {
/* An integer (fixed size in val_size) */
skiplen = i->val_ofs + i->val_size;
putord(av->encp, i->val_size, av->l);
}
else {
/* An octet string */
if (i->val_size <= 0) {
/* Variable size */
skiplen = av->l - i->val_size;
memcpy(av->encp, av->p, av->l);
}
else {
/* Fixed size */
skiplen = i->val_ofs + i->val_size;
if (av->l >= i->val_size) {
/* Limited by val_size */
memcpy(av->encp, av->p,
i->val_size);
}
else {
/* Limited by av->l, so pad */
memcpy(av->encp, av->p, av->l);
memset(av->encp + av->l, 0,
i->val_size - av->l);
}
}
}
}
/* Put vendor field in. If the item's vendor number is 'any',
use the vendor number that may be set by the just encoded
sub list. */
if (i->vnd != META_ORD_ERR) vndnr = i->vnd;
putord(blk + pos + s->vnd_ofs, s->vnd_size, vndnr);
/* Put the attribute field in */
putord(blk + pos + s->atr_ofs, s->atr_size, i->nr);
/* Put length field in */
putord(blk + pos + i->len_ofs, i->len_size,
skiplen - i->len_adj);
if (s->atr_size > 0) {
/* If A/V space, add skiplen to pos and retlen */
pos += skiplen;
retlen += skiplen;
}
else {
/* Otherwise, set retlen to the highest skiplen found */
if (skiplen > retlen) retlen = skiplen;
}
}
/* Return the vendor number of the last encoded item in this list
if that's requested. */
if (retvnd) *retvnd = vndnr;
return retlen;
}
/* Utility function for buildtree. Skips deleted items on the source list,
allocates a new item, makes it a copy of the current item on the source
list and advances the source list to the next item. The AV_FREE_P is
cleared on the copy, so that we don't double free things. */
static META_AV *getcopy(META_AV **src)
{
META_ITEM *i;
META_AV *av;
META_ORD l, n;
char *p;
/* Skip deleted and 'noenc' items on source list; return 0 if at end */
while(*src && (((*src)->flags & AV_DEL) || ((*src)->i && (*src)->i->noenc))) *src = (*src)->next;
if (!*src) return 0;
i = (*src)->i;
p = (*src)->p;
l = (*src)->l;
/* Implement attribute splitting based on maximum size as defined in
* the dictionary.
* Warning: this is a bit dirty in that it modifies the source list,
* but 'rest' is 'my' data member, so to speak, so don't whine about
* it. Warning II: 'rest' is not the number of bytes left, but an
* *offset* into the source value's data here, and only valid if > 0.
*/
n = (*src)->rest;
if (n) {
p += n;
l -= n;
if (l > i->max_size) {
l = i->max_size;
(*src)->rest += l;
}
else (*src)->rest = 0;
} else if (i && !MT_ISORD(i->val_type) &&
i->max_size > 0 && l > i->max_size) {
/* If we have an attribute and the attribute has a length field
* and the value of the length field would overflow, split the
* pair. */
l = i->max_size;
(*src)->rest = l;
}
av = (META_AV *)malloc(sizeof(META_AV));
if (!av) { msg(F_MISC, L_ERR, "meta_buildtree: ERROR: No memory!\n"); return 0; }
memset(av, 0, sizeof(META_AV));
av->i = i;
av->p = p;
av->l = l;
av->flags &= ~AV_FREE_P;
(*src)->treeitem = av; /* Record a pointer to the copy in the
tree in the source pair, so that we
can access it (and its encp) later */
if (!(*src)->rest) *src = (*src)->next;
return av;
}
/* Build a tree from a list, adding encapsulating attributes where needed.
* We use two separate stacks for this: 1. the destination stack (dstsp) that
* holds the stack of spaces from the ground space to the current space, plus
* pointers to the attribute list tails for each level, and 2. the
* encapsulating item stack, which is used to build a list of encapsulating
* items from the current source item to the current space. If we cannot reach
* the current space by walking our items' space's encapsulating attributes, we
* go one level down in the destination stack (leaving the current space and
* current list) and try again in the parent space, until the current space is
* the ground space.
*
* We need one refinement: while adding the found encapsulating item stack
* (popping encapsulating items from current space to source item) to the
* destination list, we must check 1. whether the current space is a fixed
* field space, and 2. whether the current destination list already has a same
* encapsulating item as the one we are about to add. In that case, we must
* continue at the tail of the earlier encapsulating item's sub list; once we
* need to leave its space, we automatically continue at the tail of the parent
* space by virtue of the destination stack, so we don't mess up ordering too
* much.
*
* In other words: the destination stack also needs to hold a list head, which
* only needs to be initialised and searched for destination stack spaces that
* are fixed field spaces.
*
* If this head pointer is non-zero prior to adding an encapsulating item
* in the current fixed field space, we walk this list to find a same item.
* We only need to do this for first encapsulating item we add from the item
* stack; we know in advance that the parent's sub lists of subsequent
* encapsulating items we use to reach the current item from the ground space
* are all empty, because we just added that empty parent.
*/
void meta_buildtree(META_AV *src, META_AV **dstlst, META_SPC *groundspc)
{
int dstsp; /* Destination tree stack */
META_AV **dststk[BT_STKDEPTH]; /* destination list tails */
META_AV *dstheadstk[BT_STKDEPTH]; /* destination list heads */
META_SPC *dstspcstk[BT_STKDEPTH]; /* destination spaces */
int itmsp; /* Encapsulating item stack */
META_ITEM *itmstk[BT_STKDEPTH]; /* Encapsulating items
from current space to
current source item */
META_AV *srcav; /* current source pair */
META_SPC *dstspc; /* current destination space */
META_AV *dsthead; /* current destination head */
META_AV *newav; /* encaps. pair to be added */
META_ITEM *encitem, *i; /* current encapsulating item */
/* Check parameters, init destination list and stack, get first item */
if (!dstlst) return;
*dstlst = 0;
if (!src || !groundspc) return;
dsthead = 0;
itmsp = dstsp = 0;
dstspc = groundspc;
srcav = getcopy(&src);
while(srcav) {
/* See if the current item fits in current space */
if (srcav->i->spc == dstspc) {
/* Yes, so add the item to the destination list */
if (!dsthead) dsthead = srcav;
*dstlst = srcav;
dstlst = &(srcav->next);
/* If this is a single attr. space, leave it now. */
while(dstspc->single && dstsp) {
dstspc = dstspcstk[--dstsp];
dstlst = dststk[dstsp];
dsthead = dstheadstk[dstsp];
}
/* Take next item from source list and continue */
srcav = getcopy(&src);
continue;
}
/* No, so try to reach the current space from this item's space
by backtracking encapsulating items */
for(encitem = srcav->i; encitem && encitem->spc != dstspc; ) {
/* Not there yet. First try to find an encapsulating
item for the current item's space that matches the
current item's vendor nr. */
for(i = encitem->spc->enc_items;
i && i->vnd != srcav->i->vnd;
i = i->next);
/* Couldn't match vendor, find enc. item allowing any */
if (!i) {
for(i = encitem->spc->enc_items;
i && i->vnd != META_VND_ANY;
i = i->next);
}
/* Add the current item's space's encapsulating item
* that matches the current vendor as best as we can,
* to the encapsulating item stack */
encitem = i;
itmstk[itmsp++] = encitem;
}
/* If we didn't find an encapsulation route to the current
space, go back one or more spaces on the destination stack
and retry. */
if (!encitem) {
/* Empty stack of useless obtained items */
itmsp = 0;
/* If we're not at the bottom of the space stack yet,
pop destination list and space. */
if (dstsp) {
/* Go back one space and keep soing so if the
space we get is a 'single attribute' space,
because we know that there is already an
attribute present from the fact that we're
here -- a place which could only be reached
using an encapsulating item. So all spaces
below us already have their single allowed
attribute present. */
do {
dstspc = dstspcstk[--dstsp];
dstlst = dststk[dstsp];
dsthead = dstheadstk[dstsp];
}
while(dstspc->single && dstsp);
/* Try again to add the item, directly or via
an encapsulation route, now we're at a lower
space */
continue;
}
/* We are at the bottom, but the current space cannot
* be reached by any of the current item's space's
* encapsulating attributes (recursively). There's no
* encapsulation route possible, so skip this item. */
msg(F_SEND, L_ERR, "meta_buildtree: skipping %s::%s "
"(%ld:%ld:%ld): no encapsulation "
"route to space %s (%ld)\n",
srcav->i->spc->name, srcav->i->name,
srcav->i->spc->nr, srcav->i->vnd, srcav->i->nr,
groundspc->name, groundspc->nr);
free(srcav);
srcav = getcopy(&src);
continue;
}
/* We did succeed. Now follow the enc. route that we found,
adding the encapsulating items to the destination list,
while saving the destination list and space on its stack. */
while(itmsp) {
/* Get encapsulating item from its stack (going from
* item->spc == dstspc to item->subspc == srcav->i->spc)
*/
encitem = itmstk[--itmsp];
/* If we have a destination head and we're in a fixed
* field space, search the current destination list to
* see if we already have the required encaps. item;
* if so, add to its tail. This is all expensive, but
* does not happen often. */
if (dsthead && dstspc->atr_size == 0) {
for(newav = dsthead;
newav && newav->i != encitem;
newav = newav->next);
if (newav) {
/* Save current level to stack */
dstheadstk[dstsp] = dsthead;
dststk[dstsp] = dstlst;
dstspcstk[dstsp++] = dstspc;
/* Go to found item's sub level */
dsthead = newav->sub;
dstspc = newav->i->subspace;
dstlst = &dsthead;
/* Find tail. Doesn't happen often */
while(*dstlst) dstlst=&(*dstlst)->next;
continue;
}
}
/* Otherwise, allocate a new encapsulating AV pair */
newav = (META_AV *)malloc(sizeof(META_AV));
if (!newav) { msg(F_MISC, L_ERR, "meta_buildtree: ERROR: No memory!\n"); return; }
memset(newav, 0, sizeof(META_AV));
newav->i = encitem;
/* Add the new item to the current destination list */
if (!dsthead) dsthead = newav;
*dstlst = newav;
dstlst = &(newav->next);
/* Save the destination list's head and append point
(new AV item's next field) and current dest space
on the destination stack */
dstheadstk[dstsp] = dsthead;
dststk[dstsp] = dstlst;
dstspcstk[dstsp++] = dstspc;
/* Set the destination list to this item's subitem
list and the current space to this item's subspace */
dsthead = 0;
dstlst = &(newav->sub);
dstspc = newav->i->subspace;
}
}
}
/* Here's the deal. The *msgtoav() functions get a buffer, not a ring,
so that the radius client module can read() into an aligned buffer
instead of into a ring which is then emptied to the aligned buffer.
And, we have to copy before doing getitembyspec() etc. anyway.
The ASCII and binary functions stay separate. There's no need to
virtualise them here as there would still be a difference in
semantics (call per message part or per full message) of which the
upper layer must be aware.
I'm not touching avtomsg* right now. Agreed, the binary version could be
faster, especially considering the fact that we WILL NOT make these
functions output straight to a ring, also because both printav() AND the
radius client only require a buffer, not a ring.
Instead, a case to split avtomsg into avtobinmsg and avtoascmsg could
be made: the only place where the choice must then be made dependent on
the interface's flags is in job_tochan(). But the case for separation
is not as strong as for *msgtoav(), because this always converts a full
list to a full message, regardless of the interface type. */
/* Add one AV pair from an ASCII message to an AV list. Returns -1 for
all errors. WARNING: the parameter 'buf' is modified. */
int meta_ascmsgtoav(META *m, char *buf, ssize_t len,
META_AV **head, META_AV **tail,
int flags, META_AV *acl)
{
char *end, *atr, *c, *o;
META_AV *av, *aclav;
META_VAL *v;
ssize_t rl;
/* Init av for easy cleanup and set end past end of partial message */
av = 0;
end = buf + len;
/* Skip initial spaces and tabs */
for(atr = buf; atr < end && (*atr == ' ' || *atr == '\t'); atr++);
/* End the attr at the first space, tab or equals sign */
for(c = atr; c < end && *c != ' ' && *c != '\t' && *c != '='; c++);
if (c >= end) goto meta_ascmsgtoav_invpair;
*c = 0;
/* Skip all following spaces, tabs and equals signs (Jon P. says:
be liberal in what you accept). After this, atr is the zero
terminated attribute spec and c..end is the raw value. */
for(c++; c < end && (*c == ' ' || *c == '\t' || *c == '='); c++);
/* Allocate new A/V item at this point */
av = (META_AV *)malloc(sizeof(META_AV));
if (!av) { msg(F_MISC, L_ERR, "meta_ascmsgtoav: ERROR: No memory!\n");
return -1; }
memset(av, 0, sizeof(META_AV));
/* Find the specified item in the dictionary */
msg(F_PROC, L_DEBUG, "- got attribute '%s'\n", atr);
av->i = meta_getitembyspec(m, atr);
if (!av->i) { msg(F_PROC, L_ERR, "meta_ascmsgtoav: ERROR: Unknown "
"attribute '%s'!\n", atr);
free(av); return -1; }
/* Check if the item is allowed if we have an ACL */
if (acl) {
for(aclav = acl;
aclav && aclav->i != av->i;
aclav = aclav->next);
/* Return with no error if not found in ACL */
if (!aclav) {
msg(F_PROC, L_DEBUG, " not in ACL; ignored\n");
free(av);
return 0;
}
}
/* Now parse the value; first check its style */
if (flags & AVMSG_HEXVALUE) {
/* Hex values for all data types */
if (MT_ISORD(av->i->val_type)) {
/* Ordinal type; do not allow empty values */
if (c >= end) goto meta_ascmsgtoav_invpair;
av->l = meta_atoord(c, end - c, 0, 0, &rl, 16);
if (!rl) goto meta_ascmsgtoav_invpair;
}
else if (c < end) {
/* Non-empty string type. Allocate half the number of
input bytes, rounding up. Is always enough. */
av->p = (char *)malloc((end - c + 1) >> 1);
if (!av->p) { msg(F_MISC, L_ERR, "meta_ascmsgtoav: "
"ERROR: No memory!\n");
free(av); return -1; }
/* Fill the string, one byte out per two bytes in */
for(o = av->p; end - c >= 2; c++, c++, o++) {
*o = meta_atoord(c, 2, 0, 0, &rl, 16);
if (rl != 2) break;
}
/* Set the length and flags */
av->l = o - av->p;
av->flags |= AV_FREE_P;
}
/* Add pair to list and return. */
meta_addav(head, tail, 0, 0, av);
return 0;
}
/* No hex values; do not allow empty values */
if (c >= end) goto meta_ascmsgtoav_invpair;
/* Parse value according to item's data type */
switch(av->i->val_type) {
case MT_INTEGER:
case MT_DATE:
av->l = meta_atoord(c, end - c, 0, 0, &rl, 10);
if (rl) break;
/* Try to find named constant */
*end = 0; /* allowed, see top */
v = meta_getvalbyname(m, av->i, c);
if (!v) goto meta_ascmsgtoav_invpair;
av->l = v->nr;
break;
case MT_IPADDR:
av->l = meta_atoip(c, end - c, 0, 0, &rl);
if (!rl) goto meta_ascmsgtoav_invpair;
break;
default:
/* Note: we allocate as many bytes as there are left in the
input message. This is safe, because prttoa can never
output more data than we feed it, only less. Guaranteed. */
av->p = (char *)malloc(end - c);
if (!av->p) { msg(F_MISC, L_ERR, "neta_ascmsgtoav: ERROR: No "
"memory!\n");
free(av); return -1; }
av->l = meta_prttoa(c, end - c, 0, 0, &rl, av->p, end - c);
av->flags |= AV_FREE_P;
}
/* Add pair to list and return. */
meta_addav(head, tail, 0, 0, av);
return 0;
meta_ascmsgtoav_invpair:
msg(F_PROC, L_NOTICE, "meta_ascmsgtoav: ERROR: Invalid AV pair '%s'!\n",
dbg_cvtstr(buf, len));
if (av) free(av);
return -1;
}
/* Add one or more AV pairs from a binary message to an AV list */
int meta_binmsgtoav(META *m, U_INT32_T *buf, ssize_t len,
META_AV **head, META_AV **tail,
int nocopy, META_AV *acl)
{
U_INT32_T *i, *e;
META_ORD spcnr, vndnr, atrnr;
ssize_t atrlen;
META_SPC *spc;
META_AV *av, *aclav;
/* Loop through attributes, starting at (char *)msg + 8 */
e = buf + ((len + 3) >> 2);
for(i = buf + 2; i < e; i += (atrlen + 3) >> 2) {
/* Get values from attribute header. Warning: netint32 will
evaluate its argument 4 times if little-endian ;o) */
spcnr = netint32(*i); i++;
vndnr = netint32(*i); i++;
atrnr = netint32(*i); i++;
atrlen = netint32(*i); i++;
msg(F_PROC, L_DEBUG, "- got spcnr %ld, vndnr %ld, atrnr %ld, "
"atrlen %ld\n",
(long)spcnr, (long)vndnr, (long)atrnr, (long)atrlen);
spc = meta_getspcbynr(m, spcnr);
if (!spc) { msg(F_PROC, L_NOTICE, "meta_binmsgtoav: Ignoring "
"attribute in "
"unknown space %d\n", spcnr);
continue; }
/* Allocate new A/V item at this point */
av = (META_AV *)malloc(sizeof(META_AV));
if (!av) { msg(F_MISC, L_ERR, "meta_binmsgtoav: ERROR: No "
"memory!\n"); return -1; }
memset(av, 0, sizeof(META_AV));
/* Find the item in the dictionary */
av->i = meta_getitembynr(m, spc, atrnr, vndnr);
if (!av->i) { msg(F_PROC, L_NOTICE, "meta_binmsgtoav: Ignoring "
"unknown attribute %d, "
"vendor %d, space %d\n",
atrnr, vndnr, spcnr);
free(av); continue; }
/* See if we have a non-empty receive ACL */
if (acl) {
/* We do - find attribute in ACL */
for(aclav = acl;
aclav && aclav->i != av->i;
aclav = aclav->next);
/* Skip attribute if not found */
if (!aclav) {
msg(F_PROC, L_DEBUG, " not in ACL; ignored\n");
free(av);
continue;
}
}
/* Get data according to type */
if (MT_ISORD(av->i->val_type)) {
/* Ordinal - get value from even-multiple-of-4 sized
field and store in av->l */
av->p = 0;
av->l = getord((char *)i, (atrlen + 3) & ~3);
}
else if (nocopy) {
/* String - use pointer into buffer */
av->p = (char *)i;
av->l = atrlen;
}
else {
/* String - copy to new buf at av->p, length in av->l */
av->p = (char *)malloc(atrlen);
if (!av->p) { msg(F_MISC, L_ERR, "meta_binmsgtoav: "
"ERROR: No memory!\n");
free(av); return -1; }
memcpy(av->p, (char *)i, atrlen);
av->l = atrlen;
av->flags |= AV_FREE_P;
}
/* Add A/V item to list */
if (msg_thresh[F_PROC] >= L_DEBUG) meta_printav(m, av, 0);
meta_addav(head, tail, 0, 0, av);
}
return 0;
}
/* Build a message holding an AV list in binary or ASCII form */
ssize_t meta_avtomsg(META *m, META_AV *avlst,
char *buf, ssize_t buflen,
int flags, META_AV *acl, U_INT32_T magic)
{
META_AV *av, *aclav;
META_ORD l, tmpord;
META_VND *vnd;
META_VAL *v;
char *o, *s;
int t;
/* Test ascii or binary outside of loop */
if ((flags & AVMSG_ASCII) == 0) {
/* Binary interface. Skip first 8 bytes and insert hdr later */
for(av = avlst, o = buf + 8; av; av = av->next) {
if ((av->flags & AV_DEL) &&
(flags & AVMSG_ONESHOT) == 0) continue;
/* Check if av occurs in ACL, if any */
if (acl) {
for(aclav = acl;
aclav && aclav->i != av->i;
aclav = aclav->next);
if (!aclav) continue;
}
/* Determine output length first (8 if anonymous ord) */
if (av->p) l = av->l;
else l = av->i ? (av->i->val_size > 0 ? av->i->val_size
: 0)
: 8;
/* Substract rounded up length + header size from
buffer size and check if OK */
buflen -= ((l + 3) & ~3) + 16; if (buflen < 0) break;
/* Put space number in network order in first 4
octets. This could be optimized perhaps by
introducing a putint32_aligned or simply using
htonl (which I have avoided so far). */
putord(o, 4, av->i && av->i->spc ? av->i->spc->nr : -1);
o += 4;
/* Put vendor number in next 4 octets */
putord(o, 4, av->i ? av->i->vnd : -1); o += 4;
/* Put attribute number in next 4 octets */
putord(o, 4, av->i ? av->i->nr : -1); o += 4;
/* Put length in next 4 octets */
putord(o, 4, l); o += 4;
/* Put string (av->p) or ordinal data (av->l) in */
if (av->p) {
/* Pad on right side with zeros */
memcpy(o, av->p, l);
if (l & 3) memset(o + l, 0, 4 - (l & 3));
}
else {
/* Round size of field up get zeros on left */
putord(o, (l + 3) & ~3, av->l);
}
o += (l + 3) & ~3;
if ((flags & AVMSG_ONESHOT) == 0) continue;
break;
}
/* Get the total length */
l = o - buf;
/* Put in request/reply header magic as given by caller */
putord(buf, 4, magic);
putord(buf + 4, 4, l);
return l;
}
/* Ascii interface */
for(av = avlst, o = buf; av; av = av->next) {
if ((av->flags & AV_DEL) &&
(flags & AVMSG_ONESHOT) == 0) continue;
/* Check if av occurs in ACL, if any */
if (acl) {
for(aclav = acl;
aclav && aclav->i != av->i;
aclav = aclav->next);
if (!aclav) continue;
}
/* Test if we're adding tabs in front */
if (flags & AVMSG_ADDTAB) {
buflen--; if (buflen < 0) break;
*o++ = '\t';
}
/* Test if we're writing full attribute names */
if ((flags & AVMSG_SHORTATTR) == 0) {
/* Put space name in */
s = av->i && av->i->spc ? av->i->spc->name : "UNKNOWN";
l = strlen(s);
buflen -= l + 1; if (buflen < 0) break;
memcpy(o, s, l); o += l; *o++ = ':';
/* Put vendor name in. I now regret a bit I was so
strict in that vendors may have nothing but a PEC,
as getvndbynr() here is the sole reason we need
a pointer to the META object in this function. */
s = av->i && (vnd = meta_getvndbynr(m, av->i->vnd)) ?
vnd->name : "UNKNOWN";
l = strlen(s);
buflen -= l + 1; if (buflen < 0) break;
memcpy(o, s, l); o += l; *o++ = ':';
}
/* Put attribute name and an equal sign in */
s = av->i ? av->i->name : "UNKNOWN";
l = strlen(s);
buflen -= l; if (buflen < 0) break;
memcpy(o, s, l); o += l;
/* Put equal sign in, possibly surrounded by spaces */
if (flags & AVMSG_ADDSPACES) {
buflen -= 3; if (buflen < 0) break;
memcpy(o, " = ", 3); o += 3;
}
else {
buflen--; if (buflen < 0) break;
*o++ = '=';
}
/* Test if we're showing the value's type as well */
if (flags & AVMSG_ADDTYPE) {
s = av->i ? meta_gettypebynr(av->i->val_type):"UNKNOWN";
l = strlen(s);
buflen -= l + 1; if (buflen < 0) break;
memcpy(o, s, l); o += l; *o++ = ':';
}
/* Determine length first */
if (av->p) l = av->l;
else l = av->i ? (av->i->val_size > 0 ? av->i->val_size
: 0)
: sizeof(META_ORD);
/* Test if we're writing hex encoded values (or a temp) */
if (flags & AVMSG_HEXVALUE || (!av->i && !av->p)) {
/* Yes. Check if twice the length plus a LF will fit */
buflen -= (l << 1) + 1; if (buflen < 0) break;
/* If ordinal value, convert to raw first */
s = av->p;
if (!s) {
s = (char *)&tmpord;
putord(s, l, av->l);
}
/* Write out the raw data in hex and add the LF */
hex(o, s, l); o += (l << 1); *o++ = '\n';
/* We're done, so continue or break loop here */
if ((flags & AVMSG_ONESHOT) == 0) continue;
break;
}
/* Not hex encoded but as humanly readable as possible */
t = av->i ? av->i->val_type : (av->p ? MT_STRING : MT_INTEGER);
switch(t) {
case MT_INTEGER:
if (flags & AVMSG_NAMEDCONST &&
(v = meta_getvalbynr(m, av->i, av->l))) {
l = strlen(v->name);
buflen -= l; if (buflen < 0) break;
memcpy(o, v->name, l); o += l;
break;
}
/* Note: fallthrough to decimal if not named */
case MT_DATE:
tmpord = meta_ordtoa(o, buflen, 0, 10, av->l);
o += tmpord; buflen -= tmpord;
break;
case MT_IPADDR:
tmpord = meta_iptoa(o, buflen, av->l);
o += tmpord; buflen -= tmpord;
break;
case MT_STRING:
default:
/* If actually ordinal, convert to raw first */
/* FIXME -- do we actually allow that case anywhere? */
s = av->p;
if (!s) {
s = (char *)&tmpord;
putord(s, l, av->l);
}
/* Write quote */
buflen--; if (buflen < 0) break;
*o++ = '"';
/* Write characters, encoding non-prt. chars as hex */
tmpord = meta_atoprt(s, l, 0, 0, 0,
flags & AVMSG_DBLBACKSLASH,
o, buflen);
o += tmpord; buflen -= tmpord;
/* Write quote */
buflen--; if (buflen < 0) break;
*o++ = '"';
}
/* Write LF */
buflen--; if (buflen < 0) break;
*o++ = '\n';
/* Exit here and now if this was a one-attribute operation */
if ((flags & AVMSG_ONESHOT) == 0) continue;
return o - buf;
}
/* Write extra LF if not one-shot */
if (buflen > 0) *o++ = '\n';
return o - buf;
}
/* Add an A/V item to a list before or after the item indicated by rel, or
at the beginning or the end of the list if no rel item was specified. */
void meta_addav(META_AV **head, META_AV **tail, META_AV *rel, int before,
META_AV *av)
{
if (before) {
/* Add before */
if (rel) {
/* Before existing item */
av->prev = rel->prev;
av->next = rel;
/* update previous item's next field, or the list head
if there is none */
if (rel->prev) rel->prev->next = av;
else *head = av;
/* update next (existing) item's previous field */
rel->prev = av;
}
else {
/* At the start of the list */
av->prev = 0;
av->next = *head;
*head = av;
/* update next item's previous field */
if (av->next) av->next->prev = av;
}
/* update tail if list was empty */
if (!*tail) *tail = av;
return;
}
/* Add after */
if (rel) {
/* After existing item */
av->prev = rel;
av->next = rel->next;
/* update next item's previous field, or list's tail if none */
if (rel->next) rel->next->prev = av;
else *tail = av;
/* update previous item's next field */
rel->next = av;
}
else {
/* At the end of the list */
av->prev = *tail;
av->next = 0;
*tail = av;
/* update previous item's next field */
if (av->prev) av->prev->next = av;
}
/* update head if list was empty */
if (!*head) *head = av;
}
/* Remove an AV item from a list, without freeing it */
void meta_remav(META_AV **head, META_AV **tail, META_AV *av)
{
/* Change next and previous item's linkage */
if (av->next) av->next->prev = av->prev;
if (av->prev) av->prev->next = av->next;
/* Adjust list's head or tail (optional) if necessary */
if (av == *head) *head = av->next;
if (tail && av == *tail) *tail = av->prev;
}
/* Free temporary data that may be held by an AV item */
void meta_freeavdata(META_AV *av)
{
if ((av->flags & AV_FREE_P) && av->p) {
free(av->p); av->p = 0; av->flags &= ~AV_FREE_P;
}
}
/* Free a whole list of AV items and possibly their data */
void meta_freeavlist(META_AV *avlist)
{
META_AV *av;
while(avlist) {
/* Save next, free, and continue with saved list item */
av = avlist->next;
meta_freeavdata(avlist);
free(avlist);
avlist = av;
}
}
/* Free a whole tree of AV items and possibly their data */
void meta_freeavtree(META_AV *avtree)
{
META_AV *av;
while(avtree) {
/* Recurse, freeing subtree if any */
if (avtree->sub) meta_freeavtree(avtree->sub);
/* Save next, free, and continue with saved tree item */
av = avtree->next;
meta_freeavdata(avtree);
free(avtree);
avtree = av;
}
}
void meta_printav(META *m, META_AV *av, int depth)
{
static char buf[32768];
ssize_t buflen, ret;
char *o;
int n;
buflen = sizeof(buf);
ret = 0;
o = buf;
for(n = 0; n < depth && buflen > 1; n++) {
*o++ = '\t'; *o++ ='|';
buflen -= 2; ret += 2;
}
n = meta_avtomsg(m, av, o, buflen - 1, AVMSG_ONESHOT + AVMSG_ASCII +
AVMSG_ADDTAB + AVMSG_ADDSPACES + AVMSG_NAMEDCONST,
0, 0);
buflen -= n; ret += n;
buf[ret] = 0;
msg_line(L_DEBUG, buf);
}
void meta_printavlist(META *m, META_AV *avlst, int depth)
{
META_AV *av;
for(av = avlst; av; av = av->next) {
if (av->flags & AV_DEL) continue;
meta_printav(m, av, depth);
if (av->sub) meta_printavlist(m, av->sub, depth + 1);
}
}
syntax highlighted by Code2HTML, v. 0.9.1