/* Copyright (c) 1995-1999 NEC USA, Inc.  All rights reserved.               */
/*                                                                           */
/* The redistribution, use and modification in source or binary forms of     */
/* this software is subject to the conditions set forth in the copyright     */
/* document ("Copyright") included with this distribution.                   */

/*
 * $Id: log.c,v 1.71.4.7 1999/03/10 23:18:03 steve Exp $
 */

#define __NOLOGUPDATEPROTO

#include "socks5p.h"

#include "log.h"       /* Prototypes & defines.                              */
#include "threads.h"   /* MUTEX & THREAD_SELF                                */

void *S5LogDefaultHandle = NULL;
int   S5LogShowThreadIDS   = 0;

#ifdef HAVE_STDARG_H
#include <stdarg.h>
#define VA_START(a, b) va_start((a), (b))
#define va_alist ...
#define va_dcl
#else
#include <varargs.h>
#define VA_START(a, b) va_start((a))
#endif

#ifdef HAVE_NL_TYPES_H
#include <nl_types.h>
#endif

#ifdef HAVE_SYSLOG_H
#include <syslog.h>
#ifndef LOG_DAEMON
#define LOG_DAEMON LOG_USER
#endif
#endif

typedef struct {
#ifdef HAVE_NL_TYPES_H
    nl_catd catalog;
#define NL_START(x, y)     (x)->catalog = catopen((y), 0)
#define NL_UPDATE(x, e, f) if ((x)->catalog != (nl_catd)-1) (f) = catgets((x)->catalog, 0, (e), (f))
#define NL_CLOSE(x)        if ((x)->catalog != (nl_catd)-1) catclose((x)->catalog); (x)->catalog = (nl_catd)-1
#elif defined(HAVE_DGETTEXT)
    char domain[MAXNAMELEN];
#define NL_START(x, y)     strncpy((x)->domain, name, MAX(MAXNAMELEN, strlen(name))); (x)->domain[MAX(MAXNAMELEN, strlen(name))] = '\0'
#define NL_UPDATE(x, e, f) (f) = dgettext((x)->domain, (f))
#define NL_CLOSE(x)
#else
#define NL_START(x, y) 
#define NL_UPDATE(x, e, f) 
#define NL_CLOSE(x)
#endif
    int level;
    int how;
} S5LogHandle;

static void replacePercentM(const char *inbuffer, char *outbuffer, int olen) {
    register const char *t2;
    register char *t1, ch;

    if (!outbuffer || !inbuffer) return;
    olen--;

    for (t1 = outbuffer; (ch = *inbuffer) && t1-outbuffer < olen ; ++inbuffer)
	if (inbuffer[0] == '%' && inbuffer[1] == 'm') 
	    for (++inbuffer, t2 = strerror(GETERRNO()); (t2 && t1-outbuffer < olen) && (*t1 = *t2++); t1++);
	else *t1++ = ch;
    
    *t1 = '\0';
}

