Added tolower function
[freeradius.git] / src / main / xlat.c
index 7a11c41..9c32288 100644 (file)
  *
  *   You should have received a copy of the GNU General Public License
  *   along with this program; if not, write to the Free Software
- *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
  *
- * Copyright 2000  The FreeRADIUS server project
+ * Copyright 2000,2006  The FreeRADIUS server project
  * Copyright 2000  Alan DeKok <aland@ox.org>
  */
 
-static const char rcsid[] =
-"$Id$";
+#include <freeradius-devel/ident.h>
+RCSID("$Id$")
 
-#include       "autoconf.h"
-#include       "libradius.h"
+#include       <freeradius-devel/radiusd.h>
+#include       <freeradius-devel/md5.h>
+#include       <freeradius-devel/rad_assert.h>
 
-#include       <stdio.h>
-#include       <stdlib.h>
-#include       <string.h>
 #include       <ctype.h>
 
-#include       "radiusd.h"
-
-#include       "rad_assert.h"
-
 typedef struct xlat_t {
        char            module[MAX_STRING_LEN];
        int             length;
@@ -50,17 +44,19 @@ static rbtree_t *xlat_root = NULL;
 /*
  *     Define all xlat's in the structure.
  */
-static const char *internal_xlat[] = {"check",
-                                     "request",
-                                     "reply",
-                                     "proxy-request",
-                                     "proxy-reply",
-                                     NULL};
+static const char * const internal_xlat[] = {"check",
+                                            "request",
+                                            "reply",
+                                            "proxy-request",
+                                            "proxy-reply",
+                                            "outer.request",
+                                            "outer.reply",
+                                            NULL};
 
 #if REQUEST_MAX_REGEX > 8
 #error Please fix the following line
 #endif
-static int xlat_inst[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8 };        /* up to 8 for regex */
+static const int xlat_inst[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8 };  /* up to 8 for regex */
 
 
 /*
@@ -72,25 +68,28 @@ static int valuepair2str(char * out,int outlen,VALUE_PAIR * pair,
        char buffer[MAX_STRING_LEN * 4];
 
        if (pair != NULL) {
-               vp_prints_value(buffer, sizeof(buffer), pair, 0);
+               vp_prints_value(buffer, sizeof(buffer), pair, -1);
                return func(out, outlen, buffer);
        }
 
        switch (type) {
        case PW_TYPE_STRING :
-               strNcpy(out,"_",outlen);
+               strlcpy(out,"_",outlen);
                break;
        case PW_TYPE_INTEGER :
-               strNcpy(out,"0",outlen);
+               strlcpy(out,"0",outlen);
                break;
        case PW_TYPE_IPADDR :
-               strNcpy(out,"?.?.?.?",outlen);
+               strlcpy(out,"?.?.?.?",outlen);
+               break;
+       case PW_TYPE_IPV6ADDR :
+               strlcpy(out,":?:",outlen);
                break;
        case PW_TYPE_DATE :
-               strNcpy(out,"0",outlen);
+               strlcpy(out,"0",outlen);
                break;
        default :
-               strNcpy(out,"unknown_type",outlen);
+               strlcpy(out,"unknown_type",outlen);
        }
        return strlen(out);
 }
@@ -99,9 +98,9 @@ static int valuepair2str(char * out,int outlen,VALUE_PAIR * pair,
 /*
  *     Dynamically translate for check:, request:, reply:, etc.
  */
-static int xlat_packet(void *instance, REQUEST *request,
-                      char *fmt, char *out, size_t outlen,
-                      RADIUS_ESCAPE_STRING func)
+static size_t xlat_packet(void *instance, REQUEST *request,
+                         char *fmt, char *out, size_t outlen,
+                         RADIUS_ESCAPE_STRING func)
 {
        DICT_ATTR       *da;
        VALUE_PAIR      *vp;
@@ -124,15 +123,33 @@ static int xlat_packet(void *instance, REQUEST *request,
                break;
 
        case 3:
+#ifdef WITH_PROXY
                if (request->proxy) vps = request->proxy->vps;
                packet = request->proxy;
+#endif
                break;
 
        case 4:
+#ifdef WITH_PROXY
                if (request->proxy_reply) vps = request->proxy_reply->vps;
                packet = request->proxy_reply;
+#endif
                break;
 
+       case 5:
+               if (request->parent) {
+                       vps = request->parent->packet->vps;
+                       packet = request->parent->packet;
+               }
+               break;
+                       
+       case 6:
+               if (request->parent && request->parent->reply) {
+                       vps = request->parent->reply->vps;
+                       packet = request->parent->reply;
+               }
+               break;
+                       
        default:                /* WTF? */
                return 0;
        }
@@ -142,39 +159,102 @@ static int xlat_packet(void *instance, REQUEST *request,
         */
        da = dict_attrbyname(fmt);
        if (!da) {
-               int count;
-               const char *p = strchr(fmt, '[');
+               int do_number = FALSE;
+               size_t count;
+               const char *p;
                char buffer[256];
 
-               if (!p) return 0;
                if (strlen(fmt) > sizeof(buffer)) return 0;
 
-               strNcpy(buffer, fmt, p - fmt + 1);
+               p = strchr(fmt, '[');
+               if (!p) {
+                       p = strchr(fmt, '#');
+                       if (!p) return 0;
+                       do_number = TRUE;
+               }
+
+               strlcpy(buffer, fmt, p - fmt + 1);
+
+               da = dict_attrbyname(buffer);
+               if (!da) return 0;
+
+               if (do_number) {
+                       vp = pairfind(vps, da->attr, 0);
+                       if (!vp) return 0;
+
+                       switch (da->type) {
+                       default:
+                               break;
+
+                       case PW_TYPE_INTEGER:
+                       case PW_TYPE_DATE:
+                       case PW_TYPE_SHORT:
+                       case PW_TYPE_BYTE:
+                               snprintf(out, outlen, "%u", vp->lvalue);
+                               return strlen(out);
+                       }
+
+                       goto just_print;
+               }
+
+               /*
+                *      %{Attribute-Name[#]} returns the count of
+                *      attributes of that name in the list.
+                */
+               if ((p[1] == '#') && (p[2] == ']')) {
+                       count = 0;
+
+                       for (vp = pairfind(vps, da->attr, da->vendor);
+                            vp != NULL;
+                            vp = pairfind(vp->next, da->attr, da->vendor)) {
+                               count++;
+                       }
+                       snprintf(out, outlen, "%d", (int) count);
+                       return strlen(out);
+               }
+
+               /*
+                *      %{Attribute-Name[*]} returns ALL of the
+                *      the attributes, separated by a newline.
+                */
+               if ((p[1] == '*') && (p[2] == ']')) {
+                       int total = 0;
+
+                       for (vp = pairfind(vps, da->attr, da->vendor);
+                            vp != NULL;
+                            vp = pairfind(vp->next, da->attr, da->vendor)) {
+                               count = valuepair2str(out, outlen - 1, vp, da->type, func);
+                               rad_assert(count <= outlen);
+                               total += count + 1;
+                               outlen -= (count + 1);
+                               out += count;
+
+                               *(out++) = '\n';
+
+                               if (outlen == 0) break;
+                       }
+
+                       return total;
+               }
 
                count = atoi(p + 1);
 
                /*
-                *      Check the format of the index before looking
-                *      the attribute up in the dictionary, because
-                *      it's a cheap check.
+                *      Skip the numbers.
                 */
                p += 1 + strspn(p + 1, "0123456789");
                if (*p != ']') {
-                       DEBUG2("xlat: Invalid array reference in string at %s %s",
+                       RDEBUG2("xlat: Invalid array reference in string at %s %s",
                               fmt, p);
                        return 0;
                }
 
-               da = dict_attrbyname(buffer);
-               if (!da) return 0;
-
-
                /*
                 *      Find the N'th value.
                 */
-               for (vp = pairfind(vps, da->attr);
+               for (vp = pairfind(vps, da->attr, da->vendor);
                     vp != NULL;
-                    vp = pairfind(vp->next, da->attr)) {
+                    vp = pairfind(vp->next, da->attr, da->vendor)) {
                        if (count == 0) break;
                        count--;
                }
@@ -183,25 +263,30 @@ static int xlat_packet(void *instance, REQUEST *request,
                 *      Non-existent array reference.
                 */
                if (!vp) return 0;
-
+       just_print:
                return valuepair2str(out, outlen, vp, da->type, func);
        }
 
-       vp = pairfind(vps, da->attr);
+       vp = pairfind(vps, da->attr, da->vendor);
        if (!vp) {
                /*
                 *      Some "magic" handlers, which are never in VP's, but
                 *      which are in the packet.
                 *
-                *      FIXME: Add SRC/DST IP address!
+                *      FIXME: We should really do this in a more
+                *      intelligent way...
                 */
                if (packet) {
+                       VALUE_PAIR localvp;
+
+                       memset(&localvp, 0, sizeof(localvp));
+
                        switch (da->attr) {
                        case PW_PACKET_TYPE:
                        {
                                DICT_VALUE *dval;
-                               
-                               dval = dict_valbyattr(da->attr, packet->code);
+
+                               dval = dict_valbyattr(da->attr, da->vendor, packet->code);
                                if (dval) {
                                        snprintf(out, outlen, "%s", dval->name);
                                } else {
@@ -210,10 +295,104 @@ static int xlat_packet(void *instance, REQUEST *request,
                                return strlen(out);
                        }
                        break;
-                       
+
+                       case PW_CLIENT_SHORTNAME:
+                               if (request->client && request->client->shortname) {
+                                       strlcpy(out, request->client->shortname, outlen);
+                               } else {
+                                       strlcpy(out, "<UNKNOWN-CLIENT>", outlen);
+                               }
+                               return strlen(out);
+
+                       case PW_CLIENT_IP_ADDRESS: /* the same as below */
+                       case PW_PACKET_SRC_IP_ADDRESS:
+                               if (packet->src_ipaddr.af != AF_INET) {
+                                       return 0;
+                               }
+                               localvp.attribute = da->attr;
+                               localvp.vp_ipaddr = packet->src_ipaddr.ipaddr.ip4addr.s_addr;
+                               break;
+
+                       case PW_PACKET_DST_IP_ADDRESS:
+                               if (packet->dst_ipaddr.af != AF_INET) {
+                                       return 0;
+                               }
+                               localvp.attribute = da->attr;
+                               localvp.vp_ipaddr = packet->dst_ipaddr.ipaddr.ip4addr.s_addr;
+                               break;
+
+                       case PW_PACKET_SRC_PORT:
+                               localvp.attribute = da->attr;
+                               localvp.vp_integer = packet->src_port;
+                               break;
+
+                       case PW_PACKET_DST_PORT:
+                               localvp.attribute = da->attr;
+                               localvp.vp_integer = packet->dst_port;
+                               break;
+
+                       case PW_PACKET_AUTHENTICATION_VECTOR:
+                               localvp.attribute = da->attr;
+                               memcpy(localvp.vp_strvalue, packet->vector,
+                                      sizeof(packet->vector));
+                               localvp.length = sizeof(packet->vector);
+                               break;
+
+                               /*
+                                *      Authorization, accounting, etc.
+                                */
+                       case PW_REQUEST_PROCESSING_STAGE:
+                               if (request->component) {
+                                       strlcpy(out, request->component, outlen);
+                               } else {
+                                       strlcpy(out, "server_core", outlen);
+                               }
+                               return strlen(out);
+
+                       case PW_PACKET_SRC_IPV6_ADDRESS:
+                               if (packet->src_ipaddr.af != AF_INET6) {
+                                       return 0;
+                               }
+                               localvp.attribute = da->attr;
+                               memcpy(localvp.vp_strvalue,
+                                      &packet->src_ipaddr.ipaddr.ip6addr,
+                                      sizeof(packet->src_ipaddr.ipaddr.ip6addr));
+                               break;
+
+                       case PW_PACKET_DST_IPV6_ADDRESS:
+                               if (packet->dst_ipaddr.af != AF_INET6) {
+                                       return 0;
+                               }
+                               localvp.attribute = da->attr;
+                               memcpy(localvp.vp_strvalue,
+                                      &packet->dst_ipaddr.ipaddr.ip6addr,
+                                      sizeof(packet->dst_ipaddr.ipaddr.ip6addr));
+                               break;
+
+                       case PW_VIRTUAL_SERVER:
+                               if (!request->server) return 0;
+
+                               snprintf(out, outlen, "%s", request->server);
+                               return strlen(out);
+                               break;
+
+                       case PW_MODULE_RETURN_CODE:
+                               localvp.attribute = da->attr;
+
+                               /*
+                                *      See modcall.c for a bit of a hack.
+                                */
+                               localvp.vp_integer = request->simul_max;
+                               break;
+
                        default:
+                               return 0; /* not found */
                                break;
                        }
+
+                       localvp.type = da->type;
+                       return valuepair2str(out, outlen, &localvp,
+                                            da->type, func);
                }
 
                /*
@@ -234,9 +413,9 @@ static int xlat_packet(void *instance, REQUEST *request,
 /*
  *     Pull %{0} to %{8} out of the packet.
  */
-static int xlat_regex(void *instance, REQUEST *request,
-                     char *fmt, char *out, size_t outlen,
-                     RADIUS_ESCAPE_STRING func)
+static size_t xlat_regex(void *instance, REQUEST *request,
+                        char *fmt, char *out, size_t outlen,
+                        RADIUS_ESCAPE_STRING func)
 {
        char *regex;
 
@@ -246,8 +425,8 @@ static int xlat_regex(void *instance, REQUEST *request,
         */
        fmt = fmt;              /* -Wunused */
        func = func;            /* -Wunused FIXME: do escaping? */
-       
-       regex = request_data_get(request, request,
+
+       regex = request_data_reference(request, request,
                                 REQUEST_DATA_REGEX | *(int *)instance);
        if (!regex) return 0;
 
@@ -255,12 +434,100 @@ static int xlat_regex(void *instance, REQUEST *request,
         *      Copy UP TO "freespace" bytes, including
         *      a zero byte.
         */
-       strNcpy(out, regex, outlen);
-       free(regex); /* was strdup'd */
+       strlcpy(out, regex, outlen);
        return strlen(out);
 }
 #endif                         /* HAVE_REGEX_H */
 
+
+/*
+ *     Change the debugging level.
+ */
+static size_t xlat_debug(UNUSED void *instance, REQUEST *request,
+                         char *fmt, char *out, size_t outlen,
+                         UNUSED RADIUS_ESCAPE_STRING func)
+{
+       int level = 0;
+
+       if (*fmt) level = atoi(fmt);
+
+       if (level == 0) {
+               request->options = RAD_REQUEST_OPTION_NONE;
+               request->radlog = NULL;
+       } else {
+               if (level > 4) level = 4;
+
+               request->options = level;
+               request->radlog = radlog_request;
+       }
+
+       snprintf(out, outlen, "%d", level);
+       return strlen(out);
+}
+
+
+/*
+ *     Calculate the MD5 hash of a string.
+ */
+static size_t xlat_md5(UNUSED void *instance, REQUEST *request,
+                      char *fmt, char *out, size_t outlen,
+                      UNUSED RADIUS_ESCAPE_STRING func)
+{
+       int i;
+       uint8_t digest[16];
+       FR_MD5_CTX ctx;
+       char buffer[1024];
+
+       if (!radius_xlat(buffer, sizeof(buffer), fmt, request, func)) {
+               *out = '\0';
+               return 0;
+       }
+
+       fr_MD5Init(&ctx);
+       fr_MD5Update(&ctx, (void *) buffer, strlen(buffer));
+       fr_MD5Final(digest, &ctx);
+
+       if (outlen < 33) {
+               snprintf(out, outlen, "md5_overflow");
+               return strlen(out);
+       }
+
+       for (i = 0; i < 16; i++) {
+               snprintf(out + i * 2, 3, "%02x", digest[i]);
+       }
+
+       return strlen(out);
+}
+
+
+/*
+ *     Convert a string to lowercase
+ */
+static size_t xlat_lc(UNUSED void *instance, REQUEST *request,
+                      char *fmt, char *out, size_t outlen,
+                      UNUSED RADIUS_ESCAPE_STRING func)
+{
+       char *p, *q;
+       char buffer[1024];
+
+       if (!radius_xlat(buffer, sizeof(buffer), fmt, request, func)) {
+               *out = '\0';
+               return 0;
+       }
+
+       for (p = buffer, q = out; *p != '\0'; p++, outlen--) {
+               if (outlen <= 1) break;
+
+               *(q++) = tolower((int) *p);
+       }
+
+       *q = '\0';
+
+       return strlen(out);
+}
+
+
+
 /*
  *     Compare two xlat_t structs, based ONLY on the module name.
  */
@@ -275,24 +542,23 @@ static int xlat_cmp(const void *a, const void *b)
                      ((const xlat_t *)a)->length);
 }
 
-
 /*
  *     find the appropriate registered xlat function.
  */
 static xlat_t *xlat_find(const char *module)
 {
-       char *p;
        xlat_t my_xlat;
 
-       strNcpy(my_xlat.module, module, sizeof(my_xlat.module));
-
        /*
-        *      We get passed the WHOLE string, and all we want here
-        *      is the first piece.
+        *      Look for dictionary attributes first.
         */
-       p = strchr(my_xlat.module, ':');
-       if (p) *p = '\0';
+       if ((dict_attrbyname(module) != NULL) ||
+           (strchr(module, '[') != NULL) ||
+           (strchr(module, '#') != NULL)) {
+               module = "request";
+       }
 
+       strlcpy(my_xlat.module, module, sizeof(my_xlat.module));
        my_xlat.length = strlen(my_xlat.module);
 
        return rbtree_finddata(xlat_root, &my_xlat);
@@ -340,6 +606,14 @@ int xlat_register(const char *module, RAD_XLAT_FUNC func, void *instance)
                        c->internal = TRUE;
                }
 
+               /*
+                *      New name: "control"
+                */
+               xlat_register("control", xlat_packet, &xlat_inst[0]);
+               c = xlat_find("control");
+               rad_assert(c != NULL);
+               c->internal = TRUE;
+
 #ifdef HAVE_REGEX_H
                /*
                 *      Register xlat's for regexes.
@@ -353,12 +627,28 @@ int xlat_register(const char *module, RAD_XLAT_FUNC func, void *instance)
                        c->internal = TRUE;
                }
 #endif /* HAVE_REGEX_H */
+
+
+               xlat_register("debug", xlat_debug, &xlat_inst[0]);
+               c = xlat_find("debug");
+               rad_assert(c != NULL);
+               c->internal = TRUE;
+
+               xlat_register("md5", xlat_md5, &xlat_inst[0]);
+               c = xlat_find("md5");
+               rad_assert(c != NULL);
+               c->internal = TRUE;
+
+               xlat_register("tolower", xlat_lc, &xlat_inst[0]);
+               c = xlat_find("tolower");
+               rad_assert(c != NULL);
+               c->internal = TRUE;
        }
 
        /*
         *      If it already exists, replace the instance.
         */
-       strNcpy(my_xlat.module, module, sizeof(my_xlat.module));
+       strlcpy(my_xlat.module, module, sizeof(my_xlat.module));
        my_xlat.length = strlen(my_xlat.module);
        c = rbtree_finddata(xlat_root, &my_xlat);
        if (c) {
@@ -375,11 +665,11 @@ int xlat_register(const char *module, RAD_XLAT_FUNC func, void *instance)
        /*
         *      Doesn't exist.  Create it.
         */
-       c = rad_malloc(sizeof(xlat_t));
+       c = rad_malloc(sizeof(*c));
        memset(c, 0, sizeof(*c));
 
        c->do_xlat = func;
-       strNcpy(c->module, module, sizeof(c->module));
+       strlcpy(c->module, module, sizeof(c->module));
        c->length = strlen(c->module);
        c->instance = instance;
 
@@ -401,7 +691,9 @@ void xlat_unregister(const char *module, RAD_XLAT_FUNC func)
 
        func = func;            /* -Wunused */
 
-       strNcpy(my_xlat.module, module, sizeof(my_xlat.module));
+       if (!module) return;
+
+       strlcpy(my_xlat.module, module, sizeof(my_xlat.module));
        my_xlat.length = strlen(my_xlat.module);
 
        node = rbtree_find(xlat_root, &my_xlat);
@@ -410,7 +702,6 @@ void xlat_unregister(const char *module, RAD_XLAT_FUNC func)
        rbtree_delete(xlat_root, node);
 }
 
-
 /*
  *     De-register all xlat functions,
  *     used mainly for debugging.
@@ -424,158 +715,220 @@ void xlat_free(void)
 /*
  *     Decode an attribute name into a string.
  */
-static void decode_attribute(const char **from, char **to, int freespace,
-                            int *open, REQUEST *request,
+static int decode_attribute(const char **from, char **to, int freespace,
+                            REQUEST *request,
                             RADIUS_ESCAPE_STRING func)
 {
-       char attrname[4096];
-       const char *p;
-       char *q, *pa;
-       int stop=0, found=0, retlen=0;
-       int openbraces = *open;
-       xlat_t *c;
-       size_t namelen = sizeof(attrname);
+       int     do_length = 0;
+       char    *xlat_name, *xlat_string;
+       char *p, *q, *l, *next = NULL;
+       int retlen=0;
+       const xlat_t *c;
+       int varlen;
+       char buffer[8192];
 
-       p = *from;
        q = *to;
-       pa = &attrname[0];
 
        *q = '\0';
 
        /*
-        * Skip the '{' at the front of 'p'
-        * Increment open braces
+        *      Copy the input string to an intermediate buffer where
+        *      we can mangle it.
         */
-       p++;
-       openbraces++;
+       varlen = rad_copy_variable(buffer, *from);
+       if (varlen < 0) {
+               RDEBUG2("Badly formatted variable: %s", *from);
+               return -1;
+       }
+       *from += varlen;
 
        /*
-        *  Copy over the rest of the string.
+        *      Kill the %{} around the data we are looking for.
         */
-       while ((*p) && (!stop) && (namelen > 1)) {
-               switch(*p) {
-                       /*
-                        *  Allow braces inside things, too.
-                        */
-                       case '\\':
-                               p++; /* skip it */
-                               *pa++ = *p++;
-                               break;
+       p = buffer;
+       p[varlen - 1] = '\0';   /*  */
+       p += 2;
+       if (*p == '#') {
+               p++;
+               do_length = 1;
+       }
 
-                       case '{':
-                               openbraces++;
-                               *pa++ = *p++;
-                               break;
+       /*
+        *      Handle %{%{foo}:-%{bar}}, which is useful, too.
+        *
+        *      Did I mention that this parser is garbage?
+        */
+       if ((p[0] == '%') && (p[1] == '{')) {
+               int len1, len2;
+               int expand2 = FALSE;
 
-                       case '}':
-                               openbraces--;
-                               if (openbraces == *open) {
-                                       p++;
-                                       stop=1;
-                               } else {
-                                       *pa++ = *p++;
-                               }
-                               break;
+               /*
+                *      'p' is after the start of 'buffer', so we can
+                *      safely do this.
+                */
+               len1 = rad_copy_variable(buffer, p);
+               if (len1 < 0) {
+                       RDEBUG2("Badly formatted variable: %s", p);
+                       return -1;
+               }
 
-                               /*
-                                *  Attr-Name1:-Attr-Name2
-                                *
-                                *  Use Attr-Name1, and if not found,
-                                *  use Attr-Name2.
-                                */
-                       case ':':
-                               if (p[1] == '-') {
-                                       p += 2;
-                                       stop = 1;
-                                       break;
-                               }
-                               /* else FALL-THROUGH */
+               /*
+                *      They did %{%{foo}}, which is stupid, but allowed.
+                */
+               if (!p[len1]) {
+                       RDEBUG2("Improperly nested variable; %%{%s}", p);
+                       return -1;
+               }
 
-                       default:
-                               *pa++ = *p++;
-                               break;
+               /*
+                *      It SHOULD be %{%{foo}:-%{bar}}.  If not, it's
+                *      an error.
+                */
+               if ((p[len1] != ':') || (p[len1 + 1] != '-')) {
+                       RDEBUG2("No trailing :- after variable at %s", p);
+                       return -1;
                }
-               namelen--;
-       }
-       *pa = '\0';
 
-       /*
-        *      Look up almost everything in the new tree of xlat
-        *      functions.  this makes it a little quicker...
-        */
-       if ((c = xlat_find(attrname)) != NULL) {
-               if (!c->internal) DEBUG("radius_xlat: Running registered xlat function of module %s for string \'%s\'",
-                                       c->module, attrname+ c->length + 1);
-               retlen = c->do_xlat(c->instance, request, attrname+(c->length+1), q, freespace, func);
-               /* If retlen is 0, treat it as not found */
-               if (retlen == 0) {
-                       found = 0;
+               /*
+                *      Parse the second bit.  The second bit can be
+                *      either %{foo}, or a string "foo", or a string
+                *      'foo', or just a bare word: foo
+                */
+               p += len1 + 2;
+               l = buffer + len1 + 1;
+
+               if ((p[0] == '%') && (p[1] == '{')) {
+                       len2 = rad_copy_variable(l, p);
+
+                       if (len2 < 0) {
+                               RDEBUG2("Invalid text after :- at %s", p);
+                               return -1;
+                       }
+                       p += len2;
+                       expand2 = TRUE;
+
+               } else if ((p[0] == '"') || p[0] == '\'') {
+                       getstring(&p, l, strlen(l));
+
                } else {
-                       found = 1;
+                       l = p;
+               }
+
+               /*
+                *      Expand the first one.  If we did, exit the
+                *      conditional.
+                */
+               retlen = radius_xlat(q, freespace, buffer, request, func);
+               if (retlen) {
                        q += retlen;
+                       goto done;
                }
 
+               RDEBUG2("\t... expanding second conditional");
                /*
-                *      Not in the default xlat database.  Must be
-                *      a bare attribute number.
+                *      Expand / copy the second string if required.
                 */
-       } else if ((retlen = xlat_packet(&xlat_inst[1], request, attrname,
-                                        q, freespace, func)) > 0) {
-               found = 1;
-               q += retlen;
+               if (expand2) {
+                       retlen = radius_xlat(q, freespace, l,
+                                           request, func);
+                       if (retlen) {
+                               q += retlen;
+                       }
+               } else {
+                       strlcpy(q, l, freespace);
+                       q += strlen(q);
+               }
 
                /*
-                *      Look up the name, in order to get the correct
-                *      debug message.
+                *      Else the output is an empty string.
                 */
-#ifndef NDEBUG
-       } else if (dict_attrbyname(attrname) == NULL) {
+               goto done;
+       }
+
+       /*
+        *      See if we're supposed to expand a module name.
+        */
+       xlat_name = NULL;
+       for (l = p; *l != '\0'; l++) {
+               if (*l == '\\') {
+                       l++;
+                       continue;
+               }
+
+               if (*l == ':') {
+                       xlat_name = p; /* start of name */
+                       *l = '\0';
+                       p = l + 1;
+                       break;
+               }
+
                /*
-                *      No attribute by that name, return an error.
+                *      Module names can't have spaces.
                 */
-               DEBUG2("WARNING: Attempt to use unknown xlat function, or non-existent attribute in string %%{%s}", attrname);
-#endif
-       } /* else the attribute is known, but not in the request */
+               if ((*l == ' ') || (*l == '\t')) break;
+       }
 
        /*
-        * Skip to last '}' if attr is found
-        * The rest of the stuff within the braces is
-        * useless if we found what we need
+        *      %{name} is a simple attribute reference,
+        *      or regex reference.
         */
-       if (found) {
-               while((*p != '\0') && (openbraces > *open)) {
-                       /*
-                        *      Handle escapes outside of the loop.
-                        */
-                       if (*p == '\\') {
-                               p++;
-                               if (!*p) break;
-                               p++; /* get & ignore next character */
-                               continue;
-                       }
+       if (!xlat_name) {
+               xlat_name = xlat_string = p;
+               goto do_xlat;
+       }
 
-                       switch (*p) {
-                       default:
-                               break;
+       /*
+        *      Maybe it's the old-style %{foo:-bar}
+        */
+       if (*p == '-') {
+               RDEBUG2("WARNING: Deprecated conditional expansion \":-\".  See \"man unlang\" for details");
+               p++;
 
-                               /*
-                                *  Bare brace
-                                */
-                       case '{':
-                               openbraces++;
-                               break;
+               xlat_string = xlat_name;
+               next = p;
+               goto do_xlat;
+       }
 
-                       case '}':
-                               openbraces--;
-                               break;
+       /*
+        *      FIXME: For backwards "WTF" compatibility, check for
+        *      {...}, (after the :), and copy that, too.
+        */
+
+       /* module name, followed by (possibly) per-module string */
+       xlat_string = p;
+       
+do_xlat:
+       if ((c = xlat_find(xlat_name)) != NULL) {
+               if (!c->internal) RDEBUG3("radius_xlat: Running registered xlat function of module %s for string \'%s\'",
+                                         c->module, xlat_string);
+               retlen = c->do_xlat(c->instance, request, xlat_string,
+                                   q, freespace, func);
+               if (retlen > 0) {
+                       if (do_length) {
+                               snprintf(q, freespace, "%d", retlen);
+                               retlen = strlen(q);
                        }
-                       p++;    /* skip the character */
+
+               } else if (next) {
+                       /*
+                        *      Expand the second bit.
+                        */
+                       RDEBUG2("\t... expanding second conditional");
+                       retlen = radius_xlat(q, freespace, next, request, func);
                }
+               q += retlen;
+
+       } else {
+               /*
+                *      No attribute by that name, return an error.
+                */
+               RDEBUG2("WARNING: Unknown module \"%s\" in string expansion \"%%%s\"", xlat_name, *from);
+               return -1;
        }
 
-       *open = openbraces;
-       *from = p;
+done:
        *to = q;
+       return 0;
 }
 
 /*
@@ -583,7 +936,7 @@ static void decode_attribute(const char **from, char **to, int freespace,
  *  we use this one.  It simplifies the coding, as the check for
  *  func == NULL only happens once.
  */
-static int xlat_copy(char *out, int outlen, const char *in)
+static size_t xlat_copy(char *out, size_t outlen, const char *in)
 {
        int freespace = outlen;
 
@@ -633,7 +986,7 @@ int radius_xlat(char *out, int outlen, const char *fmt,
                func = xlat_copy;
        }
 
-       q = out;
+               q = out;
        p = fmt;
        while (*p) {
                /* Calculate freespace in output */
@@ -685,58 +1038,44 @@ int radius_xlat(char *out, int outlen, const char *fmt,
                        }
                        p++;
 
-                       /*
-                        *      Hmmm... ${User-Name} is a synonym for
-                        *      %{User-Name}.
-                        *
-                        *      Why, exactly?
-                        */
-               } else if (c == '$') switch(*p) {
-                       case '{': /* Attribute by Name */
-                               decode_attribute(&p, &q, freespace, &openbraces, request, func);
-                               break;
-                       default:
-                               *q++ = c;
-                               *q++ = *p++;
-                               break;
-
                } else if (c == '%') switch(*p) {
                        case '{':
-                               decode_attribute(&p, &q, freespace, &openbraces, request, func);
+                               p--;
+                               if (decode_attribute(&p, &q, freespace, request, func) < 0) return 0;
                                break;
 
                        case '%':
                                *q++ = *p++;
                                break;
                        case 'a': /* Protocol: */
-                               q += valuepair2str(q,freespace,pairfind(request->reply->vps,PW_FRAMED_PROTOCOL),PW_TYPE_INTEGER, func);
+                               q += valuepair2str(q,freespace,pairfind(request->reply->vps,PW_FRAMED_PROTOCOL, 0),PW_TYPE_INTEGER, func);
                                p++;
                                break;
                        case 'c': /* Callback-Number */
-                               q += valuepair2str(q,freespace,pairfind(request->reply->vps,PW_CALLBACK_NUMBER),PW_TYPE_STRING, func);
+                               q += valuepair2str(q,freespace,pairfind(request->reply->vps,PW_CALLBACK_NUMBER, 0),PW_TYPE_STRING, func);
                                p++;
                                break;
                        case 'd': /* request day */
                                TM = localtime_r(&request->timestamp, &s_TM);
                                len = strftime(tmpdt, sizeof(tmpdt), "%d", TM);
                                if (len > 0) {
-                                       strNcpy(q, tmpdt, freespace);
+                                       strlcpy(q, tmpdt, freespace);
                                        q += strlen(q);
                                }
                                p++;
                                break;
                        case 'f': /* Framed IP address */
-                               q += valuepair2str(q,freespace,pairfind(request->reply->vps,PW_FRAMED_IP_ADDRESS),PW_TYPE_IPADDR, func);
+                               q += valuepair2str(q,freespace,pairfind(request->reply->vps,PW_FRAMED_IP_ADDRESS, 0),PW_TYPE_IPADDR, func);
                                p++;
                                break;
                        case 'i': /* Calling station ID */
-                               q += valuepair2str(q,freespace,pairfind(request->packet->vps,PW_CALLING_STATION_ID),PW_TYPE_STRING, func);
+                               q += valuepair2str(q,freespace,pairfind(request->packet->vps,PW_CALLING_STATION_ID, 0),PW_TYPE_STRING, func);
                                p++;
                                break;
                        case 'l': /* request timestamp */
                                snprintf(tmpdt, sizeof(tmpdt), "%lu",
                                         (unsigned long) request->timestamp);
-                               strNcpy(q,tmpdt,freespace);
+                               strlcpy(q,tmpdt,freespace);
                                q += strlen(q);
                                p++;
                                break;
@@ -744,42 +1083,42 @@ int radius_xlat(char *out, int outlen, const char *fmt,
                                TM = localtime_r(&request->timestamp, &s_TM);
                                len = strftime(tmpdt, sizeof(tmpdt), "%m", TM);
                                if (len > 0) {
-                                       strNcpy(q, tmpdt, freespace);
+                                       strlcpy(q, tmpdt, freespace);
                                        q += strlen(q);
                                }
                                p++;
                                break;
                        case 'n': /* NAS IP address */
-                               q += valuepair2str(q,freespace,pairfind(request->packet->vps,PW_NAS_IP_ADDRESS),PW_TYPE_IPADDR, func);
+                               q += valuepair2str(q,freespace,pairfind(request->packet->vps,PW_NAS_IP_ADDRESS, 0),PW_TYPE_IPADDR, func);
                                p++;
                                break;
                        case 'p': /* Port number */
-                               q += valuepair2str(q,freespace,pairfind(request->packet->vps,PW_NAS_PORT),PW_TYPE_INTEGER, func);
+                               q += valuepair2str(q,freespace,pairfind(request->packet->vps,PW_NAS_PORT, 0),PW_TYPE_INTEGER, func);
                                p++;
                                break;
                        case 's': /* Speed */
-                               q += valuepair2str(q,freespace,pairfind(request->packet->vps,PW_CONNECT_INFO),PW_TYPE_STRING, func);
+                               q += valuepair2str(q,freespace,pairfind(request->packet->vps,PW_CONNECT_INFO, 0),PW_TYPE_STRING, func);
                                p++;
                                break;
                        case 't': /* request timestamp */
                                CTIME_R(&request->timestamp, tmpdt, sizeof(tmpdt));
                                nl = strchr(tmpdt, '\n');
                                if (nl) *nl = '\0';
-                               strNcpy(q, tmpdt, freespace);
+                               strlcpy(q, tmpdt, freespace);
                                q += strlen(q);
                                p++;
                                break;
                        case 'u': /* User name */
-                               q += valuepair2str(q,freespace,pairfind(request->packet->vps,PW_USER_NAME),PW_TYPE_STRING, func);
+                               q += valuepair2str(q,freespace,pairfind(request->packet->vps,PW_USER_NAME, 0),PW_TYPE_STRING, func);
                                p++;
                                break;
                        case 'A': /* radacct_dir */
-                               strNcpy(q,radacct_dir,freespace-1);
+                               strlcpy(q,radacct_dir,freespace);
                                q += strlen(q);
                                p++;
                                break;
                        case 'C': /* ClientName */
-                               strNcpy(q,client_name(request->packet->src_ipaddr),freespace-1);
+                               strlcpy(q,request->client->shortname,freespace);
                                q += strlen(q);
                                p++;
                                break;
@@ -787,7 +1126,7 @@ int radius_xlat(char *out, int outlen, const char *fmt,
                                TM = localtime_r(&request->timestamp, &s_TM);
                                len = strftime(tmpdt, sizeof(tmpdt), "%Y%m%d", TM);
                                if (len > 0) {
-                                       strNcpy(q, tmpdt, freespace);
+                                       strlcpy(q, tmpdt, freespace);
                                        q += strlen(q);
                                }
                                p++;
@@ -796,22 +1135,22 @@ int radius_xlat(char *out, int outlen, const char *fmt,
                                TM = localtime_r(&request->timestamp, &s_TM);
                                len = strftime(tmpdt, sizeof(tmpdt), "%H", TM);
                                if (len > 0) {
-                                       strNcpy(q, tmpdt, freespace);
+                                       strlcpy(q, tmpdt, freespace);
                                        q += strlen(q);
                                }
                                p++;
                                break;
                        case 'L': /* radlog_dir */
-                               strNcpy(q,radlog_dir,freespace-1);
+                               strlcpy(q,radlog_dir,freespace);
                                q += strlen(q);
                                p++;
                                break;
                        case 'M': /* MTU */
-                               q += valuepair2str(q,freespace,pairfind(request->reply->vps,PW_FRAMED_MTU),PW_TYPE_INTEGER, func);
+                               q += valuepair2str(q,freespace,pairfind(request->reply->vps,PW_FRAMED_MTU, 0),PW_TYPE_INTEGER, func);
                                p++;
                                break;
                        case 'R': /* radius_dir */
-                               strNcpy(q,radius_dir,freespace-1);
+                               strlcpy(q,radius_dir,freespace);
                                q += strlen(q);
                                p++;
                                break;
@@ -819,7 +1158,7 @@ int radius_xlat(char *out, int outlen, const char *fmt,
                                TM = localtime_r(&request->timestamp, &s_TM);
                                len = strftime(tmpdt, sizeof(tmpdt), "%Y-%m-%d %H:%M:%S", TM);
                                if (len > 0) {
-                                       strNcpy(q, tmpdt, freespace);
+                                       strlcpy(q, tmpdt, freespace);
                                        q += strlen(q);
                                }
                                p++;
@@ -828,20 +1167,17 @@ int radius_xlat(char *out, int outlen, const char *fmt,
                                TM = localtime_r(&request->timestamp, &s_TM);
                                len = strftime(tmpdt, sizeof(tmpdt), "%Y-%m-%d-%H.%M.%S.000000", TM);
                                if (len > 0) {
-                                       strNcpy(q, tmpdt, freespace);
+                                       strlcpy(q, tmpdt, freespace);
                                        q += strlen(q);
                                }
                                p++;
                                break;
                        case 'U': /* Stripped User name */
-                               q += valuepair2str(q,freespace,pairfind(request->packet->vps,PW_STRIPPED_USER_NAME),PW_TYPE_STRING, func);
+                               q += valuepair2str(q,freespace,pairfind(request->packet->vps,PW_STRIPPED_USER_NAME, 0),PW_TYPE_STRING, func);
                                p++;
                                break;
                        case 'V': /* Request-Authenticator */
-                               if (request->packet->verified)
-                                       strNcpy(q,"Verified",freespace-1);
-                               else
-                                       strNcpy(q,"None",freespace-1);
+                               strlcpy(q,"Verified",freespace);
                                q += strlen(q);
                                p++;
                                break;
@@ -849,7 +1185,7 @@ int radius_xlat(char *out, int outlen, const char *fmt,
                                TM = localtime_r(&request->timestamp, &s_TM);
                                len = strftime(tmpdt, sizeof(tmpdt), "%Y", TM);
                                if (len > 0) {
-                                       strNcpy(q, tmpdt, freespace);
+                                       strlcpy(q, tmpdt, freespace);
                                        q += strlen(q);
                                }
                                p++;
@@ -857,7 +1193,7 @@ int radius_xlat(char *out, int outlen, const char *fmt,
                        case 'Z': /* Full request pairs except password */
                                tmp = request->packet->vps;
                                while (tmp && (freespace > 3)) {
-                                       if (tmp->attribute != PW_PASSWORD) {
+                                       if (tmp->attribute != PW_USER_PASSWORD) {
                                                *q++ = '\t';
                                                len = vp_prints(q, freespace - 2, tmp);
                                                q += len;
@@ -869,7 +1205,7 @@ int radius_xlat(char *out, int outlen, const char *fmt,
                                p++;
                                break;
                        default:
-                               DEBUG2("WARNING: Unknown variable '%%%c': See 'doc/variables.txt'", *p);
+                               RDEBUG2("WARNING: Unknown variable '%%%c': See 'doc/variables.txt'", *p);
                                if (freespace > 2) {
                                        *q++ = '%';
                                        *q++ = *p++;
@@ -879,7 +1215,7 @@ int radius_xlat(char *out, int outlen, const char *fmt,
        }
        *q = '\0';
 
-       DEBUG2("radius_xlat:  '%s'", out);
+       RDEBUG2("\texpand: %s -> %s", fmt, out);
 
        return strlen(out);
 }