/* Copyright (C) 2000-1 drscholl@users.sourceforge.net
   This is free software distributed under the terms of the
   GNU Public License.  See the file COPYING for details.

   $Id: util.c,v 1.17 2002/08/25 10:46:02 the_turner Exp $

   This file contains various utility functions useful elsewhere in this
   server */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include <fcntl.h>
#include <stdarg.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <ctype.h>
#include <stdlib.h>
#include <time.h>
#ifndef WIN32
#include <sys/time.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#else
#include <process.h>
#include <io.h>
#endif
#include "md5.h"
#include "opennap.h"
#include "debug.h"

/* fake_fputs makes it easier to write to a file, while not having
   the limitations on some UNIXes that are associated with fopen() */

int fake_fputs(const char *buf, int fd) {
    int x;
    x=strlen(buf);
    return (write (fd, buf, x) != x) ? EOF : x;
}

/* fake_fgets makes it easier to read a file one line at a time,
   while not having the limitations on some UNIXes that are
   associated with fopen() */

char *fake_fgets(char *buf, int max_len, int fd)
{
    int quotopen;    /* Ensures that "\n" enclosed in quotes do not cause line break */
    char inbuf;
    char *singleline;
    char *lineptr;
    int r=1;

    if ((singleline=(char *)malloc(max_len+1))==NULL) {
        logerr("fake_fgets", STRERROR (errno));
        return(NULL);
    }
    
    lineptr=singleline;
    inbuf='\0';
    quotopen=0;
    while((r==1) && ( ((inbuf!='\n') || quotopen)) &! ( (lineptr-singleline) > (max_len-1)) ) {
        r=read(fd, &inbuf,1);
        if (quotopen && (inbuf=='"')) {
            quotopen = 0;
        } else if (!quotopen && (inbuf=='"')) {
            quotopen = 1;
        }
        lineptr[0]=inbuf;
        lineptr++;
    }
    
    if (r!=1) {
        free(singleline);
        return(NULL);
    }
    
    lineptr[0]='\0';
    strcpy(buf,singleline);
    free(singleline);
    return(buf);
}


#ifndef HAVE_STRSEP
/* non-gnu libc systems don't have strsep(), so for them we write our own */
char *
strsep(char **pp, const char *delim)
{
char *p, *q;
if (!(p = *pp))
        return 0;
if ((q = strpbrk (p, delim)))
        {
        *pp = q + 1;
        *q = '\0';
        }
else
        *pp = 0;
return p;
}
#endif
/* writes `val' as a two-byte value in little-endian format */
void
set_val (char *d, unsigned short val)
{
    *d++ = val & 0xff;
    *d = (val >> 8) & 0xff;
/*
    val = BSWAP16 (val);
    memcpy (d, &val, 2);
*/
}

/* this is like strtok(2), except that all fields are returned as once.  nul
   bytes are written into `pkt' and `template' is updated with pointers to
   each field in `pkt' */
/* returns: number of fields found. */
int
split_line (char **template, int templatecount, char *pkt)
{
    int     i = 0;

    if (!pkt)
	return -1;
    while (ISSPACE (*pkt))
	pkt++;
    while (*pkt && i < templatecount)
    {
	if (*pkt == '"')
	{
	    /* quoted string */
	    pkt++;
	    template[i++] = pkt;
	    pkt = strchr (pkt, '"');
	    if (!pkt)
	    {
		/* bogus line */
		return -1;
	    }
	    *pkt++ = 0;
	    if (!*pkt)
		break;
	    pkt++;		/* skip the space */
	}
	else
	{
	    template[i++] = pkt;
	    pkt = strpbrk (pkt, " \t\r\n");
	    if (!pkt)
		break;
	    *pkt++ = 0;
	}
	while (ISSPACE (*pkt))
	    pkt++;
    }
    return i;
}

#ifndef ROUTING_ONLY
/* this is like split_line(), except it splits a directory specification into
   path specification and filename, based on the prefix to the believed name
   of the actual file */
