Extract tiger.[ch] and utils.[ch] as common code.
[doldaconnect.git] / common / utils.c
diff --git a/common/utils.c b/common/utils.c
new file mode 100644 (file)
index 0000000..cdf2bc6
--- /dev/null
@@ -0,0 +1,834 @@
+/*
+ *  Dolda Connect - Modular multiuser Direct Connect-style client
+ *  Copyright (C) 2004 Fredrik Tolf (fredrik@dolda2000.com)
+ *  
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *  
+ *  This program 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 General Public License for more details.
+ *  
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+*/
+#include <malloc.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <wchar.h>
+#include <iconv.h>
+#include <errno.h>
+#include <string.h>
+#include <wctype.h>
+#include <langinfo.h>
+#include <pwd.h>
+#include <unistd.h>
+#include <sys/time.h>
+#include <netinet/in.h>
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+#include <utils.h>
+#include <log.h>
+
+static char *base64set = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+static int base64rev[] = {
+    -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, 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,
+    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,
+    -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,
+    -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,
+};
+static char *base32set = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
+static int base32rev[] = {
+    -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, 26, 27, 28, 29, 30, 31, -1, -1, -1, -1, -1, -1, -1, -1,
+    -1,  0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14,
+    15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1,
+    -1,  0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14,
+    15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -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, -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, -1, -1, -1, -1, -1,
+};
+
+char *vsprintf2(char *format, va_list al)
+{
+    int ret;
+    char *buf;
+    
+    ret = vsnprintf(NULL, 0, format, al);
+    if((buf = malloc(ret + 1)) == NULL)
+    {
+       LOGOOM(ret + 1);
+       return(NULL);
+    }
+    vsnprintf(buf, ret + 1, format, al);
+    return(buf);
+}
+
+char *sprintf2(char *format, ...)
+{
+    va_list args;
+    char *buf;
+    
+    va_start(args, format);
+    buf = vsprintf2(format, args);
+    va_end(args);
+    return(buf);
+}
+
+wchar_t *vswprintf2(wchar_t *format, va_list al)
+{
+    int ret;
+    wchar_t *buf;
+    size_t bufsize;
+    
+    buf = smalloc(sizeof(wchar_t) * (bufsize = 1024));
+    while((ret = vswprintf(buf, bufsize, format, al)) < 0)
+       buf = srealloc(buf, sizeof(wchar_t) * (bufsize *= 2));
+    if(bufsize > ret + 1)
+       buf = srealloc(buf, sizeof(wchar_t) * (ret + 1));
+    return(buf);
+}
+
+wchar_t *swprintf2(wchar_t *format, ...)
+{
+    va_list args;
+    wchar_t *buf;
+    
+    va_start(args, format);
+    buf = vswprintf2(format, args);
+    va_end(args);
+    return(buf);
+}
+
+int havecharset(char *charset)
+{
+    iconv_t cd;
+    
+    if((cd = iconv_open("wchar_t", charset)) == (iconv_t)-1)
+       return(0);
+    iconv_close(cd);
+    if((cd = iconv_open(charset, "wchar_t")) == (iconv_t)-1)
+       return(0);
+    iconv_close(cd);
+    return(1);
+}
+
+wchar_t *icmbstowcs(char *mbs, char *charset)
+{
+    int ret;
+    char *buf;
+    char *p, *p2;
+    size_t len1, len2, bufsize, data;
+    iconv_t cd;
+    
+    len1 = strlen(mbs) + 1;
+    bufsize = len2 = len1 * sizeof(wchar_t);
+    if((buf = malloc(bufsize)) == NULL)
+    {
+       LOGOOM(bufsize);
+       return(NULL);
+    }
+    if(charset == NULL)
+       charset = nl_langinfo(CODESET);
+    if((cd = iconv_open("wchar_t", charset)) == (iconv_t)-1)
+    {
+#ifdef DAEMON
+       flog(LOG_ERR, "icmbstowcs: could not open iconv structure for %s: %s", charset, strerror(errno));
+#endif
+       free(buf);
+       return(NULL);
+    }
+    p = buf;
+    while(len1 > 0)
+    {
+       ret = iconv(cd, &mbs, &len1, &p, &len2);
+       if(ret < 0)
+       {
+           if(errno == E2BIG)
+           {
+               data = p - buf;
+               len2 += 128;
+               bufsize += 128;
+               if((p2 = realloc(buf, bufsize)) == NULL)
+               {
+                   LOGOOM(bufsize);
+                   free(buf);
+                   iconv_close(cd);
+                   return(NULL);
+               }
+               buf = p2;
+               p = buf + data;
+           } else {
+               free(buf);
+               iconv_close(cd);
+               return(NULL);
+           }
+       }
+    }
+    if(len2 > 0)
+       buf = realloc(buf, p - buf);
+    iconv_close(cd);
+    return((wchar_t *)buf);
+}
+
+wchar_t *icsmbstowcs(char *mbs, char *charset, wchar_t *def)
+{
+    static wchar_t *buf = NULL;
+    
+    if(buf != NULL)
+       free(buf);
+    if((buf = icmbstowcs(mbs, charset)) == NULL)
+    {
+       if((def != NULL) && (*def == L'~'))
+       {
+#ifdef DAEMON
+           flog(LOG_WARNING, "icsmbstowcs: could not convert wcs string into charset %s: %s", charset, strerror(errno));
+#endif
+           def++;
+       }
+       return(def);
+    }
+    return(buf);
+}
+
+char *icwcstombs(wchar_t *wcs, char *charset)
+{
+    int ret;
+    char *buf;
+    char *p, *p2;
+    size_t len1, len2, bufsize, data;
+    iconv_t cd;
+    
+    len1 = sizeof(wchar_t) * (wcslen(wcs) + 1);
+    bufsize = len2 = len1;
+    if((buf = malloc(bufsize)) == NULL)
+    {
+#ifdef DAEMON
+       LOGOOM(bufsize);
+#endif
+       return(NULL);
+    }
+    if(charset == NULL)
+       charset = nl_langinfo(CODESET);
+    if((cd = iconv_open(charset, "wchar_t")) == (iconv_t)-1)
+    {
+#ifdef DAEMON
+       flog(LOG_ERR, "icwcstombs: could not open iconv structure for %s: %s", charset, strerror(errno));
+#endif
+       free(buf);
+       return(NULL);
+    }
+    p = buf;
+    while(len1 > 0)
+    {
+       ret = iconv(cd, (char **)&wcs, &len1, &p, &len2);
+       if(ret < 0)
+       {
+           if(errno == E2BIG)
+           {
+               data = p - buf;
+               len2 += 128;
+               bufsize += 128;
+               if((p2 = realloc(buf, bufsize)) == NULL)
+               {
+                   LOGOOM(bufsize);
+                   free(buf);
+                   iconv_close(cd);
+                   return(NULL);
+               }
+               buf = p2;
+               p = buf + data;
+           } else {
+               free(buf);
+               iconv_close(cd);
+               return(NULL);
+           }
+       }
+    }
+    if(len2 > 0)
+       buf = realloc(buf, p - buf);
+    iconv_close(cd);
+    return(buf);
+}
+
+char *icswcstombs(wchar_t *wcs, char *charset, char *def)
+{
+    static char *buf = NULL;
+    
+    if(buf != NULL)
+       free(buf);
+    if((buf = icwcstombs(wcs, charset)) == NULL)
+    {
+       if((def != NULL) && (*def == '~'))
+       {
+#ifdef DAEMON
+           flog(LOG_WARNING, "icswcstombs: could not convert mbs string from charset %s: %s", charset, strerror(errno));
+#endif
+           def++;
+       }
+       return(def);
+    }
+    return(buf);
+}
+
+wchar_t *wcstolower(wchar_t *wcs)
+{
+    wchar_t *p;
+    
+    for(p = wcs; *p != L'\0'; p++)
+       *p = towlower(*p);
+    return(wcs);
+}
+
+wchar_t ucptowc(int ucp)
+{
+    int ret;
+    unsigned long ucpbuf;
+    char *buf;
+    char *mbsp, *p, *p2;
+    wchar_t res;
+    size_t len1, len2, bufsize, data;
+    iconv_t cd;
+    
+    ucpbuf = htonl(ucp);
+    mbsp = (char *)&ucpbuf;
+    len1 = 4;
+    bufsize = len2 = len1 * sizeof(wchar_t);
+    if((buf = malloc(bufsize)) == NULL)
+    {
+       LOGOOM(bufsize);
+       return(L'\0');
+    }
+    if((cd = iconv_open("wchar_t", "UCS-4BE")) == (iconv_t)-1)
+    {
+#ifdef DAEMON
+       flog(LOG_ERR, "ucptowc: could not open iconv structure for UCS-4BE: %s", strerror(errno));
+#endif
+       free(buf);
+       return(L'\0');
+    }
+    p = buf;
+    while(len1 > 0)
+    {
+       ret = iconv(cd, &mbsp, &len1, &p, &len2);
+       if(ret < 0)
+       {
+           if(errno == E2BIG)
+           {
+               data = p - buf;
+               len2 += 128;
+               bufsize += 128;
+               if((p2 = realloc(buf, bufsize)) == NULL)
+               {
+                   LOGOOM(bufsize);
+                   free(buf);
+                   iconv_close(cd);
+                   return(L'\0');
+               }
+               buf = p2;
+               p = buf + data;
+           } else {
+               free(buf);
+               iconv_close(cd);
+               return(L'\0');
+           }
+       }
+    }
+    if(len2 > 0)
+       buf = realloc(buf, p - buf);
+    iconv_close(cd);
+    res = *(wchar_t *)buf;
+    free(buf);
+    return(res);
+}
+
+void _sizebuf(void **buf, size_t *bufsize, size_t reqsize, size_t elsize, int algo)
+{
+    if(*bufsize >= reqsize)
+       return;
+    switch(algo)
+    {
+    case 0:
+       *buf = srealloc(*buf, elsize * ((*bufsize) = reqsize));
+       break;
+    case 1:
+       if(*bufsize == 0)
+           *bufsize = 1;
+       while(*bufsize < reqsize)
+           *bufsize <<= 1;
+       *buf = srealloc(*buf, elsize * (*bufsize));
+       break;
+    }
+}
+
+double ntime(void)
+{
+    struct timeval tv;
+    
+    gettimeofday(&tv, NULL);
+    return((double)tv.tv_sec + ((double)tv.tv_usec / 1000000.0));
+}
+
+int wcsexists(wchar_t *h, wchar_t *n)
+{
+    int i, o, nl, hl;
+    wchar_t *ln, *lh;
+    
+    ln = alloca(sizeof(*ln) * (nl = wcslen(n)));
+    for(i = 0; i < nl; i++)
+       ln[i] = towlower(n[i]);
+    lh = alloca(sizeof(*lh) * (hl = wcslen(h)));
+    if(nl > hl)
+       return(0);
+    for(i = 0; i < nl; i++)
+       lh[i] = towlower(h[i]);
+    i = 0;
+    while(1)
+    {
+       for(o = 0; o < nl; o++)
+       {
+           if(lh[i + o] != ln[o])
+               break;
+       }
+       if(o == nl)
+           return(1);
+       if(i == hl - nl)
+           return(0);
+       lh[i + nl] = towlower(h[i + nl]);
+       i++;
+    }
+}
+
+#ifndef HAVE_WCSCASECMP
+int wcscasecmp(const wchar_t *s1, const wchar_t *s2)
+{
+    while(towlower(*s1) == towlower(*s2))
+    {
+       if(*s1 == L'\0')
+           return(0);
+    }
+    return(towlower(*s1) - towlower(*s2));
+}
+#endif
+
+char *hexencode(char *data, size_t datalen)
+{
+    char *buf, this;
+    size_t bufsize, bufdata;
+    int dig;
+    
+    buf = NULL;
+    bufsize = bufdata = 0;
+    for(; datalen > 0; datalen--, data++)
+    {
+       dig = (*data & 0xF0) >> 4;
+       if(dig > 9)
+           this = 'A' + dig - 10;
+       else
+           this = dig + '0';
+       addtobuf(buf, this);
+       dig = *data & 0x0F;
+       if(dig > 9)
+           this = 'A' + dig - 10;
+       else
+           this = dig + '0';
+       addtobuf(buf, this);
+    }
+    addtobuf(buf, 0);
+    return(buf);
+}
+
+char *hexdecode(char *data, size_t *len)
+{
+    char *buf, this;
+    size_t bufsize, bufdata;
+    
+    buf = NULL;
+    bufsize = bufdata = 0;
+    for(; *data; data++)
+    {
+       if((*data >= 'A') && (*data <= 'F'))
+       {
+           this = (this & 0x0F) | ((*data - 'A' + 10) << 4);
+       } else if((*data >= '0') && (*data <= '9')) {
+           this = (this & 0x0F) | ((*data - '0') << 4);
+       } else {
+           if(buf != NULL)
+               free(buf);
+           return(NULL);
+       }
+       data++;
+       if(!*data)
+       {
+           if(buf != NULL)
+               free(buf);
+           return(NULL);
+       }
+       if((*data >= 'A') && (*data <= 'F'))
+       {
+           this = (this & 0xF0) | (*data - 'A' + 10);
+       } else if((*data >= '0') && (*data <= '9')) {
+           this = (this & 0xF0) | (*data - '0');
+       } else {
+           if(buf != NULL)
+               free(buf);
+           return(NULL);
+       }
+       addtobuf(buf, this);
+    }
+    addtobuf(buf, 0);
+    if(len != NULL)
+       *len = bufdata - 1;
+    return(buf);
+}
+
+char *base64encode(char *data, size_t datalen)
+{
+    char *buf;
+    size_t bufsize, bufdata;
+    
+    if(datalen == 0)
+       return(sstrdup(""));
+    buf = NULL;
+    bufsize = bufdata = 0;
+    while(datalen >= 3)
+    {
+       addtobuf(buf, base64set[(data[0] & 0xfc) >> 2]);
+       addtobuf(buf, base64set[((data[0] & 0x03) << 4) | ((data[1] & 0xf0) >> 4)]);
+       addtobuf(buf, base64set[((data[1] & 0x0f) << 2) | ((data[2] & 0xc0) >> 6)]);
+       addtobuf(buf, base64set[data[2] & 0x3f]);
+       datalen -= 3;
+       data += 3;
+    }
+    if(datalen == 1)
+    {
+       addtobuf(buf, base64set[(data[0] & 0xfc) >> 2]);
+       addtobuf(buf, base64set[(data[0] & 0x03) << 4]);
+       bufcat(buf, "==", 2);
+    }
+    if(datalen == 2)
+    {
+       addtobuf(buf, base64set[(data[0] & 0xfc) >> 2]);
+       addtobuf(buf, base64set[((data[0] & 0x03) << 4) | ((data[1] & 0xf0) >> 4)]);
+       addtobuf(buf, base64set[(data[1] & 0x0f) << 2]);
+       addtobuf(buf, '=');
+    }
+    addtobuf(buf, 0);
+    return(buf);
+}
+
+char *base64decode(char *data, size_t *datalen)
+{
+    int b, c;
+    char *buf, cur;
+    size_t bufsize, bufdata;
+    
+    buf = NULL;
+    bufsize = bufdata = 0;
+    cur = 0;
+    b = 8;
+    for(; *data > 0; data++)
+    {
+       c = (int)(unsigned char)*data;
+       if(c == '=')
+           break;
+       if(c == '\n')
+           continue;
+       if(base64rev[c] == -1)
+       {
+           if(buf != NULL)
+               free(buf);
+           return(NULL);
+       }
+       b -= 6;
+       if(b <= 0)
+       {
+           cur |= base64rev[c] >> -b;
+           addtobuf(buf, cur);
+           b += 8;
+           cur = 0;
+       }
+       cur |= base64rev[c] << b;
+    }
+    if(datalen != NULL)
+       *datalen = bufdata;
+    addtobuf(buf, 0);
+    return(buf);
+}
+
+char *base32encode(char *data, size_t datalen)
+{
+    char *buf;
+    size_t bufsize, bufdata;
+    
+    if(datalen == 0)
+       return(sstrdup(""));
+    buf = NULL;
+    bufsize = bufdata = 0;
+    while(datalen >= 5)
+    {
+       addtobuf(buf, base32set[((data[0] & 0xf8) >> 3)]);
+       addtobuf(buf, base32set[((data[0] & 0x07) << 2) | ((data[1] & 0xc0) >> 6)]);
+       addtobuf(buf, base32set[((data[1] & 0x3e) >> 1)]);
+       addtobuf(buf, base32set[((data[1] & 0x01) << 4) | ((data[2] & 0xf0) >> 4)]);
+       addtobuf(buf, base32set[((data[2] & 0x0f) << 1) | ((data[3] & 0x80) >> 7)]);
+       addtobuf(buf, base32set[((data[3] & 0x7c) >> 2)]);
+       addtobuf(buf, base32set[((data[3] & 0x03) << 3) | ((data[4] & 0xe0) >> 5)]);
+       addtobuf(buf, base32set[data[4] & 0x1f]);
+       datalen -= 5;
+       data += 5;
+    }
+    if(datalen == 1)
+    {
+       addtobuf(buf, base32set[((data[0] & 0xf8) >> 3)]);
+       addtobuf(buf, base32set[((data[0] & 0x07) << 2)]);
+       bufcat(buf, "======", 6);
+    }
+    if(datalen == 2)
+    {
+       addtobuf(buf, base32set[((data[0] & 0xf8) >> 3)]);
+       addtobuf(buf, base32set[((data[0] & 0x07) << 2) | ((data[1] & 0xc0) >> 6)]);
+       addtobuf(buf, base32set[((data[1] & 0x3e) >> 1)]);
+       addtobuf(buf, base32set[((data[1] & 0x01) << 4)]);
+       bufcat(buf, "====", 4);
+    }
+    if(datalen == 3)
+    {
+       addtobuf(buf, base32set[((data[0] & 0xf8) >> 3)]);
+       addtobuf(buf, base32set[((data[0] & 0x07) << 2) | ((data[1] & 0xc0) >> 6)]);
+       addtobuf(buf, base32set[((data[1] & 0x3e) >> 1)]);
+       addtobuf(buf, base32set[((data[1] & 0x01) << 4) | ((data[2] & 0xf0) >> 4)]);
+       addtobuf(buf, base32set[((data[2] & 0x0f) << 1)]);
+       bufcat(buf, "===", 3);
+    }
+    if(datalen == 4)
+    {
+       addtobuf(buf, base32set[((data[0] & 0xf8) >> 3)]);
+       addtobuf(buf, base32set[((data[0] & 0x07) << 2) | ((data[1] & 0xc0) >> 6)]);
+       addtobuf(buf, base32set[((data[1] & 0x3e) >> 1)]);
+       addtobuf(buf, base32set[((data[1] & 0x01) << 4) | ((data[2] & 0xf0) >> 4)]);
+       addtobuf(buf, base32set[((data[2] & 0x0f) << 1) | ((data[3] & 0x80) >> 7)]);
+       addtobuf(buf, base32set[((data[3] & 0x7c) >> 2)]);
+       addtobuf(buf, base32set[((data[3] & 0x03) << 3)]);
+       bufcat(buf, "=", 1);
+    }
+    addtobuf(buf, 0);
+    return(buf);
+}
+
+char *base32decode(char *data, size_t *datalen)
+{
+    int b, c;
+    char *buf, cur;
+    size_t bufsize, bufdata;
+    
+    buf = NULL;
+    bufsize = bufdata = 0;
+    cur = 0;
+    b = 8;
+    for(; *data > 0; data++)
+    {
+       c = (int)(unsigned char)*data;
+       if(c == '=')
+           break;
+       if(c == '\n')
+           continue;
+       if(base32rev[c] == -1)
+       {
+           if(buf != NULL)
+               free(buf);
+           return(NULL);
+       }
+       b -= 5;
+       if(b <= 0)
+       {
+           cur |= base32rev[c] >> -b;
+           addtobuf(buf, cur);
+           b += 8;
+           cur = 0;
+       }
+       cur |= base32rev[c] << b;
+    }
+    if(datalen != NULL)
+       *datalen = bufdata;
+    addtobuf(buf, 0);
+    return(buf);
+}
+
+void _freeparr(void **arr)
+{
+    void **buf;
+    
+    if(arr == NULL)
+       return;
+    for(buf = arr; *buf != NULL; buf++)
+       free(*buf);
+    free(arr);
+}
+
+int _parrlen(void **arr)
+{
+    int i;
+    
+    if(arr == NULL)
+       return(0);
+    for(i = 0; *arr != NULL; arr++)
+       i++;
+    return(i);
+}
+
+char *getetcpath(char *binpath)
+{
+    int f;
+    char *etcpath, *p;
+    size_t etcpathsize, etcpathdata;
+
+    etcpath = NULL;
+    etcpathsize = etcpathdata = 0;
+    f = 1;
+    do
+    {
+       if(f)
+           f = 0;
+       else
+           binpath++;
+       for(p = binpath; *p && (*p != ':'); p++);
+       for(; (p >= binpath) && (*p != '/'); p--);
+       if(p >= binpath)
+       {
+           if(etcpathdata > 0)
+               addtobuf(etcpath, ':');
+           bufcat(etcpath, binpath, p - binpath + 1);
+           bufcat(etcpath, "etc", 3);
+       }
+    } while((binpath = strchr(binpath, ':')) != NULL);
+    addtobuf(etcpath, 0);
+    return(etcpath);
+}
+
+char *findfile(char *gname, char *uname, char *homedir, int filldef)
+{
+    char *path, *binpath, *etcpath, *p;
+    struct passwd *pw;
+    int mode;
+    
+    mode = R_OK | (filldef ? W_OK : 0);
+    if(uname != NULL) {
+       if(homedir == NULL)
+           homedir = getenv("HOME");
+       if((homedir == NULL) && ((pw = getpwuid(getuid())) != NULL))
+           homedir = pw->pw_dir;
+       if((homedir != NULL) && ((path = sprintf2("%s/.%s", homedir, uname)) != NULL))
+       {
+           if(!access(path, mode))
+               return(path);
+           free(path);
+       }
+    }
+    if(gname != NULL)
+    {
+       if(strchr(gname, '/') != NULL)
+       {
+           if(!access(gname, mode))
+               return(sstrdup(gname));
+       } else {
+           if((binpath = getenv("PATH")) == NULL)
+               etcpath = sstrdup("/usr/local/etc:/etc:/usr/etc");
+           else
+               etcpath = getetcpath(binpath);
+           for(p = strtok(etcpath, ":"); p != NULL; p = strtok(NULL, ":"))
+           {
+               if((path = sprintf2("%s/%s", p, gname)) != NULL)
+               {
+                   if(!access(path, mode))
+                   {
+                       free(etcpath);
+                       return(path);
+                   }
+                   free(path);
+               }
+           }
+           free(etcpath);
+       }
+    }
+    if(filldef) {
+       if(uname && homedir)
+           return(sprintf2("%s/.%s", homedir, uname));
+       return(sprintf2("/etc/%s", gname));
+    } else {
+       return(NULL);
+    }
+}
+
+struct wcspair *newwcspair(wchar_t *key, wchar_t *val, struct wcspair **list)
+{
+    struct wcspair *pair;
+    
+    pair = smalloc(sizeof(*pair));
+    memset(pair, 0, sizeof(*pair));
+    if(key != NULL)
+       pair->key = swcsdup(key);
+    if(val != NULL)
+       pair->val = swcsdup(val);
+    if(list == NULL)
+    {
+       pair->next = NULL;
+    } else {
+       pair->next = *list;
+       *list = pair;
+    }
+    return(pair);
+}
+
+void freewcspair(struct wcspair *pair, struct wcspair **list)
+{
+    struct wcspair *cur;
+    
+    for(cur = *list; cur != NULL; list = &(cur->next), cur = cur->next)
+    {
+       if(cur == pair)
+       {
+           *list = cur->next;
+           break;
+       }
+    }
+    free(pair->key);
+    free(pair->val);
+    free(pair);
+}
+
+wchar_t *wpfind(struct wcspair *list, wchar_t *key)
+{
+    for(; list != NULL; list = list->next)
+    {
+       if(!wcscmp(list->key, key))
+           return(list->val);
+    }
+    return(NULL);
+}