From fccc1592a57588de6126e7074ea225b34d5a3791 Mon Sep 17 00:00:00 2001 From: Fabian Mauchle Date: Sat, 10 Nov 2018 14:08:46 +0100 Subject: [PATCH 01/22] add SupplementAttribute config option --- radsecproxy.c | 42 ++++++++++++++++++++++++++++++++++++++---- radsecproxy.h | 1 + 2 files changed, 39 insertions(+), 4 deletions(-) diff --git a/radsecproxy.c b/radsecproxy.c index e8d7527..1c00a9e 100644 --- a/radsecproxy.c +++ b/radsecproxy.c @@ -2512,13 +2512,14 @@ struct rewrite *getrewrite(char *alt1, char *alt2) { return NULL; } -void addrewrite(char *value, char **rmattrs, char **rmvattrs, char **addattrs, char **addvattrs, char **modattrs) +void addrewrite(char *value, char **rmattrs, char **rmvattrs, char **addattrs, + char **addvattrs, char **modattrs, char **supattrs, char** supvattrs) { struct rewrite *rewrite = NULL; int i, n; uint8_t *rma = NULL; uint32_t *p, *rmva = NULL; - struct list *adda = NULL, *moda = NULL; + struct list *adda = NULL, *moda = NULL, *supa = NULL; struct tlv *a; struct modattr *m; @@ -2591,7 +2592,36 @@ void addrewrite(char *value, char **rmattrs, char **rmvattrs, char **addattrs, c freegconfmstr(modattrs); } - if (rma || rmva || adda || moda) { + if (supattrs) { + supa = list_create(); + if (!supa) + debugx(1, DBG_ERR, "malloc failed"); + for (i = 0; supattrs[i]; i++) { + a = extractattr(supattrs[i], 0); + if (!a) + debugx(1, DBG_ERR, "addrewrite: adding invalid attribute %s", supattrs[i]); + if (!list_push(supa, a)) + debugx(1, DBG_ERR, "malloc failed"); + } + freegconfmstr(supattrs); + } + + if (supvattrs) { + if (!supa) + supa = list_create(); + if (!supa) + debugx(1, DBG_ERR, "malloc failed"); + for (i = 0; supvattrs[i]; i++) { + a = extractattr(supvattrs[i], 1); + if (!a) + debugx(1, DBG_ERR, "addrewrite: adding invalid vendor attribute %s", supvattrs[i]); + if (!list_push(supa, a)) + debugx(1, DBG_ERR, "malloc failed"); + } + freegconfmstr(supvattrs); + } + + if (rma || rmva || adda || moda || supa) { rewrite = malloc(sizeof(struct rewrite)); if (!rewrite) debugx(1, DBG_ERR, "malloc failed"); @@ -2599,6 +2629,7 @@ void addrewrite(char *value, char **rmattrs, char **rmvattrs, char **addattrs, c rewrite->removevendorattrs = rmva; rewrite->addattrs = adda; rewrite->modattrs = moda; + rewrite->supattrs = supa; } if (!hash_insert(rewriteconfs, value, strlen(value), rewrite)) @@ -3133,6 +3164,7 @@ int confrewrite_cb(struct gconffile **cf, void *arg, char *block, char *opt, cha char **rmattrs = NULL, **rmvattrs = NULL; char **addattrs = NULL, **addvattrs = NULL; char **modattrs = NULL; + char **supattrs = NULL, **supvattrs = NULL; debug(DBG_DBG, "confrewrite_cb called for %s", block); @@ -3142,10 +3174,12 @@ int confrewrite_cb(struct gconffile **cf, void *arg, char *block, char *opt, cha "addAttribute", CONF_MSTR, &addattrs, "addVendorAttribute", CONF_MSTR, &addvattrs, "modifyAttribute", CONF_MSTR, &modattrs, + "supplementAttribute", CONF_MSTR, &supattrs, + "supplementVendorAttriute", CONF_MSTR, &supvattrs, NULL )) debugx(1, DBG_ERR, "configuration error"); - addrewrite(val, rmattrs, rmvattrs, addattrs, addvattrs, modattrs); + addrewrite(val, rmattrs, rmvattrs, addattrs, addvattrs, modattrs, supattrs, supvattrs); return 1; } diff --git a/radsecproxy.h b/radsecproxy.h index 681ba60..b10f657 100644 --- a/radsecproxy.h +++ b/radsecproxy.h @@ -222,6 +222,7 @@ struct rewrite { uint32_t *removevendorattrs; struct list *addattrs; struct list *modattrs; + struct list *supattrs; }; struct protodefs { From 867c56157dfa0df828d1af3d7b957b34a6d66edc Mon Sep 17 00:00:00 2001 From: Fabian Mauchle Date: Mon, 10 Dec 2018 17:58:39 +0100 Subject: [PATCH 02/22] Implement SupplementAttribute --- radsecproxy.c | 61 ++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 51 insertions(+), 10 deletions(-) diff --git a/radsecproxy.c b/radsecproxy.c index 1c00a9e..e97f388 100644 --- a/radsecproxy.c +++ b/radsecproxy.c @@ -937,6 +937,48 @@ int dorewriteadd(struct radmsg *msg, struct list *addattrs) { return 1; } +int dorewritesup(struct radmsg *msg, struct list *supattrs) { + struct list_node *n, *p; + struct tlv *attr, *supattr; + uint8_t exist, *vendortype, *v;; + + for (n = list_first(supattrs); n; n = list_next(n)) { + supattr = (struct tlv *)n->data; + exist = 0; + for(p = list_first(msg->attrs); p; p = list_next(p)) { + attr = (struct tlv *)p->data; + if (attr->t == supattr->t && attr->t != RAD_Attr_Vendor_Specific) { + exist = 1; + break; + } else if (supattr->t == RAD_Attr_Vendor_Specific && attr->t == RAD_Attr_Vendor_Specific && + memcmp (supattr->v, attr->v, 4)) { + if (!attrvalidate(attr->v+4, attr->l-4)) { + debug(DBG_INFO, "dorewritesup: vendor attribute validation failed, no rewrite"); + return 0; + } + vendortype = (uint8_t *)supattr->v+4; + for (v=attr->v+4; v < attr->v + attr->l; v += *(v+1) + 2){ + if (*v == *vendortype) { + exist = 1; + break; + } + } + if (exist) break; + } + } + if (!exist) { + supattr = copytlv(supattr); + if (!supattr) + return 0; + if (!radmsg_add(msg, supattr)) { + freetlv(supattr); + return 0; + } + } + } + return 1; +} + int resizeattr(struct tlv *attr, uint8_t newlen) { uint8_t *newv; @@ -3169,16 +3211,15 @@ int confrewrite_cb(struct gconffile **cf, void *arg, char *block, char *opt, cha debug(DBG_DBG, "confrewrite_cb called for %s", block); if (!getgenericconfig(cf, block, - "removeAttribute", CONF_MSTR, &rmattrs, - "removeVendorAttribute", CONF_MSTR, &rmvattrs, - "addAttribute", CONF_MSTR, &addattrs, - "addVendorAttribute", CONF_MSTR, &addvattrs, - "modifyAttribute", CONF_MSTR, &modattrs, - "supplementAttribute", CONF_MSTR, &supattrs, - "supplementVendorAttriute", CONF_MSTR, &supvattrs, - NULL - )) - debugx(1, DBG_ERR, "configuration error"); + "removeAttribute", CONF_MSTR, &rmattrs, + "removeVendorAttribute", CONF_MSTR, &rmvattrs, + "addAttribute", CONF_MSTR, &addattrs, + "addVendorAttribute", CONF_MSTR, &addvattrs, + "modifyAttribute", CONF_MSTR, &modattrs, + "supplementAttribute", CONF_MSTR, &supattrs, + "supplementVendorAttriute", CONF_MSTR, &supvattrs, + NULL)) + debugx(1, DBG_ERR, "configuration error"); addrewrite(val, rmattrs, rmvattrs, addattrs, addvattrs, modattrs, supattrs, supvattrs); return 1; } From 2d02f4434ba9404838b667dc73cecdff3dab6be5 Mon Sep 17 00:00:00 2001 From: Fabian Mauchle Date: Sun, 30 Dec 2018 17:37:09 +0100 Subject: [PATCH 03/22] refactor: extract rewrite functions --- Makefile.am | 1 + radmsg.c | 46 +++- radmsg.h | 10 + radsecproxy.c | 590 -------------------------------------------------- radsecproxy.h | 23 +- rewrite.c | 563 +++++++++++++++++++++++++++++++++++++++++++++++ rewrite.h | 30 +++ 7 files changed, 651 insertions(+), 612 deletions(-) create mode 100644 rewrite.c create mode 100644 rewrite.h diff --git a/Makefile.am b/Makefile.am index 6b802cb..7c2f5fc 100644 --- a/Makefile.am +++ b/Makefile.am @@ -22,6 +22,7 @@ librsp_a_SOURCES = \ list.c list.h \ radmsg.c radmsg.h \ radsecproxy.c radsecproxy.h \ + rewrite.c rewrite.h \ tcp.c tcp.h \ tls.c tls.h \ tlscommon.c tlscommon.h \ diff --git a/radmsg.c b/radmsg.c index 00c13c9..ff4ec9f 100644 --- a/radmsg.c +++ b/radmsg.c @@ -10,7 +10,6 @@ #include #include #include "list.h" -#include "tlv11.h" #include "radmsg.h" #include "debug.h" #include @@ -327,6 +326,51 @@ struct radmsg *buf2radmsg(uint8_t *buf, uint8_t *secret, uint8_t *rqauth) { return msg; } +/* should accept both names and numeric values, only numeric right now */ +uint8_t attrname2val(char *attrname) { + int val = 0; + + val = atoi(attrname); + return val > 0 && val < 256 ? val : 0; +} + +/* ATTRNAME is on the form vendor[:type]. + If only vendor is found, TYPE is set to 256 and 1 is returned. + If type is >= 256, 1 is returned. + Otherwise, 0 is returned. +*/ +/* should accept both names and numeric values, only numeric right now */ +int vattrname2val(char *attrname, uint32_t *vendor, uint32_t *type) { + char *s; + + *vendor = atoi(attrname); + s = strchr(attrname, ':'); + if (!s) { /* Only vendor was found. */ + *type = 256; + return 1; + } + *type = atoi(s + 1); + return *type < 256; +} + +int attrvalidate(unsigned char *attrs, int length) { + while (length > 1) { + if (ATTRLEN(attrs) < 2) { + debug(DBG_INFO, "attrvalidate: invalid attribute length %d", ATTRLEN(attrs)); + return 0; + } + length -= ATTRLEN(attrs); + if (length < 0) { + debug(DBG_INFO, "attrvalidate: attribute length %d exceeds packet length", ATTRLEN(attrs)); + return 0; + } + attrs += ATTRLEN(attrs); + } + if (length) + debug(DBG_INFO, "attrvalidate: malformed packet? remaining byte after last attribute"); + return 1; +} + /* Local Variables: */ /* c-file-style: "stroustrup" */ /* End: */ diff --git a/radmsg.h b/radmsg.h index 2db35a4..0c6d0d6 100644 --- a/radmsg.h +++ b/radmsg.h @@ -2,6 +2,8 @@ /* Copyright (c) 2015, NORDUnet A/S */ /* See LICENSE for licensing information. */ +#include "tlv11.h" + #define RAD_Access_Request 1 #define RAD_Access_Accept 2 #define RAD_Access_Reject 3 @@ -32,6 +34,11 @@ struct radmsg { struct list *attrs; }; +#define ATTRTYPE(x) ((x)[0]) +#define ATTRLEN(x) ((x)[1]) +#define ATTRVAL(x) ((x) + 2) +#define ATTRVALLEN(x) ((x)[1] - 2) + void radmsg_free(struct radmsg *); struct radmsg *radmsg_init(uint8_t, uint8_t, uint8_t *); int radmsg_add(struct radmsg *, struct tlv *); @@ -42,6 +49,9 @@ int radmsg_copy_attrs(struct radmsg *dst, uint8_t type); uint8_t *radmsg2buf(struct radmsg *msg, uint8_t *); struct radmsg *buf2radmsg(uint8_t *, uint8_t *, uint8_t *); +uint8_t attrname2val(char *attrname); +int vattrname2val(char *attrname, uint32_t *vendor, uint32_t *type); +int attrvalidate(unsigned char *attrs, int length); /* Local Variables: */ /* c-file-style: "stroustrup" */ diff --git a/radsecproxy.c b/radsecproxy.c index e97f388..47120eb 100644 --- a/radsecproxy.c +++ b/radsecproxy.c @@ -75,7 +75,6 @@ static struct options options; static struct list *clconfs, *srvconfs; static struct list *realms; -static struct hash *rewriteconfs; #ifdef __CYGWIN__ extern int __declspec(dllimport) optind; @@ -788,24 +787,6 @@ void removeserversubrealms(struct list *realmlist, struct clsrvconf *srv) { } } -int attrvalidate(unsigned char *attrs, int length) { - while (length > 1) { - if (ATTRLEN(attrs) < 2) { - debug(DBG_INFO, "attrvalidate: invalid attribute length %d", ATTRLEN(attrs)); - return 0; - } - length -= ATTRLEN(attrs); - if (length < 0) { - debug(DBG_INFO, "attrvalidate: attribute length %d exceeds packet length", ATTRLEN(attrs)); - return 0; - } - attrs += ATTRLEN(attrs); - } - if (length) - debug(DBG_INFO, "attrvalidate: malformed packet? remaining byte after last attribute"); - return 1; -} - int pwdrecrypt(uint8_t *pwd, uint8_t len, char *oldsecret, char *newsecret, uint8_t *oldauth, uint8_t *newauth) { if (len < 16 || len > 128 || len % 16) { debug(DBG_WARN, "pwdrecrypt: invalid password length"); @@ -852,233 +833,6 @@ int msmppe(unsigned char *attrs, int length, uint8_t type, char *attrtxt, struct return 1; } -int findvendorsubattr(uint32_t *attrs, uint32_t vendor, uint32_t subattr) { - if (!attrs) - return 0; - - for (; attrs[0]; attrs += 2) - if (attrs[0] == vendor && attrs[1] == subattr) - return 1; - return 0; -} - -/* returns 1 if entire element is to be removed, else 0 */ -int dovendorrewriterm(struct tlv *attr, uint32_t *removevendorattrs) { - uint8_t alen, sublen; - uint32_t vendor; - uint8_t *subattrs; - - if (!removevendorattrs || attr->l <= 4) - return 0; - - memcpy(&vendor, attr->v, 4); - vendor = ntohl(vendor); - while (*removevendorattrs && *removevendorattrs != vendor) - removevendorattrs += 2; - if (!*removevendorattrs) - return 0; - - if (findvendorsubattr(removevendorattrs, vendor, 256)) - return 1; /* remove entire vendor attribute */ - - sublen = attr->l - 4; - subattrs = attr->v + 4; - - if (!attrvalidate(subattrs, sublen)) { - debug(DBG_INFO, "dovendorrewrite: vendor attribute validation failed, no rewrite"); - return 0; - } - - while (sublen > 1) { - alen = ATTRLEN(subattrs); - sublen -= alen; - if (findvendorsubattr(removevendorattrs, vendor, ATTRTYPE(subattrs))) { - memmove(subattrs, subattrs + alen, sublen); - attr->l -= alen; - } else - subattrs += alen; - } - return 0; -} - -void dorewriterm(struct radmsg *msg, uint8_t *rmattrs, uint32_t *rmvattrs) { - struct list_node *n, *p; - struct tlv *attr; - - p = NULL; - n = list_first(msg->attrs); - while (n) { - attr = (struct tlv *)n->data; - if ((rmattrs && strchr((char *)rmattrs, attr->t)) || - (rmvattrs && attr->t == RAD_Attr_Vendor_Specific && dovendorrewriterm(attr, rmvattrs))) { - list_removedata(msg->attrs, attr); - freetlv(attr); - n = p ? list_next(p) : list_first(msg->attrs); - } else { - p = n; - n = list_next(n); - } - } -} - -int dorewriteadd(struct radmsg *msg, struct list *addattrs) { - struct list_node *n; - struct tlv *a; - - for (n = list_first(addattrs); n; n = list_next(n)) { - a = copytlv((struct tlv *)n->data); - if (!a) - return 0; - if (!radmsg_add(msg, a)) { - freetlv(a); - return 0; - } - } - return 1; -} - -int dorewritesup(struct radmsg *msg, struct list *supattrs) { - struct list_node *n, *p; - struct tlv *attr, *supattr; - uint8_t exist, *vendortype, *v;; - - for (n = list_first(supattrs); n; n = list_next(n)) { - supattr = (struct tlv *)n->data; - exist = 0; - for(p = list_first(msg->attrs); p; p = list_next(p)) { - attr = (struct tlv *)p->data; - if (attr->t == supattr->t && attr->t != RAD_Attr_Vendor_Specific) { - exist = 1; - break; - } else if (supattr->t == RAD_Attr_Vendor_Specific && attr->t == RAD_Attr_Vendor_Specific && - memcmp (supattr->v, attr->v, 4)) { - if (!attrvalidate(attr->v+4, attr->l-4)) { - debug(DBG_INFO, "dorewritesup: vendor attribute validation failed, no rewrite"); - return 0; - } - vendortype = (uint8_t *)supattr->v+4; - for (v=attr->v+4; v < attr->v + attr->l; v += *(v+1) + 2){ - if (*v == *vendortype) { - exist = 1; - break; - } - } - if (exist) break; - } - } - if (!exist) { - supattr = copytlv(supattr); - if (!supattr) - return 0; - if (!radmsg_add(msg, supattr)) { - freetlv(supattr); - return 0; - } - } - } - return 1; -} - -int resizeattr(struct tlv *attr, uint8_t newlen) { - uint8_t *newv; - - if (newlen != attr->l) { - newv = realloc(attr->v, newlen); - if (!newv) - return 0; - attr->v = newv; - attr->l = newlen; - } - return 1; -} - -int dorewritemodattr(struct tlv *attr, struct modattr *modattr) { - size_t nmatch = 10, reslen = 0, start = 0; - regmatch_t pmatch[10], *pfield; - int i; - char *in, *out; - - in = stringcopy((char *)attr->v, attr->l); - if (!in) - return 0; - - if (regexec(modattr->regex, in, nmatch, pmatch, 0)) { - free(in); - return 1; - } - - out = modattr->replacement; - - for (i = start; out[i]; i++) { - if (out[i] == '\\' && out[i + 1] >= '1' && out[i + 1] <= '9') { - pfield = &pmatch[out[i + 1] - '0']; - if (pfield->rm_so >= 0) { - reslen += i - start + pfield->rm_eo - pfield->rm_so; - start = i + 2; - } - i++; - } - } - reslen += i - start; - if (reslen > 253) { - debug(DBG_INFO, "rewritten attribute length would be %d, max possible is 253, discarding message", reslen); - free(in); - return 0; - } - - if (!resizeattr(attr, reslen)) { - free(in); - return 0; - } - - start = 0; - reslen = 0; - for (i = start; out[i]; i++) { - if (out[i] == '\\' && out[i + 1] >= '1' && out[i + 1] <= '9') { - pfield = &pmatch[out[i + 1] - '0']; - if (pfield->rm_so >= 0) { - memcpy(attr->v + reslen, out + start, i - start); - reslen += i - start; - memcpy(attr->v + reslen, in + pfield->rm_so, pfield->rm_eo - pfield->rm_so); - reslen += pfield->rm_eo - pfield->rm_so; - start = i + 2; - } - i++; - } - } - free(in); - - memcpy(attr->v + reslen, out + start, i - start); - return 1; -} - -int dorewritemod(struct radmsg *msg, struct list *modattrs) { - struct list_node *n, *m; - - for (n = list_first(msg->attrs); n; n = list_next(n)) - for (m = list_first(modattrs); m; m = list_next(m)) - if (((struct tlv *)n->data)->t == ((struct modattr *)m->data)->t && - !dorewritemodattr((struct tlv *)n->data, (struct modattr *)m->data)) - return 0; - return 1; -} - -int dorewrite(struct radmsg *msg, struct rewrite *rewrite) { - int rv = 1; /* Success. */ - - if (rewrite) { - if (rewrite->removeattrs || rewrite->removevendorattrs) - dorewriterm(msg, rewrite->removeattrs, rewrite->removevendorattrs); - if (rewrite->modattrs) - if (!dorewritemod(msg, rewrite->modattrs)) - rv = 0; - if (rewrite->addattrs) - if (!dorewriteadd(msg, rewrite->addattrs)) - rv = 0; - } - return rv; -} - int rewriteusername(struct request *rq, struct tlv *attr) { char *orig = (char *)tlv2str(attr); if (!dorewritemodattr(attr, rq->from->conf->rewriteusername)) { @@ -1092,49 +846,6 @@ int rewriteusername(struct request *rq, struct tlv *attr) { return 1; } -/** Create vendor specific tlv with ATTR. ATTR is consumed (freed) if - * all is well with the new tlv, i.e. if the function returns - * !NULL. */ -static struct tlv * -makevendortlv(uint32_t vendor, struct tlv *attr) -{ - struct tlv *newtlv = NULL; - uint8_t l, *v; - - if (!attr) - return NULL; - l = attr->l + 6; - v = malloc(l); - if (v) { - vendor = htonl(vendor & 0x00ffffff); /* MSB=0 according to RFC 2865. */ - memcpy(v, &vendor, 4); - tlv2buf(v + 4, attr); - v[5] += 2; /* Vendor length increased for type and length fields. */ - newtlv = maketlv(RAD_Attr_Vendor_Specific, l, v); - free(v); - if (newtlv) - freetlv(attr); - } - return newtlv; -} - -/** Ad vendor attribute with VENDOR + ATTR and push it on MSG. ATTR - * is consumed. */ -int addvendorattr(struct radmsg *msg, uint32_t vendor, struct tlv *attr) { - struct tlv *vattr; - - vattr = makevendortlv(vendor, attr); - if (!vattr) { - freetlv(attr); - return 0; - } - if (!radmsg_add(msg, vattr)) { - freetlv(vattr); - return 0; - } - return 1; -} - void addttlattr(struct radmsg *msg, uint32_t *attrtype, uint8_t addttl) { uint8_t ttl[4]; struct tlv *attr; @@ -2404,281 +2115,6 @@ int dynamicconfig(struct server *server) { return 0; } -/* should accept both names and numeric values, only numeric right now */ -uint8_t attrname2val(char *attrname) { - int val = 0; - - val = atoi(attrname); - return val > 0 && val < 256 ? val : 0; -} - -/* ATTRNAME is on the form vendor[:type]. - If only vendor is found, TYPE is set to 256 and 1 is returned. - If type is >= 256, 1 is returned. - Otherwise, 0 is returned. -*/ -/* should accept both names and numeric values, only numeric right now */ -int vattrname2val(char *attrname, uint32_t *vendor, uint32_t *type) { - char *s; - - *vendor = atoi(attrname); - s = strchr(attrname, ':'); - if (!s) { /* Only vendor was found. */ - *type = 256; - return 1; - } - *type = atoi(s + 1); - return *type < 256; -} - -/** Extract attributes from string NAMEVAL, create a struct tlv and - * return the tlv. If VENDOR_FLAG, NAMEVAL is on the form - * "::" and otherwise it's ":". Return - * NULL if fields are missing or if conversion fails. - * - * FIXME: Should accept both names and numeric values, only numeric - * right now */ -struct tlv *extractattr(char *nameval, char vendor_flag) { - int len, name = 0; - int vendor = 0; /* Vendor 0 is reserved, see RFC 1700. */ - char *s, *s2; - struct tlv *a; - - s = strchr(nameval, ':'); - if (!s) - return NULL; - name = atoi(nameval); - - if (vendor_flag) { - s2 = strchr(s + 1, ':'); - if (!s2) - return NULL; - vendor = name; - name = atoi(s + 1); - s = s2; - } - len = strlen(s + 1); - if (len > 253) - return NULL; - - if (name < 1 || name > 255) - return NULL; - a = malloc(sizeof(struct tlv)); - if (!a) - return NULL; - - a->v = (uint8_t *)stringcopy(s + 1, 0); - if (!a->v) { - free(a); - return NULL; - } - a->t = name; - a->l = len; - - if (vendor_flag) - a = makevendortlv(vendor, a); - - return a; -} - -/* should accept both names and numeric values, only numeric right now */ -struct modattr *extractmodattr(char *nameval) { - int name = 0; - char *s, *t; - struct modattr *m; - - if (!strncasecmp(nameval, "User-Name:/", 11)) { - s = nameval + 11; - name = 1; - } else { - s = strchr(nameval, ':'); - name = atoi(nameval); - if (!s || name < 1 || name > 255 || s[1] != '/') - return NULL; - s += 2; - } - /* regexp, remove optional trailing / if present */ - if (s[strlen(s) - 1] == '/') - s[strlen(s) - 1] = '\0'; - - for (t = strchr(s, '/'); t; t = strchr(t+1, '/')) - if (t == s || t[-1] != '\\') - break; - if (!t) - return NULL; - *t = '\0'; - t++; - - m = malloc(sizeof(struct modattr)); - if (!m) { - debug(DBG_ERR, "malloc failed"); - return NULL; - } - m->t = name; - - m->replacement = stringcopy(t, 0); - if (!m->replacement) { - free(m); - debug(DBG_ERR, "malloc failed"); - return NULL; - } - - m->regex = malloc(sizeof(regex_t)); - if (!m->regex) { - free(m->replacement); - free(m); - debug(DBG_ERR, "malloc failed"); - return NULL; - } - - if (regcomp(m->regex, s, REG_ICASE | REG_EXTENDED)) { - free(m->regex); - free(m->replacement); - free(m); - debug(DBG_ERR, "failed to compile regular expression %s", s); - return NULL; - } - - return m; -} - -struct rewrite *getrewrite(char *alt1, char *alt2) { - struct rewrite *r; - - if (alt1) - if ((r = hash_read(rewriteconfs, alt1, strlen(alt1)))) - return r; - if (alt2) - if ((r = hash_read(rewriteconfs, alt2, strlen(alt2)))) - return r; - return NULL; -} - -void addrewrite(char *value, char **rmattrs, char **rmvattrs, char **addattrs, - char **addvattrs, char **modattrs, char **supattrs, char** supvattrs) -{ - struct rewrite *rewrite = NULL; - int i, n; - uint8_t *rma = NULL; - uint32_t *p, *rmva = NULL; - struct list *adda = NULL, *moda = NULL, *supa = NULL; - struct tlv *a; - struct modattr *m; - - if (rmattrs) { - for (n = 0; rmattrs[n]; n++); - rma = calloc(n + 1, sizeof(uint8_t)); - if (!rma) - debugx(1, DBG_ERR, "malloc failed"); - - for (i = 0; i < n; i++) - if (!(rma[i] = attrname2val(rmattrs[i]))) - debugx(1, DBG_ERR, "addrewrite: removing invalid attribute %s", rmattrs[i]); - freegconfmstr(rmattrs); - rma[i] = 0; - } - - if (rmvattrs) { - for (n = 0; rmvattrs[n]; n++); - rmva = calloc(2 * n + 1, sizeof(uint32_t)); - if (!rmva) - debugx(1, DBG_ERR, "malloc failed"); - - for (p = rmva, i = 0; i < n; i++, p += 2) - if (!vattrname2val(rmvattrs[i], p, p + 1)) - debugx(1, DBG_ERR, "addrewrite: removing invalid vendor attribute %s", rmvattrs[i]); - freegconfmstr(rmvattrs); - *p = 0; - } - - if (addattrs) { - adda = list_create(); - if (!adda) - debugx(1, DBG_ERR, "malloc failed"); - for (i = 0; addattrs[i]; i++) { - a = extractattr(addattrs[i], 0); - if (!a) - debugx(1, DBG_ERR, "addrewrite: adding invalid attribute %s", addattrs[i]); - if (!list_push(adda, a)) - debugx(1, DBG_ERR, "malloc failed"); - } - freegconfmstr(addattrs); - } - - if (addvattrs) { - if (!adda) - adda = list_create(); - if (!adda) - debugx(1, DBG_ERR, "malloc failed"); - for (i = 0; addvattrs[i]; i++) { - a = extractattr(addvattrs[i], 1); - if (!a) - debugx(1, DBG_ERR, "addrewrite: adding invalid vendor attribute %s", addvattrs[i]); - if (!list_push(adda, a)) - debugx(1, DBG_ERR, "malloc failed"); - } - freegconfmstr(addvattrs); - } - - if (modattrs) { - moda = list_create(); - if (!moda) - debugx(1, DBG_ERR, "malloc failed"); - for (i = 0; modattrs[i]; i++) { - m = extractmodattr(modattrs[i]); - if (!m) - debugx(1, DBG_ERR, "addrewrite: modifying invalid attribute %s", modattrs[i]); - if (!list_push(moda, m)) - debugx(1, DBG_ERR, "malloc failed"); - } - freegconfmstr(modattrs); - } - - if (supattrs) { - supa = list_create(); - if (!supa) - debugx(1, DBG_ERR, "malloc failed"); - for (i = 0; supattrs[i]; i++) { - a = extractattr(supattrs[i], 0); - if (!a) - debugx(1, DBG_ERR, "addrewrite: adding invalid attribute %s", supattrs[i]); - if (!list_push(supa, a)) - debugx(1, DBG_ERR, "malloc failed"); - } - freegconfmstr(supattrs); - } - - if (supvattrs) { - if (!supa) - supa = list_create(); - if (!supa) - debugx(1, DBG_ERR, "malloc failed"); - for (i = 0; supvattrs[i]; i++) { - a = extractattr(supvattrs[i], 1); - if (!a) - debugx(1, DBG_ERR, "addrewrite: adding invalid vendor attribute %s", supvattrs[i]); - if (!list_push(supa, a)) - debugx(1, DBG_ERR, "malloc failed"); - } - freegconfmstr(supvattrs); - } - - if (rma || rmva || adda || moda || supa) { - rewrite = malloc(sizeof(struct rewrite)); - if (!rewrite) - debugx(1, DBG_ERR, "malloc failed"); - rewrite->removeattrs = rma; - rewrite->removevendorattrs = rmva; - rewrite->addattrs = adda; - rewrite->modattrs = moda; - rewrite->supattrs = supa; - } - - if (!hash_insert(rewriteconfs, value, strlen(value), rewrite)) - debugx(1, DBG_ERR, "malloc failed"); - debug(DBG_DBG, "addrewrite: added rewrite block %s", value); -} - int setttlattr(struct options *opts, char *defaultattr) { char *ttlattr = opts->ttlattr ? opts->ttlattr : defaultattr; @@ -3202,28 +2638,6 @@ int confrealm_cb(struct gconffile **cf, void *arg, char *block, char *opt, char return 1; } -int confrewrite_cb(struct gconffile **cf, void *arg, char *block, char *opt, char *val) { - char **rmattrs = NULL, **rmvattrs = NULL; - char **addattrs = NULL, **addvattrs = NULL; - char **modattrs = NULL; - char **supattrs = NULL, **supvattrs = NULL; - - debug(DBG_DBG, "confrewrite_cb called for %s", block); - - if (!getgenericconfig(cf, block, - "removeAttribute", CONF_MSTR, &rmattrs, - "removeVendorAttribute", CONF_MSTR, &rmvattrs, - "addAttribute", CONF_MSTR, &addattrs, - "addVendorAttribute", CONF_MSTR, &addvattrs, - "modifyAttribute", CONF_MSTR, &modattrs, - "supplementAttribute", CONF_MSTR, &supattrs, - "supplementVendorAttriute", CONF_MSTR, &supvattrs, - NULL)) - debugx(1, DBG_ERR, "configuration error"); - addrewrite(val, rmattrs, rmvattrs, addattrs, addvattrs, modattrs, supattrs, supvattrs); - return 1; -} - int setprotoopts(uint8_t type, char **listenargs, char *sourcearg) { struct commonprotoopts *protoopts; @@ -3267,10 +2681,6 @@ void getmainconfig(const char *configfile) { if (!realms) debugx(1, DBG_ERR, "malloc failed"); - rewriteconfs = hash_create(); - if (!rewriteconfs) - debugx(1, DBG_ERR, "malloc failed"); - if (!getgenericconfig( &cfs, NULL, #ifdef RADPROT_UDP diff --git a/radsecproxy.h b/radsecproxy.h index b10f657..0c5dc1c 100644 --- a/radsecproxy.h +++ b/radsecproxy.h @@ -7,9 +7,9 @@ #include #include #include "list.h" -#include "tlv11.h" #include "radmsg.h" #include "gconfig.h" +#include "rewrite.h" #define DEBUG_LEVEL 2 @@ -211,20 +211,6 @@ struct realm { struct list *accsrvconfs; }; -struct modattr { - uint8_t t; - char *replacement; - regex_t *regex; -}; - -struct rewrite { - uint8_t *removeattrs; - uint32_t *removevendorattrs; - struct list *addattrs; - struct list *modattrs; - struct list *supattrs; -}; - struct protodefs { char *name; char *secretdefault; @@ -249,11 +235,6 @@ struct protodefs { #define RADLEN(x) ntohs(((uint16_t *)(x))[1]) -#define ATTRTYPE(x) ((x)[0]) -#define ATTRLEN(x) ((x)[1]) -#define ATTRVAL(x) ((x) + 2) -#define ATTRVALLEN(x) ((x)[1] - 2) - struct clsrvconf *find_clconf(uint8_t type, struct sockaddr *addr, struct list_node **cur); struct clsrvconf *find_srvconf(uint8_t type, struct sockaddr *addr, struct list_node **cur); struct clsrvconf *find_clconf_type(uint8_t type, struct list_node **cur); @@ -266,7 +247,7 @@ void freerq(struct request *rq); int radsrv(struct request *rq); void replyh(struct server *server, unsigned char *buf); struct addrinfo *resolve_hostport_addrinfo(uint8_t type, char *hostport); -uint8_t *radattr2ascii(struct tlv *attr); +uint8_t *radattr2ascii(struct tlv *attr); /* TODO: mv this to radmsg? */ pthread_attr_t pthread_attr; /* Local Variables: */ diff --git a/rewrite.c b/rewrite.c new file mode 100644 index 0000000..381d0f6 --- /dev/null +++ b/rewrite.c @@ -0,0 +1,563 @@ +/* Copyright (c) 2019, SWITCH */ +/* See LICENSE for licensing information. */ + +#include +#include +#include +#include +#include "debug.h" +#include "gconfig.h" +#include "hash.h" +#include "list.h" +#include "radmsg.h" +#include "rewrite.h" +#include "util.h" + +static struct hash *rewriteconfs; + +/** Create vendor specific tlv with ATTR. ATTR is consumed (freed) if + * all is well with the new tlv, i.e. if the function returns + * !NULL. */ +static struct tlv *makevendortlv(uint32_t vendor, struct tlv *attr){ + struct tlv *newtlv = NULL; + uint8_t l, *v; + + if (!attr) + return NULL; + l = attr->l + 6; + v = malloc(l); + if (v) { + vendor = htonl(vendor & 0x00ffffff); /* MSB=0 according to RFC 2865. */ + memcpy(v, &vendor, 4); + tlv2buf(v + 4, attr); + v[5] += 2; /* Vendor length increased for type and length fields. */ + newtlv = maketlv(RAD_Attr_Vendor_Specific, l, v); + free(v); + if (newtlv) + freetlv(attr); + } + return newtlv; +} + +/** Extract attributes from string NAMEVAL, create a struct tlv and + * return the tlv. If VENDOR_FLAG, NAMEVAL is on the form + * "::" and otherwise it's ":". Return + * NULL if fields are missing or if conversion fails. + * + * FIXME: Should accept both names and numeric values, only numeric + * right now */ +struct tlv *extractattr(char *nameval, char vendor_flag) { + int len, name = 0; + int vendor = 0; /* Vendor 0 is reserved, see RFC 1700. */ + char *s, *s2; + struct tlv *a; + + s = strchr(nameval, ':'); + if (!s) + return NULL; + name = atoi(nameval); + + if (vendor_flag) { + s2 = strchr(s + 1, ':'); + if (!s2) + return NULL; + vendor = name; + name = atoi(s + 1); + s = s2; + } + len = strlen(s + 1); + if (len > 253) + return NULL; + + if (name < 1 || name > 255) + return NULL; + a = malloc(sizeof(struct tlv)); + if (!a) + return NULL; + + a->v = (uint8_t *)stringcopy(s + 1, 0); + if (!a->v) { + free(a); + return NULL; + } + a->t = name; + a->l = len; + + if (vendor_flag) + a = makevendortlv(vendor, a); + + return a; +} + +/* should accept both names and numeric values, only numeric right now */ +struct modattr *extractmodattr(char *nameval) { + int name = 0; + char *s, *t; + struct modattr *m; + + if (!strncasecmp(nameval, "User-Name:/", 11)) { + s = nameval + 11; + name = 1; + } else { + s = strchr(nameval, ':'); + name = atoi(nameval); + if (!s || name < 1 || name > 255 || s[1] != '/') + return NULL; + s += 2; + } + /* regexp, remove optional trailing / if present */ + if (s[strlen(s) - 1] == '/') + s[strlen(s) - 1] = '\0'; + + for (t = strchr(s, '/'); t; t = strchr(t+1, '/')) + if (t == s || t[-1] != '\\') + break; + if (!t) + return NULL; + *t = '\0'; + t++; + + m = malloc(sizeof(struct modattr)); + if (!m) { + debug(DBG_ERR, "malloc failed"); + return NULL; + } + m->t = name; + + m->replacement = stringcopy(t, 0); + if (!m->replacement) { + free(m); + debug(DBG_ERR, "malloc failed"); + return NULL; + } + + m->regex = malloc(sizeof(regex_t)); + if (!m->regex) { + free(m->replacement); + free(m); + debug(DBG_ERR, "malloc failed"); + return NULL; + } + + if (regcomp(m->regex, s, REG_ICASE | REG_EXTENDED)) { + free(m->regex); + free(m->replacement); + free(m); + debug(DBG_ERR, "failed to compile regular expression %s", s); + return NULL; + } + + return m; +} + +void addrewrite(char *value, char **rmattrs, char **rmvattrs, char **addattrs, + char **addvattrs, char **modattrs, char **supattrs, char** supvattrs) +{ + struct rewrite *rewrite = NULL; + int i, n; + uint8_t *rma = NULL; + uint32_t *p, *rmva = NULL; + struct list *adda = NULL, *moda = NULL, *supa = NULL; + struct tlv *a; + struct modattr *m; + + if (rmattrs) { + for (n = 0; rmattrs[n]; n++); + rma = calloc(n + 1, sizeof(uint8_t)); + if (!rma) + debugx(1, DBG_ERR, "malloc failed"); + + for (i = 0; i < n; i++) + if (!(rma[i] = attrname2val(rmattrs[i]))) + debugx(1, DBG_ERR, "addrewrite: removing invalid attribute %s", rmattrs[i]); + freegconfmstr(rmattrs); + rma[i] = 0; + } + + if (rmvattrs) { + for (n = 0; rmvattrs[n]; n++); + rmva = calloc(2 * n + 1, sizeof(uint32_t)); + if (!rmva) + debugx(1, DBG_ERR, "malloc failed"); + + for (p = rmva, i = 0; i < n; i++, p += 2) + if (!vattrname2val(rmvattrs[i], p, p + 1)) + debugx(1, DBG_ERR, "addrewrite: removing invalid vendor attribute %s", rmvattrs[i]); + freegconfmstr(rmvattrs); + *p = 0; + } + + if (addattrs) { + adda = list_create(); + if (!adda) + debugx(1, DBG_ERR, "malloc failed"); + for (i = 0; addattrs[i]; i++) { + a = extractattr(addattrs[i], 0); + if (!a) + debugx(1, DBG_ERR, "addrewrite: adding invalid attribute %s", addattrs[i]); + if (!list_push(adda, a)) + debugx(1, DBG_ERR, "malloc failed"); + } + freegconfmstr(addattrs); + } + + if (addvattrs) { + if (!adda) + adda = list_create(); + if (!adda) + debugx(1, DBG_ERR, "malloc failed"); + for (i = 0; addvattrs[i]; i++) { + a = extractattr(addvattrs[i], 1); + if (!a) + debugx(1, DBG_ERR, "addrewrite: adding invalid vendor attribute %s", addvattrs[i]); + if (!list_push(adda, a)) + debugx(1, DBG_ERR, "malloc failed"); + } + freegconfmstr(addvattrs); + } + + if (modattrs) { + moda = list_create(); + if (!moda) + debugx(1, DBG_ERR, "malloc failed"); + for (i = 0; modattrs[i]; i++) { + m = extractmodattr(modattrs[i]); + if (!m) + debugx(1, DBG_ERR, "addrewrite: modifying invalid attribute %s", modattrs[i]); + if (!list_push(moda, m)) + debugx(1, DBG_ERR, "malloc failed"); + } + freegconfmstr(modattrs); + } + + if (supattrs) { + supa = list_create(); + if (!supa) + debugx(1, DBG_ERR, "malloc failed"); + for (i = 0; supattrs[i]; i++) { + a = extractattr(supattrs[i], 0); + if (!a) + debugx(1, DBG_ERR, "addrewrite: adding invalid attribute %s", supattrs[i]); + if (!list_push(supa, a)) + debugx(1, DBG_ERR, "malloc failed"); + } + freegconfmstr(supattrs); + } + + if (supvattrs) { + if (!supa) + supa = list_create(); + if (!supa) + debugx(1, DBG_ERR, "malloc failed"); + for (i = 0; supvattrs[i]; i++) { + a = extractattr(supvattrs[i], 1); + if (!a) + debugx(1, DBG_ERR, "addrewrite: adding invalid vendor attribute %s", supvattrs[i]); + if (!list_push(supa, a)) + debugx(1, DBG_ERR, "malloc failed"); + } + freegconfmstr(supvattrs); + } + + if (rma || rmva || adda || moda || supa) { + rewrite = malloc(sizeof(struct rewrite)); + if (!rewrite) + debugx(1, DBG_ERR, "malloc failed"); + rewrite->removeattrs = rma; + rewrite->removevendorattrs = rmva; + rewrite->addattrs = adda; + rewrite->modattrs = moda; + rewrite->supattrs = supa; + } + + if (!rewriteconfs) + rewriteconfs = hash_create(); + if (!hash_insert(rewriteconfs, value, strlen(value), rewrite)) + debugx(1, DBG_ERR, "malloc failed"); + debug(DBG_DBG, "addrewrite: added rewrite block %s", value); +} + +int confrewrite_cb(struct gconffile **cf, void *arg, char *block, char *opt, char *val) { + char **rmattrs = NULL, **rmvattrs = NULL; + char **addattrs = NULL, **addvattrs = NULL; + char **modattrs = NULL; + char **supattrs = NULL, **supvattrs = NULL; + + debug(DBG_DBG, "confrewrite_cb called for %s", block); + + if (!getgenericconfig(cf, block, + "removeAttribute", CONF_MSTR, &rmattrs, + "removeVendorAttribute", CONF_MSTR, &rmvattrs, + "addAttribute", CONF_MSTR, &addattrs, + "addVendorAttribute", CONF_MSTR, &addvattrs, + "modifyAttribute", CONF_MSTR, &modattrs, + "supplementAttribute", CONF_MSTR, &supattrs, + "supplementVendorAttriute", CONF_MSTR, &supvattrs, + NULL)) + debugx(1, DBG_ERR, "configuration error"); + addrewrite(val, rmattrs, rmvattrs, addattrs, addvattrs, modattrs, supattrs, supvattrs); + return 1; +} + +struct rewrite *getrewrite(char *alt1, char *alt2) { + struct rewrite *r; + + if (alt1) + if ((r = hash_read(rewriteconfs, alt1, strlen(alt1)))) + return r; + if (alt2) + if ((r = hash_read(rewriteconfs, alt2, strlen(alt2)))) + return r; + return NULL; +} + +int resizeattr(struct tlv *attr, uint8_t newlen) { + uint8_t *newv; + + if (newlen != attr->l) { + newv = realloc(attr->v, newlen); + if (!newv) + return 0; + attr->v = newv; + attr->l = newlen; + } + return 1; +} + +int findvendorsubattr(uint32_t *attrs, uint32_t vendor, uint32_t subattr) { + if (!attrs) + return 0; + + for (; attrs[0]; attrs += 2) + if (attrs[0] == vendor && attrs[1] == subattr) + return 1; + return 0; +} + +/* returns 1 if entire element is to be removed, else 0 */ +int dovendorrewriterm(struct tlv *attr, uint32_t *removevendorattrs) { + uint8_t alen, sublen; + uint32_t vendor; + uint8_t *subattrs; + + if (!removevendorattrs || attr->l <= 4) + return 0; + + memcpy(&vendor, attr->v, 4); + vendor = ntohl(vendor); + while (*removevendorattrs && *removevendorattrs != vendor) + removevendorattrs += 2; + if (!*removevendorattrs) + return 0; + + if (findvendorsubattr(removevendorattrs, vendor, 256)) + return 1; /* remove entire vendor attribute */ + + sublen = attr->l - 4; + subattrs = attr->v + 4; + + if (!attrvalidate(subattrs, sublen)) { + debug(DBG_INFO, "dovendorrewrite: vendor attribute validation failed, no rewrite"); + return 0; + } + + while (sublen > 1) { + alen = ATTRLEN(subattrs); + sublen -= alen; + if (findvendorsubattr(removevendorattrs, vendor, ATTRTYPE(subattrs))) { + memmove(subattrs, subattrs + alen, sublen); + attr->l -= alen; + } else + subattrs += alen; + } + return 0; +} + +void dorewriterm(struct radmsg *msg, uint8_t *rmattrs, uint32_t *rmvattrs) { + struct list_node *n, *p; + struct tlv *attr; + + p = NULL; + n = list_first(msg->attrs); + while (n) { + attr = (struct tlv *)n->data; + if ((rmattrs && strchr((char *)rmattrs, attr->t)) || + (rmvattrs && attr->t == RAD_Attr_Vendor_Specific && dovendorrewriterm(attr, rmvattrs))) { + list_removedata(msg->attrs, attr); + freetlv(attr); + n = p ? list_next(p) : list_first(msg->attrs); + } else { + p = n; + n = list_next(n); + } + } +} + +int dorewritemodattr(struct tlv *attr, struct modattr *modattr) { + size_t nmatch = 10, reslen = 0, start = 0; + regmatch_t pmatch[10], *pfield; + int i; + char *in, *out; + + in = stringcopy((char *)attr->v, attr->l); + if (!in) + return 0; + + if (regexec(modattr->regex, in, nmatch, pmatch, 0)) { + free(in); + return 1; + } + + out = modattr->replacement; + + for (i = start; out[i]; i++) { + if (out[i] == '\\' && out[i + 1] >= '1' && out[i + 1] <= '9') { + pfield = &pmatch[out[i + 1] - '0']; + if (pfield->rm_so >= 0) { + reslen += i - start + pfield->rm_eo - pfield->rm_so; + start = i + 2; + } + i++; + } + } + reslen += i - start; + if (reslen > 253) { + debug(DBG_INFO, "rewritten attribute length would be %d, max possible is 253, discarding message", reslen); + free(in); + return 0; + } + + if (!resizeattr(attr, reslen)) { + free(in); + return 0; + } + + start = 0; + reslen = 0; + for (i = start; out[i]; i++) { + if (out[i] == '\\' && out[i + 1] >= '1' && out[i + 1] <= '9') { + pfield = &pmatch[out[i + 1] - '0']; + if (pfield->rm_so >= 0) { + memcpy(attr->v + reslen, out + start, i - start); + reslen += i - start; + memcpy(attr->v + reslen, in + pfield->rm_so, pfield->rm_eo - pfield->rm_so); + reslen += pfield->rm_eo - pfield->rm_so; + start = i + 2; + } + i++; + } + } + free(in); + + memcpy(attr->v + reslen, out + start, i - start); + return 1; +} + +int dorewritemod(struct radmsg *msg, struct list *modattrs) { + struct list_node *n, *m; + + for (n = list_first(msg->attrs); n; n = list_next(n)) + for (m = list_first(modattrs); m; m = list_next(m)) + if (((struct tlv *)n->data)->t == ((struct modattr *)m->data)->t && + !dorewritemodattr((struct tlv *)n->data, (struct modattr *)m->data)) + return 0; + return 1; +} + +int dorewriteadd(struct radmsg *msg, struct list *addattrs) { + struct list_node *n; + struct tlv *a; + + for (n = list_first(addattrs); n; n = list_next(n)) { + a = copytlv((struct tlv *)n->data); + if (!a) + return 0; + if (!radmsg_add(msg, a)) { + freetlv(a); + return 0; + } + } + return 1; +} + +int dorewritesup(struct radmsg *msg, struct list *supattrs) { + struct list_node *n, *p; + struct tlv *attr, *supattr; + uint8_t exist, *vendortype, *v;; + + for (n = list_first(supattrs); n; n = list_next(n)) { + supattr = (struct tlv *)n->data; + exist = 0; + for(p = list_first(msg->attrs); p; p = list_next(p)) { + attr = (struct tlv *)p->data; + if (attr->t == supattr->t && attr->t != RAD_Attr_Vendor_Specific) { + exist = 1; + break; + } else if (supattr->t == RAD_Attr_Vendor_Specific && attr->t == RAD_Attr_Vendor_Specific && + memcmp (supattr->v, attr->v, 4)) { + if (!attrvalidate(attr->v+4, attr->l-4)) { + debug(DBG_INFO, "dorewritesup: vendor attribute validation failed, no rewrite"); + return 0; + } + vendortype = (uint8_t *)supattr->v+4; + for (v=attr->v+4; v < attr->v + attr->l; v += *(v+1) + 2){ + if (*v == *vendortype) { + exist = 1; + break; + } + } + if (exist) break; + } + } + if (!exist) { + supattr = copytlv(supattr); + if (!supattr) + return 0; + if (!radmsg_add(msg, supattr)) { + freetlv(supattr); + return 0; + } + } + } + return 1; +} + +int dorewrite(struct radmsg *msg, struct rewrite *rewrite) { + int rv = 1; /* Success. */ + + if (rewrite) { + if (rewrite->removeattrs || rewrite->removevendorattrs) + dorewriterm(msg, rewrite->removeattrs, rewrite->removevendorattrs); + if (rewrite->modattrs) + if (!dorewritemod(msg, rewrite->modattrs)) + rv = 0; + if (rewrite->supattrs) + if (!dorewritesup(msg, rewrite->supattrs)) + rv = 0; + if (rewrite->addattrs) + if (!dorewriteadd(msg, rewrite->addattrs)) + rv = 0; + } + return rv; +} + +/** Ad vendor attribute with VENDOR + ATTR and push it on MSG. ATTR + * is consumed. */ +int addvendorattr(struct radmsg *msg, uint32_t vendor, struct tlv *attr) { + struct tlv *vattr; + + vattr = makevendortlv(vendor, attr); + if (!vattr) { + freetlv(attr); + return 0; + } + if (!radmsg_add(msg, vattr)) { + freetlv(vattr); + return 0; + } + return 1; +} + +/* Local Variables: */ +/* c-file-style: "stroustrup" */ +/* End: */ diff --git a/rewrite.h b/rewrite.h new file mode 100644 index 0000000..dc2dc4f --- /dev/null +++ b/rewrite.h @@ -0,0 +1,30 @@ +/* Copyright (c) 2019, SWITCH */ +/* See LICENSE for licensing information. */ + +struct modattr { + uint8_t t; + char *replacement; + regex_t *regex; +}; + +struct rewrite { + uint8_t *removeattrs; + uint32_t *removevendorattrs; + struct list *addattrs; + struct list *modattrs; + struct list *supattrs; +}; + +int confrewrite_cb(struct gconffile **cf, void *arg, char *block, char *opt, char *val); +int dorewrite(struct radmsg *msg, struct rewrite *rewrite); +struct modattr *extractmodattr(char *nameval); +struct rewrite *getrewrite(char *alt1, char *alt2); +int resizeattr(struct tlv *attr, uint8_t newlen); + +int dorewritemodattr(struct tlv *attr, struct modattr *modattr); +int addvendorattr(struct radmsg *msg, uint32_t vendor, struct tlv *attr); + + +/* Local Variables: */ +/* c-file-style: "stroustrup" */ +/* End: */ From 42e93a9f4999d284364d531201101dd3cd4cf1eb Mon Sep 17 00:00:00 2001 From: Fabian Mauchle Date: Thu, 17 Jan 2019 17:31:31 +0100 Subject: [PATCH 04/22] some more refactoring for unit-tests add ifndef to some headers first rewrite unit-test --- list.h | 5 ++++ radmsg.h | 7 +++++- radsecproxy.c | 22 +++++++++++++++++ rewrite.c | 22 ----------------- rewrite.h | 21 +++++++++++----- tests/Makefile.am | 2 +- tests/t_rewrite | Bin 0 -> 147208 bytes tests/t_rewrite.c | 60 ++++++++++++++++++++++++++++++++++++++++++++++ 8 files changed, 109 insertions(+), 30 deletions(-) create mode 100755 tests/t_rewrite create mode 100644 tests/t_rewrite.c diff --git a/list.h b/list.h index 38169c7..f015b9d 100644 --- a/list.h +++ b/list.h @@ -1,6 +1,9 @@ /* Copyright (c) 2007,2009, UNINETT AS */ /* See LICENSE for licensing information. */ +#ifndef _LIST_H +#define _LIST_H + #ifdef SYS_SOLARIS9 #include #else @@ -44,6 +47,8 @@ struct list_node *list_next(struct list_node *node); /* returns number of nodes */ uint32_t list_count(struct list *list); +#endif /*_LIST_H*/ + /* Local Variables: */ /* c-file-style: "stroustrup" */ /* End: */ diff --git a/radmsg.h b/radmsg.h index 0c6d0d6..8925b7d 100644 --- a/radmsg.h +++ b/radmsg.h @@ -2,6 +2,9 @@ /* Copyright (c) 2015, NORDUnet A/S */ /* See LICENSE for licensing information. */ +#ifndef _RADMSG_H +#define _RADMSG_H + #include "tlv11.h" #define RAD_Access_Request 1 @@ -31,7 +34,7 @@ struct radmsg { uint8_t code; uint8_t id; uint8_t auth[20]; - struct list *attrs; + struct list *attrs; /*struct tlv*/ }; #define ATTRTYPE(x) ((x)[0]) @@ -53,6 +56,8 @@ uint8_t attrname2val(char *attrname); int vattrname2val(char *attrname, uint32_t *vendor, uint32_t *type); int attrvalidate(unsigned char *attrs, int length); +#endif /*_RADMSG_H*/ + /* Local Variables: */ /* c-file-style: "stroustrup" */ /* End: */ diff --git a/radsecproxy.c b/radsecproxy.c index 47120eb..fa7f9ff 100644 --- a/radsecproxy.c +++ b/radsecproxy.c @@ -2619,6 +2619,28 @@ int confserver_cb(struct gconffile **cf, void *arg, char *block, char *opt, char return 0; } +int confrewrite_cb(struct gconffile **cf, void *arg, char *block, char *opt, char *val) { + char **rmattrs = NULL, **rmvattrs = NULL; + char **addattrs = NULL, **addvattrs = NULL; + char **modattrs = NULL; + char **supattrs = NULL, **supvattrs = NULL; + + debug(DBG_DBG, "confrewrite_cb called for %s", block); + + if (!getgenericconfig(cf, block, + "removeAttribute", CONF_MSTR, &rmattrs, + "removeVendorAttribute", CONF_MSTR, &rmvattrs, + "addAttribute", CONF_MSTR, &addattrs, + "addVendorAttribute", CONF_MSTR, &addvattrs, + "modifyAttribute", CONF_MSTR, &modattrs, + "supplementAttribute", CONF_MSTR, &supattrs, + "supplementVendorAttriute", CONF_MSTR, &supvattrs, + NULL)) + debugx(1, DBG_ERR, "configuration error"); + addrewrite(val, rmattrs, rmvattrs, addattrs, addvattrs, modattrs, supattrs, supvattrs); + return 1; +} + int confrealm_cb(struct gconffile **cf, void *arg, char *block, char *opt, char *val) { char **servers = NULL, **accservers = NULL, *msg = NULL; uint8_t accresp = 0; diff --git a/rewrite.c b/rewrite.c index 381d0f6..248fcb5 100644 --- a/rewrite.c +++ b/rewrite.c @@ -277,28 +277,6 @@ void addrewrite(char *value, char **rmattrs, char **rmvattrs, char **addattrs, debug(DBG_DBG, "addrewrite: added rewrite block %s", value); } -int confrewrite_cb(struct gconffile **cf, void *arg, char *block, char *opt, char *val) { - char **rmattrs = NULL, **rmvattrs = NULL; - char **addattrs = NULL, **addvattrs = NULL; - char **modattrs = NULL; - char **supattrs = NULL, **supvattrs = NULL; - - debug(DBG_DBG, "confrewrite_cb called for %s", block); - - if (!getgenericconfig(cf, block, - "removeAttribute", CONF_MSTR, &rmattrs, - "removeVendorAttribute", CONF_MSTR, &rmvattrs, - "addAttribute", CONF_MSTR, &addattrs, - "addVendorAttribute", CONF_MSTR, &addvattrs, - "modifyAttribute", CONF_MSTR, &modattrs, - "supplementAttribute", CONF_MSTR, &supattrs, - "supplementVendorAttriute", CONF_MSTR, &supvattrs, - NULL)) - debugx(1, DBG_ERR, "configuration error"); - addrewrite(val, rmattrs, rmvattrs, addattrs, addvattrs, modattrs, supattrs, supvattrs); - return 1; -} - struct rewrite *getrewrite(char *alt1, char *alt2) { struct rewrite *r; diff --git a/rewrite.h b/rewrite.h index dc2dc4f..b7ead27 100644 --- a/rewrite.h +++ b/rewrite.h @@ -1,6 +1,13 @@ /* Copyright (c) 2019, SWITCH */ /* See LICENSE for licensing information. */ +#ifndef _REWRITE_H +#define _REWRITE_H + +#include +#include "list.h" +#include "radmsg.h" + struct modattr { uint8_t t; char *replacement; @@ -8,14 +15,15 @@ struct modattr { }; struct rewrite { - uint8_t *removeattrs; - uint32_t *removevendorattrs; - struct list *addattrs; - struct list *modattrs; - struct list *supattrs; + uint8_t *removeattrs; /*NULL terminated*/ + uint32_t *removevendorattrs; /*NULL terminated*/ + struct list *addattrs; /*struct tlv*/ + struct list *modattrs; /*struct modattr*/ + struct list *supattrs; /*struct tlv*/ }; -int confrewrite_cb(struct gconffile **cf, void *arg, char *block, char *opt, char *val); +void addrewrite(char *value, char **rmattrs, char **rmvattrs, char **addattrs, + char **addvattrs, char **modattrs, char **supattrs, char** supvattrs); int dorewrite(struct radmsg *msg, struct rewrite *rewrite); struct modattr *extractmodattr(char *nameval); struct rewrite *getrewrite(char *alt1, char *alt2); @@ -24,6 +32,7 @@ int resizeattr(struct tlv *attr, uint8_t newlen); int dorewritemodattr(struct tlv *attr, struct modattr *modattr); int addvendorattr(struct radmsg *msg, uint32_t vendor, struct tlv *attr); +#endif /*_REWRITE_H*/ /* Local Variables: */ /* c-file-style: "stroustrup" */ diff --git a/tests/Makefile.am b/tests/Makefile.am index b8fcd19..c9feb2c 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -1,6 +1,6 @@ AUTOMAKE_OPTIONS = foreign -check_PROGRAMS = t_fticks +check_PROGRAMS = t_fticks t_rewrite AM_CFLAGS = -g -Wall -Werror @SSL_CFLAGS@ @TARGET_CFLAGS@ LDADD = $(top_builddir)/librsp.a @SSL_LIBS@ diff --git a/tests/t_rewrite b/tests/t_rewrite new file mode 100755 index 0000000000000000000000000000000000000000..f238b52603ca7cd7a29e8a7c33935b09bdf5bad7 GIT binary patch literal 147208 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¢gJ^z}v_yJ^0j%NR|9+Z)Om9xB3{Hj#@ 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 z z2f0d6&a7x^R%Afi)yY#}rV@`(lo*~0tNHrR@KSg@0+G3skG z_~gLf!TgdJ@w&-TL}t^&U+51c2vyr4u0*g+s`4e(RW!z)<YdCrY7NFPT&#Kfh0TZK8pXA5iMt&6Y#4q5VE$x7mMmqwQcGSyh zj&w+jwsmUB9P%4Wo){;mewj$zj(dw;O?P=0!#-eWu&b$Ax589Xc18OQqn!6wIA6$e zklH$amg^;esjC&XO$$-h`j9-IP>ZAnQ9~R6~cl9ABbbG0H`{C;-*DM9U_Y8jD=QwZT zINv}&aX-g-L&ho$FNl^L*t&zKoz6#j2jZa%Sl+le2dTh&fFDX6+--xcxXpCXEcX!d zy~roWWBdA@TchGitSTD zUyS&0DDB0#a*=KTe(CQMi4x*O`)+c1t9uA%QJ6JHq^$4HpxXpGm2|>B(4hP2zZ>jz zN%IhHPUz($$_M&8Nssm!eiP%w@b}YU^f$VO!;vQ4CgmKA@lvqG=qHxNMLEX;UkrSh z2qEXaJvg!Rb8^-c(I=F1KIpcAZfBAXQ}P1WaK)8(gV5!Cv@O7?K%cKRfPOBHj#%x5 ze4YS)0r1yQU!uO7(WQ^;ikY4@$Gl-RLHxZA`WnR9-#5nNdF?I2D0~>*X>IV-EZa*R zXAZIx{BjX*Z$Z4?r=#D3{?3~xid_qA=&>&5wW)gl)816@t4EywAuB-tS?w*raRoo! zkWm8x5ke)$mCk_U;Lj+ZhjqaSmKXY1k{k!@x7U&HL;hUm8yE4%fL{+h)RVl3w_}}9 z-vyrQi&dTQ0nm>Hz6SWq41HMT7k)vE7b>~2hbs>)vbu*NT@P%>8t|Emb<1xl%{13x z*@id@^V}L^yl+H4&z!!>{Mjy#Z6UI6$g=s2{O6I+Gl4&2zR*Jvm=MtW?Oo&#$NFc4 zA?JB^@EzFwEuHKjkY#J>gyMuVA3R4Qbv%!!@`hcV*KR~J4H?s1^)}mWXgE}1ZTPe- z+Yipi8sV_;ZyUg01OK100iD`IeQ}+(6zj8g<_mp2DaPH+&W08|u&UcQRcxlKd*c zZyWgWMW6pDpO4*8aDLoqD2#sSdGLD@>(f!Vr@joo3#VD+%0ZkBuPf^P2y|OO_ut*s zn~GzmG48mGamV>J-vITBd2uY(u@ct9TUhUn$@y8VcWievbf0Pj^!}__mO!55Bk`xA zd=1J^CL%RY-GDeTH8C*{7wMk>eKY9qBcdqTPR?t~5CL>p;7~8GgZ>2Qt4S~HXu8WA z(!Hbzo`B{!J_EV}tkd_BE){2p_z7z^BeuYRNKGn}*|%I5nE`2#ys%zx1)q(^I5X@n zwH`?6cZTbP!*;w&^`_$6hrrLXH<6f1xr~c`YBTT(@NA=bO~lP^;1>Y@EsQq`0iF z68ka-QQrqdi25+OS=Wt)$@R$$_@}aeQ=gJ`Cl)XNrat=1cTwNm{n(#keHb?-E=REo zI9{};Gq_)c{7VEz|6sXn>tXD;O z?`w&~RV;5@W07tEUID(IxYT%r)gta+njCil#;+LGVm-rhyD2Zq-Nte$J3@5B#ey03 zPO*l)A}-|q*EW<}!E#--gX`(JA__?k8`d`#`(YCH-QG0H8S7QyH*Rtrw%N9JTuYFQ zdaFdaCsA&qaM%_v`h{k=zsfcFE4f#a)g}F|)D$ zhfsep_A5W`=ucDQGBu9}(1x^aT??RO*@CDDgSgWYe_9rng59`=Y_=aCt=K2M6!&oM zo`vvO)Iaw-4+6i0c-V_o|2C|Wxvv4EoR$S^LH7vzq(9`os0aJ3D_IUS4Yt+rgj2E* zB;k|Mi#{K10Nqm1m61;Px5A`HAjna{W|zGgAdi$`C2g0 zCr~~kHS^ikygH}CH(T)Q1yzb}fKk9K#}`O>_0$uozOPT*VX75Zc$rV619ebxJh(vI znQI<1HXmf$$^akVyp(FA#WBmz#`=pki@hX(=vjL5u5fFP}(F{1yW?+m)x2Nmz=A%dG#L z5w3U*`h)?Sa-1;W=AJ&ICHx3Zlo)W^nN3K#7KW- zq+5-2uaUlKq#qmUNh8e|VAOAUlJjANa? z*>G54b0l^uqth2-Texz?m_Ez#-C z1yLt*=~HN4SA|1<1(`BEGG{~|{;ZcR2*~z26eFcMGWpVen(Xj1v1JZKXQkOQ`65u7 zoH0cp(NipX2yBY0mN9>!>76g7?Fn<47aP z1oE8RPPfYfC~&s1giQcG=WdeO1)w>-~;OvV@ z#y(iC1hC%8O$GZfc_ql2ou3dGEk^)6>g3}t`;`LNu?O_ z{jbVV_!%-> zw*LkyJ#PX&ZTyn9N-myxkp_i({x>q++q;xI~n3+>DAHxumc;j`Duk4{@I(Wigw# zLP=D|hveu0m*Gy=VRAf(d#9TfN{;U!)9Ddt$E(EmW?7HJLwp~>(!mF~&c3|&f*sq` zTLwz*#V(SsMoarVC}5KGVNG%`fkifMiS3J%M3U?(Z}ZO!r+7PbHgxRF zp-ohP_7d?y+d?TC)6fGT%E+TlQn*Xl`%>=Dv9s0V4&jEVpG)pT_AqJaLKeH6%&7&P zcIdM}W)-pmYU7fhAXonw0hBcK_xQ*d#WIxj65dFcg1wZH!@7n^(yfrjF2y0IL+%5V zyci$HLDxC-;N)h^dIex$%zEaO_May|W7bQ~Z^q09bgRyePENSAw5w)Q!e8RUWAD|1 zB$F=9?&7VKNtb4K)8#whZT|~thHz>2K3_4LF3sLoC#6f%?;V+RY4+^zgTx~_sRH?e zYmsd>^GaZN$s397L5}iZ-pa*Uj~h|gII!p`&yf{W?br?CUUyStPI(3BT%w4RMO=>x z^!1={KTV!JD2|(r&~GJAIw^*=8jL3+o0q(iQ$laSX&5>2p?M#E$E!)XfK-?ErCrJR zwO5^@AF=&Q0DS}c{&(PJGWZX8-(Wuza_1bg0EjW!5RTM=(jXs7OA?8IOZ!5eL(ubU zN;uMEZ?q#%mnNUmOaas0G!QwT2EyOUxj^*581}#@bU@Y$gNNCf zJ~+oF)-aVN_8Ym3!Fb6V7bI(#${M~89y-fuWZ55eT_7q{Se%Ch2b{IELmIq{yyxa! z;6pZc^~ERoZU+8B(bIFuSDtgBU^AC&0^%Lc&scO3@jabsye}u-{ z0=#lOV^CM-Dme^hUe7foyH_w6?)e>a*2xTP^LqDTnFnN{B6&zb?=6IXq{Frg3wlpx z$w!R*!3Divh6QClX22H|^xjHlj~noyg5FusTISCTcwj;A#|S@Rz&QozJ6dO-ovh~9 zGQTLc56|~zJ}t8c`Z_Xq%P+FYBZ%B9KZbkBb0dLv0obKJ<%IUhze1LsF}Nd-EbWZJ z9apg-4zWgae#HY%POj0MCv|h+8qIl{DsecQ@##FndykWAH0N2~=Qz1WbDrb9*I7v# zan546hif#aB=ni*xr-UH017-@qd9E?@OijKbJ_)H0M|$TdRj_H0L$)UevQ&%H|r)`Itkj(VUyuNGC&5 z=g)X6Ik`r2{+!KqKsMa}N{l|(21kE};~0vIu|u)N*ddm^DX?#v*uIkEVVGxL565Ys zoL}Rkhm)trCFh@b--BZhp$UC^PF5f0I2n{=^}R(9F(}dR9Zm)%S=sNSX08><&+$tz z@?`ZQt&y=9io0AK(Uc=ac9H}R>-^Q)b40KS4 zKF)DDoDd6@yPSfPlc(5RJ;IdIc>)x!EE2GB-=_d#9p`e*1A{DYRFC=lZQO;F;V?YiI(ydNeDVKW@JL56|z zNSd#g^?=VHY6}Q9V$e=KlR^$EOWjL@uS8L~ANiXz;DeL|m%MQVe8z>MWIq=Cf3&>` zd{xEyK0fE3NyttDH)MwZ*$4pxNyr662mx7SNdVb**^{uzCPBrWxPgig*IElo zZC!Dp+FGpDs#Vd}U9?tPOHo^EYyCaX%$$4A1@+thKmA`moXoq-JMX;n&dfV==A5$~ zOxa2y#b1FkdfW({mBH*+0nAw@tgh7W&ji*2pn^#dlRFnd=iK`DqQzVK#)zXf>WAW;-O#hUeF&5jr{*T?)>d;c5fq2>SP$N8l_z1&v#M% z@Dc~#Zt!fBJcZ{%GXDmHW}^(zXe`T2qa1SOnPP|%E%_^huScd9Q^s0knqc)8`5ze~ z+v>8gM3H~Og}=?V`C25&1h2sXt0ts4%RJnFbUh}K>^4W9JZ7!v6%KSrBp zf2DY5;+l+}mi?Zh68two&M4Lm1V2dv_$J--MuLsW06E8K@FjqKIL&C#(;@ndpd`iX z>5x6tE}T-J5`%rt5@FrULFG*X7=IM-Iu<}VYnEdm*~ihCcVco@32Q&+h3a&8z2Fp} zz`F@P4!qULY3=+BfK@C|fh}DDE)G-iHbXT$-NNB%UlsuLid)$$`gBJtmgR<^i4bdZ z(dW@;U|-o{$=pJj+mqhZPZTdVEFECUzs3M}0(eUETm4jfqERNq+0~{|ME-4t^GR_2 zUURD9{jaVPJ!k}!7{5vMD=cu8S|#!a?TcT`4w4Vnh%q0o~(Ta*@C zp~dxvN-aid>US96F2hMJ=4#HEj=O024-JI{Z!iVJ@?RS?3*LQN!C8eNsHrXgv+pgq zui<3Dzjq4W$%3aD3J3JhObN1B+p4=i3d^_Ya1XqxOY{vl>Q^vx}F91b^saG1$9@rg23YL#g+Ei`1jbG+pHPc5%M` zy_^j$P8;JNY8jB@HHw`2?V-{KG5 z*<{a%Rete(+46@OPL8ebx;QbY<}Y;7zIM~9MDb=r8;TasztvS-RqE*m3u~GGa~J2# z@8$fHi*x7qa%T35RQA5_E#+yco=DnHDGS*(Tpi zp?}ui2Z1f+79%)z0C@vuw9*IcFq{|^qHK+@vQ+--0p+hXLfFezBRhLp3-A{k{6yIN z5`~|n@IP?z-(vU?Y?MFl;z#$v_V*-&mHVLb5knafR%4nGdKeny9Y!KE8}F#& zH*zXhSq3!$D8+O!5(?d)3@-pGe9wqn4dqan7lm=Pg`!QQe_QLSJZy*43_u_ zNZuYe4f%H@tVvb24}T_zl4oM^w^pq{$D5q$AwcBDb})j8Qnh-lRm<602q@(;8#x$? z+yr>QE()wuOWeN`?xaKwm%^?C)VgVi#ntFM!taq@ua?0F0T1LVbE_%369%wGSiEzo zmYDB@L7c&A-DNls�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 literal 0 HcmV?d00001 diff --git a/tests/t_rewrite.c b/tests/t_rewrite.c new file mode 100644 index 0000000..9a47026 --- /dev/null +++ b/tests/t_rewrite.c @@ -0,0 +1,60 @@ +/* Copyright (C) 2019, SWITCH */ +/* See LICENSE for licensing information. */ + +#include +#include +#include +#include "../rewrite.h" +#include "../radmsg.h" + +/*origattrs and expectedattrs as struct tlv*/ +/*return 0 if expected; 1 otherwise or error*/ +static int +_check_rewrite(struct list *origattrs, struct rewrite *rewrite, struct list *expectedattrs) { + struct radmsg msg; + struct list_node *n,*m; + + msg.attrs = origattrs; + + if(!dorewrite(&msg, rewrite)) + return 1; + + if(list_count(expectedattrs) != list_count(origattrs)) { + printf("bad attribute list length!"); + return 1; + } + m=list_first(origattrs); + for(n=list_first(expectedattrs); n; n=list_next(n)) { + if (((struct tlv *)n->data)->t != ((struct tlv *)m->data)->t || + ((struct tlv *)n->data)->l != ((struct tlv *)m->data)->l || + memcmp(((struct tlv *)n->data)->v, ((struct tlv *)m->data)->v, ((struct tlv *)n->data)->l) ) { + + printf("attribute list not as expected"); + return 1; + } + m=list_next(m); + } + return 0; +} + +int +main (int argc, char *argv[]) +{ + struct list *origattrs, *expectedattrs; + struct rewrite rewrite; + + origattrs=list_create(); + expectedattrs=list_create(); + + rewrite.removeattrs = NULL; + rewrite.removevendorattrs = NULL; + rewrite.addattrs = list_create(); + rewrite.modattrs = list_create(); + rewrite.supattrs = list_create(); + + /* test 1: empty noop */ + if (_check_rewrite(origattrs, &rewrite, expectedattrs)) + return 1; + + return 0; +} From d645dfd1abe7239b7c3a7c773b2b6adf84b2f973 Mon Sep 17 00:00:00 2001 From: Fabian Mauchle Date: Sun, 3 Feb 2019 21:09:20 +0100 Subject: [PATCH 05/22] move knowledge about attributes (tlv) on wire to radmsg.c --- .gitignore | 1 + radmsg.c | 42 ++++++++++++++++++++++++++++++++++++++---- radmsg.h | 1 + rewrite.c | 28 ++-------------------------- tests/t_rewrite | Bin 147208 -> 0 bytes tlv11.c | 12 ------------ tlv11.h | 1 - 7 files changed, 42 insertions(+), 43 deletions(-) delete mode 100755 tests/t_rewrite diff --git a/.gitignore b/.gitignore index cc0ae36..c9675e8 100644 --- a/.gitignore +++ b/.gitignore @@ -23,5 +23,6 @@ radsecproxy radsecproxy-conf radsecproxy-hash tests/t_fticks +tests/t_rewrite tests/*.log tests/*.trs diff --git a/radmsg.c b/radmsg.c index ff4ec9f..28373da 100644 --- a/radmsg.c +++ b/radmsg.c @@ -205,6 +205,18 @@ int _radsign(unsigned char *rad, unsigned char *sec) { return 1; } +uint8_t *tlv2buf(uint8_t *p, const struct tlv *tlv) { + p[0] = tlv->t; + p[1] = tlv->l+2; + if (tlv->l) { + if (tlv->v) + memcpy(p+2, tlv->v, tlv->l); + else + memset(p+2, 0, tlv->l); + } + return p; +} + uint8_t *radmsg2buf(struct radmsg *msg, uint8_t *secret) { struct list_node *node; struct tlv *tlv; @@ -233,10 +245,9 @@ uint8_t *radmsg2buf(struct radmsg *msg, uint8_t *secret) { for (node = list_first(msg->attrs); node; node = list_next(node)) { tlv = (struct tlv *)node->data; p = tlv2buf(p, tlv); - p[-1] += 2; - if (tlv->t == RAD_Attr_Message_Authenticator && secret) - msgauth = p; - p += tlv->l; + if (tlv->t == RAD_Attr_Message_Authenticator && secret) + msgauth = ATTRVAL(p); + p += tlv->l + 2; } if (msgauth && !_createmessageauth(buf, msgauth, secret)) { free(buf); @@ -371,6 +382,29 @@ int attrvalidate(unsigned char *attrs, int length) { return 1; } +/** Create vendor specific tlv with ATTR. ATTR is consumed (freed) if + * all is well with the new tlv, i.e. if the function returns + * !NULL. */ +struct tlv *makevendortlv(uint32_t vendor, struct tlv *attr){ + struct tlv *newtlv = NULL; + uint8_t l, *v; + + if (!attr) + return NULL; + l = attr->l + 2 + 4; + v = malloc(l); + if (v) { + vendor = htonl(vendor & 0x00ffffff); /* MSB=0 according to RFC 2865. */ + memcpy(v, &vendor, 4); + tlv2buf(v + 4, attr); + newtlv = maketlv(RAD_Attr_Vendor_Specific, l, v); + free(v); + if (newtlv) + freetlv(attr); + } + return newtlv; +} + /* Local Variables: */ /* c-file-style: "stroustrup" */ /* End: */ diff --git a/radmsg.h b/radmsg.h index 8925b7d..a5ebf40 100644 --- a/radmsg.h +++ b/radmsg.h @@ -55,6 +55,7 @@ struct radmsg *buf2radmsg(uint8_t *, uint8_t *, uint8_t *); uint8_t attrname2val(char *attrname); int vattrname2val(char *attrname, uint32_t *vendor, uint32_t *type); int attrvalidate(unsigned char *attrs, int length); +struct tlv *makevendortlv(uint32_t vendor, struct tlv *attr); #endif /*_RADMSG_H*/ diff --git a/rewrite.c b/rewrite.c index 248fcb5..6f15129 100644 --- a/rewrite.c +++ b/rewrite.c @@ -15,30 +15,6 @@ static struct hash *rewriteconfs; -/** Create vendor specific tlv with ATTR. ATTR is consumed (freed) if - * all is well with the new tlv, i.e. if the function returns - * !NULL. */ -static struct tlv *makevendortlv(uint32_t vendor, struct tlv *attr){ - struct tlv *newtlv = NULL; - uint8_t l, *v; - - if (!attr) - return NULL; - l = attr->l + 6; - v = malloc(l); - if (v) { - vendor = htonl(vendor & 0x00ffffff); /* MSB=0 according to RFC 2865. */ - memcpy(v, &vendor, 4); - tlv2buf(v + 4, attr); - v[5] += 2; /* Vendor length increased for type and length fields. */ - newtlv = maketlv(RAD_Attr_Vendor_Specific, l, v); - free(v); - if (newtlv) - freetlv(attr); - } - return newtlv; -} - /** Extract attributes from string NAMEVAL, create a struct tlv and * return the tlv. If VENDOR_FLAG, NAMEVAL is on the form * "::" and otherwise it's ":". Return @@ -325,8 +301,8 @@ int dovendorrewriterm(struct tlv *attr, uint32_t *removevendorattrs) { vendor = ntohl(vendor); while (*removevendorattrs && *removevendorattrs != vendor) removevendorattrs += 2; - if (!*removevendorattrs) - return 0; + if (!*removevendorattrs) + return 0; if (findvendorsubattr(removevendorattrs, vendor, 256)) return 1; /* remove entire vendor attribute */ diff --git a/tests/t_rewrite b/tests/t_rewrite deleted file mode 100755 index f238b52603ca7cd7a29e8a7c33935b09bdf5bad7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 147208 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¢gJ^z}v_yJ^0j%NR|9+Z)Om9xB3{Hj#@ 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 z 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�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 diff --git a/tlv11.c b/tlv11.c index cb9a3da..7516d6c 100644 --- a/tlv11.c +++ b/tlv11.c @@ -105,18 +105,6 @@ uint8_t *tlv2str(struct tlv *tlv) { return s; } -uint8_t *tlv2buf(uint8_t *p, const struct tlv *tlv) { - *p++ = tlv->t; - *p++ = tlv->l; - if (tlv->l) { - if (tlv->v) - memcpy(p, tlv->v, tlv->l); - else - memset(p, 0, tlv->l); - } - return p; -} - /* Local Variables: */ /* c-file-style: "stroustrup" */ /* End: */ diff --git a/tlv11.h b/tlv11.h index 87909c0..5384481 100644 --- a/tlv11.h +++ b/tlv11.h @@ -16,7 +16,6 @@ struct list *copytlvlist(struct list *); void freetlvlist(struct list *); void rmtlv(struct list *, uint8_t); uint8_t *tlv2str(struct tlv *tlv); -uint8_t *tlv2buf(uint8_t *, const struct tlv *tlv); /* Local Variables: */ /* c-file-style: "stroustrup" */ From d6c3354052b5abb95479cc095e046b27d2e97822 Mon Sep 17 00:00:00 2001 From: Fabian Mauchle Date: Tue, 5 Feb 2019 17:37:57 +0100 Subject: [PATCH 06/22] -switch tests to TAP driver -test resizeattr -test rewrite remove -fix removeVendorAttribute doesn't remove Attribute if no Sub-Attributes left --- .gitignore | 2 + configure.ac | 2 + radmsg.h | 2 + rewrite.c | 7 +- tests/Makefile.am | 6 +- tests/t_fticks.c | 35 +++++---- tests/t_resizeattr.c | 61 ++++++++++++++++ tests/t_rewrite.c | 167 +++++++++++++++++++++++++++++++++++++++---- 8 files changed, 255 insertions(+), 27 deletions(-) create mode 100644 tests/t_resizeattr.c diff --git a/.gitignore b/.gitignore index c9675e8..c53f184 100644 --- a/.gitignore +++ b/.gitignore @@ -22,7 +22,9 @@ TAGS radsecproxy radsecproxy-conf radsecproxy-hash +build-aux/* tests/t_fticks tests/t_rewrite +tests/t_resizeattr tests/*.log tests/*.trs diff --git a/configure.ac b/configure.ac index a36a054..7c1ebe2 100644 --- a/configure.ac +++ b/configure.ac @@ -4,10 +4,12 @@ dnl See LICENSE for licensing information. AC_INIT(radsecproxy, 1.7.2, https://radsecproxy.github.io) AC_CANONICAL_TARGET +AC_CONFIG_AUX_DIR([build-aux]) AM_INIT_AUTOMAKE AC_PROG_CC AC_PROG_RANLIB AC_CHECK_FUNCS([mallopt]) +AC_REQUIRE_AUX_FILE([tap-driver.sh]) udp=yes AC_ARG_ENABLE(udp, diff --git a/radmsg.h b/radmsg.h index a5ebf40..1fc4d3b 100644 --- a/radmsg.h +++ b/radmsg.h @@ -7,6 +7,8 @@ #include "tlv11.h" +#define RAD_Max_Attr_Value_Length 253 + #define RAD_Access_Request 1 #define RAD_Access_Accept 2 #define RAD_Access_Reject 3 diff --git a/rewrite.c b/rewrite.c index 6f15129..0072a66 100644 --- a/rewrite.c +++ b/rewrite.c @@ -268,9 +268,12 @@ struct rewrite *getrewrite(char *alt1, char *alt2) { int resizeattr(struct tlv *attr, uint8_t newlen) { uint8_t *newv; + if (newlen > RAD_Max_Attr_Value_Length) + return 0; + if (newlen != attr->l) { newv = realloc(attr->v, newlen); - if (!newv) + if (newlen && !newv) return 0; attr->v = newv; attr->l = newlen; @@ -324,6 +327,8 @@ int dovendorrewriterm(struct tlv *attr, uint32_t *removevendorattrs) { } else subattrs += alen; } + if (attr->l <= 4) + return 1; return 0; } diff --git a/tests/Makefile.am b/tests/Makefile.am index c9feb2c..4254574 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -1,6 +1,10 @@ AUTOMAKE_OPTIONS = foreign -check_PROGRAMS = t_fticks t_rewrite +#LOG_DRIVER = ./tap-driver.sh +LOG_DRIVER = env AM_TAP_AWK='$(AWK)' $(SHELL) \ + $(top_srcdir)/build-aux/tap-driver.sh + +check_PROGRAMS = t_fticks t_rewrite t_resizeattr AM_CFLAGS = -g -Wall -Werror @SSL_CFLAGS@ @TARGET_CFLAGS@ LDADD = $(top_builddir)/librsp.a @SSL_LIBS@ diff --git a/tests/t_fticks.c b/tests/t_fticks.c index b1d852a..2f47459 100644 --- a/tests/t_fticks.c +++ b/tests/t_fticks.c @@ -37,18 +37,29 @@ _check_hash(const char *mac, const char *key, const char *hash, const char*hmac) int main (int argc, char *argv[]) { - if (_check_hash(MAC1, KEY1, HASH1, HMAC1) != 0) - return 1; - /* Again, for good measure. (Or rather to make sure there's no - state left.) */ - if (_check_hash(MAC1, KEY1, HASH1, HMAC1) != 0) - return 1; - if (_check_hash(MAC1_UC, KEY1, HASH1, HMAC1) != 0) - return 1; - if (_check_hash(MAC1_APPENDED, KEY1, HASH1, HMAC1) != 0) - return 1; - if (_check_hash(MAC1_WEIRD, KEY1, HASH1, HMAC1) != 0) - return 1; + int testcount = 5; + printf("1..%d\n", testcount); + testcount = 1; + + if (_check_hash(MAC1, KEY1, HASH1, HMAC1) != 0) + printf("not "); + printf("ok %d - basic hash\n", testcount++); + + /* Again, for good measure. (Or rather to make sure there's no + state left.) */ + if (_check_hash(MAC1, KEY1, HASH1, HMAC1) != 0) + printf("not "); + printf("ok %d - hash stateless\n", testcount++); + + if (_check_hash(MAC1_UC, KEY1, HASH1, HMAC1) != 0) + printf("not "); + printf("ok %d - hash uppercase\n", testcount++); + if (_check_hash(MAC1_APPENDED, KEY1, HASH1, HMAC1) != 0) + printf("not "); + printf("ok %d - hash appended\n", testcount++); + if (_check_hash(MAC1_WEIRD, KEY1, HASH1, HMAC1) != 0) + printf("not "); + printf("ok %d - hash weird\n", testcount++); return 0; } diff --git a/tests/t_resizeattr.c b/tests/t_resizeattr.c new file mode 100644 index 0000000..2b5214c --- /dev/null +++ b/tests/t_resizeattr.c @@ -0,0 +1,61 @@ +/* Copyright (C) 2019, SWITCH */ +/* See LICENSE for licensing information. */ + +#include +#include +#include +#include "../rewrite.h" + +int test_resize(int start_size, int target_size, uint8_t shouldfail) { + + uint8_t *value = malloc(start_size); + struct tlv *attr; + + memset(value, 42, start_size); + attr = maketlv(1,start_size,value); + + if (!resizeattr(attr, target_size)) + return shouldfail; + else if (shouldfail) + return 0; + + if (attr->l != target_size) + return 0; + + if (memcmp(attr->v, value, target_size <= start_size ? target_size : start_size)) + return 0; + + freetlv(attr); + free(value); + return 1; +} + +int main (int argc, char *argv[]) +{ + int testcount = 4; + + printf("1..%d\n", testcount); + testcount = 1; + + /* test resizeattr normal */ + if (!test_resize(4, 8, 0)) + printf("not "); + printf("ok %d - resizeattr\n", testcount++); + + /* test resizeattr to 0 */ + if (!test_resize(4, 0, 0)) + printf ("not "); + printf("ok %d - resizeattr to zero\n", testcount++); + + /* test resizeattr to max size */ + if (!test_resize(128, 253, 0)) + printf ("not "); + printf("ok %d - resizeattr to max size\n", testcount++); + + /* test resizeattr to oversize */ + if (!test_resize(128, 254, 1)) + printf ("not "); + printf("ok %d - resizeattr to oversize\n", testcount++); + + return 0; +} diff --git a/tests/t_rewrite.c b/tests/t_rewrite.c index 9a47026..f01c051 100644 --- a/tests/t_rewrite.c +++ b/tests/t_rewrite.c @@ -6,30 +6,33 @@ #include #include "../rewrite.h" #include "../radmsg.h" +#include "../debug.h" /*origattrs and expectedattrs as struct tlv*/ /*return 0 if expected; 1 otherwise or error*/ static int -_check_rewrite(struct list *origattrs, struct rewrite *rewrite, struct list *expectedattrs) { +_check_rewrite(struct list *origattrs, struct rewrite *rewrite, struct list *expectedattrs, int shouldfail) { struct radmsg msg; struct list_node *n,*m; msg.attrs = origattrs; - if(!dorewrite(&msg, rewrite)) + if(dorewrite(&msg, rewrite) == shouldfail) { + if (shouldfail) + printf("dorewrite expected to fail, but it didn't\n"); + else + printf("dorewrite failed\n"); return 1; + } - if(list_count(expectedattrs) != list_count(origattrs)) { - printf("bad attribute list length!"); + if(list_count(expectedattrs) != list_count(msg.attrs)) { + printf("bad attribute list length! expected %d, was %d\n", list_count(expectedattrs), list_count(msg.attrs)); return 1; } m=list_first(origattrs); for(n=list_first(expectedattrs); n; n=list_next(n)) { - if (((struct tlv *)n->data)->t != ((struct tlv *)m->data)->t || - ((struct tlv *)n->data)->l != ((struct tlv *)m->data)->l || - memcmp(((struct tlv *)n->data)->v, ((struct tlv *)m->data)->v, ((struct tlv *)n->data)->l) ) { - - printf("attribute list not as expected"); + if (!eqtlv((struct tlv *)n->data, (struct tlv *)m->data)) { + printf("attribute list not as expected\n"); return 1; } m=list_next(m); @@ -37,11 +40,29 @@ _check_rewrite(struct list *origattrs, struct rewrite *rewrite, struct list *exp return 0; } +void _list_clear(struct list *list) { + void *data; + while ( (data = list_shift(list)) ) + free(data); +} + +void _reset_rewrite(struct rewrite *rewrite) { + rewrite->removeattrs = NULL; + rewrite->removevendorattrs = NULL; + _list_clear(rewrite->addattrs); + _list_clear(rewrite->modattrs); + _list_clear(rewrite->supattrs); +} + int main (int argc, char *argv[]) { + int testcount = 6; struct list *origattrs, *expectedattrs; struct rewrite rewrite; + char *username = "user@realm"; + + debug_init("t_rewrite"); origattrs=list_create(); expectedattrs=list_create(); @@ -52,9 +73,129 @@ main (int argc, char *argv[]) rewrite.modattrs = list_create(); rewrite.supattrs = list_create(); - /* test 1: empty noop */ - if (_check_rewrite(origattrs, &rewrite, expectedattrs)) - return 1; + printf("1..%d\n", testcount); + testcount = 1; + + /* test empty rewrite */ + { + list_push(origattrs, maketlv(RAD_Attr_User_Name, sizeof(username), username)); + list_push(expectedattrs, maketlv(RAD_Attr_User_Name, sizeof(username), username)); + if (_check_rewrite(origattrs, &rewrite, expectedattrs, 0)) + printf("not "); + printf("ok %d - empty rewrite\n", testcount++); + _list_clear(origattrs); + _list_clear(expectedattrs); + _reset_rewrite(&rewrite); + } + + /* test removeattr */ + { + uint8_t removeattrs[] = {1,2,0}; + + rewrite.removeattrs = removeattrs; + list_push(origattrs, maketlv(1, sizeof(username), username)); + list_push(origattrs, maketlv(3, sizeof(username), username)); + + list_push(expectedattrs, maketlv(3, sizeof(username), username)); + + if (_check_rewrite(origattrs, &rewrite, expectedattrs, 0)) + printf("not "); + printf("ok %d - removeattrs\n", testcount++); + _list_clear(origattrs); + _list_clear(expectedattrs); + _reset_rewrite(&rewrite); + } + + /* test removevendorattrs full: remove a vendor attribute completely*/ + { + uint32_t removevendorattrs[] = {42,256,0}; + uint8_t value = 42; + + rewrite.removevendorattrs = removevendorattrs; + list_push(origattrs, maketlv(1, sizeof(username), username)); + list_push(origattrs, makevendortlv(42, maketlv(1, 1, &value))); + list_push(origattrs, makevendortlv(43, maketlv(1, 1, &value))); + + list_push(expectedattrs, maketlv(1, sizeof(username), username)); + list_push(expectedattrs, makevendortlv(43, maketlv(1, 1, &value))); + + if (_check_rewrite(origattrs, &rewrite, expectedattrs, 0)) + printf("not "); + printf("ok %d - removevendorattrs full\n", testcount++); + _list_clear(origattrs); + _list_clear(expectedattrs); + _reset_rewrite(&rewrite); + } - return 0; + /* test removevendorattrs last element: remove vendor attribute if last subattribute removed*/ + { + uint32_t removevendorattrs[] = {42,2,0}; /*,45,12}; remove vendor 42, type 2; vendor 43 all, vendor 45 type 12} */ + uint8_t value = 42; + + rewrite.removevendorattrs = removevendorattrs; + list_push(origattrs, makevendortlv(42, maketlv(1, 1, &value))); + list_push(origattrs, makevendortlv(42, maketlv(2, 1, &value))); + list_push(origattrs, makevendortlv(43, maketlv(2, 1, &value))); + + list_push(expectedattrs, makevendortlv(42, maketlv(1, 1, &value))); + list_push(expectedattrs, makevendortlv(43, maketlv(2, 1, &value))); + + if (_check_rewrite(origattrs, &rewrite, expectedattrs, 0)) + printf("not "); + printf("ok %d - removevendorattrs last element\n", testcount++); + _list_clear(origattrs); + _list_clear(expectedattrs); + _reset_rewrite(&rewrite); + } + + /* test removevendorattrs non-rfc: dont remove if format doesn't follow rfc recommendation*/ + { + uint32_t removevendorattrs[] = {42,1,0}; + uint8_t vendor_nonrfc[] = {0, 0, 0, 45, 1, 0x12, 0x23}; + + rewrite.removevendorattrs = removevendorattrs; + list_push(origattrs, maketlv(26, 7, vendor_nonrfc)); + + list_push(expectedattrs, maketlv(26, 7, vendor_nonrfc)); + + if (_check_rewrite(origattrs, &rewrite, expectedattrs, 0)) + printf("not "); + printf("ok %d - removevendorattrs non-rfc\n", testcount++); + _list_clear(origattrs); + _list_clear(expectedattrs); + _reset_rewrite(&rewrite); + } + + /* test removevendorattrs partial attribute */ + { + uint32_t removevendorattrs[] = {42,2,0}; + uint8_t vendor_long1_in[] = {0,0,0,42,2,3,0,1,3,0}; + uint8_t vendor_long1_out[] = {0,0,0,42,1,3,0}; + uint8_t vendor_long2_in[] = {0,0,0,42,1,3,0,2,3,0}; + uint8_t vendor_long2_out[] = {0,0,0,42,1,3,0}; + uint8_t vendor_long3_in[] = {0,0,0,42,1,3,0,2,3,0,3,3,0}; + uint8_t vendor_long3_out[] = {0,0,0,42,1,3,0,3,3,0}; + + rewrite.removevendorattrs = removevendorattrs; + list_push(origattrs, maketlv(26, sizeof(vendor_long1_in), vendor_long1_in)); + list_push(origattrs, maketlv(26, sizeof(vendor_long2_in), vendor_long2_in)); + list_push(origattrs, maketlv(26, sizeof(vendor_long3_in), vendor_long3_in)); + + list_push(expectedattrs, maketlv(26, sizeof(vendor_long1_out), vendor_long1_out)); + list_push(expectedattrs, maketlv(26, sizeof(vendor_long2_out), vendor_long2_out)); + list_push(expectedattrs, maketlv(26, sizeof(vendor_long3_out), vendor_long3_out)); + + if (_check_rewrite(origattrs, &rewrite, expectedattrs, 0)) + printf("not "); + printf("ok %d - removevendorattrs sub-attribute\n", testcount++); + _list_clear(origattrs); + _list_clear(expectedattrs); + _reset_rewrite(&rewrite); + } + + + + + + return 0; } From de1968249e8b2d8e42167b31338d79f084a450d4 Mon Sep 17 00:00:00 2001 From: Fabian Mauchle Date: Tue, 5 Feb 2019 18:13:36 +0100 Subject: [PATCH 07/22] test rewrite add and supplement -fix attribute size bounds check --- radmsg.c | 4 +- tests/t_rewrite.c | 105 +++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 106 insertions(+), 3 deletions(-) diff --git a/radmsg.c b/radmsg.c index 28373da..7bcbabb 100644 --- a/radmsg.c +++ b/radmsg.c @@ -51,7 +51,7 @@ struct radmsg *radmsg_init(uint8_t code, uint8_t id, uint8_t *auth) { int radmsg_add(struct radmsg *msg, struct tlv *attr) { if (!msg || !msg->attrs) return 1; - if (!attr) + if (!attr || attr->l > RAD_Max_Attr_Value_Length) return 0; return list_push(msg->attrs, attr); } @@ -389,7 +389,7 @@ struct tlv *makevendortlv(uint32_t vendor, struct tlv *attr){ struct tlv *newtlv = NULL; uint8_t l, *v; - if (!attr) + if (!attr || attr->l > (RAD_Max_Attr_Value_Length - 6)) return NULL; l = attr->l + 2 + 4; v = malloc(l); diff --git a/tests/t_rewrite.c b/tests/t_rewrite.c index f01c051..b2c4af1 100644 --- a/tests/t_rewrite.c +++ b/tests/t_rewrite.c @@ -57,7 +57,7 @@ void _reset_rewrite(struct rewrite *rewrite) { int main (int argc, char *argv[]) { - int testcount = 6; + int testcount = 12; struct list *origattrs, *expectedattrs; struct rewrite rewrite; char *username = "user@realm"; @@ -193,6 +193,109 @@ main (int argc, char *argv[]) _reset_rewrite(&rewrite); } + /* test simple add */ + { + char *value = "hello world"; + + list_push(rewrite.addattrs, maketlv(1, sizeof(value), value)); + list_push(expectedattrs, maketlv(1,sizeof(value), value)); + + if (_check_rewrite(origattrs, &rewrite, expectedattrs, 0)) + printf("not "); + printf("ok %d - addattribute simple\n", testcount++); + + _list_clear(origattrs); + _list_clear(expectedattrs); + _reset_rewrite(&rewrite); + } + + /* test add with existing attributes*/ + { + char *value = "hello world"; + uint8_t value2 = 42; + + list_push(rewrite.addattrs, maketlv(1, sizeof(value), value)); + list_push(origattrs, maketlv(2, sizeof(value), value)); + list_push(origattrs, maketlv(1, 1, &value2)); + + list_push(expectedattrs, maketlv(2,sizeof(value), value)); + list_push(expectedattrs, maketlv(1,1, &value2)); + list_push(expectedattrs, maketlv(1,sizeof(value), value)); + + if (_check_rewrite(origattrs, &rewrite, expectedattrs, 0)) + printf("not "); + printf("ok %d - addattribute with existing attributes\n", testcount++); + + _list_clear(origattrs); + _list_clear(expectedattrs); + _reset_rewrite(&rewrite); + } + + /* test add null*/ + { + list_push(rewrite.addattrs, maketlv(1, 0, NULL)); + list_push(expectedattrs, maketlv(1,0, NULL)); + + if (_check_rewrite(origattrs, &rewrite, expectedattrs, 0)) + printf("not "); + printf("ok %d - addattribute null\n", testcount++); + + _list_clear(origattrs); + _list_clear(expectedattrs); + _reset_rewrite(&rewrite); + } + + /* test add too big*/ + { + uint8_t *value = malloc(254); + memset(value, 0, 254); + + list_push(rewrite.addattrs, maketlv(1, 254, value)); + + if (_check_rewrite(origattrs, &rewrite, expectedattrs, 1)) + printf("not "); + printf("ok %d - addattribute too big\n", testcount++); + + free(value); + _list_clear(origattrs); + _list_clear(expectedattrs); + _reset_rewrite(&rewrite); + } + + /* test supplement non-existing*/ + { + char *value = "hello world"; + + list_push(rewrite.supattrs, maketlv(1, sizeof(value), value)); + list_push(expectedattrs, maketlv(1,sizeof(value), value)); + + if (_check_rewrite(origattrs, &rewrite, expectedattrs, 0)) + printf("not "); + printf("ok %d - suppattrs non existing\n", testcount++); + + _list_clear(origattrs); + _list_clear(expectedattrs); + _reset_rewrite(&rewrite); + } + + /* test supplement existing*/ + { + char *value = "hello world"; + char *value2 = "hello radsec"; + + list_push(rewrite.supattrs, maketlv(1, sizeof(value2), value2)); + list_push(origattrs, maketlv(1,sizeof(value), value)); + list_push(expectedattrs, maketlv(1,sizeof(value), value)); + + if (_check_rewrite(origattrs, &rewrite, expectedattrs, 0)) + printf("not "); + printf("ok %d - suppattrs existing\n", testcount++); + + _list_clear(origattrs); + _list_clear(expectedattrs); + _reset_rewrite(&rewrite); + } + From 0cb624b9a3016d438dffe53ddc856c68f55d367b Mon Sep 17 00:00:00 2001 From: Fabian Mauchle Date: Sun, 10 Feb 2019 17:11:42 +0100 Subject: [PATCH 08/22] test rewrite modifyAttribute --- tests/t_rewrite.c | 189 +++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 170 insertions(+), 19 deletions(-) diff --git a/tests/t_rewrite.c b/tests/t_rewrite.c index b2c4af1..6becac1 100644 --- a/tests/t_rewrite.c +++ b/tests/t_rewrite.c @@ -57,7 +57,7 @@ void _reset_rewrite(struct rewrite *rewrite) { int main (int argc, char *argv[]) { - int testcount = 12; + int testcount = 18; struct list *origattrs, *expectedattrs; struct rewrite rewrite; char *username = "user@realm"; @@ -78,8 +78,8 @@ main (int argc, char *argv[]) /* test empty rewrite */ { - list_push(origattrs, maketlv(RAD_Attr_User_Name, sizeof(username), username)); - list_push(expectedattrs, maketlv(RAD_Attr_User_Name, sizeof(username), username)); + list_push(origattrs, maketlv(RAD_Attr_User_Name, strlen(username), username)); + list_push(expectedattrs, maketlv(RAD_Attr_User_Name, strlen(username), username)); if (_check_rewrite(origattrs, &rewrite, expectedattrs, 0)) printf("not "); printf("ok %d - empty rewrite\n", testcount++); @@ -93,10 +93,10 @@ main (int argc, char *argv[]) uint8_t removeattrs[] = {1,2,0}; rewrite.removeattrs = removeattrs; - list_push(origattrs, maketlv(1, sizeof(username), username)); - list_push(origattrs, maketlv(3, sizeof(username), username)); + list_push(origattrs, maketlv(1, strlen(username), username)); + list_push(origattrs, maketlv(3, strlen(username), username)); - list_push(expectedattrs, maketlv(3, sizeof(username), username)); + list_push(expectedattrs, maketlv(3, strlen(username), username)); if (_check_rewrite(origattrs, &rewrite, expectedattrs, 0)) printf("not "); @@ -112,11 +112,11 @@ main (int argc, char *argv[]) uint8_t value = 42; rewrite.removevendorattrs = removevendorattrs; - list_push(origattrs, maketlv(1, sizeof(username), username)); + list_push(origattrs, maketlv(1, strlen(username), username)); list_push(origattrs, makevendortlv(42, maketlv(1, 1, &value))); list_push(origattrs, makevendortlv(43, maketlv(1, 1, &value))); - list_push(expectedattrs, maketlv(1, sizeof(username), username)); + list_push(expectedattrs, maketlv(1, strlen(username), username)); list_push(expectedattrs, makevendortlv(43, maketlv(1, 1, &value))); if (_check_rewrite(origattrs, &rewrite, expectedattrs, 0)) @@ -197,8 +197,8 @@ main (int argc, char *argv[]) { char *value = "hello world"; - list_push(rewrite.addattrs, maketlv(1, sizeof(value), value)); - list_push(expectedattrs, maketlv(1,sizeof(value), value)); + list_push(rewrite.addattrs, maketlv(1, strlen(value), value)); + list_push(expectedattrs, maketlv(1,strlen(value), value)); if (_check_rewrite(origattrs, &rewrite, expectedattrs, 0)) printf("not "); @@ -214,13 +214,13 @@ main (int argc, char *argv[]) char *value = "hello world"; uint8_t value2 = 42; - list_push(rewrite.addattrs, maketlv(1, sizeof(value), value)); - list_push(origattrs, maketlv(2, sizeof(value), value)); + list_push(rewrite.addattrs, maketlv(1, strlen(value), value)); + list_push(origattrs, maketlv(2, strlen(value), value)); list_push(origattrs, maketlv(1, 1, &value2)); - list_push(expectedattrs, maketlv(2,sizeof(value), value)); + list_push(expectedattrs, maketlv(2,strlen(value), value)); list_push(expectedattrs, maketlv(1,1, &value2)); - list_push(expectedattrs, maketlv(1,sizeof(value), value)); + list_push(expectedattrs, maketlv(1,strlen(value), value)); if (_check_rewrite(origattrs, &rewrite, expectedattrs, 0)) printf("not "); @@ -266,8 +266,8 @@ main (int argc, char *argv[]) { char *value = "hello world"; - list_push(rewrite.supattrs, maketlv(1, sizeof(value), value)); - list_push(expectedattrs, maketlv(1,sizeof(value), value)); + list_push(rewrite.supattrs, maketlv(1, strlen(value), value)); + list_push(expectedattrs, maketlv(1,strlen(value), value)); if (_check_rewrite(origattrs, &rewrite, expectedattrs, 0)) printf("not "); @@ -283,9 +283,9 @@ main (int argc, char *argv[]) char *value = "hello world"; char *value2 = "hello radsec"; - list_push(rewrite.supattrs, maketlv(1, sizeof(value2), value2)); - list_push(origattrs, maketlv(1,sizeof(value), value)); - list_push(expectedattrs, maketlv(1,sizeof(value), value)); + list_push(rewrite.supattrs, maketlv(1, strlen(value2), value2)); + list_push(origattrs, maketlv(1,strlen(value), value)); + list_push(expectedattrs, maketlv(1,strlen(value), value)); if (_check_rewrite(origattrs, &rewrite, expectedattrs, 0)) printf("not "); @@ -296,7 +296,158 @@ main (int argc, char *argv[]) _reset_rewrite(&rewrite); } + /* test modify no match*/ + { + char *value = "hello world"; + char *value2 = "foo bar"; + struct modattr *mod = malloc(sizeof(struct modattr)); + regex_t regex; + + mod->t = 1; + mod->regex = ®ex; + mod->replacement = value2; + regcomp(mod->regex, "hello bar", REG_ICASE | REG_EXTENDED); + + list_push(rewrite.modattrs, mod); + list_push(origattrs, maketlv(1,strlen(value), value)); + list_push(expectedattrs, maketlv(1,strlen(value), value)); + + if (_check_rewrite(origattrs, &rewrite, expectedattrs, 0)) + printf("not "); + printf("ok %d - modify attribute no match\n", testcount++); + + _list_clear(origattrs); + _list_clear(expectedattrs); + _reset_rewrite(&rewrite); + } + + /* test modify match full replace*/ + { + char *value = "hello world"; + char *value2 = "foo bar"; + struct modattr *mod = malloc(sizeof(struct modattr)); + regex_t regex; + + mod->t = 1; + mod->regex = ®ex; + mod->replacement = value2; + regcomp(mod->regex, "hello world", REG_ICASE | REG_EXTENDED); + + list_push(rewrite.modattrs, mod); + list_push(origattrs, maketlv(1,strlen(value), value)); + list_push(expectedattrs, maketlv(1,strlen(value2), value2)); + + if (_check_rewrite(origattrs, &rewrite, expectedattrs, 0)) + printf("not "); + printf("ok %d - modify attribute match full replace\n", testcount++); + + _list_clear(origattrs); + _list_clear(expectedattrs); + _reset_rewrite(&rewrite); + } + + /* test modify match partial replace*/ + { + char *value = "hello world"; + char *value2 = "hello foo"; + struct modattr *mod = malloc(sizeof(struct modattr)); + regex_t regex; + + mod->t = 1; + mod->regex = ®ex; + mod->replacement = "\\1 foo"; + regcomp(mod->regex, "(hello) world", REG_ICASE | REG_EXTENDED); + + list_push(rewrite.modattrs, mod); + list_push(origattrs, maketlv(1,strlen(value), value)); + list_push(expectedattrs, maketlv(1,strlen(value2), value2)); + + if (_check_rewrite(origattrs, &rewrite, expectedattrs, 0)) + printf("not "); + printf("ok %d - modify attribute match full replace\n", testcount++); + + _list_clear(origattrs); + _list_clear(expectedattrs); + _reset_rewrite(&rewrite); + } + + /* test modify max length*/ + { + char *value = "hello radsecproxy..."; /*make this 20 chars long 8*/ + char value2[254]; + int i; + struct modattr *mod = malloc(sizeof(struct modattr)); + regex_t regex; + + for (i=0; i<253; i+=20){ + memcpy(value2+i, value, 20); + } + memcpy(value2+i-20, "and another13\0", 14); + + mod->t = 1; + mod->regex = ®ex; + mod->replacement = "\\1\\1\\1\\1\\1\\1\\1\\1\\1\\1\\1\\1and another13"; + regcomp(mod->regex, "(.*)", REG_ICASE | REG_EXTENDED); + + list_push(rewrite.modattrs, mod); + list_push(origattrs, maketlv(1,strlen(value), value)); + list_push(expectedattrs, maketlv(1,strlen(value2), value2)); + + if (_check_rewrite(origattrs, &rewrite, expectedattrs, 0)) + printf("not "); + printf("ok %d - modify attribute max length\n", testcount++); + + _list_clear(origattrs); + _list_clear(expectedattrs); + _reset_rewrite(&rewrite); + } + + /* test modify too long*/ + { + char *value = "hello radsecproxy..."; /*make this 20 chars long 8*/ + struct modattr *mod = malloc(sizeof(struct modattr)); + regex_t regex; + mod->t = 1; + mod->regex = ®ex; + mod->replacement = "\\1\\1\\1\\1\\1\\1\\1\\1\\1\\1\\1\\1and another14!"; + regcomp(mod->regex, "(.*)", REG_ICASE | REG_EXTENDED); + + list_push(rewrite.modattrs, mod); + list_push(origattrs, maketlv(1,strlen(value), value)); + list_push(expectedattrs, maketlv(1,strlen(value), value)); + if (_check_rewrite(origattrs, &rewrite, expectedattrs, 1)) + printf("not "); + printf("ok %d - modify attribute too long\n", testcount++); + + _list_clear(origattrs); + _list_clear(expectedattrs); + _reset_rewrite(&rewrite); + } + + /* test modify regex replace*/ + { + char *value = "hello"; + char *value2 = "hellohellohellohellohellohellohellohellohello"; + struct modattr *mod = malloc(sizeof(struct modattr)); + regex_t regex; + mod->t = 1; + mod->regex = ®ex; + mod->replacement = "\\1\\2\\3\\4\\5\\6\\7\\8\\9"; + regcomp(mod->regex, "(((((((((hello)))))))))", REG_ICASE | REG_EXTENDED); + + list_push(rewrite.modattrs, mod); + list_push(origattrs, maketlv(1,strlen(value), value)); + list_push(expectedattrs, maketlv(1,strlen(value2), value2)); + + if (_check_rewrite(origattrs, &rewrite, expectedattrs, 0)) + printf("not "); + printf("ok %d - modify attribute regex replace\n", testcount++); + + _list_clear(origattrs); + _list_clear(expectedattrs); + _reset_rewrite(&rewrite); + } From a3e64eb674c5bbc2fe076346dff323d2206cbc43 Mon Sep 17 00:00:00 2001 From: Fabian Mauchle Date: Thu, 11 Apr 2019 07:39:31 +0200 Subject: [PATCH 09/22] handle %00 in rewrite configs --- .gitignore | 1 + gconfig.c | 20 +++++++++------ gconfig.h | 1 + radsecproxy.conf.5 | 19 ++++++++------ rewrite.c | 23 +++++++++++++---- tests/Makefile.am | 2 +- tests/t_rewrite_config.c | 53 ++++++++++++++++++++++++++++++++++++++++ 7 files changed, 98 insertions(+), 21 deletions(-) create mode 100644 tests/t_rewrite_config.c diff --git a/.gitignore b/.gitignore index c53f184..fa95497 100644 --- a/.gitignore +++ b/.gitignore @@ -25,6 +25,7 @@ radsecproxy-hash build-aux/* tests/t_fticks tests/t_rewrite +tests/t_rewrite_config tests/t_resizeattr tests/*.log tests/*.trs diff --git a/gconfig.c b/gconfig.c index 6a5d26b..81fe63e 100644 --- a/gconfig.c +++ b/gconfig.c @@ -366,16 +366,20 @@ uint8_t hexdigit2int(char d) { return 0; } -void unhex(char *s) { +int unhex(char *s, uint8_t process_null) { + int len = 0; char *t; for (t = s; *t; s++) { - if (*t == '%' && isxdigit((int)t[1]) && isxdigit((int)t[2])) { - *s = 16 * hexdigit2int(t[1]) + hexdigit2int(t[2]); - t += 3; - } else - *s = *t++; + if (*t == '%' && isxdigit((int)t[1]) && isxdigit((int)t[2]) && + (process_null || !(t[1]=='0' && t[2]=='0'))) { + *s = 16 * hexdigit2int(t[1]) + hexdigit2int(t[2]); + t += 3; + } else + *s = *t++; + len++; } *s = '\0'; + return len; } typedef int (*t_fptr)(struct gconffile **, void *, char *, char *, char *); @@ -466,7 +470,7 @@ int getgenericconfig(struct gconffile **cf, char *block, ...) { debug(DBG_ERR, "configuration error, option %s already set to %s", opt, *str); goto errexit; } - unhex(val); + unhex(val,0); *str = val; break; case CONF_MSTR: @@ -479,7 +483,7 @@ int getgenericconfig(struct gconffile **cf, char *block, ...) { debug(DBG_ERR, "malloc failed"); goto errexit; } - unhex(val); + unhex(val,0); newmstr[n] = val; newmstr[n + 1] = NULL; *mstr = newmstr; diff --git a/gconfig.h b/gconfig.h index 3cb34b3..a5276f1 100644 --- a/gconfig.h +++ b/gconfig.h @@ -26,6 +26,7 @@ int popgconf(struct gconffile **cf); void freegconfmstr(char **mstr); void freegconf(struct gconffile **cf); struct gconffile *openconfigfile(const char *file); +int unhex(char *s, uint8_t process_null); /* Local Variables: */ /* c-file-style: "stroustrup" */ diff --git a/radsecproxy.conf.5 b/radsecproxy.conf.5 index 1916f1e..6ff97a9 100644 --- a/radsecproxy.conf.5 +++ b/radsecproxy.conf.5 @@ -61,11 +61,13 @@ blocktype name { .fi .RE -Option value characters can also be written in hex. This is done by writing the -character % followed by two hexadecimal digits. If a % is used without two -following hexadecimal digits, the % and the following characters are used as -written. If you want to write a % and not use this decoding, you may of course -write % in hex; i.e., %25. +Option value characters can also be written in hex for options requiring a +string type value. This is done by writing the character % followed by two +hexadecimal digits. If a % is used without two following hexadecimal digits, the +% and the following characters are used as written. If you want to write a % and +not use this decoding, you may of course write % in hex; i.e., %25. As %00 would +terminate a string, this value is not converted in most cases, except when used +with rewrite statements. Some options allow or require the use of regular expressions, denoted as \fIregex\fR. The POSIX extended RE system is used, see @@ -808,8 +810,11 @@ block are: .RS Add an \fIattribute\fR to the radius mesage and set it to \fIvalue\fR. The \fIattribute\fR must be specified using the numerical attribute id. The -\fIvalue\fR can either be numerical, a string, or a hex value. See the -\fBCONFIGURATION SYNTAX\fR section for details. +\fIvalue\fR can either be numerical, a string, or a hex value. If the value +starts with a number, it is interpreted as a 32bit unsigned integer. Use the ' +character at the start of the value to force string interpretation. When using +hex value, it is recommended to also lead with ' to avoid unintended numeric +interpretation. See the \fBCONFIGURATION SYNTAX\fR section for further details. .RE .BI "AddVendorAttribute " vendor \fR: subattribute \fR: value diff --git a/rewrite.c b/rewrite.c index 0072a66..58e6aff 100644 --- a/rewrite.c +++ b/rewrite.c @@ -1,7 +1,7 @@ /* Copyright (c) 2019, SWITCH */ /* See LICENSE for licensing information. */ -#include +#include #include #include #include @@ -25,6 +25,7 @@ static struct hash *rewriteconfs; struct tlv *extractattr(char *nameval, char vendor_flag) { int len, name = 0; int vendor = 0; /* Vendor 0 is reserved, see RFC 1700. */ + uint32_t ival=0; char *s, *s2; struct tlv *a; @@ -41,9 +42,21 @@ struct tlv *extractattr(char *nameval, char vendor_flag) { name = atoi(s + 1); s = s2; } - len = strlen(s + 1); - if (len > 253) - return NULL; + + s++; + if (isdigit(*s)) { + ival = atoi(s); + ival = htonl(ival); + len = 4; + s = (char *)&ival; + } else { + if (*s == '\'') + s++; + + len = unhex(s,1); + if (len > 253) + return NULL; + } if (name < 1 || name > 255) return NULL; @@ -51,7 +64,7 @@ struct tlv *extractattr(char *nameval, char vendor_flag) { if (!a) return NULL; - a->v = (uint8_t *)stringcopy(s + 1, 0); + a->v = (uint8_t *)stringcopy(s, len); if (!a->v) { free(a); return NULL; diff --git a/tests/Makefile.am b/tests/Makefile.am index 4254574..a795140 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -4,7 +4,7 @@ AUTOMAKE_OPTIONS = foreign LOG_DRIVER = env AM_TAP_AWK='$(AWK)' $(SHELL) \ $(top_srcdir)/build-aux/tap-driver.sh -check_PROGRAMS = t_fticks t_rewrite t_resizeattr +check_PROGRAMS = t_fticks t_rewrite t_resizeattr t_rewrite_config AM_CFLAGS = -g -Wall -Werror @SSL_CFLAGS@ @TARGET_CFLAGS@ LDADD = $(top_builddir)/librsp.a @SSL_LIBS@ diff --git a/tests/t_rewrite_config.c b/tests/t_rewrite_config.c new file mode 100644 index 0000000..2c79331 --- /dev/null +++ b/tests/t_rewrite_config.c @@ -0,0 +1,53 @@ +/* Copyright (C) 2019, SWITCH */ +/* See LICENSE for licensing information. */ + +#include +#include +#include +#include "../rewrite.h" +#include "../radmsg.h" +#include "../debug.h" +#include "../util.h" + +int +main (int argc, char *argv[]) +{ + struct rewrite *result; + char *rewritename = "rewrite"; + char **addattrs; + int numtests = 1, i; + struct tlv *tlv, *expected; + uint8_t expectedvalue[] = {'1',0,0,'1','A'}; + + printf("1..%d\n", numtests); + numtests = 1; + + addattrs = malloc(2); + addattrs[0] = stringcopy("1:'1%00%001%41", 0); + addattrs[1] = NULL; + + expected = maketlv(1,5,expectedvalue); + + addrewrite(rewritename, NULL, NULL, addattrs, + NULL, NULL, NULL, NULL); + + result = getrewrite(rewritename, NULL); + + if (result->addattrs->first) { + tlv = (struct tlv *)result->addattrs->first->data; + if (!eqtlv(tlv, expected)) { + printf ("tlv value was: 0x"); + for (i = 0; i < tlv->l; i++) { + printf ("%x", *((tlv->v)+i)); + } + printf ("\n"); + printf ("not "); + } + printf("ok %d - rewrite config\n", numtests++); + } else { + printf("not ok %d - rewrite ocnfig\n", numtests++); + } + + + return 0; +} From 4a3361f433feccd60d3cf2614431c538eefad0a0 Mon Sep 17 00:00:00 2001 From: Fabian Mauchle Date: Mon, 15 Apr 2019 07:43:36 +0200 Subject: [PATCH 10/22] move resizeattr/resizetlv to respective knowledge owners --- radmsg.c | 9 +++++++++ radmsg.h | 1 + rewrite.c | 23 +---------------------- rewrite.h | 1 - tlv11.c | 12 ++++++++++++ tlv11.h | 1 + 6 files changed, 24 insertions(+), 23 deletions(-) diff --git a/radmsg.c b/radmsg.c index 7bcbabb..750b166 100644 --- a/radmsg.c +++ b/radmsg.c @@ -405,6 +405,15 @@ struct tlv *makevendortlv(uint32_t vendor, struct tlv *attr){ return newtlv; } +int resizeattr(struct tlv *attr, uint8_t newlen) { + if (newlen > RAD_Max_Attr_Value_Length) + return 0; + + if (resizetlv(attr, newlen)) + return 1; + return 0; +} + /* Local Variables: */ /* c-file-style: "stroustrup" */ /* End: */ diff --git a/radmsg.h b/radmsg.h index 1fc4d3b..fadc375 100644 --- a/radmsg.h +++ b/radmsg.h @@ -58,6 +58,7 @@ uint8_t attrname2val(char *attrname); int vattrname2val(char *attrname, uint32_t *vendor, uint32_t *type); int attrvalidate(unsigned char *attrs, int length); struct tlv *makevendortlv(uint32_t vendor, struct tlv *attr); +int resizeattr(struct tlv *attr, uint8_t newlen); #endif /*_RADMSG_H*/ diff --git a/rewrite.c b/rewrite.c index 58e6aff..13921db 100644 --- a/rewrite.c +++ b/rewrite.c @@ -278,22 +278,6 @@ struct rewrite *getrewrite(char *alt1, char *alt2) { return NULL; } -int resizeattr(struct tlv *attr, uint8_t newlen) { - uint8_t *newv; - - if (newlen > RAD_Max_Attr_Value_Length) - return 0; - - if (newlen != attr->l) { - newv = realloc(attr->v, newlen); - if (newlen && !newv) - return 0; - attr->v = newv; - attr->l = newlen; - } - return 1; -} - int findvendorsubattr(uint32_t *attrs, uint32_t vendor, uint32_t subattr) { if (!attrs) return 0; @@ -393,13 +377,8 @@ int dorewritemodattr(struct tlv *attr, struct modattr *modattr) { } } reslen += i - start; - if (reslen > 253) { - debug(DBG_INFO, "rewritten attribute length would be %d, max possible is 253, discarding message", reslen); - free(in); - return 0; - } - if (!resizeattr(attr, reslen)) { + debug(DBG_INFO, "rewritten attribute to length %d failed, discarding message", reslen); free(in); return 0; } diff --git a/rewrite.h b/rewrite.h index b7ead27..7176898 100644 --- a/rewrite.h +++ b/rewrite.h @@ -27,7 +27,6 @@ void addrewrite(char *value, char **rmattrs, char **rmvattrs, char **addattrs, int dorewrite(struct radmsg *msg, struct rewrite *rewrite); struct modattr *extractmodattr(char *nameval); struct rewrite *getrewrite(char *alt1, char *alt2); -int resizeattr(struct tlv *attr, uint8_t newlen); int dorewritemodattr(struct tlv *attr, struct modattr *modattr); int addvendorattr(struct radmsg *msg, uint32_t vendor, struct tlv *attr); diff --git a/tlv11.c b/tlv11.c index 7516d6c..d570b39 100644 --- a/tlv11.c +++ b/tlv11.c @@ -105,6 +105,18 @@ uint8_t *tlv2str(struct tlv *tlv) { return s; } +struct tlv *resizetlv(struct tlv *tlv, uint8_t newlen) { + uint8_t *newv; + if (newlen != tlv->l) { + newv = realloc(tlv->v, newlen); + if (newlen && !newv) + return NULL; + tlv->v = newv; + tlv->l = newlen; + } + return tlv; +} + /* Local Variables: */ /* c-file-style: "stroustrup" */ /* End: */ diff --git a/tlv11.h b/tlv11.h index 5384481..84db3d7 100644 --- a/tlv11.h +++ b/tlv11.h @@ -16,6 +16,7 @@ struct list *copytlvlist(struct list *); void freetlvlist(struct list *); void rmtlv(struct list *, uint8_t); uint8_t *tlv2str(struct tlv *tlv); +struct tlv *resizetlv(struct tlv *, uint8_t); /* Local Variables: */ /* c-file-style: "stroustrup" */ From a4c21e08423c6640bacac64dc2fe9bf4b7f2dafa Mon Sep 17 00:00:00 2001 From: Fabian Mauchle Date: Tue, 16 Apr 2019 18:33:15 +0200 Subject: [PATCH 11/22] treat client/server secret as byte array, not string. Fix #31 --- radmsg.c | 30 +++++++++++++-------------- radmsg.h | 4 ++-- radsecproxy.c | 57 ++++++++++++++++++++++++++++----------------------- radsecproxy.h | 4 +++- 4 files changed, 51 insertions(+), 44 deletions(-) diff --git a/radmsg.c b/radmsg.c index 750b166..6828f0d 100644 --- a/radmsg.c +++ b/radmsg.c @@ -120,7 +120,7 @@ int radmsg_copy_attrs(struct radmsg *dst, return n; } -int _checkmsgauth(unsigned char *rad, uint8_t *authattr, uint8_t *secret) { +int _checkmsgauth(unsigned char *rad, uint8_t *authattr, uint8_t *secret, int secret_len) { int result = 0; /* Fail. */ static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER; struct hmac_md5_ctx hmacctx; @@ -132,7 +132,7 @@ int _checkmsgauth(unsigned char *rad, uint8_t *authattr, uint8_t *secret) { memcpy(auth, authattr, 16); memset(authattr, 0, 16); - hmac_md5_set_key(&hmacctx, strlen((char *) secret), secret); + hmac_md5_set_key(&hmacctx, secret_len, secret); hmac_md5_update(&hmacctx, RADLEN(rad), rad); hmac_md5_digest(&hmacctx, sizeof(hash), hash); @@ -149,7 +149,7 @@ int _checkmsgauth(unsigned char *rad, uint8_t *authattr, uint8_t *secret) { return result; } -int _validauth(unsigned char *rad, unsigned char *reqauth, unsigned char *sec) { +int _validauth(unsigned char *rad, unsigned char *reqauth, unsigned char *sec, int sec_len) { static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER; struct md5_ctx mdctx; unsigned char hash[MD5_DIGEST_SIZE]; @@ -163,7 +163,7 @@ int _validauth(unsigned char *rad, unsigned char *reqauth, unsigned char *sec) { md5_update(&mdctx, 16, reqauth); if (len > 20) md5_update(&mdctx, len - 20, rad + 20); - md5_update(&mdctx, strlen((char *) sec), sec); + md5_update(&mdctx, sec_len, sec); md5_digest(&mdctx, sizeof(hash), hash); result = !memcmp(hash, rad + 4, 16); @@ -172,7 +172,7 @@ int _validauth(unsigned char *rad, unsigned char *reqauth, unsigned char *sec) { return result; } -int _createmessageauth(unsigned char *rad, unsigned char *authattrval, uint8_t *secret) { +int _createmessageauth(unsigned char *rad, unsigned char *authattrval, uint8_t *secret, int secret_len) { static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER; struct hmac_md5_ctx hmacctx; @@ -182,7 +182,7 @@ int _createmessageauth(unsigned char *rad, unsigned char *authattrval, uint8_t * pthread_mutex_lock(&lock); memset(authattrval, 0, 16); - hmac_md5_set_key(&hmacctx, strlen((char *) secret), secret); + hmac_md5_set_key(&hmacctx, secret_len, secret); hmac_md5_update(&hmacctx, RADLEN(rad), rad); hmac_md5_digest(&hmacctx, MD5_DIGEST_SIZE, authattrval); @@ -190,7 +190,7 @@ int _createmessageauth(unsigned char *rad, unsigned char *authattrval, uint8_t * return 1; } -int _radsign(unsigned char *rad, unsigned char *sec) { +int _radsign(unsigned char *rad, unsigned char *sec, int sec_len) { static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER; struct md5_ctx mdctx; @@ -198,7 +198,7 @@ int _radsign(unsigned char *rad, unsigned char *sec) { md5_init(&mdctx); md5_update(&mdctx, RADLEN(rad), rad); - md5_update(&mdctx, strlen((char *) sec), sec); + md5_update(&mdctx, sec_len, sec); md5_digest(&mdctx, MD5_DIGEST_SIZE, rad + 4); pthread_mutex_unlock(&lock); @@ -217,7 +217,7 @@ uint8_t *tlv2buf(uint8_t *p, const struct tlv *tlv) { return p; } -uint8_t *radmsg2buf(struct radmsg *msg, uint8_t *secret) { +uint8_t *radmsg2buf(struct radmsg *msg, uint8_t *secret, int secret_len) { struct list_node *node; struct tlv *tlv; int size; @@ -249,12 +249,12 @@ uint8_t *radmsg2buf(struct radmsg *msg, uint8_t *secret) { msgauth = ATTRVAL(p); p += tlv->l + 2; } - if (msgauth && !_createmessageauth(buf, msgauth, secret)) { + if (msgauth && !_createmessageauth(buf, msgauth, secret, secret_len)) { free(buf); return NULL; } if (secret) { - if ((msg->code == RAD_Access_Accept || msg->code == RAD_Access_Reject || msg->code == RAD_Access_Challenge || msg->code == RAD_Accounting_Response || msg->code == RAD_Accounting_Request) && !_radsign(buf, secret)) { + if ((msg->code == RAD_Access_Accept || msg->code == RAD_Access_Reject || msg->code == RAD_Access_Challenge || msg->code == RAD_Accounting_Response || msg->code == RAD_Accounting_Request) && !_radsign(buf, secret, secret_len)) { free(buf); return NULL; } @@ -265,7 +265,7 @@ uint8_t *radmsg2buf(struct radmsg *msg, uint8_t *secret) { } /* if secret set we also validate message authenticator if present */ -struct radmsg *buf2radmsg(uint8_t *buf, uint8_t *secret, uint8_t *rqauth) { +struct radmsg *buf2radmsg(uint8_t *buf, uint8_t *secret, int secret_len, uint8_t *rqauth) { struct radmsg *msg; uint8_t t, l, *v = NULL, *p, auth[16]; uint16_t len; @@ -277,13 +277,13 @@ struct radmsg *buf2radmsg(uint8_t *buf, uint8_t *secret, uint8_t *rqauth) { if (secret && buf[0] == RAD_Accounting_Request) { memset(auth, 0, 16); - if (!_validauth(buf, auth, secret)) { + if (!_validauth(buf, auth, secret, secret_len)) { debug(DBG_WARN, "buf2radmsg: Accounting-Request message authentication failed"); return NULL; } } - if (rqauth && secret && !_validauth(buf, rqauth, secret)) { + if (rqauth && secret && !_validauth(buf, rqauth, secret, secret_len)) { debug(DBG_WARN, "buf2radmsg: Invalid auth, ignoring reply"); return NULL; } @@ -315,7 +315,7 @@ struct radmsg *buf2radmsg(uint8_t *buf, uint8_t *secret, uint8_t *rqauth) { if (t == RAD_Attr_Message_Authenticator && secret) { if (rqauth) memcpy(buf + 4, rqauth, 16); - if (l != 16 || !_checkmsgauth(buf, v, secret)) { + if (l != 16 || !_checkmsgauth(buf, v, secret, secret_len)) { debug(DBG_WARN, "buf2radmsg: message authentication failed"); if (rqauth) memcpy(buf + 4, msg->auth, 16); diff --git a/radmsg.h b/radmsg.h index fadc375..77ce8e5 100644 --- a/radmsg.h +++ b/radmsg.h @@ -52,8 +52,8 @@ struct list *radmsg_getalltype(const struct radmsg *msg, uint8_t type); int radmsg_copy_attrs(struct radmsg *dst, const struct radmsg *src, uint8_t type); -uint8_t *radmsg2buf(struct radmsg *msg, uint8_t *); -struct radmsg *buf2radmsg(uint8_t *, uint8_t *, uint8_t *); +uint8_t *radmsg2buf(struct radmsg *msg, uint8_t *, int); +struct radmsg *buf2radmsg(uint8_t *, uint8_t *, int, uint8_t *); uint8_t attrname2val(char *attrname); int vattrname2val(char *attrname, uint32_t *vendor, uint32_t *type); int attrvalidate(unsigned char *attrs, int length); diff --git a/radsecproxy.c b/radsecproxy.c index fa7f9ff..9ed972f 100644 --- a/radsecproxy.c +++ b/radsecproxy.c @@ -446,7 +446,7 @@ int _internal_sendrq(struct server *to, uint8_t id, struct request *rq) { if (!to->requests[id].rq) { rq->newid = id; rq->msg->id = id; - rq->buf = radmsg2buf(rq->msg, (uint8_t *)to->conf->secret); + rq->buf = radmsg2buf(rq->msg, to->conf->secret, to->conf->secret_len); if (!rq->buf) { pthread_mutex_unlock(to->requests[id].lock); debug(DBG_ERR, "sendrq: radmsg2buf failed"); @@ -525,7 +525,7 @@ void sendreply(struct request *rq) { struct client *to = rq->from; if (!rq->replybuf) - rq->replybuf = radmsg2buf(rq->msg, (uint8_t *)to->conf->secret); + rq->replybuf = radmsg2buf(rq->msg, to->conf->secret, to->conf->secret_len); radmsg_free(rq->msg); rq->msg = NULL; if (!rq->replybuf) { @@ -551,7 +551,7 @@ void sendreply(struct request *rq) { pthread_mutex_unlock(&to->replyq->mutex); } -static int pwdcrypt(char encrypt_flag, uint8_t *in, uint8_t len, char *shared, uint8_t sharedlen, uint8_t *auth) { +static int pwdcrypt(char encrypt_flag, uint8_t *in, uint8_t len, uint8_t *shared, uint8_t sharedlen, uint8_t *auth) { static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER; struct md5_ctx mdctx; unsigned char hash[MD5_DIGEST_SIZE], *input; @@ -562,7 +562,7 @@ static int pwdcrypt(char encrypt_flag, uint8_t *in, uint8_t len, char *shared, u md5_init(&mdctx); input = auth; for (;;) { - md5_update(&mdctx, sharedlen, (uint8_t *) shared); + md5_update(&mdctx, sharedlen, shared); md5_update(&mdctx, 16, input); md5_digest(&mdctx, sizeof(hash), hash); for (i = 0; i < 16; i++) @@ -787,34 +787,34 @@ void removeserversubrealms(struct list *realmlist, struct clsrvconf *srv) { } } -int pwdrecrypt(uint8_t *pwd, uint8_t len, char *oldsecret, char *newsecret, uint8_t *oldauth, uint8_t *newauth) { +int pwdrecrypt(uint8_t *pwd, uint8_t len, uint8_t *oldsecret, int oldsecret_len, uint8_t *newsecret, int newsecret_len, uint8_t *oldauth, uint8_t *newauth) { if (len < 16 || len > 128 || len % 16) { debug(DBG_WARN, "pwdrecrypt: invalid password length"); return 0; } - if (!pwdcrypt(0, pwd, len, oldsecret, strlen(oldsecret), oldauth)) { + if (!pwdcrypt(0, pwd, len, oldsecret, oldsecret_len, oldauth)) { debug(DBG_WARN, "pwdrecrypt: cannot decrypt password"); return 0; } #ifdef DEBUG printfchars(NULL, "pwdrecrypt: password", "%02x ", pwd, len); #endif - if (!pwdcrypt(1, pwd, len, newsecret, strlen(newsecret), newauth)) { + if (!pwdcrypt(1, pwd, len, newsecret, newsecret_len, newauth)) { debug(DBG_WARN, "pwdrecrypt: cannot encrypt password"); return 0; } return 1; } -int msmpprecrypt(uint8_t *msmpp, uint8_t len, char *oldsecret, char *newsecret, uint8_t *oldauth, uint8_t *newauth) { +int msmpprecrypt(uint8_t *msmpp, uint8_t len, uint8_t *oldsecret, int oldsecret_len, uint8_t *newsecret, int newsecret_len, uint8_t *oldauth, uint8_t *newauth) { if (len < 18) return 0; - if (!msmppdecrypt(msmpp + 2, len - 2, (uint8_t *)oldsecret, strlen(oldsecret), oldauth, msmpp)) { + if (!msmppdecrypt(msmpp + 2, len - 2, oldsecret, oldsecret_len, oldauth, msmpp)) { debug(DBG_WARN, "msmpprecrypt: failed to decrypt msppe key"); return 0; } - if (!msmppencrypt(msmpp + 2, len - 2, (uint8_t *)newsecret, strlen(newsecret), newauth, msmpp)) { + if (!msmppencrypt(msmpp + 2, len - 2, newsecret, newsecret_len, newauth, msmpp)) { debug(DBG_WARN, "msmpprecrypt: failed to encrypt msppe key"); return 0; } @@ -822,12 +822,12 @@ int msmpprecrypt(uint8_t *msmpp, uint8_t len, char *oldsecret, char *newsecret, } int msmppe(unsigned char *attrs, int length, uint8_t type, char *attrtxt, struct request *rq, - char *oldsecret, char *newsecret) { + uint8_t *oldsecret, int oldsecret_len, uint8_t *newsecret, int newsecret_len) { unsigned char *attr; for (attr = attrs; (attr = attrget(attr, length - (attr - attrs), type)); attr += ATTRLEN(attr)) { debug(DBG_DBG, "msmppe: Got %s", attrtxt); - if (!msmpprecrypt(ATTRVAL(attr), ATTRVALLEN(attr), oldsecret, newsecret, rq->buf + 4, rq->rqauth)) + if (!msmpprecrypt(ATTRVAL(attr), ATTRVALLEN(attr), oldsecret, oldsecret_len, newsecret, newsecret_len, rq->buf + 4, rq->rqauth)) return 0; } return 1; @@ -1224,7 +1224,7 @@ int radsrv(struct request *rq) { int ttlres; char tmp[INET6_ADDRSTRLEN]; - msg = buf2radmsg(rq->buf, (uint8_t *)from->conf->secret, NULL); + msg = buf2radmsg(rq->buf, from->conf->secret, from->conf->secret_len, NULL); free(rq->buf); rq->buf = NULL; @@ -1344,14 +1344,14 @@ int radsrv(struct request *rq) { attr = radmsg_gettype(msg, RAD_Attr_User_Password); if (attr) { debug(DBG_DBG, "radsrv: found userpwdattr with value length %d", attr->l); - if (!pwdrecrypt(attr->v, attr->l, from->conf->secret, to->conf->secret, rq->rqauth, msg->auth)) + if (!pwdrecrypt(attr->v, attr->l, from->conf->secret, from->conf->secret_len, to->conf->secret, to->conf->secret_len, rq->rqauth, msg->auth)) goto rmclrqexit; } attr = radmsg_gettype(msg, RAD_Attr_Tunnel_Password); if (attr) { debug(DBG_DBG, "radsrv: found tunnelpwdattr with value length %d", attr->l); - if (!pwdrecrypt(attr->v, attr->l, from->conf->secret, to->conf->secret, rq->rqauth, msg->auth)) + if (!pwdrecrypt(attr->v, attr->l, from->conf->secret, from->conf->secret_len, to->conf->secret, to->conf->secret_len, rq->rqauth, msg->auth)) goto rmclrqexit; } @@ -1402,7 +1402,7 @@ void replyh(struct server *server, unsigned char *buf) { goto errunlock; } - msg = buf2radmsg(buf, (uint8_t *)server->conf->secret, rqout->rq->msg->auth); + msg = buf2radmsg(buf, server->conf->secret, server->conf->secret_len, rqout->rq->msg->auth); #ifdef DEBUG printfchars(NULL, "origauth/buf+4", "%02x ", buf + 4, 16); #endif @@ -1456,9 +1456,9 @@ void replyh(struct server *server, unsigned char *buf) { subattrs = attr->v + 4; if (!attrvalidate(subattrs, sublen) || !msmppe(subattrs, sublen, RAD_VS_ATTR_MS_MPPE_Send_Key, "MS MPPE Send Key", - rqout->rq, server->conf->secret, from->conf->secret) || + rqout->rq, server->conf->secret, server->conf->secret_len, from->conf->secret, from->conf->secret_len) || !msmppe(subattrs, sublen, RAD_VS_ATTR_MS_MPPE_Recv_Key, "MS MPPE Recv Key", - rqout->rq, server->conf->secret, from->conf->secret)) + rqout->rq, server->conf->secret, server->conf->secret_len, from->conf->secret, from->conf->secret_len)) break; } if (node) { @@ -2132,6 +2132,7 @@ void freeclsrvconf(struct clsrvconf *conf) { if (conf->hostsrc) freegconfmstr(conf->hostsrc); free(conf->portsrc); + free(conf->confsecret); free(conf->secret); free(conf->tls); free(conf->matchcertattr); @@ -2225,7 +2226,7 @@ int mergesrvconf(struct clsrvconf *dst, struct clsrvconf *src) { if (!mergeconfstring(&dst->name, &src->name) || !mergeconfmstring(&dst->hostsrc, &src->hostsrc) || !mergeconfstring(&dst->portsrc, &src->portsrc) || - !mergeconfstring(&dst->secret, &src->secret) || + !mergeconfstring(&dst->confsecret, &src->confsecret) || !mergeconfstring(&dst->tls, &src->tls) || !mergeconfstring(&dst->matchcertattr, &src->matchcertattr) || !mergeconfstring(&dst->confrewritein, &src->confrewritein) || @@ -2287,7 +2288,7 @@ int confclient_cb(struct gconffile **cf, void *arg, char *block, char *opt, char "host", CONF_MSTR, &conf->hostsrc, "IPv4Only", CONF_BLN, &ipv4only, "IPv6Only", CONF_BLN, &ipv6only, - "secret", CONF_STR, &conf->secret, + "secret", CONF_STR, &conf->confsecret, #if defined(RADPROT_TLS) || defined(RADPROT_DTLS) "tls", CONF_STR, &conf->tls, "matchcertificateattribute", CONF_STR, &conf->matchcertattr, @@ -2376,13 +2377,15 @@ int confclient_cb(struct gconffile **cf, void *arg, char *block, char *opt, char !resolvehostports(conf->hostports, conf->hostaf, conf->pdef->socktype)) debugx(1, DBG_ERR, "%s: resolve failed, exiting", __func__); - if (!conf->secret) { + if (!conf->confsecret) { if (!conf->pdef->secretdefault) debugx(1, DBG_ERR, "error in block %s, secret must be specified for transport type %s", block, conf->pdef->name); - conf->secret = stringcopy(conf->pdef->secretdefault, 0); - if (!conf->secret) + conf->confsecret = stringcopy(conf->pdef->secretdefault, 0); + if (!conf->confsecret) debugx(1, DBG_ERR, "malloc failed"); } + conf->secret = (unsigned char *)stringcopy(conf->confsecret, 0); + conf->secret_len = unhex((char *)conf->secret, 1); if (conf->tlsconf) { for (entry = list_first(clconfs); entry; entry = list_next(entry)) { @@ -2485,7 +2488,7 @@ int confserver_cb(struct gconffile **cf, void *arg, char *block, char *opt, char "IPv4Only", CONF_BLN, &ipv4only, "IPv6Only", CONF_BLN, &ipv6only, "port", CONF_STR, &conf->portsrc, - "secret", CONF_STR, &conf->secret, + "secret", CONF_STR, &conf->confsecret, #if defined(RADPROT_TLS) || defined(RADPROT_DTLS) "tls", CONF_STR, &conf->tls, "MatchCertificateAttribute", CONF_STR, &conf->matchcertattr, @@ -2589,17 +2592,19 @@ int confserver_cb(struct gconffile **cf, void *arg, char *block, char *opt, char goto errexit; } - if (!conf->secret) { + if (!conf->confsecret) { if (!conf->pdef->secretdefault) { debug(DBG_ERR, "error in block %s, secret must be specified for transport type %s", block, conf->pdef->name); goto errexit; } - conf->secret = stringcopy(conf->pdef->secretdefault, 0); + conf->confsecret = stringcopy(conf->pdef->secretdefault, 0); if (!conf->secret) { debug(DBG_ERR, "malloc failed"); goto errexit; } } + conf->secret = (unsigned char *)stringcopy(conf->confsecret,0); + conf->secret_len = unhex((char *)conf->secret,1); if (resconf) return 1; diff --git a/radsecproxy.h b/radsecproxy.h index 0c5dc1c..5f7d4db 100644 --- a/radsecproxy.h +++ b/radsecproxy.h @@ -135,7 +135,9 @@ struct clsrvconf { int hostaf; char *portsrc; struct list *hostports; - char *secret; + char *confsecret; + uint8_t *secret; + int secret_len; char *tls; char *matchcertattr; regex_t *certcnregex; From c49d8cbfd22b33c4fc95dc0e24d463760534db96 Mon Sep 17 00:00:00 2001 From: Fabian Mauchle Date: Tue, 16 Apr 2019 19:11:41 +0200 Subject: [PATCH 12/22] update manpage and changelog --- ChangeLog | 6 ++++++ radsecproxy.conf.5 | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/ChangeLog b/ChangeLog index 228a532..bd6139e 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,8 +1,14 @@ changes since 1.7.2 + New features: + - Rewrite: supplement attribute (add attribute if not present) (#19) + Misc: - No longer require docbook2x tools, but include plain manpages - Fail on startup if overlapping clients with different tls blocks + Bug fixes: + - Handle %00 in config correctly (#31) + 2018-09-03 1.7.2 Misc: - Always copy proxy-state attributes in own responses diff --git a/radsecproxy.conf.5 b/radsecproxy.conf.5 index 6ff97a9..15dc0d4 100644 --- a/radsecproxy.conf.5 +++ b/radsecproxy.conf.5 @@ -67,7 +67,7 @@ hexadecimal digits. If a % is used without two following hexadecimal digits, the % and the following characters are used as written. If you want to write a % and not use this decoding, you may of course write % in hex; i.e., %25. As %00 would terminate a string, this value is not converted in most cases, except when used -with rewrite statements. +with rewrite statements or secrets. Some options allow or require the use of regular expressions, denoted as \fIregex\fR. The POSIX extended RE system is used, see From c0c1ea40c674cd6c4d1f6ba4859348cce86cb523 Mon Sep 17 00:00:00 2001 From: Fabian Mauchle Date: Thu, 18 Apr 2019 06:46:42 +0200 Subject: [PATCH 13/22] implement whitelist mode for attribute rewrite --- rewrite.c | 15 +++---- rewrite.h | 1 + tests/t_rewrite.c | 101 +++++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 109 insertions(+), 8 deletions(-) diff --git a/rewrite.c b/rewrite.c index 13921db..4e41cf7 100644 --- a/rewrite.c +++ b/rewrite.c @@ -289,7 +289,7 @@ int findvendorsubattr(uint32_t *attrs, uint32_t vendor, uint32_t subattr) { } /* returns 1 if entire element is to be removed, else 0 */ -int dovendorrewriterm(struct tlv *attr, uint32_t *removevendorattrs) { +int dovendorrewriterm(struct tlv *attr, uint32_t *removevendorattrs, int inverted) { uint8_t alen, sublen; uint32_t vendor; uint8_t *subattrs; @@ -318,18 +318,19 @@ int dovendorrewriterm(struct tlv *attr, uint32_t *removevendorattrs) { while (sublen > 1) { alen = ATTRLEN(subattrs); sublen -= alen; - if (findvendorsubattr(removevendorattrs, vendor, ATTRTYPE(subattrs))) { + if (!!findvendorsubattr(removevendorattrs, vendor, ATTRTYPE(subattrs)) != !!inverted) { memmove(subattrs, subattrs + alen, sublen); attr->l -= alen; } else subattrs += alen; } - if (attr->l <= 4) + if ((attr->l <= 4) != !!inverted) return 1; return 0; } -void dorewriterm(struct radmsg *msg, uint8_t *rmattrs, uint32_t *rmvattrs) { +/*if inverted is true, remove all attributes except those listed */ +void dorewriterm(struct radmsg *msg, uint8_t *rmattrs, uint32_t *rmvattrs, int inverted) { struct list_node *n, *p; struct tlv *attr; @@ -337,8 +338,8 @@ void dorewriterm(struct radmsg *msg, uint8_t *rmattrs, uint32_t *rmvattrs) { n = list_first(msg->attrs); while (n) { attr = (struct tlv *)n->data; - if ((rmattrs && strchr((char *)rmattrs, attr->t)) || - (rmvattrs && attr->t == RAD_Attr_Vendor_Specific && dovendorrewriterm(attr, rmvattrs))) { + if (((rmattrs && strchr((char *)rmattrs, attr->t)) || + (rmvattrs && attr->t == RAD_Attr_Vendor_Specific && dovendorrewriterm(attr, rmvattrs, inverted))) != !!inverted) { list_removedata(msg->attrs, attr); freetlv(attr); n = p ? list_next(p) : list_first(msg->attrs); @@ -478,7 +479,7 @@ int dorewrite(struct radmsg *msg, struct rewrite *rewrite) { if (rewrite) { if (rewrite->removeattrs || rewrite->removevendorattrs) - dorewriterm(msg, rewrite->removeattrs, rewrite->removevendorattrs); + dorewriterm(msg, rewrite->removeattrs, rewrite->removevendorattrs, rewrite->whitelist_mode); if (rewrite->modattrs) if (!dorewritemod(msg, rewrite->modattrs)) rv = 0; diff --git a/rewrite.h b/rewrite.h index 7176898..3b2b62d 100644 --- a/rewrite.h +++ b/rewrite.h @@ -15,6 +15,7 @@ struct modattr { }; struct rewrite { + uint8_t whitelist_mode; uint8_t *removeattrs; /*NULL terminated*/ uint32_t *removevendorattrs; /*NULL terminated*/ struct list *addattrs; /*struct tlv*/ diff --git a/tests/t_rewrite.c b/tests/t_rewrite.c index 6becac1..bc4fd33 100644 --- a/tests/t_rewrite.c +++ b/tests/t_rewrite.c @@ -47,6 +47,7 @@ void _list_clear(struct list *list) { } void _reset_rewrite(struct rewrite *rewrite) { + rewrite->whitelist_mode = 0; rewrite->removeattrs = NULL; rewrite->removevendorattrs = NULL; _list_clear(rewrite->addattrs); @@ -57,7 +58,7 @@ void _reset_rewrite(struct rewrite *rewrite) { int main (int argc, char *argv[]) { - int testcount = 18; + int testcount = 22; struct list *origattrs, *expectedattrs; struct rewrite rewrite; char *username = "user@realm"; @@ -67,6 +68,7 @@ main (int argc, char *argv[]) origattrs=list_create(); expectedattrs=list_create(); + rewrite.whitelist_mode=0; rewrite.removeattrs = NULL; rewrite.removevendorattrs = NULL; rewrite.addattrs = list_create(); @@ -449,6 +451,103 @@ main (int argc, char *argv[]) _reset_rewrite(&rewrite); } + /* test whitelist rewrite */ + { + uint8_t whitelistattrs[] = {1,0}; + rewrite.whitelist_mode=1; + rewrite.removeattrs = whitelistattrs; + + list_push(origattrs, maketlv(1, strlen(username), username)); + list_push(origattrs, maketlv(3, strlen(username), username)); + list_push(origattrs, makevendortlv(42, maketlv(1, strlen(username), username))); + + list_push(expectedattrs, maketlv(1, strlen(username), username)); + + if (_check_rewrite(origattrs, &rewrite, expectedattrs, 0)) + printf("not "); + printf("ok %d - whitelistattrs\n", testcount++); + _list_clear(origattrs); + _list_clear(expectedattrs); + _reset_rewrite(&rewrite); + + } + + /* test whitelist vendor rewrite */ + { + uint32_t whitelistvendorattrs[] = {42,256,0}; + uint8_t value = 42; + uint8_t vendor_nonrfc_in[] = {0,0,0,42,1,2,3,4}; + + rewrite.whitelist_mode=1; + rewrite.removevendorattrs = whitelistvendorattrs; + list_push(origattrs, maketlv(1, strlen(username), username)); + list_push(origattrs, makevendortlv(42, maketlv(1, 1, &value))); + list_push(origattrs, makevendortlv(43, maketlv(1, 1, &value))); + list_push(origattrs, maketlv(26, sizeof(vendor_nonrfc_in), vendor_nonrfc_in)); + + list_push(expectedattrs, makevendortlv(42, maketlv(1, 1, &value))); + list_push(expectedattrs, maketlv(26, sizeof(vendor_nonrfc_in), vendor_nonrfc_in)); + + if (_check_rewrite(origattrs, &rewrite, expectedattrs, 0)) + printf("not "); + printf("ok %d - whitelistvendorattrs\n", testcount++); + _list_clear(origattrs); + _list_clear(expectedattrs); + _reset_rewrite(&rewrite); + } + + /* test whitelist vendor rewrite subattribute*/ + { + uint32_t whitelistvendorattrs[] = {42,1,0}; + uint8_t value = 42; + uint8_t vendor_long1_in[] = {0,0,0,42,2,3,0,1,3,0}; + uint8_t vendor_long1_out[] = {0,0,0,42,1,3,0}; + uint8_t vendor_nonrfc_in[] = {0,0,0,42,1,2,3,4}; + + rewrite.whitelist_mode=1; + rewrite.removevendorattrs = whitelistvendorattrs; + list_push(origattrs, makevendortlv(42, maketlv(1, 1, &value))); + list_push(origattrs, makevendortlv(43, maketlv(1, 1, &value))); + list_push(origattrs, makevendortlv(42, maketlv(2, 1, &value))); + list_push(origattrs, maketlv(26, sizeof(vendor_long1_in), vendor_long1_in)); + list_push(origattrs, maketlv(26, sizeof(vendor_nonrfc_in), vendor_nonrfc_in)); + + list_push(expectedattrs, makevendortlv(42, maketlv(1, 1, &value))); + list_push(expectedattrs, maketlv(26, sizeof(vendor_long1_out), vendor_long1_out)); + + if (_check_rewrite(origattrs, &rewrite, expectedattrs, 0)) + printf("not "); + printf("ok %d - whitelistvendorattrs\n", testcount++); + _list_clear(origattrs); + _list_clear(expectedattrs); + _reset_rewrite(&rewrite); + } + + /* test whitelist vendor rewrite combined*/ + { + uint32_t whitelistvendorattrs[] = {42,1,0}; + uint8_t whitelistattrs[] = {1,0}; + uint8_t value = 42; + + rewrite.whitelist_mode=1; + rewrite.removeattrs = whitelistattrs; + rewrite.removevendorattrs = whitelistvendorattrs; + list_push(origattrs, maketlv(1, strlen(username), username)); + list_push(origattrs, maketlv(3, strlen(username), username)); + list_push(origattrs, makevendortlv(42, maketlv(1, 1, &value))); + list_push(origattrs, makevendortlv(43, maketlv(1, 1, &value))); + list_push(origattrs, makevendortlv(43, maketlv(2, 1, &value))); + + list_push(expectedattrs, maketlv(1, strlen(username), username)); + list_push(expectedattrs, makevendortlv(42, maketlv(1, 1, &value))); + + if (_check_rewrite(origattrs, &rewrite, expectedattrs, 0)) + printf("not "); + printf("ok %d - whitelistvendorattrs\n", testcount++); + _list_clear(origattrs); + _list_clear(expectedattrs); + _reset_rewrite(&rewrite); + } return 0; From 7f1f48135af1d533cf9903b98676be3cd0a16fad Mon Sep 17 00:00:00 2001 From: Fabian Mauchle Date: Thu, 18 Apr 2019 07:07:56 +0200 Subject: [PATCH 14/22] add config for whitelist mode --- radsecproxy.c | 12 +++++++++++- rewrite.c | 3 ++- rewrite.h | 2 +- 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/radsecproxy.c b/radsecproxy.c index 9ed972f..2ba5b0e 100644 --- a/radsecproxy.c +++ b/radsecproxy.c @@ -2625,7 +2625,9 @@ int confserver_cb(struct gconffile **cf, void *arg, char *block, char *opt, char } int confrewrite_cb(struct gconffile **cf, void *arg, char *block, char *opt, char *val) { + uint8_t whitelist_mode = 0; char **rmattrs = NULL, **rmvattrs = NULL; + char **wlattrs = NULL, **wlvattrs = NULL; char **addattrs = NULL, **addvattrs = NULL; char **modattrs = NULL; char **supattrs = NULL, **supvattrs = NULL; @@ -2633,8 +2635,11 @@ int confrewrite_cb(struct gconffile **cf, void *arg, char *block, char *opt, cha debug(DBG_DBG, "confrewrite_cb called for %s", block); if (!getgenericconfig(cf, block, + "whitelistMode", CONF_BLN, &whitelist_mode, "removeAttribute", CONF_MSTR, &rmattrs, "removeVendorAttribute", CONF_MSTR, &rmvattrs, + "whitelistAttribute", CONF_MSTR, &wlattrs, + "whitelistVendorAttribute", CONF_MSTR, &wlvattrs, "addAttribute", CONF_MSTR, &addattrs, "addVendorAttribute", CONF_MSTR, &addvattrs, "modifyAttribute", CONF_MSTR, &modattrs, @@ -2642,7 +2647,12 @@ int confrewrite_cb(struct gconffile **cf, void *arg, char *block, char *opt, cha "supplementVendorAttriute", CONF_MSTR, &supvattrs, NULL)) debugx(1, DBG_ERR, "configuration error"); - addrewrite(val, rmattrs, rmvattrs, addattrs, addvattrs, modattrs, supattrs, supvattrs); + addrewrite(val, whitelist_mode, whitelist_mode? wlattrs : rmattrs, whitelist_mode? wlvattrs : rmvattrs, + addattrs, addvattrs, modattrs, supattrs, supvattrs); + + freegconfmstr(whitelist_mode? rmattrs : wlattrs); + freegconfmstr(whitelist_mode? rmvattrs : wlvattrs); + return 1; } diff --git a/rewrite.c b/rewrite.c index 4e41cf7..da4f53f 100644 --- a/rewrite.c +++ b/rewrite.c @@ -139,7 +139,7 @@ struct modattr *extractmodattr(char *nameval) { return m; } -void addrewrite(char *value, char **rmattrs, char **rmvattrs, char **addattrs, +void addrewrite(char *value, uint8_t whitelist_mode, char **rmattrs, char **rmvattrs, char **addattrs, char **addvattrs, char **modattrs, char **supattrs, char** supvattrs) { struct rewrite *rewrite = NULL; @@ -252,6 +252,7 @@ void addrewrite(char *value, char **rmattrs, char **rmvattrs, char **addattrs, rewrite = malloc(sizeof(struct rewrite)); if (!rewrite) debugx(1, DBG_ERR, "malloc failed"); + rewrite->whitelist_mode = whitelist_mode; rewrite->removeattrs = rma; rewrite->removevendorattrs = rmva; rewrite->addattrs = adda; diff --git a/rewrite.h b/rewrite.h index 3b2b62d..d8edecf 100644 --- a/rewrite.h +++ b/rewrite.h @@ -23,7 +23,7 @@ struct rewrite { struct list *supattrs; /*struct tlv*/ }; -void addrewrite(char *value, char **rmattrs, char **rmvattrs, char **addattrs, +void addrewrite(char *value, uint8_t whitelist_mode, char **rmattrs, char **rmvattrs, char **addattrs, char **addvattrs, char **modattrs, char **supattrs, char** supvattrs); int dorewrite(struct radmsg *msg, struct rewrite *rewrite); struct modattr *extractmodattr(char *nameval); From aef43eaca83797936b9064217111487686813aea Mon Sep 17 00:00:00 2001 From: Fabian Mauchle Date: Thu, 18 Apr 2019 09:49:34 +0200 Subject: [PATCH 15/22] update manpage and changelog --- ChangeLog | 1 + radsecproxy.conf.5 | 53 +++++++++++++++++++++++++++++++++++++++++----- 2 files changed, 49 insertions(+), 5 deletions(-) diff --git a/ChangeLog b/ChangeLog index bd6139e..ce1ff48 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,6 +1,7 @@ changes since 1.7.2 New features: - Rewrite: supplement attribute (add attribute if not present) (#19) + - Rewrite whitelist mode Misc: - No longer require docbook2x tools, but include plain manpages diff --git a/radsecproxy.conf.5 b/radsecproxy.conf.5 index 15dc0d4..2b00fe1 100644 --- a/radsecproxy.conf.5 +++ b/radsecproxy.conf.5 @@ -803,6 +803,17 @@ use another, then you would be fine only defining two rewrite blocks named used for rewrite on input. No rewriting is done on output unless explicitly specified using the \fBRewriteOut\fR option. +The rewrite actions are performed in this sequence: +.RS +1. RemoveAttribute (or WhitelistAttribute) +.br +2. ModifyAttribute +.br +3. SupplementAttribute +.br +4. AddAttribute +.RE + All options can be specified multiple times. The allowed options in a rewrite block are: @@ -821,7 +832,22 @@ interpretation. See the \fBCONFIGURATION SYNTAX\fR section for further details. .RS Add a vendor attribute to the radius message, specified by \fIvendor\fR and \fIsubattribute\fR. Both \fIvendor\fR and \fIsubattribute\fR must be specified -as numerical values. The format of \fIvalue\fR is the same as for \fBaddAttibute\fR above. +as numerical values. The format of \fIvalue\fR is the same as for \fBaddAttribute\fR above. +.RE + +.BI "SupplementAttribute " attribute \fR: value +.RS +Add an \fIattribute\fR to the radius mesage and set it to \fIvalue\fR, only if +the attribute is not yet present on the message. The format of \fIvalue\fR is +the same as for \fBaddAttribute\fR above. +.RE + +.BI "ModifyAttribute " attribute \fR:/ regex \fR/ replace \fR/ +.RS +Modify the given \fIattribute\fR using the \fIregex\fR \fIreplace\fR pattern. As +above, \fIattribute\fR must be specified by a numerical value. Example usage: + +modifyAttribute 1:/^(.*)@local$/\e1@example.com/ .RE .BI "RemoveAttribute " attribute @@ -836,13 +862,30 @@ Remove all vendor attributes that match the given \fIvendor\fR and the given vendor id are removed. .RE -.BI "ModifyAttribute " attribute \fR:/ regex \fR/ replace \fR/ +.BR "WhitelistMode (" on | off ) .RS -Modify the given \fIattribute\fR using the \fIregex\fR \fIreplace\fR pattern. As -above, \fIattribute\fR must be specified by a numerical value. Example usage: +Enable whitelist mode. All attributes except those configured with +\fBWhitelistAttrbiute\fR or \fBWhitelistVendorAttribute\fR will be removed. +While whitelist mode is active, \fBRemoveAttribute\fR and +\fBRemoveVendorAttribute\fR statements are ignored. +.RE -modifyAttribute 1:/^(.*)@local$/\e1@example.com/ +.BI "WhitelistAttribute " attribute +.RS +Do not remove attributes with the given id when \fBWhitelistMode\fR is on. +Ignored otherwise. .RE + +.BI "WhitelistVendorAttribute " vendor [\fR: subattribute ] +.RS +Do not remove vendor attributes that match the given \fIvendor\fR and +\fIsubattribute\fR when \fBWhitelistMode\fR is on. Ignored otherwise. + +If the \fIsubattribute\fR is omitted, the complete vendor attribute is +whitelisted. Otherwise only the specified subattribute is kept but all other +subattributes are removed. +.RE + .SH "SEE ALSO" \fBradsecproxy\fR(1), .URL https://tools.ietf.org/html/rfc6614 " Transport Layer Security (TLS) Encryption for RADIUS " From 3e6bd73fa6dd9fa90fb46c15920461c32215358c Mon Sep 17 00:00:00 2001 From: Fabian Mauchle Date: Thu, 18 Apr 2019 15:43:57 +0200 Subject: [PATCH 16/22] test and fix supplementVendorAttriute update manpage --- radsecproxy.conf.5 | 7 +++++++ rewrite.c | 4 ++-- tests/t_rewrite.c | 22 +++++++++++++++++++++- tests/t_rewrite_config.c | 2 +- 4 files changed, 31 insertions(+), 4 deletions(-) diff --git a/radsecproxy.conf.5 b/radsecproxy.conf.5 index 2b00fe1..b45e5be 100644 --- a/radsecproxy.conf.5 +++ b/radsecproxy.conf.5 @@ -842,6 +842,13 @@ the attribute is not yet present on the message. The format of \fIvalue\fR is the same as for \fBaddAttribute\fR above. .RE +.BI "SupplementVendorAttribute " vendor \fR: subattribute \fR: value +.RS +Add a vendor attribute to the radius message only if the \fIsubattribute\fR of +this \fIvendor\fR is not yet present on the message. The format of is the same +as for \fBaddVendorAttribute\fR above. +.RE + .BI "ModifyAttribute " attribute \fR:/ regex \fR/ replace \fR/ .RS Modify the given \fIattribute\fR using the \fIregex\fR \fIreplace\fR pattern. As diff --git a/rewrite.c b/rewrite.c index da4f53f..925e22c 100644 --- a/rewrite.c +++ b/rewrite.c @@ -447,13 +447,13 @@ int dorewritesup(struct radmsg *msg, struct list *supattrs) { exist = 1; break; } else if (supattr->t == RAD_Attr_Vendor_Specific && attr->t == RAD_Attr_Vendor_Specific && - memcmp (supattr->v, attr->v, 4)) { + memcmp (supattr->v, attr->v, 4)==0) { if (!attrvalidate(attr->v+4, attr->l-4)) { debug(DBG_INFO, "dorewritesup: vendor attribute validation failed, no rewrite"); return 0; } vendortype = (uint8_t *)supattr->v+4; - for (v=attr->v+4; v < attr->v + attr->l; v += *(v+1) + 2){ + for (v=attr->v+4; v < attr->v + attr->l; v += *(v+1)){ if (*v == *vendortype) { exist = 1; break; diff --git a/tests/t_rewrite.c b/tests/t_rewrite.c index bc4fd33..05ea72f 100644 --- a/tests/t_rewrite.c +++ b/tests/t_rewrite.c @@ -58,7 +58,7 @@ void _reset_rewrite(struct rewrite *rewrite) { int main (int argc, char *argv[]) { - int testcount = 22; + int testcount = 23; struct list *origattrs, *expectedattrs; struct rewrite rewrite; char *username = "user@realm"; @@ -298,6 +298,26 @@ main (int argc, char *argv[]) _reset_rewrite(&rewrite); } + /* test supplement vendor*/ + { + uint8_t value = 42; + uint8_t vendor_long1_in[] = {0,0,0,42,2,3,0,1,3,0}; + + list_push(rewrite.supattrs, makevendortlv(42, maketlv(1, 1, &value))); + list_push(rewrite.supattrs, makevendortlv(42, maketlv(3, 1, &value))); + list_push(origattrs, maketlv(26, sizeof(vendor_long1_in), vendor_long1_in)); + list_push(expectedattrs, maketlv(26, sizeof(vendor_long1_in), vendor_long1_in)); + list_push(expectedattrs, makevendortlv(42, maketlv(3, 1, &value))); + + if (_check_rewrite(origattrs, &rewrite, expectedattrs, 0)) + printf("not "); + printf("ok %d - suppattrs vendor\n", testcount++); + + _list_clear(origattrs); + _list_clear(expectedattrs); + _reset_rewrite(&rewrite); + } + /* test modify no match*/ { char *value = "hello world"; diff --git a/tests/t_rewrite_config.c b/tests/t_rewrite_config.c index 2c79331..ecf1ada 100644 --- a/tests/t_rewrite_config.c +++ b/tests/t_rewrite_config.c @@ -28,7 +28,7 @@ main (int argc, char *argv[]) expected = maketlv(1,5,expectedvalue); - addrewrite(rewritename, NULL, NULL, addattrs, + addrewrite(rewritename, 0, NULL, NULL, addattrs, NULL, NULL, NULL, NULL); result = getrewrite(rewritename, NULL); From 51630d48e815c99128683bc4d2282d8f89a0fa14 Mon Sep 17 00:00:00 2001 From: Fabian Mauchle Date: Wed, 24 Apr 2019 07:11:33 +0200 Subject: [PATCH 17/22] implement rewritevendorattribute --- radmsg.h | 1 + rewrite.c | 83 ++++++++++++++++++++++++++++++++++++++++++----- rewrite.h | 2 ++ tests/t_rewrite.c | 62 ++++++++++++++++++++++++++++++++++- 4 files changed, 138 insertions(+), 10 deletions(-) diff --git a/radmsg.h b/radmsg.h index 77ce8e5..8999bc0 100644 --- a/radmsg.h +++ b/radmsg.h @@ -52,6 +52,7 @@ struct list *radmsg_getalltype(const struct radmsg *msg, uint8_t type); int radmsg_copy_attrs(struct radmsg *dst, const struct radmsg *src, uint8_t type); +uint8_t *tlv2buf(uint8_t *p, const struct tlv *tlv); uint8_t *radmsg2buf(struct radmsg *msg, uint8_t *, int); struct radmsg *buf2radmsg(uint8_t *, uint8_t *, int, uint8_t *); uint8_t attrname2val(char *attrname); diff --git a/rewrite.c b/rewrite.c index 925e22c..1afe26b 100644 --- a/rewrite.c +++ b/rewrite.c @@ -257,6 +257,7 @@ void addrewrite(char *value, uint8_t whitelist_mode, char **rmattrs, char **rmva rewrite->removevendorattrs = rmva; rewrite->addattrs = adda; rewrite->modattrs = moda; + rewrite->modvattrs = NULL; rewrite->supattrs = supa; } @@ -406,14 +407,78 @@ int dorewritemodattr(struct tlv *attr, struct modattr *modattr) { return 1; } -int dorewritemod(struct radmsg *msg, struct list *modattrs) { - struct list_node *n, *m; +int replacesubtlv(struct tlv *vendortlv, uint8_t *p, struct tlv *newtlv) { + int size_diff; + uint8_t rem_size, *next_attr; + + size_diff = newtlv->l - ATTRLEN(p); + next_attr = p+ATTRLEN(p); + rem_size = (vendortlv->v + vendortlv->l) - next_attr; + + if (size_diff < 0) + memmove(next_attr + size_diff, next_attr, rem_size); + if (!resizeattr(vendortlv, vendortlv->l+size_diff)) + return 0; + if (size_diff > 0) + memmove(next_attr + size_diff, next_attr, rem_size); + + tlv2buf(p, newtlv); + return 1; +} - for (n = list_first(msg->attrs); n; n = list_next(n)) - for (m = list_first(modattrs); m; m = list_next(m)) - if (((struct tlv *)n->data)->t == ((struct modattr *)m->data)->t && - !dorewritemodattr((struct tlv *)n->data, (struct modattr *)m->data)) +int dorewritemodvattr(struct tlv *vendortlv, struct modattr *modvattr) { + uint8_t *p; + struct tlv *tmpattr; + + if (vendortlv->l <= 4 || !attrvalidate(vendortlv->v+4, vendortlv->l-4)) + return 0; + for (p = vendortlv->v+4; p < (vendortlv->v + vendortlv->l); p = p+ATTRLEN(p)) { + if (ATTRTYPE(p) == modvattr->t) { + tmpattr = maketlv(ATTRTYPE(p), ATTRVALLEN(p), ATTRVAL(p)); + if (dorewritemodattr(tmpattr, modvattr)) { + int size_diff = tmpattr->l - ATTRVALLEN(p); + uint8_t *next_attr = p+ATTRLEN(p); + uint8_t rem_size = (vendortlv->v + vendortlv->l) - next_attr; + + if (size_diff < 0) + memmove(next_attr + size_diff, next_attr, rem_size); + if (!resizeattr(vendortlv, vendortlv->l+size_diff)) + return 0; + if (size_diff > 0) + memmove(next_attr + size_diff, next_attr, rem_size); + + tlv2buf(p, tmpattr); + } else { + freetlv(tmpattr); return 0; + } + freetlv(tmpattr); + } + } + return 1; +} + +int dorewritemod(struct radmsg *msg, struct list *modattrs, struct list *modvattrs) { + struct list_node *n, *m; + uint32_t vendor; + + for (n = list_first(msg->attrs); n; n = list_next(n)) { + struct tlv *attr = (struct tlv *)n->data; + if (attr->t == RAD_Attr_Vendor_Specific) { + memcpy(&vendor, attr->v, 4); + vendor = ntohl(vendor); + for (m = list_first(modvattrs); m; m = list_next(m)) { + if (vendor == ((struct modattr *)m->data)->vendor && + !dorewritemodvattr(attr, (struct modattr*)m->data)) + return 0; + } + } else { + for (m = list_first(modattrs); m; m = list_next(m)) + if (((struct tlv *)n->data)->t == ((struct modattr *)m->data)->t && + !dorewritemodattr((struct tlv *)n->data, (struct modattr *)m->data)) + return 0; + } + } return 1; } @@ -480,9 +545,9 @@ int dorewrite(struct radmsg *msg, struct rewrite *rewrite) { if (rewrite) { if (rewrite->removeattrs || rewrite->removevendorattrs) - dorewriterm(msg, rewrite->removeattrs, rewrite->removevendorattrs, rewrite->whitelist_mode); - if (rewrite->modattrs) - if (!dorewritemod(msg, rewrite->modattrs)) + dorewriterm(msg, rewrite->removeattrs, rewrite->removevendorattrs, rewrite->whitelist_mode); + if (rewrite->modattrs || rewrite->modvattrs) + if (!dorewritemod(msg, rewrite->modattrs, rewrite->modvattrs)) rv = 0; if (rewrite->supattrs) if (!dorewritesup(msg, rewrite->supattrs)) diff --git a/rewrite.h b/rewrite.h index d8edecf..ae8e93f 100644 --- a/rewrite.h +++ b/rewrite.h @@ -10,6 +10,7 @@ struct modattr { uint8_t t; + uint32_t vendor; char *replacement; regex_t *regex; }; @@ -20,6 +21,7 @@ struct rewrite { uint32_t *removevendorattrs; /*NULL terminated*/ struct list *addattrs; /*struct tlv*/ struct list *modattrs; /*struct modattr*/ + struct list *modvattrs; /*struct modattr*/ struct list *supattrs; /*struct tlv*/ }; diff --git a/tests/t_rewrite.c b/tests/t_rewrite.c index 05ea72f..fe67fd5 100644 --- a/tests/t_rewrite.c +++ b/tests/t_rewrite.c @@ -52,13 +52,14 @@ void _reset_rewrite(struct rewrite *rewrite) { rewrite->removevendorattrs = NULL; _list_clear(rewrite->addattrs); _list_clear(rewrite->modattrs); + _list_clear(rewrite->modvattrs); _list_clear(rewrite->supattrs); } int main (int argc, char *argv[]) { - int testcount = 23; + int testcount = 25; struct list *origattrs, *expectedattrs; struct rewrite rewrite; char *username = "user@realm"; @@ -73,6 +74,7 @@ main (int argc, char *argv[]) rewrite.removevendorattrs = NULL; rewrite.addattrs = list_create(); rewrite.modattrs = list_create(); + rewrite.modvattrs = list_create(); rewrite.supattrs = list_create(); printf("1..%d\n", testcount); @@ -471,6 +473,64 @@ main (int argc, char *argv[]) _reset_rewrite(&rewrite); } + /* test modify vendor*/ + { + struct modattr *mod = malloc(sizeof(struct modattr)); + regex_t regex; + uint8_t vendorattrin[] = {0,0,0,42,1,3,'b',1,3,'a',2,3,0,1,3,'a'}; + uint8_t vendorattrout[] = {0,0,0,42,1,3,'b',1,4,'b','b',2,3,0,1,4,'b','b'}; + + mod->t = 1; + mod->vendor = 42; + mod->regex = ®ex; + mod->replacement = "bb"; + regcomp(mod->regex, "a", REG_ICASE | REG_EXTENDED); + + list_push(rewrite.modvattrs, mod); + list_push(origattrs, maketlv(RAD_Attr_Vendor_Specific,sizeof(vendorattrin), vendorattrin)); + list_push(expectedattrs, maketlv(RAD_Attr_Vendor_Specific,sizeof(vendorattrout), vendorattrout)); + + if (_check_rewrite(origattrs, &rewrite, expectedattrs, 0)) + printf("not "); + printf("ok %d - modify vendor\n", testcount++); + + _list_clear(origattrs); + _list_clear(expectedattrs); + _reset_rewrite(&rewrite); + } + + /* test modify vendor too long (total vendor attribute too long) */ + { + struct modattr *mod = malloc(sizeof(struct modattr)); + regex_t regex; + uint8_t vendorattrin[RAD_Max_Attr_Value_Length]; + + memset(vendorattrin, 0, RAD_Max_Attr_Value_Length); + vendorattrin[3] = 42; + vendorattrin[4] = 1; + vendorattrin[5] = 3; + vendorattrin[6] = 'a'; + vendorattrin[7] = 2; + vendorattrin[8] = RAD_Max_Attr_Value_Length - 7; + + mod->t = 1; + mod->vendor = 42; + mod->regex = ®ex; + mod->replacement = "bb"; + regcomp(mod->regex, "a", REG_ICASE | REG_EXTENDED); + + list_push(rewrite.modvattrs, mod); + list_push(origattrs, maketlv(RAD_Attr_Vendor_Specific,sizeof(vendorattrin), vendorattrin)); + + if (_check_rewrite(origattrs, &rewrite, origattrs, 1)) + printf("not "); + printf("ok %d - modify vendor too long\n", testcount++); + + _list_clear(origattrs); + _list_clear(expectedattrs); + _reset_rewrite(&rewrite); + } + /* test whitelist rewrite */ { uint8_t whitelistattrs[] = {1,0}; From f4aace9570d5e596dfa54c7f38bf9bb6f1ae3e4e Mon Sep 17 00:00:00 2001 From: Fabian Mauchle Date: Wed, 24 Apr 2019 07:44:34 +0200 Subject: [PATCH 18/22] configure ModifyVendorAttribute update manpage update ChangeLog --- ChangeLog | 1 + radsecproxy.c | 5 +++-- radsecproxy.conf.5 | 7 +++++++ rewrite.c | 35 ++++++++++++++++++++++++++++++++--- rewrite.h | 2 +- 5 files changed, 44 insertions(+), 6 deletions(-) diff --git a/ChangeLog b/ChangeLog index ce1ff48..71a1490 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,6 +1,7 @@ changes since 1.7.2 New features: - Rewrite: supplement attribute (add attribute if not present) (#19) + - Rewrite: modify vendor attribute - Rewrite whitelist mode Misc: diff --git a/radsecproxy.c b/radsecproxy.c index 2ba5b0e..d3711f4 100644 --- a/radsecproxy.c +++ b/radsecproxy.c @@ -2629,7 +2629,7 @@ int confrewrite_cb(struct gconffile **cf, void *arg, char *block, char *opt, cha char **rmattrs = NULL, **rmvattrs = NULL; char **wlattrs = NULL, **wlvattrs = NULL; char **addattrs = NULL, **addvattrs = NULL; - char **modattrs = NULL; + char **modattrs = NULL, **modvattrs = NULL; char **supattrs = NULL, **supvattrs = NULL; debug(DBG_DBG, "confrewrite_cb called for %s", block); @@ -2643,12 +2643,13 @@ int confrewrite_cb(struct gconffile **cf, void *arg, char *block, char *opt, cha "addAttribute", CONF_MSTR, &addattrs, "addVendorAttribute", CONF_MSTR, &addvattrs, "modifyAttribute", CONF_MSTR, &modattrs, + "modifyVendorAttribute", CONF_MSTR, &modvattrs, "supplementAttribute", CONF_MSTR, &supattrs, "supplementVendorAttriute", CONF_MSTR, &supvattrs, NULL)) debugx(1, DBG_ERR, "configuration error"); addrewrite(val, whitelist_mode, whitelist_mode? wlattrs : rmattrs, whitelist_mode? wlvattrs : rmvattrs, - addattrs, addvattrs, modattrs, supattrs, supvattrs); + addattrs, addvattrs, modattrs, modvattrs, supattrs, supvattrs); freegconfmstr(whitelist_mode? rmattrs : wlattrs); freegconfmstr(whitelist_mode? rmvattrs : wlvattrs); diff --git a/radsecproxy.conf.5 b/radsecproxy.conf.5 index b45e5be..a8b785b 100644 --- a/radsecproxy.conf.5 +++ b/radsecproxy.conf.5 @@ -857,6 +857,13 @@ above, \fIattribute\fR must be specified by a numerical value. Example usage: modifyAttribute 1:/^(.*)@local$/\e1@example.com/ .RE +.BI "ModifyVendorAttribute " vendor \fR: subattribute \fR:/ regex \fR/ replace \fR/ +.RS +Modify the given \fIsubattribute\fR of given \fIvendor\fR using the \fIregex\fR +\fIreplace\fR pattern. Other than the added vendor, the same syntax as for +\fBModifyAttribute\fR applies. +.RE + .BI "RemoveAttribute " attribute .RS Remove all attributes with the given id. diff --git a/rewrite.c b/rewrite.c index 1afe26b..32a84ad 100644 --- a/rewrite.c +++ b/rewrite.c @@ -139,14 +139,29 @@ struct modattr *extractmodattr(char *nameval) { return m; } +struct modattr *extractmodvattr(char *nameval) { + uint32_t vendor; + char *s; + struct modattr *modvattr; + + s = strchr(nameval, ':'); + vendor = atoi(nameval); + if (!s || !vendor || !strchr(s,':')) + return NULL; + modvattr = extractmodattr(s+1); + if (modvattr) + modvattr ->vendor = vendor; + return modvattr; +} + void addrewrite(char *value, uint8_t whitelist_mode, char **rmattrs, char **rmvattrs, char **addattrs, - char **addvattrs, char **modattrs, char **supattrs, char** supvattrs) + char **addvattrs, char **modattrs, char **modvattrs, char **supattrs, char** supvattrs) { struct rewrite *rewrite = NULL; int i, n; uint8_t *rma = NULL; uint32_t *p, *rmva = NULL; - struct list *adda = NULL, *moda = NULL, *supa = NULL; + struct list *adda = NULL, *moda = NULL, *modva = NULL, *supa = NULL; struct tlv *a; struct modattr *m; @@ -219,6 +234,20 @@ void addrewrite(char *value, uint8_t whitelist_mode, char **rmattrs, char **rmva freegconfmstr(modattrs); } + if (modvattrs) { + modva = list_create(); + if (!modva) + debugx(1, DBG_ERR, "malloc failed"); + for (i = 0; modvattrs[i]; i++) { + m = extractmodvattr(modvattrs[i]); + if (!m) + debugx(1, DBG_ERR, "addrewrite: modifying invalid vendor attribute %s", modvattrs[i]); + if (!list_push(modva, m)) + debugx(1, DBG_ERR, "malloc failed"); + } + freegconfmstr(modvattrs); + } + if (supattrs) { supa = list_create(); if (!supa) @@ -257,7 +286,7 @@ void addrewrite(char *value, uint8_t whitelist_mode, char **rmattrs, char **rmva rewrite->removevendorattrs = rmva; rewrite->addattrs = adda; rewrite->modattrs = moda; - rewrite->modvattrs = NULL; + rewrite->modvattrs = modva; rewrite->supattrs = supa; } diff --git a/rewrite.h b/rewrite.h index ae8e93f..3356a00 100644 --- a/rewrite.h +++ b/rewrite.h @@ -26,7 +26,7 @@ struct rewrite { }; void addrewrite(char *value, uint8_t whitelist_mode, char **rmattrs, char **rmvattrs, char **addattrs, - char **addvattrs, char **modattrs, char **supattrs, char** supvattrs); + char **addvattrs, char **modattrs, char **modvattrs, char **supattrs, char** supvattrs); int dorewrite(struct radmsg *msg, struct rewrite *rewrite); struct modattr *extractmodattr(char *nameval); struct rewrite *getrewrite(char *alt1, char *alt2); From 774236f0c2a290df22ff70da76cf4bc2b492a1e0 Mon Sep 17 00:00:00 2001 From: Fabian Mauchle Date: Thu, 2 May 2019 12:59:04 +0200 Subject: [PATCH 19/22] fix t_rewrite_config not updated to addrewrite() function. --- tests/t_rewrite_config.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/t_rewrite_config.c b/tests/t_rewrite_config.c index ecf1ada..1717b08 100644 --- a/tests/t_rewrite_config.c +++ b/tests/t_rewrite_config.c @@ -29,7 +29,7 @@ main (int argc, char *argv[]) expected = maketlv(1,5,expectedvalue); addrewrite(rewritename, 0, NULL, NULL, addattrs, - NULL, NULL, NULL, NULL); + NULL, NULL, NULL, NULL, NULL); result = getrewrite(rewritename, NULL); From b3a25b9bda8efb967fd8c12f1f09c6e71c33e159 Mon Sep 17 00:00:00 2001 From: Fabian Mauchle Date: Fri, 3 May 2019 12:58:33 +0200 Subject: [PATCH 20/22] fix typo in rewrite config parser --- radsecproxy.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/radsecproxy.c b/radsecproxy.c index d3711f4..b250120 100644 --- a/radsecproxy.c +++ b/radsecproxy.c @@ -2645,7 +2645,7 @@ int confrewrite_cb(struct gconffile **cf, void *arg, char *block, char *opt, cha "modifyAttribute", CONF_MSTR, &modattrs, "modifyVendorAttribute", CONF_MSTR, &modvattrs, "supplementAttribute", CONF_MSTR, &supattrs, - "supplementVendorAttriute", CONF_MSTR, &supvattrs, + "supplementVendorAttribute", CONF_MSTR, &supvattrs, NULL)) debugx(1, DBG_ERR, "configuration error"); addrewrite(val, whitelist_mode, whitelist_mode? wlattrs : rmattrs, whitelist_mode? wlvattrs : rmvattrs, From 24f1cc20d7fedcd49bb7d8d267527799f7414435 Mon Sep 17 00:00:00 2001 From: Fabian Mauchle Date: Wed, 15 May 2019 18:19:05 +0200 Subject: [PATCH 21/22] fix memory corruption in rewrite-vendor --- hash.c | 1 - hash.h | 1 + rewrite.c | 32 +++++++++++++++++++------------- 3 files changed, 20 insertions(+), 14 deletions(-) diff --git a/hash.c b/hash.c index 19d6c18..051c7e1 100644 --- a/hash.c +++ b/hash.c @@ -3,7 +3,6 @@ #include #include -#include #include "list.h" #include "hash.h" diff --git a/hash.h b/hash.h index d22a88c..ccb2917 100644 --- a/hash.h +++ b/hash.h @@ -4,6 +4,7 @@ #ifndef SYS_SOLARIS9 #include #endif +#include struct hash { struct list *hashlist; diff --git a/rewrite.c b/rewrite.c index 32a84ad..b383103 100644 --- a/rewrite.c +++ b/rewrite.c @@ -456,27 +456,33 @@ int replacesubtlv(struct tlv *vendortlv, uint8_t *p, struct tlv *newtlv) { } int dorewritemodvattr(struct tlv *vendortlv, struct modattr *modvattr) { - uint8_t *p; struct tlv *tmpattr; + int offset; if (vendortlv->l <= 4 || !attrvalidate(vendortlv->v+4, vendortlv->l-4)) return 0; - for (p = vendortlv->v+4; p < (vendortlv->v + vendortlv->l); p = p+ATTRLEN(p)) { - if (ATTRTYPE(p) == modvattr->t) { - tmpattr = maketlv(ATTRTYPE(p), ATTRVALLEN(p), ATTRVAL(p)); + for (offset = 4; offset < vendortlv->l; offset += ATTRLEN(vendortlv->v+offset)) { + if (ATTRTYPE(vendortlv->v+offset) == modvattr->t) { + tmpattr = maketlv(ATTRTYPE(vendortlv->v+offset), ATTRVALLEN(vendortlv->v+offset), ATTRVAL(vendortlv->v+offset)); if (dorewritemodattr(tmpattr, modvattr)) { - int size_diff = tmpattr->l - ATTRVALLEN(p); - uint8_t *next_attr = p+ATTRLEN(p); - uint8_t rem_size = (vendortlv->v + vendortlv->l) - next_attr; + int size_diff = tmpattr->l - ATTRVALLEN(vendortlv->v+offset); + int rem_size = vendortlv->l - offset - ATTRLEN(vendortlv->v+offset); + uint8_t *next; - if (size_diff < 0) - memmove(next_attr + size_diff, next_attr, rem_size); - if (!resizeattr(vendortlv, vendortlv->l+size_diff)) - return 0; if (size_diff > 0) - memmove(next_attr + size_diff, next_attr, rem_size); + if (!resizeattr(vendortlv, vendortlv->l+size_diff)) { + freetlv(tmpattr); + return 0; + } + next = vendortlv->v + offset + ATTRLEN(vendortlv->v+offset); + memmove(next + size_diff, next, rem_size); + if (size_diff < 0) + if (!resizeattr(vendortlv, vendortlv->l+size_diff)) { + freetlv(tmpattr); + return 0; + } - tlv2buf(p, tmpattr); + tlv2buf(vendortlv->v+offset, tmpattr); } else { freetlv(tmpattr); return 0; From 964038edbae2c67e570ad7e39b3849897f20ace9 Mon Sep 17 00:00:00 2001 From: Fabian Mauchle Date: Fri, 17 May 2019 17:10:24 +0200 Subject: [PATCH 22/22] fix minor valgrind reports in unit tests --- tests/t_resizeattr.c | 17 +++--- tests/t_rewrite.c | 124 +++++++++++++++++++++++---------------- tests/t_rewrite_config.c | 1 + 3 files changed, 81 insertions(+), 61 deletions(-) diff --git a/tests/t_resizeattr.c b/tests/t_resizeattr.c index 2b5214c..db20d7c 100644 --- a/tests/t_resizeattr.c +++ b/tests/t_resizeattr.c @@ -10,24 +10,23 @@ int test_resize(int start_size, int target_size, uint8_t shouldfail) { uint8_t *value = malloc(start_size); struct tlv *attr; + int result = 1; memset(value, 42, start_size); attr = maketlv(1,start_size,value); if (!resizeattr(attr, target_size)) - return shouldfail; + result = shouldfail; else if (shouldfail) - return 0; - - if (attr->l != target_size) - return 0; - - if (memcmp(attr->v, value, target_size <= start_size ? target_size : start_size)) - return 0; + result = 0; + else if (attr->l != target_size) + result = 0; + else if (memcmp(attr->v, value, target_size <= start_size ? target_size : start_size)) + result = 0; freetlv(attr); free(value); - return 1; + return result; } int main (int argc, char *argv[]) diff --git a/tests/t_rewrite.c b/tests/t_rewrite.c index fe67fd5..6793be8 100644 --- a/tests/t_rewrite.c +++ b/tests/t_rewrite.c @@ -46,14 +46,20 @@ void _list_clear(struct list *list) { free(data); } +void _tlv_list_clear(struct list *list) { + struct tlv *tlv; + while ( (tlv = (struct tlv *)list_shift(list)) ) + freetlv(tlv); +} + void _reset_rewrite(struct rewrite *rewrite) { rewrite->whitelist_mode = 0; rewrite->removeattrs = NULL; rewrite->removevendorattrs = NULL; - _list_clear(rewrite->addattrs); + _tlv_list_clear(rewrite->addattrs); _list_clear(rewrite->modattrs); _list_clear(rewrite->modvattrs); - _list_clear(rewrite->supattrs); + _tlv_list_clear(rewrite->supattrs); } int @@ -87,8 +93,8 @@ main (int argc, char *argv[]) if (_check_rewrite(origattrs, &rewrite, expectedattrs, 0)) printf("not "); printf("ok %d - empty rewrite\n", testcount++); - _list_clear(origattrs); - _list_clear(expectedattrs); + _tlv_list_clear(origattrs); + _tlv_list_clear(expectedattrs); _reset_rewrite(&rewrite); } @@ -105,8 +111,8 @@ main (int argc, char *argv[]) if (_check_rewrite(origattrs, &rewrite, expectedattrs, 0)) printf("not "); printf("ok %d - removeattrs\n", testcount++); - _list_clear(origattrs); - _list_clear(expectedattrs); + _tlv_list_clear(origattrs); + _tlv_list_clear(expectedattrs); _reset_rewrite(&rewrite); } @@ -126,8 +132,8 @@ main (int argc, char *argv[]) if (_check_rewrite(origattrs, &rewrite, expectedattrs, 0)) printf("not "); printf("ok %d - removevendorattrs full\n", testcount++); - _list_clear(origattrs); - _list_clear(expectedattrs); + _tlv_list_clear(origattrs); + _tlv_list_clear(expectedattrs); _reset_rewrite(&rewrite); } @@ -147,8 +153,8 @@ main (int argc, char *argv[]) if (_check_rewrite(origattrs, &rewrite, expectedattrs, 0)) printf("not "); printf("ok %d - removevendorattrs last element\n", testcount++); - _list_clear(origattrs); - _list_clear(expectedattrs); + _tlv_list_clear(origattrs); + _tlv_list_clear(expectedattrs); _reset_rewrite(&rewrite); } @@ -165,8 +171,8 @@ main (int argc, char *argv[]) if (_check_rewrite(origattrs, &rewrite, expectedattrs, 0)) printf("not "); printf("ok %d - removevendorattrs non-rfc\n", testcount++); - _list_clear(origattrs); - _list_clear(expectedattrs); + _tlv_list_clear(origattrs); + _tlv_list_clear(expectedattrs); _reset_rewrite(&rewrite); } @@ -192,8 +198,8 @@ main (int argc, char *argv[]) if (_check_rewrite(origattrs, &rewrite, expectedattrs, 0)) printf("not "); printf("ok %d - removevendorattrs sub-attribute\n", testcount++); - _list_clear(origattrs); - _list_clear(expectedattrs); + _tlv_list_clear(origattrs); + _tlv_list_clear(expectedattrs); _reset_rewrite(&rewrite); } @@ -208,8 +214,8 @@ main (int argc, char *argv[]) printf("not "); printf("ok %d - addattribute simple\n", testcount++); - _list_clear(origattrs); - _list_clear(expectedattrs); + _tlv_list_clear(origattrs); + _tlv_list_clear(expectedattrs); _reset_rewrite(&rewrite); } @@ -230,8 +236,8 @@ main (int argc, char *argv[]) printf("not "); printf("ok %d - addattribute with existing attributes\n", testcount++); - _list_clear(origattrs); - _list_clear(expectedattrs); + _tlv_list_clear(origattrs); + _tlv_list_clear(expectedattrs); _reset_rewrite(&rewrite); } @@ -244,8 +250,8 @@ main (int argc, char *argv[]) printf("not "); printf("ok %d - addattribute null\n", testcount++); - _list_clear(origattrs); - _list_clear(expectedattrs); + _tlv_list_clear(origattrs); + _tlv_list_clear(expectedattrs); _reset_rewrite(&rewrite); } @@ -261,8 +267,8 @@ main (int argc, char *argv[]) printf("ok %d - addattribute too big\n", testcount++); free(value); - _list_clear(origattrs); - _list_clear(expectedattrs); + _tlv_list_clear(origattrs); + _tlv_list_clear(expectedattrs); _reset_rewrite(&rewrite); } @@ -277,8 +283,8 @@ main (int argc, char *argv[]) printf("not "); printf("ok %d - suppattrs non existing\n", testcount++); - _list_clear(origattrs); - _list_clear(expectedattrs); + _tlv_list_clear(origattrs); + _tlv_list_clear(expectedattrs); _reset_rewrite(&rewrite); } @@ -295,8 +301,8 @@ main (int argc, char *argv[]) printf("not "); printf("ok %d - suppattrs existing\n", testcount++); - _list_clear(origattrs); - _list_clear(expectedattrs); + _tlv_list_clear(origattrs); + _tlv_list_clear(expectedattrs); _reset_rewrite(&rewrite); } @@ -315,8 +321,8 @@ main (int argc, char *argv[]) printf("not "); printf("ok %d - suppattrs vendor\n", testcount++); - _list_clear(origattrs); - _list_clear(expectedattrs); + _tlv_list_clear(origattrs); + _tlv_list_clear(expectedattrs); _reset_rewrite(&rewrite); } @@ -340,8 +346,9 @@ main (int argc, char *argv[]) printf("not "); printf("ok %d - modify attribute no match\n", testcount++); - _list_clear(origattrs); - _list_clear(expectedattrs); + regfree(®ex); + _tlv_list_clear(origattrs); + _tlv_list_clear(expectedattrs); _reset_rewrite(&rewrite); } @@ -365,8 +372,9 @@ main (int argc, char *argv[]) printf("not "); printf("ok %d - modify attribute match full replace\n", testcount++); - _list_clear(origattrs); - _list_clear(expectedattrs); + regfree(®ex); + _tlv_list_clear(origattrs); + _tlv_list_clear(expectedattrs); _reset_rewrite(&rewrite); } @@ -390,8 +398,9 @@ main (int argc, char *argv[]) printf("not "); printf("ok %d - modify attribute match full replace\n", testcount++); - _list_clear(origattrs); - _list_clear(expectedattrs); + regfree(®ex); + _tlv_list_clear(origattrs); + _tlv_list_clear(expectedattrs); _reset_rewrite(&rewrite); } @@ -421,8 +430,9 @@ main (int argc, char *argv[]) printf("not "); printf("ok %d - modify attribute max length\n", testcount++); - _list_clear(origattrs); - _list_clear(expectedattrs); + regfree(®ex); + _tlv_list_clear(origattrs); + _tlv_list_clear(expectedattrs); _reset_rewrite(&rewrite); } @@ -444,8 +454,9 @@ main (int argc, char *argv[]) printf("not "); printf("ok %d - modify attribute too long\n", testcount++); - _list_clear(origattrs); - _list_clear(expectedattrs); + regfree(®ex); + _tlv_list_clear(origattrs); + _tlv_list_clear(expectedattrs); _reset_rewrite(&rewrite); } @@ -468,8 +479,9 @@ main (int argc, char *argv[]) printf("not "); printf("ok %d - modify attribute regex replace\n", testcount++); - _list_clear(origattrs); - _list_clear(expectedattrs); + regfree(®ex); + _tlv_list_clear(origattrs); + _tlv_list_clear(expectedattrs); _reset_rewrite(&rewrite); } @@ -494,8 +506,9 @@ main (int argc, char *argv[]) printf("not "); printf("ok %d - modify vendor\n", testcount++); - _list_clear(origattrs); - _list_clear(expectedattrs); + regfree(®ex); + _tlv_list_clear(origattrs); + _tlv_list_clear(expectedattrs); _reset_rewrite(&rewrite); } @@ -526,8 +539,9 @@ main (int argc, char *argv[]) printf("not "); printf("ok %d - modify vendor too long\n", testcount++); - _list_clear(origattrs); - _list_clear(expectedattrs); + regfree(®ex); + _tlv_list_clear(origattrs); + _tlv_list_clear(expectedattrs); _reset_rewrite(&rewrite); } @@ -546,8 +560,8 @@ main (int argc, char *argv[]) if (_check_rewrite(origattrs, &rewrite, expectedattrs, 0)) printf("not "); printf("ok %d - whitelistattrs\n", testcount++); - _list_clear(origattrs); - _list_clear(expectedattrs); + _tlv_list_clear(origattrs); + _tlv_list_clear(expectedattrs); _reset_rewrite(&rewrite); } @@ -571,8 +585,8 @@ main (int argc, char *argv[]) if (_check_rewrite(origattrs, &rewrite, expectedattrs, 0)) printf("not "); printf("ok %d - whitelistvendorattrs\n", testcount++); - _list_clear(origattrs); - _list_clear(expectedattrs); + _tlv_list_clear(origattrs); + _tlv_list_clear(expectedattrs); _reset_rewrite(&rewrite); } @@ -598,8 +612,8 @@ main (int argc, char *argv[]) if (_check_rewrite(origattrs, &rewrite, expectedattrs, 0)) printf("not "); printf("ok %d - whitelistvendorattrs\n", testcount++); - _list_clear(origattrs); - _list_clear(expectedattrs); + _tlv_list_clear(origattrs); + _tlv_list_clear(expectedattrs); _reset_rewrite(&rewrite); } @@ -624,11 +638,17 @@ main (int argc, char *argv[]) if (_check_rewrite(origattrs, &rewrite, expectedattrs, 0)) printf("not "); printf("ok %d - whitelistvendorattrs\n", testcount++); - _list_clear(origattrs); - _list_clear(expectedattrs); + _tlv_list_clear(origattrs); + _tlv_list_clear(expectedattrs); _reset_rewrite(&rewrite); } + list_destroy(origattrs); + list_destroy(expectedattrs); + list_destroy(rewrite.addattrs); + list_destroy(rewrite.modattrs); + list_destroy(rewrite.modvattrs); + list_destroy(rewrite.supattrs); return 0; } diff --git a/tests/t_rewrite_config.c b/tests/t_rewrite_config.c index 1717b08..97ae7fc 100644 --- a/tests/t_rewrite_config.c +++ b/tests/t_rewrite_config.c @@ -48,6 +48,7 @@ main (int argc, char *argv[]) printf("not ok %d - rewrite ocnfig\n", numtests++); } + freetlv(expected); return 0; }