/* returns: pointer to filename */

char *split_filename (char *fqfn) {
    char   *lastptr;
    char   *firstptr = fqfn;
    char    currchar;
    char    seperator;
    int     depth;

    if (!fqfn) {
        return NULL;
    }
    while (ISSPACE (*fqfn)) {
        fqfn++;
    }
    depth = 0;
    seperator = 0;
    while ((currchar = *fqfn++)) {
        if (!seperator) {
            if (currchar == '/')
                seperator = '/';
            else if (currchar == '\\')
                seperator = '\\';
        }
    }
    lastptr = --fqfn;
    while (fqfn-- > firstptr && depth < Index_Path_Depth) {
        if (*fqfn == seperator) {
/* I don't understand that tertiary! When can (fqfn + 1) be false? */
/*            lastptr = (fqfn + 1) ? (fqfn + 1) : fqfn; */
            lastptr = fqfn + 1;
            depth++;
        }
    }
    return lastptr;
}
#endif /* ! ROUTING_ONLY */

static char hex[] = "0123456789ABCDEF";

void
expand_hex (char *v, int vsize)
{
    int     i;

    for (i = vsize - 1; i >= 0; i--)
    {
	v[2 * i + 1] = hex[v[i] & 0xf];
	v[2 * i] = hex[(v[i] >> 4) & 0xf];
    }
}

void
init_random (void)
{
    ASSERT (global.current_time != 0);

    /* force generation of a different seed if respawning quickly by adding
       the pid of the current process */
    srand (global.current_time + getuid () + getpid ());
}

void
get_random_bytes (char *d, int dsize)
{
    int     i = 0, v;

    while (i < dsize)
    {
	v = rand ();
	d[i++] = v & 0xff;
	if (i < dsize)
	    d[i++] = (v >> 8) & 0xff;
	if (i < dsize)
	    d[i++] = (v >> 16) & 0xff;
	if (i < dsize)
	    d[i++] = (v >> 24) & 0xff;
    }
}

    /* generate our own nonce value */
char   *
generate_nonce (void)
{
    char   *nonce;

    nonce = MALLOC (17);
    if (!nonce)
    {
	OUTOFMEMORY ("generate_nonce");
	return 0;
    }
    nonce[16] = 0;

    get_random_bytes (nonce, 8);

    /* expand the binary data into hex for transport */
    expand_hex (nonce, 8);

    return nonce;
}

CHANNEL *
new_channel (void)
{
    CHANNEL *c = CALLOC (1, sizeof (CHANNEL));

    if (!c)
    {
	OUTOFMEMORY ("new_channel");
	return 0;
    }
#ifdef DEBUG
    c->magic = MAGIC_CHANNEL;
#endif
    return c;
}

char   *
strfcpy (char *dest, const char *src, size_t destlen)
{
    strncpy (dest, src, destlen);
    dest[destlen - 1] = 0;
    return dest;
}



void inline logerr_code (const char *func, const char *text, int err) {
    log_message ("%s: %s %s (errno %d)", func, text, STRERROR (err), err);
}



/* Log message unconditionally. This fn processes VarArgs stuff only */

void log_message (const char *fmt, ...) {
    char    buf[1024];
    va_list ap;

    va_start (ap, fmt);
    vsnprintf (buf, sizeof (buf) - 1, fmt, ap);
    va_end (ap);
    log_message_deliver (buf);
}


/* Log message conditionally. This fn processes VarArgs stuff only */

void log_message_level (int level, const char *fmt, ...) {
    char    buf[1024];
    va_list ap;

    if (level & Log_Level) {
        va_start (ap, fmt);
        vsnprintf (buf, sizeof (buf) - 1, fmt, ap);
        va_end (ap);
        log_message_deliver (buf);
    }
}


/* Deliver message to log channel and / or stdout / logfile. */