void S5LogvUpdate(const void *handle, int level, int msgID, const char *oformat, va_list pvar) {
#define FMT_BUFLEN 2*1024 + 2*10
    char fmt_cpy[FMT_BUFLEN], format[FMT_BUFLEN];
    S5LogHandle *h = (S5LogHandle *)handle;
    int serrno = GETERRNO();
#ifndef HAVE_VSNPRINTF
    static FILE *tmpFile = NULL;
#endif

    *fmt_cpy = '\0';

    /* If S5LogHandle has not been initialized, do it before we start;       */
    /* Saves the mess of making sure it gets called ahead of time.           */
    if (h == NULL) {
	S5LogStart(&S5LogDefaultHandle, -1, -1, "libsocks5");
	h = (S5LogHandle *)S5LogDefaultHandle;
    }

    /* If the handle is invalid, don't log.                                  */
    /* If the maximum log level is too low for this message, don't log.      */
    /* If something that we call forces us to log a message, don't log.      */
    if (!h || !(h->how) || h->level == -1 || level > h->level) return;

    /* Change the format if the message is in the catalog...                 */
    NL_UPDATE(h, msgID, oformat);

    if (!oformat) return;

    /* Print the pid & maybe the thread id in format here.  Skip forward if  */
    /* you don't want it later (e.g. if syslogging).                         */
    sprintf(format, "%05d:", (int)getpid());
    if (S5LogShowThreadIDS) sprintf(format+strlen(format), "%06d:", (int)THREAD_SELF());
    strcat(format, " ");
    
    replacePercentM(oformat, format + strlen(format), sizeof(format) - strlen(format));

#ifdef HAVE_VSNPRINTF
    if (vsnprintf(fmt_cpy, FMT_BUFLEN-1, format, pvar) < 0) {
	fmt_cpy[FMT_BUFLEN-1] = '\0';
    }
#else
    /* Original idea for VSPRINTF verification courtesy of Sten Gunterbuer   */
    /* (sten@ERGON.CH) a la BUGTRAQ.  Another solution is to use pipes or a  */
    /* temp file, but I'm not convinced it would be more efficient (2 laps   */
    /* through the formatting versus 1 and a bunch of byte copies through    */
    /* the kernel).                                                          */
    /*                                                                       */
    /* Thanks to Zach Brown (zab@zabbo.net) for finding the problem...       */
    if (tmpFile == NULL && (tmpFile = tmpfile()) == NULL) {
	sprintf(fmt_cpy, "ERROR: Unable to verify string for vsprintf");
	if ((h->level - 1) > S5_LOG_INFO) h->level = S5_LOG_INFO;
	else h->level--;
	level = S5_LOG_ERROR;
    } else if (vfprintf(tmpFile, format, pvar) > FMT_BUFLEN-1) {
	sprintf(fmt_cpy, "ERROR: String verification failed when trying to log message with format: %s", format);
	if ((h->level - 1) > S5_LOG_INFO) h->level = S5_LOG_INFO;
	else h->level--;
	level = S5_LOG_ERROR;
    } else {
	rewind(tmpFile);
	vsprintf(fmt_cpy, format, pvar);
    }
#endif

    /* Log to the Local log facility, e.g. Stderr on Unix and maybe a window */
    /* or something on NT.  Neither system can deal with a NULL format so    */
    /* check that here too.                                                  */
    if (h->how & S5_LOG_LOCAL && format) {
	fprintf(stderr, "%s\n", fmt_cpy);
	fflush(stderr);
    }

    /* Log to the system logging facility -- e.g. Syslog on Unix & the       */
    /* EventLog on Windows NT.                                               */
    if (h->how & S5_LOG_SYSTEM) {
#if defined(HAVE_SYSLOG_H) && defined(SYSLOG_FAC)
	int slfac, offset = 6; 

	if      (level == S5_LOG_ERROR)  				slfac = LOG_ERR;
	else if (level == S5_LOG_INFO)    				slfac = LOG_NOTICE;
	else if (level >  S5_LOG_INFO && level < S5_LOG_DEBUG(5)) 	slfac = LOG_WARNING;
	else if (level >  S5_LOG_DEBUG(0) && level < S5_LOG_DEBUG(10))  slfac = LOG_NOTICE;
	else if (level >  S5_LOG_DEBUG(5) && level < S5_LOG_DEBUG(15))  slfac = LOG_INFO;
	else if (level >= S5_LOG_DEBUG(15))				slfac = LOG_DEBUG;

	/* skip "%05d:", and maybe another " "                               */
	if (!S5LogShowThreadIDS) offset++;
	syslog(slfac, fmt_cpy + offset);
#else
#endif
    }

    SETERRNO(serrno); /* restore errno, just in case...?                     */
    return;
}

void  S5LogUpdate(const void *handle, int level, int msgID, const char *format, va_alist) va_dcl {
    va_list pvar;
#ifdef HAVE_STDARG_H
    va_start(pvar, format);
#else
    VA_START(pvar, format);
#endif
    S5LogvUpdate(handle, level, msgID, format, pvar);
    va_end(pvar);
}

void S5LogStart(void **vhp, int how, int level, const char *name) {
    S5LogHandle **hp = (S5LogHandle **)vhp;
    char buf[1024], *tmp;
/*
    char tbuf[1024];
    time_t now = time(NULL);
*/

    sprintf(buf, "%s", name);

    if (!(*hp) && ((*hp) = (S5LogHandle *)malloc(sizeof(S5LogHandle))) == NULL) {
	/* no where to store things, so just skip it.                        */
	return;
    } else {
	if (how == -1) {
	    (*hp)->how = 0;
	    if (getenv("SOCKS5_LOG_SYSLOG")) (*hp)->how |= S5_LOG_SYSTEM;
 	    if (getenv("SOCKS5_LOG_STDERR")) (*hp)->how |= S5_LOG_LOCAL;
	} else (*hp)->how = how;

	if (level == -1) {
	    if ((tmp = getenv("SOCKS5_DEBUG"))) {
		if (isdigit((unsigned char)*tmp)) (*hp)->level = S5_LOG_DEBUG(atoi(tmp));
	        else (*hp)->level = S5_LOG_DEBUG(25);
	    } else (*hp)->level = -1;
	} else (*hp)->level = level;

	NL_START(*hp, buf);

    }

#if defined(HAVE_SYSLOG_H) && defined(SYSLOG_FAC)
    /* Only the first person to call this gets to open the log, that way the */
    /* monitor doesn't clobber the server's socks5 name in the logs.         */
    if ((*hp)->how & S5_LOG_SYSTEM){
	static int logopened = 0;
	
	if (!logopened) {
	    logopened = 1;
	    openlog(name, LOG_PID, SYSLOG_FAC);
	}
    }
#endif

/* Following code is commented out so that we can remove DontLoop...         */
/*
    if (restart && (*hp)->how != 0) {
    	MUTEX_LOCK(lt_mutex);
    	strftime(tbuf, sizeof(tbuf), "%c", REAL(localtime)(&now));
    	S5LogUpdate((*hp), S5_LOG_DEBUG(0), 0, "%s Logging (re)started at %s", name, tbuf);
    	MUTEX_UNLOCK(lt_mutex);
    }
*/
}

void S5LogEnd(void *vh) {
    S5LogHandle *h = (S5LogHandle *)vh;

    if (!h) return;
    NL_CLOSE(h);

#if defined(HAVE_SYSLOG_H) && defined(SYSLOG_FAC)
    closelog();
#endif

    
    free(h);
}

