/* ** Copyright (c) 2005-2007 Sendmail, Inc. and its suppliers. ** All rights reserved. */ #ifndef lint static char util_c_id[] = "@(#)$Id: util.c,v 1.15 2007/12/17 23:59:01 msk Exp $"; #endif /* !lint */ /* system includes */ #include #include #include #include #include /* libsm includes */ #include /* libdkim includes */ #include "dkim.h" #include "util.h" /* ** DKIM_COLLAPSE -- remove spaces from a string ** ** Parameters: ** str -- string to process ** ** Return value: ** None. */ void dkim_collapse(u_char *str) { u_char *q; u_char *r; assert(str != NULL); for (q = str, r = str; *q != '\0'; q++) { if (!isspace(*q)) { if (q != r) *r = *q; r++; } } *r = '\0'; } /* ** DKIM_ISLWSP -- return true iff a character is some kind of linear ** whitespace ** ** Parameters: ** c -- character being examined ** ** Return value: ** TRUE iff 'c' refers to something considered linear whitespace. */ bool dkim_islwsp(u_int c) { return (isascii(c) && isspace(c) && c != '\n' && c != '\r'); } /* ** DKIM_ADDRCMP -- like strcmp(), except for e-mail addresses (i.e. local-part ** is case-sensitive, while the domain part is not) ** ** Parameters: ** s1, s2 -- the strings ** ** Return value: ** Like strcmp(). However, if "s2" contains only a domain name, ** don't bother comparing the local-parts. */ int dkim_addrcmp(u_char *s1, u_char *s2) { int ret; u_char *at1, *at2; assert(s1 != NULL); assert(s2 != NULL); at1 = (u_char *) strchr(s1, '@'); at2 = (u_char *) strchr(s2, '@'); /* if one or both contain no "@"s, just be strcmp() */ if (at1 == NULL || at2 == NULL) return strcmp(s1, s2); /* first check the local-parts */ *at1 = '\0'; *at2 = '\0'; /* if "s2" contains only a domain name, skip the local-part check */ if (at2 != s2) { /* compare the local-parts, case-sensitive */ ret = strcmp(s1, s2); if (ret != 0) { *at1 = '@'; *at2 = '@'; return ret; } } /* now compare the domains, case-insensitive */ ret = strcasecmp(at1 + 1, at2 + 1); *at1 = '@'; *at2 = '@'; return ret; } /* ** DKIM_HDRLIST -- build up a header list for use in a regexp ** ** Parameters: ** buf -- where to write ** buflen -- bytes at "buf" ** hdrlist -- array of header names ** first -- first call ** ** Return value: ** TRUE iff everything fit. */ bool dkim_hdrlist(u_char *buf, size_t buflen, u_char **hdrlist, bool first) { int c; int len; char *p; char *q; char *end; assert(buf != NULL); assert(hdrlist != NULL); for (c = 0; ; c++) { if (hdrlist[c] == NULL) break; if (!first) { len = sm_strlcat(buf, "|", buflen); if (len >= buflen) return FALSE; } else { len = strlen(buf); } first = FALSE; q = &buf[len]; end = &buf[buflen - 1]; for (p = hdrlist[c]; *p != '\0'; p++) { if (q >= end) return FALSE; switch (*p) { case '*': *q = '.'; q++; if (q >= end) return FALSE; *q = '*'; q++; break; case '.': *q = '\\'; q++; if (q >= end) return FALSE; *q = '.'; q++; break; default: *q = *p; q++; break; } } } return TRUE; } /* ** DKIM_LOWERHDR -- convert a string (presumably a header) to all lowercase, ** but only up to a colon ** ** Parameters: ** str -- string to modify ** ** Return value: ** None. */ void dkim_lowerhdr(unsigned char *str) { unsigned char *p; assert(str != NULL); for (p = str; *p != '\0'; p++) { if (*p == ':') return; if (isascii(*p) && isupper(*p)) *p = tolower(*p); } } /* ** DKIM_HEXCHAR -- translate a hexadecimal character ** ** Parameters: ** c -- character to translate ** ** Return value: ** Decimal equivalent. */ int dkim_hexchar(int c) { switch (c) { case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': return c - '0'; case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': return 10 + c - 'A'; case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': return 10 + c - 'a'; default: assert(0); return -1; } } /* ** DKIM_QP_DECODE -- decode a quoted-printable string ** ** Parameters: ** in -- input ** out -- output ** outlen -- bytes available at "out" ** ** Return value: ** >= 0 -- number of bytes in output ** -1 -- parse error */ int dkim_qp_decode(unsigned char *in, unsigned char *out, int outlen) { unsigned char next1; unsigned char next2 = 0; int xl; unsigned char const *p; unsigned char *q; unsigned char *pos; unsigned char const *start; unsigned char const *stop; unsigned char *end; unsigned char const *hexdigits = "0123456789ABCDEF"; assert(in != NULL); assert(out != NULL); start = NULL; stop = NULL; end = out + outlen; for (p = in, q = out; *p != '\0' && q <= end; p++) { switch (*p) { case '=': next1 = *(p + 1); if (next1 != '\0') next2 = *(p + 2); /* = at EOL */ if (next1 == '\n' || (next1 == '\r' && next2 == '\n')) { stop = p; if (start != NULL) { unsigned char const *x; for (x = start; x <= stop; x++) { if (q <= end) { *q = *x; q++; } } } start = NULL; stop = NULL; p++; if (next2 == '\n') p++; break; } /* = elsewhere */ pos = (unsigned char *) strchr(hexdigits, next1); if (pos == NULL) return -1; xl = (pos - (unsigned char *) hexdigits) * 16; pos = (unsigned char *) strchr(hexdigits, next2); if (pos == NULL) return -1; xl += (pos - (unsigned char *) hexdigits); stop = p; if (start != NULL) { unsigned char const *x; for (x = start; x < stop; x++) { if (q <= end) { *q = *x; q++; } } } start = NULL; stop = NULL; *q = xl; q++; p += 2; break; case ' ': case '\t': if (start == NULL) start = p; break; case '\r': break; case '\n': if (stop == NULL) stop = p; if (start != NULL) { unsigned char const *x; for (x = start; x <= stop; x++) { if (q <= end) { *q = *x; q++; } } } if (p > in && *(p - 1) != '\r') { if (q <= end) { *q = '\n'; q++; } } else { if (q <= end) { *q = '\r'; q++; } if (q <= end) { *q = '\n'; q++; } } start = NULL; stop = NULL; break; default: if (start == NULL) start = p; stop = p; break; } } if (start != NULL) { unsigned char const *x; for (x = start; x < p; x++) { if (q <= end) { *q = *x; q++; } } } return q - out; } /* ** DKIM_STRTOUL -- convert string to unsigned long ** ** Parameters: ** str -- string to convert ** endptr -- pointer to store string after value ** base -- base to convert from ** ** Return value: ** Value of string as unsigned long */ unsigned long dkim_strtoul(const char *str, char **endptr, int base) { char start = '+'; static char cutlim = ULONG_MAX % 10; char c; bool erange = FALSE; static unsigned long cutoff = ULONG_MAX / 10; unsigned long value = 0; const char *subj; const char *cur; if (base != 10) return strtoul(str, endptr, base); if (str == NULL) { errno = EINVAL; return value; } subj = str; while (*subj != '\0' && isspace(*subj)) subj++; if (*subj == '-' || *subj == '+') start = *subj++; for (cur = subj; *cur >= '0' && *cur <= '9'; cur++) { if (erange) continue; c = *cur - '0'; if ((value > cutoff) || (value == cutoff && c > cutlim)) { erange = TRUE; continue; } value = (value * 10) + c; } if (cur == subj) { if (endptr != NULL) *endptr = (char *) str; errno = EINVAL; return 0; } if (endptr != NULL) *endptr = (char *) cur; if (erange) { errno = ERANGE; return ULONG_MAX; } if (start == '-') return -value; else return value; } /* ** DKIM_STRTOULL -- convert string to unsigned long long ** ** Parameters: ** str -- string to convert ** endptr -- pointer to store string after value ** base -- base to convert from ** ** Return value: ** Value of string as unsigned long long */ unsigned long long dkim_strtoull(const char *str, char **endptr, int base) { char start = '+'; char c; bool erange = FALSE; static char cutlim = ULLONG_MAX % 10; static unsigned long long cutoff = ULLONG_MAX / 10; unsigned long long value = 0; const char *subj; const char *cur; if (base != 10) return strtoull(str, endptr, base); if (str == NULL) { errno = EINVAL; return value; } subj = str; while (*subj && isspace(*subj)) subj++; if (*subj == '-' || *subj == '+') start = *subj++; for (cur = subj; *cur >= '0' && *cur <= '9'; cur++) { if (erange) continue; c = *cur - '0'; if ((value > cutoff) || (value == cutoff && c > cutlim)) { erange = 1; continue; } value = (value * 10) + c; } if (cur == subj) { if (endptr != NULL) *endptr = (char *) str; errno = EINVAL; return 0; } if (endptr != NULL) *endptr = (char *) cur; if (erange != 0) { errno = ERANGE; return ULLONG_MAX; } if (start == '-') return -value; else return value; }