void log_message_deliver (char *msg) {
 char       buf[1024];
 char      *bufptr;
 char      *msgstdout;
 struct tm *brokentime;
 int        msglen;
static int  Logging = 0;

    if (option (ON_LOG_CHANNEL) && !Logging) {
        strcpy (buf + 4, "&LOG opennap ");  /* No real need for strfcpy() here */
        bufptr = buf + strlen (buf + 4) + 4;
    } else {
        bufptr = buf;
    }
    msgstdout = bufptr;         /* Save pointer to start of actual output */
    if (strlen (global.log_timefmt)) {
        brokentime = localtime (&global.current_time);
        strftime (bufptr, sizeof (buf) - (bufptr - buf) - 1, global.log_timefmt, brokentime);
/*        printf ("Time template: \"%s\", result: \"%s\"\n", global.log_timefmt, bufptr); */
        bufptr += strlen (bufptr);
        *bufptr++ = ' ';
    }
    strfcpy (bufptr, msg, sizeof (buf) - (bufptr - buf));
    if (option (ON_LOG_CHANNEL) && !Logging) {  /* Prepare raw message to be sent */
        msglen = strlen (buf + 4);
        set_tag (buf, MSG_SERVER_PUBLIC);
        set_len (buf, msglen);
/*        printf ("log_message_deliver: tag+length: 0x%08lx, length: %d, msg: \"%s\"\n", *((long int *) buf), msglen, buf + 4); */
        Logging++;
        (void) send_to_channel ("&LOG", buf, msglen + 4);
        Logging--;
    }
    if (option (ON_LOG_STDOUT)) {
        fputs (msgstdout, stdout);
        fputc ('\n', stdout);
        fflush (stdout);
    }
}


/* like next_arg(), except we don't skip over additional whitespace */
char   *
next_arg_noskip (char **s)
{
    char   *r = *s;

    *s = strchr (r, ' ');
    if (*s)
	*(*s)++ = 0;
    return r;
}

char   *
next_arg (char **s)
{
    char   *r = *s;

    if (!r)
	return 0;
    while (ISSPACE (*r))
	r++;
    if (!*r)
	return 0;
    if (*r == '"')
    {
	r++;
	*s = strchr (r, '"');
    }
    else
	*s = strpbrk (r, " \t\r\n");
    if (*s)
    {
	*(*s)++ = 0;
	while (ISSPACE (**s))
	    ++ * s;
	if (!**s)
	    *s = 0;		/* no more arguments */
    }
    return r;
}

char   *
strlower (char *s)
{
    char   *r = s;

    ASSERT (s != 0);
    while (*s)
	*s++ = tolower ((unsigned char) *s);
    return r;
}

int
safe_realloc (void **ptr, int bytes)
{
    void   *t;

    t = REALLOC (*ptr, bytes);
    if (!t)
	return -1;
    *ptr = t;
    return 0;
}

void
print_args (int ac, char **av)
{
    int     i;

    printf ("print_args: [%d]", ac);
    for (i = 0; i < ac; i++)
	printf (" \"%s\"", av[i]);
    fputc ('\n', stdout);
}

static char alphabet[] =
    "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
#define alphabet(c) alphabet[(unsigned int)c]

static int
b64_encode (char *out, int *outsize, char *in, int insize)
{
    unsigned char a, b, c, d;
    char   *pout = out;

    while (insize > 0)
    {
	c = d = 0xff;
	a = (*in >> 2) & 0x3f;
	b = (*in & 0x3) << 4;
	in++;
	insize--;
	if (insize)
	{
	    b |= (*in >> 4) & 0xf;
	    c = (*in & 0xf) << 2;
	    in++;
	    insize--;
	    if (insize)
	    {
		c |= (*in >> 6) & 0x3;
		d = *in & 0x3f;
		in++;
		insize--;
	    }
	}
	*out++ = alphabet (a);
	*out++ = alphabet (b);
	if (c != 0xff)
	{
	    *out++ = alphabet (c);
	    if (d != 0xff)
		*out++ = alphabet (d);
	    else
		*out++ = '=';
	}
	else
	{
	    *out++ = '=';
	    *out++ = '=';
	}
    }
    *out = 0;
    *outsize = out - pout;
    return 0;
}

