diff options
Diffstat (limited to 'src/lib/print.c')
-rw-r--r-- | src/lib/print.c | 790 |
1 files changed, 790 insertions, 0 deletions
diff --git a/src/lib/print.c b/src/lib/print.c new file mode 100644 index 0000000..57455b6 --- /dev/null +++ b/src/lib/print.c @@ -0,0 +1,790 @@ +/* + * print.c Routines to print stuff. + * + * Version: $Id$ + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + * + * Copyright 2000,2006 The FreeRADIUS server project + */ + +RCSID("$Id$") + +#include <freeradius-devel/libradius.h> + +#include <ctype.h> + +/** Checks for utf-8, taken from http://www.w3.org/International/questions/qa-forms-utf-8 + * + * @param str input string. + * @param inlen length of input string. May be -1 if str is \0 terminated. + */ +int fr_utf8_char(uint8_t const *str, ssize_t inlen) +{ + if (inlen == 0) return 0; + + if (inlen < 0) inlen = 4; /* longest char */ + + if (*str < 0x20) return 0; + + if (*str <= 0x7e) return 1; /* 1 */ + + if (*str <= 0xc1) return 0; + + if (inlen < 2) return 0; + + if ((str[0] >= 0xc2) && /* 2 */ + (str[0] <= 0xdf) && + (str[1] >= 0x80) && + (str[1] <= 0xbf)) { + return 2; + } + + if (inlen < 3) return 0; + + if ((str[0] == 0xe0) && /* 3 */ + (str[1] >= 0xa0) && + (str[1] <= 0xbf) && + (str[2] >= 0x80) && + (str[2] <= 0xbf)) { + return 3; + } + + if ((str[0] >= 0xe1) && /* 4a */ + (str[0] <= 0xec) && + (str[1] >= 0x80) && + (str[1] <= 0xbf) && + (str[2] >= 0x80) && + (str[2] <= 0xbf)) { + return 3; + } + + if ((str[0] >= 0xee) && /* 4b */ + (str[0] <= 0xef) && + (str[1] >= 0x80) && + (str[1] <= 0xbf) && + (str[2] >= 0x80) && + (str[2] <= 0xbf)) { + return 3; + } + + if ((str[0] == 0xed) && /* 5 */ + (str[1] >= 0x80) && + (str[1] <= 0x9f) && + (str[2] >= 0x80) && + (str[2] <= 0xbf)) { + return 3; + } + + if (inlen < 4) return 0; + + if ((str[0] == 0xf0) && /* 6 */ + (str[1] >= 0x90) && + (str[1] <= 0xbf) && + (str[2] >= 0x80) && + (str[2] <= 0xbf) && + (str[3] >= 0x80) && + (str[3] <= 0xbf)) { + return 4; + } + + if ((str[0] >= 0xf1) && /* 6 */ + (str[0] <= 0xf3) && + (str[1] >= 0x80) && + (str[1] <= 0xbf) && + (str[2] >= 0x80) && + (str[2] <= 0xbf) && + (str[3] >= 0x80) && + (str[3] <= 0xbf)) { + return 4; + } + + + if ((str[0] == 0xf4) && /* 7 */ + (str[1] >= 0x80) && + (str[1] <= 0x8f) && + (str[2] >= 0x80) && + (str[2] <= 0xbf) && + (str[3] >= 0x80) && + (str[3] <= 0xbf)) { + return 4; + } + + /* + * Invalid UTF-8 Character + */ + return 0; +} + +/** Return a pointer to the first UTF8 char in a string. + * + * @param[out] chr_len Where to write the length of the multibyte char passed in chr (may be NULL). + * @param[in] str Haystack. + * @param[in] chr Multibyte needle. + * @return The position of chr in str or NULL if not found. + */ +char const *fr_utf8_strchr(int *chr_len, char const *str, char const *chr) +{ + int cchr; + + cchr = fr_utf8_char((uint8_t const *)chr, -1); + if (cchr == 0) cchr = 1; + if (chr_len) *chr_len = cchr; + + while (*str) { + int schr; + + schr = fr_utf8_char((uint8_t const *) str, -1); + if (schr == 0) schr = 1; + if (schr != cchr) goto next; + + if (memcmp(str, chr, schr) == 0) { + return (char const *) str; + } + next: + str += schr; + } + + return NULL; +} + +/** Escape any non printable or non-UTF8 characters in the input string + * + * @note Return value should be checked with is_truncated + * @note Will always \0 terminate unless outlen == 0. + * + * @param[in] in string to escape. + * @param[in] inlen length of string to escape (lets us deal with embedded NULs) + * @param[out] out where to write the escaped string. + * @param[out] outlen the length of the buffer pointed to by out. + * @param[in] quote the quotation character + * @return the number of bytes it WOULD HAVE written to the buffer, not including the trailing NUL + */ +size_t fr_prints(char *out, size_t outlen, char const *in, ssize_t inlen, char quote) +{ + uint8_t const *p = (uint8_t const *) in; + size_t utf8; + size_t used; + size_t freespace; + + /* No input, so no output... */ + if (!in) { + if (out && outlen) *out = '\0'; + return 0; + } + + /* Figure out the length of the input string */ + if (inlen < 0) inlen = strlen(in); + + /* + * No quotation character, just use memcpy, ensuring we + * don't overflow the output buffer. + */ + if (!quote) { + if (!out) return inlen; + + if ((size_t)inlen >= outlen) { + memcpy(out, in, outlen - 1); + out[outlen - 1] = '\0'; + } else { + memcpy(out, in, inlen); + out[inlen] = '\0'; + } + + return inlen; + } + + /* + * Check the output buffer and length. Zero both of them + * out if either are zero. + */ + freespace = outlen; + if (freespace == 0) out = NULL; + if (!out) freespace = 0; + + used = 0; + + while (inlen > 0) { + int sp = 0; + + /* + * Hack: never print trailing zero. + * Some clients send pings with an off-by-one + * length (confused with strings in C). + */ + if ((inlen == 1) && (*p == '\0')) { + inlen--; + break; + } + + /* + * Always escape the quotation character. + */ + if (*p == quote) { + sp = quote; + goto do_escape; + } + + /* + * Escape the backslash ONLY for single quoted strings. + */ + if (quote == '\'') { + if (*p == '\\') { + sp = '\\'; + } + goto do_escape; + } + + /* + * Try to convert 0x0a --> \r, etc. + * Backslashes get handled specially. + */ + switch (*p) { + case '\r': + sp = 'r'; + break; + + case '\n': + sp = 'n'; + break; + + case '\t': + sp = 't'; + break; + + case '\\': + sp = '\\'; + break; + + default: + sp = '\0'; + break; + } /* escape the character at *p */ + + do_escape: + if (sp) { + if ((freespace > 0) && (freespace <= 2)) { + if (out) out[used] = '\0'; + out = NULL; + freespace = 0; + + } else if (freespace > 2) { /* room for char AND trailing zero */ + if (out) { + out[used] = '\\'; + out[used + 1] = sp; + } + freespace -= 2; + } + + used += 2; + p++; + inlen--; + continue; + } + + /* + * All strings are UTF-8 clean. + */ + utf8 = fr_utf8_char(p, inlen); + + /* + * If we have an invalid UTF-8 character, it gets + * copied over as a 1-byte character for single + * quoted strings. Which means that the output + * isn't strictly UTF-8, but oh well... + * + * For double quoted strints, the invalid + * characters get escaped as octal encodings. + */ + if (utf8 == 0) { + if (quote == '\'') { + utf8 = 1; + + } else { + if ((freespace > 0) && (freespace <= 4)) { + if (out) out[used] = '\0'; + out = NULL; + freespace = 0; + + } else if (freespace > 4) { /* room for char AND trailing zero */ + if (out) snprintf(out + used, freespace, "\\%03o", *p); + freespace -= 4; + } + + used += 4; + p++; + inlen--; + continue; + } + } + + if ((freespace > 0) && (freespace <= utf8)) { + if (out) out[used] = '\0'; + out = NULL; + freespace = 0; + + } else if (freespace > utf8) { /* room for char AND trailing zero */ + if (out) memcpy(out + used, p, utf8); + freespace -= utf8; + } + + used += utf8; + p += utf8; + inlen -= utf8; + } + + /* + * Ensure that the output buffer is always zero terminated. + */ + if (out && freespace) out[used] = '\0'; + + return used; +} + +/** Find the length of the buffer required to fully escape a string with fr_prints + * + * Were assuming here that's it's cheaper to figure out the length and do one + * alloc than repeatedly expand the buffer when we find extra chars which need + * to be added. + * + * @param in string to calculate the escaped length for. + * @param inlen length of the input string, if < 0 strlen will be used to check the length. + * @param[in] quote the quotation character. + * @return the size of buffer required to hold the escaped string including the NUL byte. + */ +size_t fr_prints_len(char const *in, ssize_t inlen, char quote) +{ + return fr_prints(NULL, 0, in, inlen, quote) + 1; +} + +/** Escape string that may contain binary data, and write it to a new buffer + * + * This is useful in situations where we expect printable strings as input, + * but under some conditions may get binary data. A good example is libldap + * and the arrays of struct berval ldap_get_values_len returns. + * + * @param[in] ctx To allocate new buffer in. + * @param[in] in String to escape. + * @param[in] inlen Length of string. Should be >= 0 if the data may contain + * embedded \0s. Must be >= 0 if data may not be \0 terminated. + * If < 0 inlen will be calculated using strlen. + * @param[in] quote the quotation character. + * @return new buffer holding the escaped string. + */ +char *fr_aprints(TALLOC_CTX *ctx, char const *in, ssize_t inlen, char quote) +{ + size_t len, ret; + char *out; + + len = fr_prints_len(in, inlen, quote); + + out = talloc_array(ctx, char, len); + ret = fr_prints(out, len, in, inlen, quote); + + /* + * This is a fatal error, but fr_assert is the strongest + * assert we're allowed to use in library functions. + */ + if (!fr_assert(ret == (len - 1))) { + talloc_free(out); + return NULL; + } + + return out; +} + +/** Print the value of an attribute to a string + * + * @param[out] out Where to write the string. + * @param[in] outlen Size of outlen (must be at least 3 bytes). + * @param[in] vp to print. + * @param[in] quote Char to add before and after printed value, if 0 no char will be added, if < 0 raw string will be + * added. + * @return the length of data written to out, or a value >= outlen on truncation. + */ +size_t vp_prints_value(char *out, size_t outlen, VALUE_PAIR const *vp, char quote) +{ + VERIFY_VP(vp); + + if (vp->type == VT_XLAT) { + return snprintf(out, outlen, "%c%s%c", quote, vp->value.xlat, quote); + } + + return value_data_prints(out, outlen, vp->da->type, vp->da, &vp->data, vp->vp_length, quote); +} + +/** Print one attribute value to a string + * + * @param ctx to allocate string in. + * @param vp to print. + * @param[in] quote the quotation character + * @return a talloced buffer with the attribute operator and value. + */ +char *vp_aprints_value(TALLOC_CTX *ctx, VALUE_PAIR const *vp, char quote) +{ + VERIFY_VP(vp); + + if (vp->type == VT_XLAT) { + return fr_aprints(ctx, vp->value.xlat, talloc_array_length(vp->value.xlat) - 1, quote); + } + + return value_data_aprints(ctx, vp->da->type, vp->da, &vp->data, vp->vp_length, quote); +} + +char *vp_aprints_type(TALLOC_CTX *ctx, PW_TYPE type) +{ + switch (type) { + case PW_TYPE_STRING : + return talloc_typed_strdup(ctx, "_"); + + case PW_TYPE_INTEGER64: + case PW_TYPE_SIGNED: + case PW_TYPE_BYTE: + case PW_TYPE_SHORT: + case PW_TYPE_INTEGER: + case PW_TYPE_DATE : + return talloc_typed_strdup(ctx, "0"); + + case PW_TYPE_IPV4_ADDR : + return talloc_typed_strdup(ctx, "?.?.?.?"); + + case PW_TYPE_IPV4_PREFIX: + return talloc_typed_strdup(ctx, "?.?.?.?/?"); + + case PW_TYPE_IPV6_ADDR: + return talloc_typed_strdup(ctx, "[:?:]"); + + case PW_TYPE_IPV6_PREFIX: + return talloc_typed_strdup(ctx, "[:?:]/?"); + + case PW_TYPE_OCTETS: + return talloc_typed_strdup(ctx, "??"); + + case PW_TYPE_ETHERNET: + return talloc_typed_strdup(ctx, "??:??:??:??:??:??:??:??"); + +#ifdef WITH_ASCEND_BINARY + case PW_TYPE_ABINARY: + return talloc_typed_strdup(ctx, "??"); +#endif + + default : + break; + } + + return talloc_typed_strdup(ctx, "<UNKNOWN-TYPE>"); +} + +/** Prints attribute enumv escaped suitably for use as JSON enumv + * + * Returns < 0 if the buffer may be (or have been) too small to write the encoded + * JSON value to. + * + * @param out Where to write the string. + * @param outlen Length of output buffer. + * @param vp to print. + * @param raw_value if true, the raw value is printed and not the enumerated attribute value + * @return the length of data written to out, or a value >= outlen on truncation. + */ +size_t vp_prints_value_json(char *out, size_t outlen, VALUE_PAIR const *vp, bool raw_value) +{ + char const *q; + size_t len, freespace = outlen; + /* attempt to print raw_value when has_value is false, or raw_value is false, but only + if has_tag is also false */ + bool raw = (raw_value || !vp->da->flags.has_value) && !vp->da->flags.has_tag; + + if (raw) { + switch (vp->da->type) { + case PW_TYPE_INTEGER: + return snprintf(out, freespace, "%u", vp->vp_integer); + + case PW_TYPE_SHORT: + return snprintf(out, freespace, "%u", (unsigned int) vp->vp_short); + + case PW_TYPE_BYTE: + return snprintf(out, freespace, "%u", (unsigned int) vp->vp_byte); + + default: + break; + } + } + + /* Indicate truncation */ + if (freespace < 2) return outlen + 1; + *out++ = '"'; + freespace--; + + switch (vp->da->type) { + case PW_TYPE_STRING: + for (q = vp->vp_strvalue; q < vp->vp_strvalue + vp->vp_length; q++) { + /* Indicate truncation */ + if (freespace < 3) return outlen + 1; + + if (*q == '"') { + *out++ = '\\'; + *out++ = '"'; + freespace -= 2; + } else if (*q == '\\') { + *out++ = '\\'; + *out++ = '\\'; + freespace -= 2; + } else if (*q == '/') { + *out++ = '\\'; + *out++ = '/'; + freespace -= 2; + } else if (*q >= ' ') { + *out++ = *q; + freespace--; + } else { + *out++ = '\\'; + freespace--; + + switch (*q) { + case '\b': + *out++ = 'b'; + freespace--; + break; + + case '\f': + *out++ = 'f'; + freespace--; + break; + + case '\n': + *out++ = 'n'; + freespace--; + break; + + case '\r': + *out++ = 'r'; + freespace--; + break; + + case '\t': + *out++ = 't'; + freespace--; + break; + default: + len = snprintf(out, freespace, "u%04X", (uint8_t) *q); + if (is_truncated(len, freespace)) return (outlen - freespace) + len; + out += len; + freespace -= len; + } + } + } + break; + + default: + len = vp_prints_value(out, freespace, vp, 0); + if (is_truncated(len, freespace)) return (outlen - freespace) + len; + out += len; + freespace -= len; + break; + } + + /* Indicate truncation */ + if (freespace < 2) return outlen + 1; + *out++ = '"'; + freespace--; + *out = '\0'; // We don't increment out, because the nul byte should not be included in the length + + return outlen - freespace; +} + +/* + * This is a hack, and has to be kept in sync with tokens.h + */ +static char const *vp_tokens[] = { + "?", /* T_INVALID */ + "EOL", /* T_EOL */ + "{", + "}", + "(", + ")", + ",", + ";", + "++", + "+=", + "-=", + ":=", + "=", + "!=", + ">=", + ">", + "<=", + "<", + "=~", + "!~", + "=*", + "!*", + "==", + "#", + "<BARE-WORD>", + "<\"STRING\">", + "<'STRING'>", + "<`STRING`>" +}; + +/** Print one attribute and value to a string + * + * Print a VALUE_PAIR in the format: +@verbatim + <attribute_name>[:tag] <op> <value> +@endverbatim + * to a string. + * + * @param out Where to write the string. + * @param outlen Length of output buffer. + * @param vp to print. + * @return the length of data written to out, or a value >= outlen on truncation. + */ +size_t vp_prints(char *out, size_t outlen, VALUE_PAIR const *vp) +{ + char const *token = NULL; + size_t len, freespace = outlen; + + if (!out) return 0; + + *out = '\0'; + if (!vp || !vp->da) return 0; + + VERIFY_VP(vp); + + if ((vp->op > T_INVALID) && (vp->op < T_TOKEN_LAST)) { + token = vp_tokens[vp->op]; + } else { + token = "<INVALID-TOKEN>"; + } + + if (vp->da->flags.has_tag && (vp->tag != TAG_ANY)) { + len = snprintf(out, freespace, "%s:%d %s ", vp->da->name, vp->tag, token); + } else { + len = snprintf(out, freespace, "%s %s ", vp->da->name, token); + } + + if (is_truncated(len, freespace)) return len; + out += len; + freespace -= len; + + len = vp_prints_value(out, freespace, vp, '"'); + if (is_truncated(len, freespace)) return (outlen - freespace) + len; + freespace -= len; + + return (outlen - freespace); +} + +/** Print one attribute and value to FP + * + * Complete string with '\\t' and '\\n' is written to buffer before printing to + * avoid issues when running with multiple threads. + * + * @param fp to output to. + * @param vp to print. + */ +void vp_print(FILE *fp, VALUE_PAIR const *vp) +{ + char buf[1024]; + char *p = buf; + size_t len; + + VERIFY_VP(vp); + + *p++ = '\t'; + len = vp_prints(p, sizeof(buf) - 1, vp); + if (!len) { + return; + } + p += len; + + /* + * Deal with truncation gracefully + */ + if (((size_t) (p - buf)) >= (sizeof(buf) - 2)) { + p = buf + (sizeof(buf) - 2); + } + + *p++ = '\n'; + *p = '\0'; + + fputs(buf, fp); +} + + +/** Print a list of attributes and enumv + * + * @param fp to output to. + * @param const_vp to print. + */ +void vp_printlist(FILE *fp, VALUE_PAIR const *const_vp) +{ + VALUE_PAIR *vp; + vp_cursor_t cursor; + + memcpy(&vp, &const_vp, sizeof(vp)); /* const work-arounds */ + + for (vp = fr_cursor_init(&cursor, &vp); vp; vp = fr_cursor_next(&cursor)) { + vp_print(fp, vp); + } +} + +/** Print one attribute and value to a string + * + * Print a VALUE_PAIR in the format: +@verbatim + <attribute_name>[:tag] <op> <value> +@endverbatim + * to a string. + * + * @param ctx to allocate string in. + * @param vp to print. + * @param[in] quote the quotation character + * @return a talloced buffer with the attribute operator and value. + */ +char *vp_aprints(TALLOC_CTX *ctx, VALUE_PAIR const *vp, char quote) +{ + char const *token = NULL; + char *str, *value; + + if (!vp || !vp->da) return 0; + + VERIFY_VP(vp); + + if ((vp->op > T_INVALID) && (vp->op < T_TOKEN_LAST)) { + token = vp_tokens[vp->op]; + } else { + token = "<INVALID-TOKEN>"; + } + + value = vp_aprints_value(ctx, vp, quote); + + if (vp->da->flags.has_tag && (vp->tag != TAG_ANY)) { + if (quote && (vp->da->type == PW_TYPE_STRING)) { + str = talloc_asprintf(ctx, "%s:%d %s %c%s%c", vp->da->name, vp->tag, token, quote, value, quote); + } else { + str = talloc_asprintf(ctx, "%s:%d %s %s", vp->da->name, vp->tag, token, value); + } + } else { + if (quote && (vp->da->type == PW_TYPE_STRING)) { + str = talloc_asprintf(ctx, "%s %s %c%s%c", vp->da->name, token, quote, value, quote); + } else { + str = talloc_asprintf(ctx, "%s %s %s", vp->da->name, token, value); + } + } + + talloc_free(value); + + return str; +} |