2 * xlat.c Translate strings. This is the first version of xlat
3 * incorporated to RADIUS
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
21 * Copyright 2000,2006 The FreeRADIUS server project
22 * Copyright 2000 Alan DeKok <aland@ox.org>
25 #include <freeradius-devel/ident.h>
28 #include <freeradius-devel/radiusd.h>
29 #include <freeradius-devel/md5.h>
30 #include <freeradius-devel/rad_assert.h>
34 typedef struct xlat_t {
35 char module[MAX_STRING_LEN];
38 RAD_XLAT_FUNC do_xlat;
39 int internal; /* not allowed to re-define these */
42 static rbtree_t *xlat_root = NULL;
45 * Define all xlat's in the structure.
47 static const char * const internal_xlat[] = {"check",
56 #if REQUEST_MAX_REGEX > 8
57 #error Please fix the following line
59 static int xlat_inst[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8 }; /* up to 8 for regex */
63 * Convert the value on a VALUE_PAIR to string
65 static int valuepair2str(char * out,int outlen,VALUE_PAIR * pair,
66 int type, RADIUS_ESCAPE_STRING func)
68 char buffer[MAX_STRING_LEN * 4];
71 vp_prints_value(buffer, sizeof(buffer), pair, -1);
72 return func(out, outlen, buffer);
77 strlcpy(out,"_",outlen);
79 case PW_TYPE_INTEGER :
80 strlcpy(out,"0",outlen);
83 strlcpy(out,"?.?.?.?",outlen);
85 case PW_TYPE_IPV6ADDR :
86 strlcpy(out,":?:",outlen);
89 strlcpy(out,"0",outlen);
92 strlcpy(out,"unknown_type",outlen);
99 * Dynamically translate for check:, request:, reply:, etc.
101 static size_t xlat_packet(void *instance, REQUEST *request,
102 char *fmt, char *out, size_t outlen,
103 RADIUS_ESCAPE_STRING func)
107 VALUE_PAIR *vps = NULL;
108 RADIUS_PACKET *packet = NULL;
110 switch (*(int*) instance) {
112 vps = request->config_items;
116 vps = request->packet->vps;
117 packet = request->packet;
121 vps = request->reply->vps;
122 packet = request->reply;
127 if (request->proxy) vps = request->proxy->vps;
128 packet = request->proxy;
134 if (request->proxy_reply) vps = request->proxy_reply->vps;
135 packet = request->proxy_reply;
140 if (request->parent) {
141 vps = request->parent->packet->vps;
142 packet = request->parent->packet;
147 if (request->parent && request->parent->reply) {
148 vps = request->parent->reply->vps;
149 packet = request->parent->reply;
158 * The "format" string is the attribute name.
160 da = dict_attrbyname(fmt);
162 int do_number = FALSE;
167 if (strlen(fmt) > sizeof(buffer)) return 0;
169 p = strchr(fmt, '[');
171 p = strchr(fmt, '#');
176 strlcpy(buffer, fmt, p - fmt + 1);
178 da = dict_attrbyname(buffer);
182 vp = pairfind(vps, da->attr, 0);
189 case PW_TYPE_INTEGER:
193 snprintf(out, outlen, "%u", vp->lvalue);
201 * %{Attribute-Name[#]} returns the count of
202 * attributes of that name in the list.
204 if ((p[1] == '#') && (p[2] == ']')) {
207 for (vp = pairfind(vps, da->attr, da->vendor);
209 vp = pairfind(vp->next, da->attr, da->vendor)) {
212 snprintf(out, outlen, "%d", (int) count);
217 * %{Attribute-Name[*]} returns ALL of the
218 * the attributes, separated by a newline.
220 if ((p[1] == '*') && (p[2] == ']')) {
223 for (vp = pairfind(vps, da->attr, da->vendor);
225 vp = pairfind(vp->next, da->attr, da->vendor)) {
226 count = valuepair2str(out, outlen - 1, vp, da->type, func);
227 rad_assert(count <= outlen);
229 outlen -= (count + 1);
234 if (outlen == 0) break;
245 p += 1 + strspn(p + 1, "0123456789");
247 RDEBUG2("xlat: Invalid array reference in string at %s %s",
253 * Find the N'th value.
255 for (vp = pairfind(vps, da->attr, da->vendor);
257 vp = pairfind(vp->next, da->attr, da->vendor)) {
258 if (count == 0) break;
263 * Non-existent array reference.
267 return valuepair2str(out, outlen, vp, da->type, func);
270 vp = pairfind(vps, da->attr, da->vendor);
273 * Some "magic" handlers, which are never in VP's, but
274 * which are in the packet.
276 * FIXME: We should really do this in a more
282 memset(&localvp, 0, sizeof(localvp));
289 dval = dict_valbyattr(da->attr, da->vendor, packet->code);
291 snprintf(out, outlen, "%s", dval->name);
293 snprintf(out, outlen, "%d", packet->code);
299 case PW_CLIENT_SHORTNAME:
300 if (request->client && request->client->shortname) {
301 strlcpy(out, request->client->shortname, outlen);
303 strlcpy(out, "<UNKNOWN-CLIENT>", outlen);
307 case PW_CLIENT_IP_ADDRESS: /* the same as below */
308 case PW_PACKET_SRC_IP_ADDRESS:
309 if (packet->src_ipaddr.af != AF_INET) {
312 localvp.attribute = da->attr;
313 localvp.vp_ipaddr = packet->src_ipaddr.ipaddr.ip4addr.s_addr;
316 case PW_PACKET_DST_IP_ADDRESS:
317 if (packet->dst_ipaddr.af != AF_INET) {
320 localvp.attribute = da->attr;
321 localvp.vp_ipaddr = packet->dst_ipaddr.ipaddr.ip4addr.s_addr;
324 case PW_PACKET_SRC_PORT:
325 localvp.attribute = da->attr;
326 localvp.vp_integer = packet->src_port;
329 case PW_PACKET_DST_PORT:
330 localvp.attribute = da->attr;
331 localvp.vp_integer = packet->dst_port;
334 case PW_PACKET_AUTHENTICATION_VECTOR:
335 localvp.attribute = da->attr;
336 memcpy(localvp.vp_strvalue, packet->vector,
337 sizeof(packet->vector));
338 localvp.length = sizeof(packet->vector);
342 * Authorization, accounting, etc.
344 case PW_REQUEST_PROCESSING_STAGE:
345 if (request->component) {
346 strlcpy(out, request->component, outlen);
348 strlcpy(out, "server_core", outlen);
352 case PW_PACKET_SRC_IPV6_ADDRESS:
353 if (packet->src_ipaddr.af != AF_INET6) {
356 localvp.attribute = da->attr;
357 memcpy(localvp.vp_strvalue,
358 &packet->src_ipaddr.ipaddr.ip6addr,
359 sizeof(packet->src_ipaddr.ipaddr.ip6addr));
362 case PW_PACKET_DST_IPV6_ADDRESS:
363 if (packet->dst_ipaddr.af != AF_INET6) {
366 localvp.attribute = da->attr;
367 memcpy(localvp.vp_strvalue,
368 &packet->dst_ipaddr.ipaddr.ip6addr,
369 sizeof(packet->dst_ipaddr.ipaddr.ip6addr));
372 case PW_VIRTUAL_SERVER:
373 if (!request->server) return 0;
375 snprintf(out, outlen, "%s", request->server);
379 case PW_MODULE_RETURN_CODE:
380 localvp.attribute = da->attr;
383 * See modcall.c for a bit of a hack.
385 localvp.vp_integer = request->simul_max;
389 return 0; /* not found */
393 localvp.type = da->type;
394 return valuepair2str(out, outlen, &localvp,
404 if (!vps) return 0; /* silently fail */
407 * Convert the VP to a string, and return it.
409 return valuepair2str(out, outlen, vp, da->type, func);
414 * Pull %{0} to %{8} out of the packet.
416 static size_t xlat_regex(void *instance, REQUEST *request,
417 char *fmt, char *out, size_t outlen,
418 RADIUS_ESCAPE_STRING func)
423 * We cheat: fmt is "0" to "8", but those numbers
424 * are already in the "instance".
426 fmt = fmt; /* -Wunused */
427 func = func; /* -Wunused FIXME: do escaping? */
429 regex = request_data_reference(request, request,
430 REQUEST_DATA_REGEX | *(int *)instance);
431 if (!regex) return 0;
434 * Copy UP TO "freespace" bytes, including
437 strlcpy(out, regex, outlen);
440 #endif /* HAVE_REGEX_H */
444 * Change the debugging level.
446 static size_t xlat_debug(UNUSED void *instance, REQUEST *request,
447 char *fmt, char *out, size_t outlen,
448 UNUSED RADIUS_ESCAPE_STRING func)
452 if (*fmt) level = atoi(fmt);
455 request->options = RAD_REQUEST_OPTION_NONE;
456 request->radlog = NULL;
458 if (level > 4) level = 4;
460 request->options = level;
461 request->radlog = radlog_request;
464 snprintf(out, outlen, "%d", level);
470 * Calculate the MD5 hash of a string.
472 static size_t xlat_md5(UNUSED void *instance, REQUEST *request,
473 char *fmt, char *out, size_t outlen,
474 UNUSED RADIUS_ESCAPE_STRING func)
481 if (!radius_xlat(buffer, sizeof(buffer), fmt, request, func)) {
487 fr_MD5Update(&ctx, (void *) buffer, strlen(buffer));
488 fr_MD5Final(digest, &ctx);
491 snprintf(out, outlen, "md5_overflow");
495 for (i = 0; i < 16; i++) {
496 snprintf(out + i * 2, 3, "%02x", digest[i]);
504 * Convert a string to lowercase
506 static size_t xlat_lc(UNUSED void *instance, REQUEST *request,
507 char *fmt, char *out, size_t outlen,
508 UNUSED RADIUS_ESCAPE_STRING func)
513 if (outlen <= 1) return 0;
515 if (!radius_xlat(buffer, sizeof(buffer), fmt, request, func)) {
520 for (p = buffer, q = out; *p != '\0'; p++, outlen--) {
521 if (outlen <= 1) break;
523 *(q++) = tolower((int) *p);
533 * Convert a string to uppercase
535 static size_t xlat_uc(UNUSED void *instance, REQUEST *request,
536 char *fmt, char *out, size_t outlen,
537 UNUSED RADIUS_ESCAPE_STRING func)
542 if (outlen <= 1) return 0;
544 if (!radius_xlat(buffer, sizeof(buffer), fmt, request, func)) {
549 for (p = buffer, q = out; *p != '\0'; p++, outlen--) {
550 if (outlen <= 1) break;
552 *(q++) = toupper((int) *p);
562 * Compare two xlat_t structs, based ONLY on the module name.
564 static int xlat_cmp(const void *a, const void *b)
566 if (((const xlat_t *)a)->length != ((const xlat_t *)b)->length) {
567 return ((const xlat_t *)a)->length - ((const xlat_t *)b)->length;
570 return memcmp(((const xlat_t *)a)->module,
571 ((const xlat_t *)b)->module,
572 ((const xlat_t *)a)->length);
576 * find the appropriate registered xlat function.
578 static xlat_t *xlat_find(const char *module)
583 * Look for dictionary attributes first.
585 if ((dict_attrbyname(module) != NULL) ||
586 (strchr(module, '[') != NULL) ||
587 (strchr(module, '#') != NULL)) {
591 strlcpy(my_xlat.module, module, sizeof(my_xlat.module));
592 my_xlat.length = strlen(my_xlat.module);
594 return rbtree_finddata(xlat_root, &my_xlat);
599 * Register an xlat function.
601 int xlat_register(const char *module, RAD_XLAT_FUNC func, void *instance)
606 if ((module == NULL) || (strlen(module) == 0)) {
607 DEBUG("xlat_register: Invalid module name");
612 * First time around, build up the tree...
614 * FIXME: This code should be hoisted out of this function,
615 * and into a global "initialization". But it isn't critical...
623 xlat_root = rbtree_create(xlat_cmp, free, 0);
625 DEBUG("xlat_register: Failed to create tree.");
630 * Register the internal packet xlat's.
632 for (i = 0; internal_xlat[i] != NULL; i++) {
633 xlat_register(internal_xlat[i], xlat_packet, &xlat_inst[i]);
634 c = xlat_find(internal_xlat[i]);
635 rad_assert(c != NULL);
640 * New name: "control"
642 xlat_register("control", xlat_packet, &xlat_inst[0]);
643 c = xlat_find("control");
644 rad_assert(c != NULL);
649 * Register xlat's for regexes.
652 for (i = 0; i <= REQUEST_MAX_REGEX; i++) {
654 xlat_register(buffer, xlat_regex, &xlat_inst[i]);
655 c = xlat_find(buffer);
656 rad_assert(c != NULL);
659 #endif /* HAVE_REGEX_H */
662 xlat_register("debug", xlat_debug, &xlat_inst[0]);
663 c = xlat_find("debug");
664 rad_assert(c != NULL);
667 xlat_register("md5", xlat_md5, &xlat_inst[0]);
668 c = xlat_find("md5");
669 rad_assert(c != NULL);
672 xlat_register("tolower", xlat_lc, &xlat_inst[0]);
673 c = xlat_find("tolower");
674 rad_assert(c != NULL);
677 xlat_register("toupper", xlat_uc, &xlat_inst[0]);
678 c = xlat_find("toupper");
679 rad_assert(c != NULL);
684 * If it already exists, replace the instance.
686 strlcpy(my_xlat.module, module, sizeof(my_xlat.module));
687 my_xlat.length = strlen(my_xlat.module);
688 c = rbtree_finddata(xlat_root, &my_xlat);
691 DEBUG("xlat_register: Cannot re-define internal xlat");
696 c->instance = instance;
701 * Doesn't exist. Create it.
703 c = rad_malloc(sizeof(*c));
704 memset(c, 0, sizeof(*c));
707 strlcpy(c->module, module, sizeof(c->module));
708 c->length = strlen(c->module);
709 c->instance = instance;
711 rbtree_insert(xlat_root, c);
717 * Unregister an xlat function.
719 * We can only have one function to call per name, so the
720 * passing of "func" here is extraneous.
722 void xlat_unregister(const char *module, RAD_XLAT_FUNC func)
727 func = func; /* -Wunused */
731 strlcpy(my_xlat.module, module, sizeof(my_xlat.module));
732 my_xlat.length = strlen(my_xlat.module);
734 node = rbtree_find(xlat_root, &my_xlat);
737 rbtree_delete(xlat_root, node);
741 * De-register all xlat functions,
742 * used mainly for debugging.
746 rbtree_free(xlat_root);
751 * Decode an attribute name into a string.
753 static int decode_attribute(const char **from, char **to, int freespace,
755 RADIUS_ESCAPE_STRING func)
758 char *xlat_name, *xlat_string;
759 char *p, *q, *l, *next = NULL;
770 * Copy the input string to an intermediate buffer where
773 varlen = rad_copy_variable(buffer, *from);
775 RDEBUG2("Badly formatted variable: %s", *from);
781 * Kill the %{} around the data we are looking for.
784 p[varlen - 1] = '\0'; /* */
792 * Handle %{%{foo}:-%{bar}}, which is useful, too.
794 * Did I mention that this parser is garbage?
796 if ((p[0] == '%') && (p[1] == '{')) {
801 * 'p' is after the start of 'buffer', so we can
804 len1 = rad_copy_variable(buffer, p);
806 RDEBUG2("Badly formatted variable: %s", p);
811 * They did %{%{foo}}, which is stupid, but allowed.
814 RDEBUG2("Improperly nested variable; %%{%s}", p);
819 * It SHOULD be %{%{foo}:-%{bar}}. If not, it's
822 if ((p[len1] != ':') || (p[len1 + 1] != '-')) {
823 RDEBUG2("No trailing :- after variable at %s", p);
828 * Parse the second bit. The second bit can be
829 * either %{foo}, or a string "foo", or a string
830 * 'foo', or just a bare word: foo
833 l = buffer + len1 + 1;
835 if ((p[0] == '%') && (p[1] == '{')) {
836 len2 = rad_copy_variable(l, p);
839 RDEBUG2("Invalid text after :- at %s", p);
845 } else if ((p[0] == '"') || p[0] == '\'') {
846 getstring((const char **) &p, l, strlen(l));
853 * Expand the first one. If we did, exit the
856 retlen = radius_xlat(q, freespace, buffer, request, func);
862 RDEBUG2("\t... expanding second conditional");
864 * Expand / copy the second string if required.
867 retlen = radius_xlat(q, freespace, l,
873 strlcpy(q, l, freespace);
878 * Else the output is an empty string.
884 * See if we're supposed to expand a module name.
887 for (l = p; *l != '\0'; l++) {
894 xlat_name = p; /* start of name */
901 * Module names can't have spaces.
903 if ((*l == ' ') || (*l == '\t')) break;
907 * %{name} is a simple attribute reference,
908 * or regex reference.
911 xlat_name = xlat_string = p;
916 * Maybe it's the old-style %{foo:-bar}
919 RDEBUG2("WARNING: Deprecated conditional expansion \":-\". See \"man unlang\" for details");
922 xlat_string = xlat_name;
928 * FIXME: For backwards "WTF" compatibility, check for
929 * {...}, (after the :), and copy that, too.
932 /* module name, followed by (possibly) per-module string */
936 if ((c = xlat_find(xlat_name)) != NULL) {
937 if (!c->internal) RDEBUG3("radius_xlat: Running registered xlat function of module %s for string \'%s\'",
938 c->module, xlat_string);
939 retlen = c->do_xlat(c->instance, request, xlat_string,
943 snprintf(q, freespace, "%d", retlen);
949 * Expand the second bit.
951 RDEBUG2("\t... expanding second conditional");
952 retlen = radius_xlat(q, freespace, next, request, func);
958 * No attribute by that name, return an error.
960 RDEBUG2("WARNING: Unknown module \"%s\" in string expansion \"%%%s\"", xlat_name, *from);
970 * If the caller doesn't pass xlat an escape function, then
971 * we use this one. It simplifies the coding, as the check for
972 * func == NULL only happens once.
974 static size_t xlat_copy(char *out, size_t outlen, const char *in)
976 int freespace = outlen;
978 rad_assert(outlen > 0);
980 while ((*in) && (freespace > 1)) {
984 * FIXME: Do escaping of bad stuff!
992 return (outlen - freespace); /* count does not include NUL */
996 * Replace %<whatever> in a string.
998 * See 'doc/variables.txt' for more information.
1000 int radius_xlat(char *out, int outlen, const char *fmt,
1001 REQUEST *request, RADIUS_ESCAPE_STRING func)
1003 int c, len, freespace;
1008 struct tm *TM, s_TM;
1009 char tmpdt[40]; /* For temporary storing of dates */
1013 * Catch bad modules.
1015 if (!fmt || !out || !request) return 0;
1018 * Ensure that we always have an escaping function.
1027 /* Calculate freespace in output */
1028 freespace = outlen - (q - out);
1033 if ((c != '%') && (c != '$') && (c != '\\')) {
1035 * We check if we're inside an open brace. If we are
1036 * then we assume this brace is NOT literal, but is
1037 * a closing brace and apply it
1039 if ((c == '}') && openbraces) {
1049 * There's nothing after this character, copy
1050 * the last '%' or "$' or '\\' over to the output
1076 } else if (c == '%') switch(*p) {
1079 if (decode_attribute(&p, &q, freespace, request, func) < 0) return 0;
1085 case 'a': /* Protocol: */
1086 q += valuepair2str(q,freespace,pairfind(request->reply->vps,PW_FRAMED_PROTOCOL, 0),PW_TYPE_INTEGER, func);
1089 case 'c': /* Callback-Number */
1090 q += valuepair2str(q,freespace,pairfind(request->reply->vps,PW_CALLBACK_NUMBER, 0),PW_TYPE_STRING, func);
1093 case 'd': /* request day */
1094 TM = localtime_r(&request->timestamp, &s_TM);
1095 len = strftime(tmpdt, sizeof(tmpdt), "%d", TM);
1097 strlcpy(q, tmpdt, freespace);
1102 case 'f': /* Framed IP address */
1103 q += valuepair2str(q,freespace,pairfind(request->reply->vps,PW_FRAMED_IP_ADDRESS, 0),PW_TYPE_IPADDR, func);
1106 case 'i': /* Calling station ID */
1107 q += valuepair2str(q,freespace,pairfind(request->packet->vps,PW_CALLING_STATION_ID, 0),PW_TYPE_STRING, func);
1110 case 'l': /* request timestamp */
1111 snprintf(tmpdt, sizeof(tmpdt), "%lu",
1112 (unsigned long) request->timestamp);
1113 strlcpy(q,tmpdt,freespace);
1117 case 'm': /* request month */
1118 TM = localtime_r(&request->timestamp, &s_TM);
1119 len = strftime(tmpdt, sizeof(tmpdt), "%m", TM);
1121 strlcpy(q, tmpdt, freespace);
1126 case 'n': /* NAS IP address */
1127 q += valuepair2str(q,freespace,pairfind(request->packet->vps,PW_NAS_IP_ADDRESS, 0),PW_TYPE_IPADDR, func);
1130 case 'p': /* Port number */
1131 q += valuepair2str(q,freespace,pairfind(request->packet->vps,PW_NAS_PORT, 0),PW_TYPE_INTEGER, func);
1134 case 's': /* Speed */
1135 q += valuepair2str(q,freespace,pairfind(request->packet->vps,PW_CONNECT_INFO, 0),PW_TYPE_STRING, func);
1138 case 't': /* request timestamp */
1139 CTIME_R(&request->timestamp, tmpdt, sizeof(tmpdt));
1140 nl = strchr(tmpdt, '\n');
1142 strlcpy(q, tmpdt, freespace);
1146 case 'u': /* User name */
1147 q += valuepair2str(q,freespace,pairfind(request->packet->vps,PW_USER_NAME, 0),PW_TYPE_STRING, func);
1150 case 'A': /* radacct_dir */
1151 strlcpy(q,radacct_dir,freespace);
1155 case 'C': /* ClientName */
1156 strlcpy(q,request->client->shortname,freespace);
1160 case 'D': /* request date */
1161 TM = localtime_r(&request->timestamp, &s_TM);
1162 len = strftime(tmpdt, sizeof(tmpdt), "%Y%m%d", TM);
1164 strlcpy(q, tmpdt, freespace);
1169 case 'H': /* request hour */
1170 TM = localtime_r(&request->timestamp, &s_TM);
1171 len = strftime(tmpdt, sizeof(tmpdt), "%H", TM);
1173 strlcpy(q, tmpdt, freespace);
1178 case 'L': /* radlog_dir */
1179 strlcpy(q,radlog_dir,freespace);
1184 q += valuepair2str(q,freespace,pairfind(request->reply->vps,PW_FRAMED_MTU, 0),PW_TYPE_INTEGER, func);
1187 case 'R': /* radius_dir */
1188 strlcpy(q,radius_dir,freespace);
1192 case 'S': /* request timestamp in SQL format*/
1193 TM = localtime_r(&request->timestamp, &s_TM);
1194 len = strftime(tmpdt, sizeof(tmpdt), "%Y-%m-%d %H:%M:%S", TM);
1196 strlcpy(q, tmpdt, freespace);
1201 case 'T': /* request timestamp */
1202 TM = localtime_r(&request->timestamp, &s_TM);
1203 len = strftime(tmpdt, sizeof(tmpdt), "%Y-%m-%d-%H.%M.%S.000000", TM);
1205 strlcpy(q, tmpdt, freespace);
1210 case 'U': /* Stripped User name */
1211 q += valuepair2str(q,freespace,pairfind(request->packet->vps,PW_STRIPPED_USER_NAME, 0),PW_TYPE_STRING, func);
1214 case 'V': /* Request-Authenticator */
1215 strlcpy(q,"Verified",freespace);
1219 case 'Y': /* request year */
1220 TM = localtime_r(&request->timestamp, &s_TM);
1221 len = strftime(tmpdt, sizeof(tmpdt), "%Y", TM);
1223 strlcpy(q, tmpdt, freespace);
1228 case 'Z': /* Full request pairs except password */
1229 tmp = request->packet->vps;
1230 while (tmp && (freespace > 3)) {
1231 if (tmp->attribute != PW_USER_PASSWORD) {
1233 len = vp_prints(q, freespace - 2, tmp);
1235 freespace -= (len + 2);
1243 RDEBUG2("WARNING: Unknown variable '%%%c': See 'doc/variables.txt'", *p);
1244 if (freespace > 2) {
1253 RDEBUG2("\texpand: %s -> %s", fmt, out);