static char b64_lookup[128] = {
    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
    -1, -1, -1, -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
};

#define b64_lookup(c) b64_lookup[(unsigned int)c]

static int
b64_decode (char *out, int *outsize, const char *in)
{
    unsigned char a, b, c, d;
    unsigned char b2, b3;
    char   *pout = out;

    while (*in)
    {
	a = b64_lookup (*in++);
	b = b64_lookup (*in++);
	*out++ = a << 2 | b >> 4;
	b2 = b << 4;
	if (*in && *in != '=')
	{
	    c = b64_lookup (*in++);
	    b2 |= c >> 2;
	    *out++ = b2;
	    b3 = c << 6;
	    if (*in && *in != '=')
	    {
		d = b64_lookup (*in++);
		b3 |= d;
		*out++ = b3;
	    }
	    else
		break;
	}
	else
	    break;
    }
    *outsize = out - pout;
    return 0;
}


int check_pass (const char *info, const char *pass)
{
    struct md5_ctx md;
    char    hash[16], real[16];
    int     realsize;

    ASSERT (info != 0);
    ASSERT (pass != 0);
    if (*info != '1' || *(info + 1) != ',')
	return -1;
    info += 2;
    md5_init_ctx (&md);
    md5_process_bytes (info, 8, &md);
    info += 8;
    if (*info != ',')
	return -1;
    info++;
    md5_process_bytes (pass, strlen (pass), &md);
    md5_finish_ctx (&md, hash);
    realsize = sizeof (real);
    b64_decode (real, &realsize, info);
    ASSERT (realsize == 16);
    if (memcmp (real, hash, 16) == 0)
	return 0;
    return -1;
}


char   *generate_pass (const char *pass)
{
    struct md5_ctx md;
    char    hash[16];
    char    output[36];		/* 1,xxxxxxxx,xxxxxxxxxxxxxxxxxxxxxxx== */
    int     outsize;
    int     i;

    ASSERT (pass != 0);
    output[0] = '1';
    output[1] = ',';
    get_random_bytes (output + 2, 8);
    for (i = 0; i < 8; i++)
	output[i + 2] = alphabet[((unsigned int) output[i + 2]) % 64];
    output[10] = ',';
    md5_init_ctx (&md);
    md5_process_bytes (output + 2, 8, &md);
    md5_process_bytes (pass, strlen (pass), &md);
    md5_finish_ctx (&md, hash);
    outsize = sizeof (output) - 11;
    b64_encode (output + 11, &outsize, hash, 16);
    output[sizeof (output) - 3] = 0;	/* strip the trailing == */
    return (STRDUP (output));
}


CHANNEL *find_channel (LIST * channels, const char *s)
{
    for (; channels; channels = channels->next)
	if (!strcasecmp (((CHANNEL *) channels->data)->name, s))
	    return channels->data;
    return 0;
}


void inline free_pointer (void *p)
{
    FREE (p);
}


/* check to make sure this string is a valid host name.  include the glob
 * characters
 */
int invalid_host (const char *p) {
    while (*p) {
        if (!isalnum ((int)*p) || !strchr (".-?*", *p))
            return 1;
        p++;
    }
    return 0;
}


char* get_user ( CONNECTION *con, int mode ) {
    switch (mode) {
        case 1: snprintf ( Buf, sizeof (Buf), "%s!%s", con->user->nick, con->host );
                break;
        case 2: snprintf ( Buf, sizeof (Buf), "%s!%s (%s)",
                        con->user->nick, con->host, con->user->clientinfo );
                break;
        case 3: snprintf ( Buf, sizeof (Buf), "%s!%s (%s:%hu:%hu)",
                        con->user->nick, con->host, con->user->server,
                        con->user->conport, con->compress );
                break;
        default: snprintf ( Buf, sizeof (Buf), "%s!%s (%s:%s:%hu:%hu)",
                        con->user->nick, con->host, con->user->clientinfo,
                        con->user->server, con->user->conport, con->compress );
    }
    return Buf;
}


