3 * @brief String expansion ("translation"). Implements %Attribute -> value
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/rad_assert.h>
30 #include <freeradius-devel/base64.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",
57 static const char * const xlat_foreach_names[] = {"Foreach-Variable-0",
70 #if REQUEST_MAX_REGEX > 8
71 #error Please fix the following line
73 static int xlat_inst[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8 }; /* up to 8 for regex */
76 * @brief Convert the value on a VALUE_PAIR to string
78 static int valuepair2str(char * out,int outlen,VALUE_PAIR * pair, int type)
81 vp_prints_value(out, outlen, pair, -1);
87 strlcpy(out,"_",outlen);
89 case PW_TYPE_INTEGER64:
92 strlcpy(out,"0",outlen);
95 strlcpy(out,"?.?.?.?",outlen);
97 case PW_TYPE_IPV6ADDR :
98 strlcpy(out,":?:",outlen);
101 strlcpy(out,"0",outlen);
104 strlcpy(out,"unknown_type",outlen);
110 * Dynamically translate for check:, request:, reply:, etc.
112 static size_t xlat_packet(void *instance, REQUEST *request,
113 const char *fmt, char *out, size_t outlen)
117 VALUE_PAIR *vps = NULL;
118 RADIUS_PACKET *packet = NULL;
120 switch (*(int*) instance) {
122 vps = request->config_items;
126 vps = request->packet->vps;
127 packet = request->packet;
131 vps = request->reply->vps;
132 packet = request->reply;
137 if (request->proxy) vps = request->proxy->vps;
138 packet = request->proxy;
144 if (request->proxy_reply) vps = request->proxy_reply->vps;
145 packet = request->proxy_reply;
150 if (request->parent) {
151 vps = request->parent->packet->vps;
152 packet = request->parent->packet;
157 if (request->parent && request->parent->reply) {
158 vps = request->parent->reply->vps;
159 packet = request->parent->reply;
164 if (request->parent) {
165 vps = request->parent->config_items;
174 * The "format" string is the attribute name.
176 da = dict_attrbyname(fmt);
178 int do_number = FALSE;
179 int do_array = FALSE;
180 int do_count = FALSE;
183 size_t count = 0, total;
187 if (strlen(fmt) > sizeof(buffer)) return 0;
189 strlcpy(buffer, fmt, sizeof(buffer));
192 * %{Attribute-name#} - print integer version of it.
194 p = buffer + strlen(buffer) - 1;
201 * %{Attribute-Name:tag} - get the name with the specified
204 p = strchr(buffer, ':');
212 * Allow %{Attribute-Name:tag[...]}
218 * %{Attribute-Name[...] does more stuff
226 } else if (p[1] == '*') {
230 p += 1 + strspn(p + 1, "0123456789");
232 RDEBUG2("xlat: Invalid array reference in string at %s %s",
240 * We COULD argue about %{Attribute-Name[#]#} etc.
241 * But that looks like more work than it's worth.
244 da = dict_attrbyname(buffer);
248 * No array, print the tagged attribute.
251 vp = pairfind(vps, da->attr, da->vendor, tag);
258 * Array[#] - return the total
261 for (vp = pairfind(vps, da->attr, da->vendor, tag);
263 vp = pairfind(vp->next, da->attr, da->vendor, tag)) {
267 snprintf(out, outlen, "%d", (int) total);
272 * %{Attribute-Name[*]} returns ALL of the
273 * the attributes, separated by a newline.
276 for (vp = pairfind(vps, da->attr, da->vendor, tag);
278 vp = pairfind(vp->next, da->attr, da->vendor, tag)) {
279 count = valuepair2str(out, outlen - 1, vp, da->type);
280 rad_assert(count <= outlen);
282 outlen -= (count + 1);
287 if (outlen <= 1) break;
295 * Find the N'th value.
297 for (vp = pairfind(vps, da->attr, da->vendor, tag);
299 vp = pairfind(vp->next, da->attr, da->vendor, tag)) {
300 if (total == count) break;
309 * Non-existent array reference.
315 if ((vp->type != PW_TYPE_IPADDR) &&
316 (vp->type != PW_TYPE_INTEGER) &&
317 (vp->type != PW_TYPE_SHORT) &&
318 (vp->type != PW_TYPE_BYTE) &&
319 (vp->type != PW_TYPE_DATE)) {
324 return snprintf(out, outlen, "%u", vp->vp_integer);
327 return valuepair2str(out, outlen, vp, da->type);
330 vp = pairfind(vps, da->attr, da->vendor, TAG_ANY);
333 * Some "magic" handlers, which are never in VP's, but
334 * which are in the packet.
336 * @bug FIXME: We should really do this in a more
342 memset(&localvp, 0, sizeof(localvp));
349 dval = dict_valbyattr(da->attr, da->vendor, packet->code);
351 snprintf(out, outlen, "%s", dval->name);
353 snprintf(out, outlen, "%d", packet->code);
359 case PW_CLIENT_SHORTNAME:
360 if (request->client && request->client->shortname) {
361 strlcpy(out, request->client->shortname, outlen);
363 strlcpy(out, "<UNKNOWN-CLIENT>", outlen);
367 case PW_CLIENT_IP_ADDRESS: /* the same as below */
368 case PW_PACKET_SRC_IP_ADDRESS:
369 if (packet->src_ipaddr.af != AF_INET) {
372 localvp.attribute = da->attr;
373 localvp.vp_ipaddr = packet->src_ipaddr.ipaddr.ip4addr.s_addr;
376 case PW_PACKET_DST_IP_ADDRESS:
377 if (packet->dst_ipaddr.af != AF_INET) {
380 localvp.attribute = da->attr;
381 localvp.vp_ipaddr = packet->dst_ipaddr.ipaddr.ip4addr.s_addr;
384 case PW_PACKET_SRC_PORT:
385 localvp.attribute = da->attr;
386 localvp.vp_integer = packet->src_port;
389 case PW_PACKET_DST_PORT:
390 localvp.attribute = da->attr;
391 localvp.vp_integer = packet->dst_port;
394 case PW_PACKET_AUTHENTICATION_VECTOR:
395 localvp.attribute = da->attr;
396 memcpy(localvp.vp_strvalue, packet->vector,
397 sizeof(packet->vector));
398 localvp.length = sizeof(packet->vector);
402 * Authorization, accounting, etc.
404 case PW_REQUEST_PROCESSING_STAGE:
405 if (request->component) {
406 strlcpy(out, request->component, outlen);
408 strlcpy(out, "server_core", outlen);
412 case PW_PACKET_SRC_IPV6_ADDRESS:
413 if (packet->src_ipaddr.af != AF_INET6) {
416 localvp.attribute = da->attr;
417 memcpy(localvp.vp_strvalue,
418 &packet->src_ipaddr.ipaddr.ip6addr,
419 sizeof(packet->src_ipaddr.ipaddr.ip6addr));
422 case PW_PACKET_DST_IPV6_ADDRESS:
423 if (packet->dst_ipaddr.af != AF_INET6) {
426 localvp.attribute = da->attr;
427 memcpy(localvp.vp_strvalue,
428 &packet->dst_ipaddr.ipaddr.ip6addr,
429 sizeof(packet->dst_ipaddr.ipaddr.ip6addr));
432 case PW_VIRTUAL_SERVER:
433 if (!request->server) return 0;
435 snprintf(out, outlen, "%s", request->server);
439 case PW_MODULE_RETURN_CODE:
440 localvp.attribute = da->attr;
443 * See modcall.c for a bit of a hack.
445 localvp.vp_integer = request->simul_max;
449 return 0; /* not found */
453 localvp.type = da->type;
454 return valuepair2str(out, outlen, &localvp, da->type);
463 if (!vps) return 0; /* silently fail */
466 * Convert the VP to a string, and return it.
468 return valuepair2str(out, outlen, vp, da->type);
472 * @brief Print data as integer, not as VALUE.
474 static size_t xlat_integer(UNUSED void *instance, REQUEST *request,
475 const char *fmt, char *out, size_t outlen)
481 while (isspace((int) *fmt)) fmt++;
483 if ((radius_get_vp(request, fmt, &vp) < 0) || !vp) {
492 if (vp->length > 8) {
496 memcpy(&integer, &(vp->vp_octets), vp->length);
498 return snprintf(out, outlen, "%llu", ntohll(integer));
500 case PW_TYPE_INTEGER64:
501 return snprintf(out, outlen, "%llu", vp->vp_integer64);
504 case PW_TYPE_INTEGER:
508 return snprintf(out, outlen, "%u", vp->vp_integer);
516 * @brief Print data as hex, not as VALUE.
518 static size_t xlat_hex(UNUSED void *instance, REQUEST *request,
519 const char *fmt, char *out, size_t outlen)
523 uint8_t buffer[MAX_STRING_LEN];
527 while (isspace((int) *fmt)) fmt++;
529 if ((radius_get_vp(request, fmt, &vp) < 0) || !vp) {
534 ret = rad_vp2data(vp, buffer, sizeof(buffer));
538 * Don't truncate the data.
540 if ((ret < 0 ) || (outlen < (len * 2))) {
545 for (i = 0; i < len; i++) {
546 snprintf(out + 2*i, 3, "%02x", buffer[i]);
553 * @brief Print data as base64, not as VALUE
555 static size_t xlat_base64(UNUSED void *instance, REQUEST *request,
556 const char *fmt, char *out, size_t outlen)
559 uint8_t buffer[MAX_STRING_LEN];
564 while (isspace((int) *fmt)) fmt++;
566 if ((radius_get_vp(request, fmt, &vp) < 0) || !vp) {
571 ret = rad_vp2data(vp, buffer, sizeof(buffer));
579 enc = FR_BASE64_ENC_LENGTH(len);
582 * Don't truncate the data.
584 if (outlen < (enc + 1)) {
589 fr_base64_encode(buffer, len, out, outlen);
595 * @brief Prints the current module processing the request
597 static size_t xlat_module(UNUSED void *instance, REQUEST *request,
598 UNUSED const char *fmt, char *out, size_t outlen)
600 strlcpy(out, request->module, outlen);
607 * @brief Implements the Foreach-Variable-X
611 static size_t xlat_foreach(void *instance, REQUEST *request,
612 UNUSED const char *fmt, char *out, size_t outlen)
617 * See modcall, "FOREACH" for how this works.
619 pvp = (VALUE_PAIR **) request_data_reference(request, radius_get_vp,
626 return valuepair2str(out, outlen, (*pvp), (*pvp)->type);
631 * @brief Print data as string, if possible.
633 * If attribute "Foo" is defined as "octets" it will normally
634 * be printed as 0x0a0a0a. The xlat "%{string:Foo}" will instead
637 static size_t xlat_string(UNUSED void *instance, REQUEST *request,
638 const char *fmt, char *out, size_t outlen)
643 while (isspace((int) *fmt)) fmt++;
651 if (radius_get_vp(request, fmt, &vp) < 0) goto nothing;
653 if (!vp) goto nothing;
655 if (vp->type != PW_TYPE_OCTETS) goto nothing;
657 len = fr_print_string(vp->vp_strvalue, vp->length, out, outlen);
664 * @brief xlat expand string attribute value
666 static size_t xlat_xlat(UNUSED void *instance, REQUEST *request,
667 const char *fmt, char *out, size_t outlen)
671 while (isspace((int) *fmt)) fmt++;
679 if (radius_get_vp(request, fmt, &vp) < 0) goto nothing;
681 if (!vp) goto nothing;
683 return radius_xlat(out, outlen, vp->vp_strvalue, request, NULL, NULL);
688 * @brief Expand regexp matches %{0} to %{8}
690 static size_t xlat_regex(void *instance, REQUEST *request,
691 const char *fmt, char *out, size_t outlen)
696 * We cheat: fmt is "0" to "8", but those numbers
697 * are already in the "instance".
699 fmt = fmt; /* -Wunused */
701 regex = request_data_reference(request, request,
702 REQUEST_DATA_REGEX | *(int *)instance);
703 if (!regex) return 0;
706 * Copy UP TO "freespace" bytes, including
709 strlcpy(out, regex, outlen);
712 #endif /* HAVE_REGEX_H */
715 * @brief Dynamically change the debugging level for the current request
719 static size_t xlat_debug(UNUSED void *instance, REQUEST *request,
720 const char *fmt, char *out, size_t outlen)
725 * Expand to previous (or current) level
727 snprintf(out, outlen, "%d", request->options & RAD_REQUEST_OPTION_DEBUG4);
730 * Assume we just want to get the current value and NOT set it to 0
737 request->options = RAD_REQUEST_OPTION_NONE;
738 request->radlog = NULL;
740 if (level > 4) level = 4;
742 request->options = level;
743 request->radlog = radlog_request;
751 * Compare two xlat_t structs, based ONLY on the module name.
753 static int xlat_cmp(const void *a, const void *b)
755 if (((const xlat_t *)a)->length != ((const xlat_t *)b)->length) {
756 return ((const xlat_t *)a)->length - ((const xlat_t *)b)->length;
759 return memcmp(((const xlat_t *)a)->module,
760 ((const xlat_t *)b)->module,
761 ((const xlat_t *)a)->length);
766 * find the appropriate registered xlat function.
768 static xlat_t *xlat_find(const char *module)
772 strlcpy(my_xlat.module, module, sizeof(my_xlat.module));
773 my_xlat.length = strlen(my_xlat.module);
775 return rbtree_finddata(xlat_root, &my_xlat);
780 * @brief Register an xlat function.
782 * @param module xlat name
783 * @param func xlat function to be called
784 * @param instance argument to xlat function
785 * @return 0 on success, -1 on failure
787 int xlat_register(const char *module, RAD_XLAT_FUNC func, void *instance)
792 if (!module || !*module) {
793 DEBUG("xlat_register: Invalid module name");
798 * First time around, build up the tree...
800 * FIXME: This code should be hoisted out of this function,
801 * and into a global "initialization". But it isn't critical...
809 xlat_root = rbtree_create(xlat_cmp, free, 0);
811 DEBUG("xlat_register: Failed to create tree.");
816 * Register the internal packet xlat's.
818 for (i = 0; internal_xlat[i] != NULL; i++) {
819 xlat_register(internal_xlat[i], xlat_packet, &xlat_inst[i]);
820 c = xlat_find(internal_xlat[i]);
821 rad_assert(c != NULL);
826 for (i = 0; xlat_foreach_names[i] != NULL; i++) {
827 xlat_register(xlat_foreach_names[i],
828 xlat_foreach, &xlat_inst[i]);
829 c = xlat_find(xlat_foreach_names[i]);
830 rad_assert(c != NULL);
836 * New name: "control"
838 xlat_register("control", xlat_packet, &xlat_inst[0]);
839 c = xlat_find("control");
840 rad_assert(c != NULL);
843 #define XLAT_REGISTER(_x) xlat_register(Stringify(_x), xlat_ ## _x, NULL); \
844 c = xlat_find(Stringify(_x)); \
845 rad_assert(c != NULL); \
848 XLAT_REGISTER(integer);
850 XLAT_REGISTER(base64);
851 XLAT_REGISTER(string);
853 XLAT_REGISTER(module);
857 * Register xlat's for regexes.
860 for (i = 0; i <= REQUEST_MAX_REGEX; i++) {
862 xlat_register(buffer, xlat_regex, &xlat_inst[i]);
863 c = xlat_find(buffer);
864 rad_assert(c != NULL);
867 #endif /* HAVE_REGEX_H */
870 xlat_register("debug", xlat_debug, &xlat_inst[0]);
871 c = xlat_find("debug");
872 rad_assert(c != NULL);
877 * If it already exists, replace the instance.
879 strlcpy(my_xlat.module, module, sizeof(my_xlat.module));
880 my_xlat.length = strlen(my_xlat.module);
881 c = rbtree_finddata(xlat_root, &my_xlat);
884 DEBUG("xlat_register: Cannot re-define internal xlat");
889 c->instance = instance;
894 * Doesn't exist. Create it.
896 c = rad_malloc(sizeof(*c));
897 memset(c, 0, sizeof(*c));
900 strlcpy(c->module, module, sizeof(c->module));
901 c->length = strlen(c->module);
902 c->instance = instance;
904 rbtree_insert(xlat_root, c);
910 * @brief Unregister an xlat function.
912 * We can only have one function to call per name, so the
913 * passing of "func" here is extraneous.
915 * @param module xlat to unregister
919 void xlat_unregister(const char *module, RAD_XLAT_FUNC func, void *instance)
924 func = func; /* -Wunused */
928 strlcpy(my_xlat.module, module, sizeof(my_xlat.module));
929 my_xlat.length = strlen(my_xlat.module);
931 c = rbtree_finddata(xlat_root, &my_xlat);
934 if (c->instance != instance) return;
936 rbtree_deletebydata(xlat_root, c);
940 * @brief De-register all xlat functions, used mainly for debugging.
944 rbtree_free(xlat_root);
949 * @brief Decode an attribute name into a string.
951 * This expands the various formats:
956 * calls radius_xlat() to do most of the work
958 * @param from string to expand
959 * @param to buffer for output
960 * @param freespace space remaining in output buffer
961 * @param request current server request
962 * @param func optional function to escape output; passed to radius_xlat()
963 * @return 0 on success, -1 on failure
965 static int decode_attribute(const char **from, char **to, int freespace,
967 RADIUS_ESCAPE_STRING func, void *funcarg)
970 const char *module_name, *xlat_str;
971 char *p, *q, *l, *next = NULL;
982 * Copy the input string to an intermediate buffer where
985 varlen = rad_copy_variable(buffer, *from);
987 RDEBUG2("ERROR: Badly formatted variable: %s", *from);
993 * Kill the %{} around the data we are looking for.
996 p[varlen - 1] = '\0'; /* */
1004 * Handle %{%{foo}:-%{bar}}, which is useful, too.
1006 * Did I mention that this parser is garbage?
1008 if ((p[0] == '%') && (p[1] == '{')) {
1010 int expand2 = FALSE;
1013 * 'p' is after the start of 'buffer', so we can
1016 len1 = rad_copy_variable(buffer, p);
1018 RDEBUG2("ERROR: Badly formatted variable: %s", p);
1023 * They did %{%{foo}}, which is stupid, but allowed.
1026 RDEBUG2("Improperly nested variable; %%{%s}", p);
1031 * It SHOULD be %{%{foo}:-%{bar}}. If not, it's
1034 if ((p[len1] != ':') || (p[len1 + 1] != '-')) {
1035 RDEBUG2("No trailing :- after variable at %s", p);
1040 * Parse the second bit. The second bit can be
1041 * either %{foo}, or a string "foo", or a string
1042 * 'foo', or just a bare word: foo
1045 l = buffer + len1 + 1;
1047 if ((p[0] == '%') && (p[1] == '{')) {
1048 len2 = rad_copy_variable(l, p);
1051 RDEBUG2("ERROR: Invalid text after :- at %s", p);
1057 } else if ((p[0] == '"') || p[0] == '\'') {
1058 getstring((const char **) &p, l, strlen(l));
1065 * Expand the first one. If we did, exit the
1068 retlen = radius_xlat(q, freespace, buffer, request, func, funcarg);
1074 RDEBUG2("\t... expanding second conditional");
1076 * Expand / copy the second string if required.
1079 retlen = radius_xlat(q, freespace, l,
1080 request, func, funcarg);
1085 strlcpy(q, l, freespace);
1090 * Else the output is an empty string.
1096 * See if we're supposed to expand a module name.
1099 for (l = p; *l != '\0'; l++) {
1104 module_name = p; /* start of name */
1111 * Module names can't have spaces.
1113 if ((*l == ' ') || (*l == '\t')) break;
1117 * %{name} is a simple attribute reference,
1118 * or regex reference.
1121 if (isdigit(*p) && !p[1]) { /* regex 0..8 */
1122 module_name = xlat_str = p;
1130 * Maybe it's the old-style %{foo:-bar}
1133 RDEBUG2("WARNING: Deprecated conditional expansion \":-\". See \"man unlang\" for details");
1136 xlat_str = module_name;
1142 * FIXME: For backwards "WTF" compatibility, check for
1143 * {...}, (after the :), and copy that, too.
1146 /* module name, followed by (possibly) per-module string */
1151 * Just "foo". Maybe it's a magic attr, which doesn't
1154 * If we can't find that, then assume it's a dictionary
1155 * attribute in the request.
1157 * Else if it's module:foo, look for module, and pass it "foo".
1160 c = xlat_find(xlat_str);
1161 if (!c) c = xlat_find("request");
1163 c = xlat_find(module_name);
1167 RDEBUG2("WARNING: Unknown Attribute \"%s\" in string expansion \"%%%s\"", xlat_str, *from);
1169 RDEBUG2("WARNING: Unknown module \"%s\" in string expansion \"%%%s\"", module_name, *from);
1174 if (!c->internal) RDEBUG3("radius_xlat: Running registered xlat function of module %s for string \'%s\'",
1175 c->module, xlat_str);
1177 /* xlat to a temporary buffer, then escape */
1179 retlen = c->do_xlat(c->instance, request, xlat_str, tmpbuf, sizeof(tmpbuf));
1181 retlen = func(request, q, freespace, tmpbuf, funcarg);
1183 RDEBUG2("\tescape: \'%s\' -> \'%s\'", tmpbuf, q);
1184 } else if (retlen < 0) {
1185 RDEBUG2("String escape failed");
1189 retlen = c->do_xlat(c->instance, request, xlat_str, q, freespace);
1193 snprintf(q, freespace, "%d", retlen);
1199 * Expand the second bit.
1201 RDEBUG2("\t... expanding second conditional");
1202 retlen = radius_xlat(q, freespace, next, request, func, funcarg);
1212 * @brief Replace %whatever in a string.
1214 * See 'doc/variables.txt' for more information.
1216 * @param out output buffer
1217 * @param outlen size of output buffer
1218 * @param fmt string to expand
1219 * @param request current request
1220 * @param func function to escape final value e.g. SQL quoting
1221 * @return length of string written @bug should really have -1 for failure
1223 size_t radius_xlat(char *out, int outlen, const char *fmt,
1225 RADIUS_ESCAPE_STRING func, void *funcarg)
1227 int c, len, freespace;
1232 struct tm *TM, s_TM;
1233 char tmpdt[40]; /* For temporary storing of dates */
1236 * Catch bad modules.
1238 if (!fmt || !out || !request) return 0;
1243 /* Calculate freespace in output */
1244 freespace = outlen - (q - out);
1249 if ((c != '%') && (c != '$') && (c != '\\')) {
1251 * We check if we're inside an open brace. If we are
1252 * then we assume this brace is NOT literal, but is
1253 * a closing brace and apply it
1260 * There's nothing after this character, copy
1261 * the last '%' or "$' or '\\' over to the output
1287 } else if (c == '%') switch(*p) {
1290 if (decode_attribute(&p, &q, freespace, request, func, funcarg) < 0) return 0;
1296 case 'd': /* request day */
1297 TM = localtime_r(&request->timestamp, &s_TM);
1298 len = strftime(tmpdt, sizeof(tmpdt), "%d", TM);
1300 strlcpy(q, tmpdt, freespace);
1305 case 'l': /* request timestamp */
1306 snprintf(tmpdt, sizeof(tmpdt), "%lu",
1307 (unsigned long) request->timestamp);
1308 strlcpy(q,tmpdt,freespace);
1312 case 'm': /* request month */
1313 TM = localtime_r(&request->timestamp, &s_TM);
1314 len = strftime(tmpdt, sizeof(tmpdt), "%m", TM);
1316 strlcpy(q, tmpdt, freespace);
1321 case 't': /* request timestamp */
1322 CTIME_R(&request->timestamp, tmpdt, sizeof(tmpdt));
1323 nl = strchr(tmpdt, '\n');
1325 strlcpy(q, tmpdt, freespace);
1329 case 'C': /* ClientName */
1330 strlcpy(q,request->client->shortname,freespace);
1334 case 'D': /* request date */
1335 TM = localtime_r(&request->timestamp, &s_TM);
1336 len = strftime(tmpdt, sizeof(tmpdt), "%Y%m%d", TM);
1338 strlcpy(q, tmpdt, freespace);
1343 case 'G': /* request minute */
1344 TM = localtime_r(&request->timestamp, &s_TM);
1345 len = strftime(tmpdt, sizeof(tmpdt), "%M", TM);
1347 strlcpy(q, tmpdt, freespace);
1352 case 'H': /* request hour */
1353 TM = localtime_r(&request->timestamp, &s_TM);
1354 len = strftime(tmpdt, sizeof(tmpdt), "%H", TM);
1356 strlcpy(q, tmpdt, freespace);
1361 case 'I': /* Request ID */
1362 snprintf(tmpdt, sizeof(tmpdt), "%i", request->packet->id);
1363 strlcpy(q, tmpdt, freespace);
1367 case 'S': /* request timestamp in SQL format*/
1368 TM = localtime_r(&request->timestamp, &s_TM);
1369 len = strftime(tmpdt, sizeof(tmpdt), "%Y-%m-%d %H:%M:%S", TM);
1371 strlcpy(q, tmpdt, freespace);
1376 case 'T': /* request timestamp */
1377 TM = localtime_r(&request->timestamp, &s_TM);
1378 len = strftime(tmpdt, sizeof(tmpdt), "%Y-%m-%d-%H.%M.%S.000000", TM);
1380 strlcpy(q, tmpdt, freespace);
1385 case 'V': /* Request-Authenticator */
1386 strlcpy(q,"Verified",freespace);
1390 case 'Y': /* request year */
1391 TM = localtime_r(&request->timestamp, &s_TM);
1392 len = strftime(tmpdt, sizeof(tmpdt), "%Y", TM);
1394 strlcpy(q, tmpdt, freespace);
1399 case 'Z': /* Full request pairs except password */
1400 tmp = request->packet->vps;
1401 while (tmp && (freespace > 3)) {
1402 if (tmp->attribute != PW_USER_PASSWORD) {
1404 len = vp_prints(q, freespace - 2, tmp);
1406 freespace -= (len + 2);
1414 RDEBUG2("WARNING: Unknown variable '%%%c': See 'doc/variables.txt'", *p);
1415 if (freespace > 2) {
1424 RDEBUG2("\texpand: '%s' -> '%s'", fmt, out);