/* Miscellaneous routines. * * IRC Services is copyright (c) 1996-2007 Andrew Church. * E-mail: * Parts written by Andrew Kempe and others. * This program is free but copyrighted software; see the file COPYING for * details. */ #include "services.h" #include /*************************************************************************/ /* Table used by irc_tolower(). This table follows the RFC 1459 * requirement that [ \ ] and { | } be considered equivalent. Protocols * which do not follow this requirement should modify the table * accordingly. */ unsigned char irc_lowertable[256] = { 0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07, 0x08,0x09,0x0A,0x0B,0x0C,0x0D,0x0E,0x0F, 0x10,0x11,0x12,0x13,0x14,0x15,0x16,0x17, 0x18,0x19,0x1A,0x1B,0x1C,0x1D,0x1E,0x1F, 0x20,0x21,0x22,0x23,0x24,0x25,0x26,0x27, 0x28,0x29,0x2A,0x2B,0x2C,0x2D,0x2E,0x2F, 0x30,0x31,0x32,0x33,0x34,0x35,0x36,0x37, 0x38,0x39,0x3A,0x3B,0x3C,0x3D,0x3E,0x3F, 0x40,0x61,0x62,0x63,0x64,0x65,0x66,0x67, 0x68,0x69,0x6A,0x6B,0x6C,0x6D,0x6E,0x6F, 0x70,0x71,0x72,0x73,0x74,0x75,0x76,0x77, 0x78,0x79,0x7A,0x7B,0x7C,0x7D,0x5E,0x5F, 0x60,0x61,0x62,0x63,0x64,0x65,0x66,0x67, 0x68,0x69,0x6A,0x6B,0x6C,0x6D,0x6E,0x6F, 0x70,0x71,0x72,0x73,0x74,0x75,0x76,0x77, 0x78,0x79,0x7A,0x7B,0x7C,0x7D,0x7E,0x7F, 0x80,0x81,0x82,0x83,0x84,0x85,0x86,0x87, 0x88,0x89,0x8A,0x8B,0x8C,0x8D,0x8E,0x8F, 0x90,0x91,0x92,0x93,0x94,0x95,0x96,0x97, 0x98,0x99,0x9A,0x9B,0x9C,0x9D,0x9E,0x9F, 0xA0,0xA1,0xA2,0xA3,0xA4,0xA5,0xA6,0xA7, 0xA8,0xA9,0xAA,0xAB,0xAC,0xAD,0xAE,0xAF, 0xB0,0xB1,0xB2,0xB3,0xB4,0xB5,0xB6,0xB7, 0xB8,0xB9,0xBA,0xBB,0xBC,0xBD,0xBE,0xBF, 0xC0,0xC1,0xC2,0xC3,0xC4,0xC5,0xC6,0xC7, 0xC8,0xC9,0xCA,0xCB,0xCC,0xCD,0xCE,0xCF, 0xD0,0xD1,0xD2,0xD3,0xD4,0xD5,0xD6,0xD7, 0xD8,0xD9,0xDA,0xDB,0xDC,0xDD,0xDE,0xDF, 0xE0,0xE1,0xE2,0xE3,0xE4,0xE5,0xE6,0xE7, 0xE8,0xE9,0xEA,0xEB,0xEC,0xED,0xEE,0xEF, 0xF0,0xF1,0xF2,0xF3,0xF4,0xF5,0xF6,0xF7, 0xF8,0xF9,0xFA,0xFB,0xFC,0xFD,0xFE,0xFF, }; /*************************************************************************/ /* irc_tolower: Like toupper/tolower, but for nicknames and channel names; * the RFC requires that '[' and '{', '\' and '|', ']' and '}' * be pairwise equivalent in such names. Declared inline for * irc_str[n]icmp()'s benefit. */ inline unsigned char irc_tolower(char c) { return irc_lowertable[(uint8)c]; } /*************************************************************************/ /* irc_str[n]icmp: Like str[n]icmp, but for nicknames and channel names. */ int irc_stricmp(const char *s1, const char *s2) { register char c1, c2; while ((c1 = (char)irc_tolower(*s1)) == (c2 = (char)irc_tolower(*s2))) { if (c1 == 0) return 0; s1++; s2++; } return c1= oldlen) { if (strncmp(ptr, old, oldlen) != 0) { left--; ptr++; continue; } if (diff > avail) break; if (diff != 0) memmove(ptr+oldlen+diff, ptr+oldlen, left+1); strncpy(ptr, new, newlen); ptr += newlen; left -= oldlen; } return s; } /*************************************************************************/ /* strtok_remaining: Return the result of strtok(NULL, "") with any * leading or trailing whitespace discarded. If nothing * but whitespace is left, return NULL. */ char *strtok_remaining(void) { char *s = strtok(NULL, ""), *t; if (s) { while (isspace(*s)) s++; t = s + strlen(s)-1; while (t >= s && isspace(*t)) *t-- = 0; if (!*s) return NULL; } return s; } /*************************************************************************/ /*************************************************************************/ /* merge_args: Take an argument count and argument vector and merge them * into a single string in which each argument is separated by * a space. */ char *merge_args(int argc, char **argv) { int i; static char s[4096]; char *t; t = s; for (i = 0; i < argc; i++) t += snprintf(t, sizeof(s)-(t-s), "%s%s", *argv++, (i DOMPART_MAXLEN) return 0; s += i; if (*s) s++; } if (s-str > DOMAIN_MAXLEN || *s) return 0; if (s[-1] == '.') return 0; return 1; } /*************************************************************************/ /* Check whether the given string is a valid E-mail address. A valid * E-mail address: * - Contains none of the following characters: * + control characters (\000-\037) * + space (\040) * + vertical bar ('|') (because some mailers try to pipe with it) * + RFC-822 specials, except [ ] @ . * - Contains exactly one '@', which may not be the first character. * - Contains a valid domain name or an IP address in square brackets * after the '@'. * - Does not contain [ or ] except when used with an IP address as above. */ int valid_email(const char *str) { const unsigned char *s; const char *atmark; for (s = (const unsigned char *)str; *s; s++) { if (*s <= '\040') // 040 == 0x20 == ' ' return 0; if (strchr("|,:;\\\"()<>", *s)) return 0; } /* Find the @, and abort if there isn't one */ atmark = strchr(str, '@'); if (!atmark || atmark == str) return 0; atmark++; /* Don't allow [] in username */ s = (const unsigned char *)strpbrk(str, "[]"); if (s && (const char *)s < atmark) return 0; /* Check for a [1.2.3.4] type of domain */ if (*atmark == '[') { unsigned char ipstr[16]; const char *bracket = strchr(atmark+1, ']'); int len = bracket - (atmark+1); /* Valid IP addresses have no more than 15 characters */ if (len <= 15 && !bracket[1]) { strncpy((char *)ipstr, atmark+1, len); ipstr[len] = 0; /* Use pack_ip() to see if it's a valid address */ if (pack_ip((const char *)ipstr)) return 1; } } /* Don't allow [] in domains except for the above case */ if (strpbrk(atmark, "[]")) return 0; /* Valid domain names cannot contain '@' so we just check valid_domain(). * Also prohibit domains without dots. */ return strchr(atmark, '.') && valid_domain(atmark); } /*************************************************************************/ /* Check whether the given string is a valid URL. A valid URL: * - Contains neither control characters (\000-\037) nor spaces (\040). * - Contains a series of letters followed by "://" followed by a valid * domain name, possibly followed by a : and a numeric port number in * the range 1-65535, followed either by a slash and possibly more text * by nothing. */ int valid_url(const char *str) { const unsigned char *s, *colon, *host; static const char letters[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; char domainbuf[DOMAIN_MAXLEN+1]; for (s = (const unsigned char *)str; *s; s++) { if (*s <= '\040') // 040 == 0x20 == ' ' return 0; } s = (const unsigned char *)strstr(str, "://"); if (!s) return 0; /* No "://" */ if (strspn(str, letters) != s - (const unsigned char *)str) return 0; /* Protocol has non-alphabetic characters */ host = s+3; colon = (const unsigned char *)strchr((const char *)host, ':'); /* s will eventually point to the expected end of the host string */ s = host + strcspn((const char *)host, "/"); /* Make sure the port is valid if present. */ if (colon && colon < s) { const unsigned char *tmp; int port = strtol((const char *)colon+1, (char **)&tmp, 10); if (port < 1 || port > 65535 || tmp != s) return 0; /* Invalid port number or non-numeric characters */ s = colon; } /* The string from host through s-1 must be a valid domain name. * Check length (must be >=1, <=DOMAIN_MAXLEN), then copy into * temporary buffer and check. Also discard domain names without * dots in them. */ if (s-host < 1 || s-host > DOMAIN_MAXLEN) return 0; memcpy(domainbuf, host, s-host); domainbuf[s-host] = 0; return strchr(domainbuf, '.') && valid_domain(domainbuf); } /*************************************************************************/ /*************************************************************************/ /* Process a string containing a number/range list in the form * "n1[-n2][,n3[-n4]]...", calling a caller-specified routine for each * number in the list. If the callback returns -1, stop immediately. * Returns the sum of all nonnegative return values from the callback. * If `count_ret' is non-NULL, *count_ret will be set to the total number * of times the callback was called. * * The number list will never contain duplicates and will always be sorted * ascendingly. This means the callback routines don't have to worry about * being called twice for the same index. -TheShadow * Also, the only values accepted are 0-65536 (inclusive), to avoid someone * giving us 0-2^31 and causing freezes or out-of-memory. Numbers outside * this range will be ignored. * * The callback should be of type range_callback_t, which is defined as: * int (*range_callback_t)(User *u, int num, va_list args) */ int process_numlist(const char *numstr, int *count_ret, range_callback_t callback, User *u, ...) { int n1, n2, min, max, i; int retval = 0; int numcount = 0; va_list args; static char numflag[65536/8+1]; /* 1 bit per index 0-65536 inclusive */ memset(numflag, 0, sizeof(numflag)); min = 65536; max = 0; va_start(args, u); /* This algorithm ignores invalid characters, ignores a dash * when it precedes a comma, and ignores everything from the * end of a valid number or range to the next comma or null. */ while (*numstr) { n1 = n2 = strtol(numstr, (char **)&numstr, 10); numstr += strcspn(numstr, "0123456789,-"); if (*numstr == '-') { numstr++; numstr += strcspn(numstr, "0123456789,"); if (isdigit(*numstr)) { n2 = strtol(numstr, (char **)&numstr, 10); numstr += strcspn(numstr, "0123456789,-"); } } if (n1 < 0) n1 = 0; if (n2 > 65536) n2 = 65536; if (n1 < min) min = n1; if (n2 > max) max = n2; while (n1 <= n2) { if ((n1&7) == 0 && n1+7 <= n2) { /* Set a whole byte at once */ numflag[n1>>3] = 0xFF; n1 += 8; } else { /* Set just a single bit */ numflag[n1>>3] |= 1 << (n1&7); n1++; } } numstr += strcspn(numstr, ","); if (*numstr) numstr++; } /* Now call the callback routine for each index. */ numcount = 0; for (i = min; i <= max; i++) { va_list args_copy; int res; if (!(numflag[i>>3] & (1 << (i&7)))) continue; numcount++; va_copy(args_copy, args); res = callback(u, i, args_copy); va_end(args_copy); if (debug) log("debug: process_numlist: tried to do %d; result = %d", i, res); if (res < 0) break; retval += res; } va_end(args); if (count_ret) *count_ret = numcount; return retval; } /*************************************************************************/ /*************************************************************************/ /* time_msec: Return the current time to millisecond resolution. */ uint32 time_msec(void) { #if HAVE_GETTIMEOFDAY struct timeval tv; gettimeofday(&tv, NULL); return tv.tv_sec*1000 + tv.tv_usec/1000; #else return time(NULL) * 1000; #endif } /*************************************************************************/ /* strtotime: Convert a string into a time_t. Essentially the same as * strtol(), but assumes base 10 and returns a time_t. */ time_t strtotime(const char *str, char **endptr) { time_t t = 0; while (*str >= '0' && *str <= '9') { if (t > MAX_TIME_T/10 || MAX_TIME_T - t*10 < *str-'0') { t = MAX_TIME_T; errno = ERANGE; } else { t = t*10 + *str-'0'; } str++; } if (endptr) *endptr = (char *)str; return t; } /*************************************************************************/ /* dotime: Return the number of seconds corresponding to the given time * string. If the given string does not represent a valid time, * return -1. * * A time string is either a plain integer (representing a number * of seconds), an integer followed by one of these characters: * "s" (seconds), "m" (minutes), "h" (hours), or "d" (days), or a * sequence of such integer-character pairs (without separators, * e.g. "1h30m"). */ int dotime(const char *s) { int amount; amount = strtol(s, (char **)&s, 10); if (*s) { char c = *s++; int rest = dotime(s); if (rest < 0) return -1; switch (c) { case 's': return rest + amount; case 'm': return rest + amount*60; case 'h': return rest + amount*3600; case 'd': return rest + amount*86400; default : return -1; } } else { return amount; } } /*************************************************************************/ /*************************************************************************/ /* Translate an IPv4 dotted-quad address into binary (4 bytes). Returns * NULL if the given string is not in dotted-quad format or `ipaddr' is * NULL. The returned buffer is static and will be overwritten on * subsequent calls. */ uint8 *pack_ip(const char *ipaddr) { static uint8 ipbuf[4]; const char *s, *s2; int i; long tmp; if (!ipaddr) return NULL; s = ipaddr; for (i = 0; i < 4; i++) { if (i > 0 && *s++ != '.') return NULL; if (isspace(*s)) /* because strtol() will skip whitespace */ return NULL; tmp = strtol(s, (char **)&s2, 10); if (s2 == s || tmp < 0 || tmp > 255) return NULL; ipbuf[i] = (uint8)tmp; s = s2; } if (*s) return NULL; return ipbuf; } /*************************************************************************/ /* Translate a 4-byte binary IPv4 to its dotted-quad (ASCII) representation. * Always succeeds and always returns a null-terminated string <= 15 * characters in length, unless `ip' is NULL, in which case the function * returns NULL. The returned buffer is static and will be overwritten on * subsequent calls. */ char *unpack_ip(const uint8 *ip) { static char ipbuf[17]; /* 1 byte extra to catch overflow (paranoia) */ if (!ip) return NULL; snprintf(ipbuf, sizeof(ipbuf), "%u.%u.%u.%u", ip[0], ip[1], ip[2], ip[3]); if (strlen(ipbuf) > 15) fatal("BUG: unpack_ip(): result too long (%s)", ipbuf); return ipbuf; } /*************************************************************************/ /* Translate an IPv6 ASCII address into binary (16 bytes). Return NULL if * the given string is not in IPv6 format or `ipaddr' is NULL. The * returned buffer is static and will be overwritten on subsequent calls. */ uint8 *pack_ip6(const char *ipaddr) { static uint8 ipbuf[16]; int words[8]; int wordnum; const char *s, *t; int i; if (!ipaddr) return NULL; /* Parse the address into 16-bit words */ s = ipaddr; wordnum = 0; if (*s == ':') { words[wordnum++] = 0; s++; } while (*s) { if (wordnum >= 8) { /* too many words, abort */ return NULL; } if (*s == ':') { /* mark "::" with a -1 */ words[wordnum++] = -1; s++; } else { words[wordnum++] = (int)strtol(s, (char **)&t, 16); if (*t && *t != ':') { /* invalid syntax */ return NULL; } if (*t) { t++; /* skip past delimiter */ if (!*t) { /* trailing ":", convert to :0000 */ if (wordnum >= 8) return NULL; words[wordnum++] = 0; } } s = t; } } /* Expand "::" into as many zeros as needed, and make sure there * aren't multiple occurrences of "::" */ for (i = 0; i < wordnum; i++) { if (words[i] == -1) break; } if (i < wordnum) { /* found a "::" */ int j, offset; for (j = i+1; j < wordnum; j++) { if (words[j] == -1) { /* multiple "::" */ return NULL; } } offset = 8-wordnum; for (j = 7; j >= i; j--) { if (j-offset > i) words[j] = words[j-offset]; else words[j] = 0; } wordnum = 8; } /* Make sure we have exactly 8 words */ if (wordnum != 8) return NULL; /* Convert to binary and return */ for (i = 0; i < 8; i++) { ipbuf[i*2 ] = words[i] >> 8; ipbuf[i*2+1] = words[i] & 255; } return ipbuf; } /*************************************************************************/ /* Translate a 16-byte binary IPv6 address to its ASCII representation. * Always succeeds and always returns a null-terminated string <= 39 * characters in length, unless `ip' is NULL, in which case the function * returns NULL. The returned buffer is static and will be overwritten on * subsequent calls. * * The address string returned by this function will always use 4-digit * hexadecimal numbers for each word in the address, but will omit all-zero * words when possible. */ char *unpack_ip6(const uint8 *ip) { static char ipbuf[41]; /* 1 byte extra to catch overflow (paranoia) */ char *out, *s; int i; if (!ip) return NULL; out = ipbuf; for (i = 0; i < 8; i++) { /* Skip 0000 at beginning or end */ if ((i != 0 && i != 7) || ip[i*2] || ip[i*2+1]) { out += snprintf(out, sizeof(ipbuf)-(out-ipbuf), "%02X%02X", ip[i*2], ip[i*2+1]); } if (i != 7) *out++ = ':'; } if (strlen(ipbuf) > 39) fatal("BUG: unpack_ip6(): result too long (%s)", ipbuf); if ((s = strstr(ipbuf,":0000:")) != NULL) { /* Compress zeros */ memmove(s+1, s+5, strlen(s+5)+1); s++; while (strncmp(s,":0000:",6) == 0) memmove(s, s+5, strlen(s+5)+1); } return ipbuf; } /*************************************************************************/ /*************************************************************************/ /* Lock the data directory if possible; return nonzero on success, zero on * failure (data directory already locked or cannot create lock file). * On failure, errno will be EEXIST if the directory was already locked or * a value other than EEXIST if an error occurred creating the lock file. * * This does not attempt to correct for NFS brokenness w.r.t. O_EXCL and * will contain a race condition when used on an NFS filesystem (or any * other filesystem which does not support O_EXCL properly). */ int lock_data(void) { int fd; errno = 0; fd = open(LockFilename, O_WRONLY | O_CREAT | O_EXCL, 0); if (fd >= 0) { close(fd); return 1; } return 0; } /*************************************************************************/ /* Check whether the data directory is locked without actually attempting * to lock it. Returns 1 if locked, 0 if not, or -1 if an error occurred * while trying to check (in which case errno will be set to an appropriate * value, i.e. whatever access() returned). */ int is_data_locked(void) { errno = 0; if (access(LockFilename, F_OK) == 0) return 1; if (errno == ENOENT) return 0; return -1; } /*************************************************************************/ /* Unlock the data directory. Assumes we locked it in the first place. * Returns a positive nonzero value on success, zero on failure (unable to * remove the lock file), or -1 if the lock file didn't exist in the first * place (possibly because it was removed by another (misbehaving) program). */ int unlock_data(void) { errno = 0; if (unlink(LockFilename) == 0) return 1; if (errno == ENOENT) return -1; return 0; } /*************************************************************************/ /*************************************************************************/ /* Encode buffer `in' of size `insize' to buffer `out' of size `outsize' * using base64, appending a trailing null. Returns the number of bytes * required by the encoded string. (The amount of space needed to store * the encoded string can be found with encode_base64(...,NULL,0).) * Returns -1 if the input or output buffer is NULL (except when the * corresponding size is zero) or if the input or output size is negative. * If `out' is too small to hold the entire encoded string, it will contain * the first `outsize'-1 bytes of the encoded string followed by a null. */ static const char base64_chars[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; int encode_base64(const void *in, int insize, char *out, int outsize) { int required = ((insize+2)/3*4)+1; const uint8 *inp = in; int inpos, outpos; if (insize < 0 || outsize < 0) return -1; if (outsize == 0) return required; if (!out) return -1; if (insize == 0) { /* outsize is at least 1 */ *out = 0; return 1; } if (!in) return -1; outsize--; /* leave room for trailing \0 */ /* Actually do the encoding */ outpos = 0; for (inpos = 0; inpos < insize; inpos += 3) { uint8 i0, i1, i2; char o0, o1, o2, o3; i0 = inp[inpos]; o0 = base64_chars[i0>>2]; if (inpos+1 < insize) { i1 = inp[inpos+1]; o1 = base64_chars[(i0&3)<<4 | i1>>4]; if (inpos+2 < insize) { i2 = inp[inpos+2]; o2 = base64_chars[(i1&15)<<2 | i2>>6]; o3 = base64_chars[i2&63]; } else { o2 = base64_chars[(i1&15)<<2]; o3 = '='; } } else { o1 = base64_chars[(i0&3)<<4]; o2 = '='; o3 = '='; } if (outpos < outsize) out[outpos++] = o0; if (outpos < outsize) out[outpos++] = o1; if (outpos < outsize) out[outpos++] = o2; if (outpos < outsize) out[outpos++] = o3; } /* Terminate the string and return; outpos is constrained above to * still be within the buffer (remember that outsize was decremented * before the loop) */ out[outpos] = 0; return required; } /*************************************************************************/ /* Decode base64-encoded string `in' (null-terminated) to buffer `out' of * size `outsize'. Returns the number of bytes required by the decoded * data. (The amount of space needed to store the decoded data can be * found with decode_base64(...,NULL,0).) Returns -1 if the input string * is NULL, or if the output buffer is NULL (except when the size is zero) * or the size is negative. If `out' is too small to hold all of the * decoded data, it will contain the first `outsize' bytes. */ static const char base64_array[256] = { -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, /* 0x00 */ -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,62,-1,-1,-1,63, 52,53,54,55,56,57,58,59,60,61,-1,-1,-1,-1,-1,-1, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13,14, /* 0x40 */ 15,16,17,18,19,20,21,22,23,24,25,-1,-1,-1,-1,-1, -1,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40, 41,42,43,44,45,46,47,48,49,50,51,-1,-1,-1,-1,-1, -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, /* 0x80 */ -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, /* 0xC0 */ -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, }; int decode_base64(const char *in, void *out, int outsize) { uint8 *outp = out; int outpos; if (!in || outsize < 0) return -1; outpos = 0; while (*in) { int i0, i1, i2, i3; i0 = *in++; if (*in) { i1 = *in++; if (*in) { i2 = *in++; if (*in) { i3 = *in++; } else { i3 = 0; } } else { i2 = 0; i3 = 0; } } else { i1 = 0; i2 = 0; i3 = 0; } i0 = base64_array[(int)i0 & 255]; i1 = base64_array[(int)i1 & 255]; i2 = base64_array[(int)i2 & 255]; i3 = base64_array[(int)i3 & 255]; if (i0 < 0 || i1 < 0) break; /* Only store if buffer space is available; increment outpos anyway * to keep track of total space required (for return value) */ if (outpos < outsize) outp[outpos] = i0<<2 | i1>>4; outpos++; if (i2 < 0) break; if (outpos < outsize) outp[outpos] = (i1&15)<<4 | i2>>2; outpos++; if (i3 < 0) break; if (outpos < outsize) outp[outpos] = (i2&3)<<6 | i3; outpos++; } return outpos; } /*************************************************************************/