/* borowed from lopster ;) */
char *print_size(char *str, int bs, double bytes) {
    if (bytes < 1024)
	snprintf(str, bs, "%ld B", (long) bytes);
    else if (bytes < 1024 * 128)
	snprintf(str, bs, "%.2f KB", bytes / 1024.0);
    else if (bytes < 1024 * 1024)
	snprintf(str, bs, "%.1f KB", bytes / 1024.0);
    else if (bytes < 1024 * 1024 * 128)
	snprintf(str, bs, "%.2f MB", bytes / 1024.0 / 1024.0);
    else if (bytes < 1024 * 1024 * 1024)
	snprintf(str, bs, "%.1f MB", bytes / 1024.0 / 1024.0);
    else
	snprintf(str, bs, "%.1f GB", bytes / 1024.0 / 1024.0 / 1024.0);
    return str;
}

#ifdef WIN32
char *win32_strerror(int WSAErr) {
 char *err = 0;
    switch (WSAErr) {
        case 0:     err = "No Error"; break;
        case 10004: err = "Interrupted system call"; break;
        case 10009: err = "Bad file number"; break;
        case 10013: err = "Permission denied"; break;
        case 10014: err = "Bad address"; break;
        case 10022: err = "Invalid argument"; break;
        case 10024: err = "Too many open files"; break;
        case 10035: err = "Operation would block"; break;
        case 10036: err = "Operation now in progress"; break;
        case 10037: err = "Operation already in progress"; break;
        case 10038: err = "Socket operation on non-socket"; break;
        case 10039: err = "Destination address required"; break;
        case 10040: err = "Message too long"; break;
        case 10041: err = "Protocol wrong type for socket"; break;
        case 10042: err = "Bad protocol option"; break;
        case 10043: err = "Protocol not supported"; break;
        case 10044: err = "Socket type not supported"; break;
        case 10045: err = "Operation not supported on socket"; break;
        case 10046: err = "Protocol family not supported"; break;
        case 10047: err = "Address family not supported by protocol family"; break;
        case 10048: err = "Address already in use"; break;
        case 10049: err = "Can't assign requested address"; break;
        case 10050: err = "Network is down"; break;
        case 10051: err = "Network is unreachable"; break;
        case 10052: err = "Net dropped connection or reset"; break;
        case 10053: err = "Software caused connection abort"; break;
        case 10054: err = "Connection reset by peer"; break;
        case 10055: err = "No buffer space available"; break;
        case 10056: err = "Socket is already connected"; break;
        case 10057: err = "Socket is not connected"; break;
        case 10058: err = "Can't send after socket shutdown"; break;
        case 10059: err = "Too many references, can't splice"; break;
        case 10060: err = "Connection timed out"; break;
        case 10061: err = "Connection refused"; break;
        case 10062: err = "Too many levels of symbolic links"; break;
        case 10063: err = "File name too long"; break;
        case 10064: err = "Host is down"; break;
        case 10065: err = "No Route to Host"; break;
        case 10066: err = "Directory not empty"; break;
        case 10067: err = "Too many processes"; break;
        case 10068: err = "Too many users"; break;
        case 10069: err = "Disc Quota Exceeded"; break;
        case 10070: err = "Stale NFS file handle"; break;
        case 10091: err = "Network SubSystem is unavailable"; break;
        case 10092: err = "WINSOCK DLL Version out of range"; break;
        case 10093: err = "Successful WSASTARTUP not yet performed"; break;
        case 10071: err = "Too many levels of remote in path"; break;
        case 11001: err = "Host not found"; break;
        case 11002: err = "Non-Authoritative Host not found"; break;
        case 11003: err = "Non-Recoverable errors: FORMERR, REFUSED, NOTIMP"; break;
        case 11004: err = "Valid name, no data record of requested type, No address, look for MX record"; break;
        default: err = strerror( WSAErr );
    }
    return err;
}
#endif
