
/*
 * log.c
 *
 * Written by Toshiharu OHNO <tony-o@iij.ad.jp>
 * Copyright (c) 1993, Internet Initiative Japan, Inc. All rights reserved.
 * See ``COPYRIGHT.iij''
 * 
 * Rewritten by Archie Cobbs <archie@freebsd.org>
 * Copyright (c) 1995-1999 Whistle Communications, Inc. All rights reserved.
 * See ``COPYRIGHT.whistle''
 */

#include "ppp.h"
#ifdef SYSLOG_FACILITY
#include <syslog.h>
#endif

/*
 * DEFINITIONS
 */

  #define DUMP_BYTES_PER_LINE	16
  #define ROUNDUP(x,r)		(((x)%(r))?((x)+((r)-((x)%(r)))):(x))
  #define MAX_LOG_LINE		500

/* Log option descriptor */

  struct logopt
  {
    int		mask;
    const char	*name;
    const char	*desc;
  };

/*
 * GLOBAL VARIABLES
 */

  int	gLogOptions = LG_DEFAULT_OPT | LG_ALWAYS;
#ifdef SYSLOG_FACILITY
  char	gSysLogIdent[32];
#endif

/*
 * INTERNAL VARIABLES
 */

#ifndef SYSLOG_FACILITY
  static FILE		*logfp = stderr;
#endif

  #define ADD_OPT(x,d)	{ LG_ ##x, #x, d },

  static struct logopt	LogOptionList[] =
  {
#ifdef LG_BUND
    ADD_OPT(BUND,	"Bundle events")
#endif
#ifdef LG_LINK
    ADD_OPT(LINK,	"Link events")
#endif
#ifdef LG_LCP
    ADD_OPT(LCP,	"LCP events and negotiation")
#endif
#ifdef LG_AUTH
    ADD_OPT(AUTH,	"Link authentication events")
#endif
#ifdef LG_IPCP
    ADD_OPT(IPCP,	"IPCP events and negotiation")
#endif
#ifdef LG_CCP
    ADD_OPT(CCP,	"CCP events and negotiation")
#endif
#ifdef LG_CCP2
    ADD_OPT(CCP2,	"CCP additional debugging output")
#endif
#ifdef LG_CCP3
    ADD_OPT(CCP3,	"CCP complete packet dumps")
#endif
#ifdef LG_ECP
    ADD_OPT(ECP,	"ECP events and negotiation")
#endif
#ifdef LG_ECP2
    ADD_OPT(ECP2,	"ECP extra debugging output")
#endif
#ifdef LG_FSM
    ADD_OPT(FSM,	"All FSM events (except echo & reset)")
#endif
#ifdef LG_ECHO
    ADD_OPT(ECHO,	"Echo/reply events for all automata")
#endif
#ifdef LG_PHYS
    ADD_OPT(PHYS,	"Physical layer events")
#endif
#ifdef LG_CHAT
    ADD_OPT(CHAT,	"Modem chat script")
#endif
#ifdef LG_CHAT2
    ADD_OPT(CHAT2,	"Chat script extra debugging output")
#endif
#ifdef LG_IFACE
    ADD_OPT(IFACE,	"IP interface and route management")
#endif
#ifdef LG_FRAME
    ADD_OPT(FRAME,	"Dump all incoming & outgoing frames")
#endif
#ifdef LG_PPTP
    ADD_OPT(PPTP,	"PPTP high level events")
#endif
#ifdef LG_PPTP2
    ADD_OPT(PPTP2,	"PPTP more detailed events")
#endif
#ifdef LG_PPTP3
    ADD_OPT(PPTP3,	"PPTP packet dumps")
#endif
#ifdef LG_RADIUS
    ADD_OPT(RADIUS,	"Radius authentication events")
#endif
#ifdef LG_CONSOLE
    ADD_OPT(CONSOLE,	"Log to the console as well as the log file")
#endif
  };

  #define NUM_LOG_LEVELS (sizeof(LogOptionList) / sizeof(*LogOptionList))

/*
 * INTERNAL FUNCTIONS
 */

  static int	logprintf(const char *, ...);
  static int	vlogprintf(const char *fmt, va_list ap);

  static void	LogDoDumpBuf(int (*func)(const char *fmt, ...),
  		  int (*vfunc)(const char *fmt, va_list ap),
		  int timestamp, const u_char *buf, int count,
		  const char *fmt, va_list ap);
  static void	LogDoDumpBp(int (*func)(const char *fmt, ...),
  		  int (*vfunc)(const char *fmt, va_list ap),
		  int timestamp, Mbuf bp, const char *fmt, va_list ap);

#ifndef SYSLOG_FACILITY
  static void	LogTimeStamp(int (*func)(const char *fmt, ...));
#else
  #define LogTimeStamp(c)	do{}while(0)
#endif

/*
 * LogOpen()
 */

int
LogOpen(void)
{
#ifdef SYSLOG_FACILITY
  if (*gSysLogIdent)
    openlog(gSysLogIdent, 0, SYSLOG_FACILITY);
  return(0);
#else
  if ((logfp = fopen(LG_FILE, "a")) == NULL)
  {
    warn("mpd: can't open log file \"" LG_FILE"\"");
    if ((logfp = fopen("/dev/null", "w")) == NULL)
      logfp = stderr;
    return(-1);
  }
  (void) fcntl(fileno(logfp), F_SETFD, 1);
  return(0);
#endif
}

/*
 * LogClose()
 */

void
LogClose(void)
{
#ifndef SYSLOG_FACILITY
  fflush(logfp);
  fclose(logfp);
  logfp = NULL;
#endif
}

/*
 * LogCommand()
 */

int
LogCommand(int ac, char *av[], void *arg)
{
  int	k, bits, add;

  if (ac == 0)
  {
    #define LG_FMT	"    %-12s  %-10s  %s\n"

    printf(LG_FMT, "Log Option", "Enabled", "Description");
    printf(LG_FMT, "----------", "-------", "-----------");
    for (k = 0; k < NUM_LOG_LEVELS; k++)
    {
      int	j;
      char	buf[100];

      snprintf(buf, sizeof(buf), "%s", LogOptionList[k].desc);
      for (j = 0; buf[j]; j++)
	buf[j] = tolower(buf[j]);
      printf("  " LG_FMT, LogOptionList[k].name,
	(gLogOptions & LogOptionList[k].mask) ? "Yes" : "No", buf);
    }
    return(0);
  }

  while (ac--)
  {
    switch (**av)
    {
      case '+':
	(*av)++;
      default:
	add = TRUE;
	break;
      case '-':
	add = FALSE;
	(*av)++;
	break;
    }
    for (k = 0;
      k < NUM_LOG_LEVELS && strcasecmp(*av, LogOptionList[k].name);
      k++);
    if (k < NUM_LOG_LEVELS)
      bits = LogOptionList[k].mask;
    else
    {
      if (!strcasecmp(*av, "all"))
      {
	for (bits = k = 0; k < NUM_LOG_LEVELS; k++)
	  bits |= LogOptionList[k].mask;
	bits &= ~LG_CONSOLE;
      }
      else
      {
	printf("\"%s\" is unknown. Enter \"log\" for list.\n", *av);
	bits = 0;
      }
    }
    if (add)
      gLogOptions |= bits;
    else
      gLogOptions &= ~bits;
    av++;
  }
  return(0);
}

/*
 * LogPrintf()
 *
 * The way to print something to the log
 */

void
LogPrintf(const char *fmt, ...)
{
  va_list	args;

  va_start(args, fmt);
  LogTimeStamp(logprintf);
  vlogprintf(fmt, args);
  vlogprintf("\n", args);		/* XXX args will be ignored */
  if (gLogOptions & LG_CONSOLE)
  {
    vfprintf(stdout, fmt, args);
    putc('\n', stdout);
    fflush(stdout);
  }
  va_end(args);
}

/*
 * LogConsole()
 *
 * Print something to the console.
 */

void
LogConsole(const char *fmt, ...)
{
  va_list	args;

  va_start(args, fmt);
  vfprintf(stdout, fmt, args);
  putc('\n', stdout);
  fflush(stdout);
  va_end(args);
}

/*
 * LogDumpBp()
 *
 * Dump the contents of an Mbuf to the log
 */

void
LogDumpBp(int level, Mbuf bp, const char *fmt, ...)
{
  int		log, console;
  va_list	ap;

/* Where to we log it? */

  log = (level & gLogOptions) || (level & LG_ALWAYS);
  console = (log && (gLogOptions & LG_CONSOLE)) || (level & LG_CONSOLE);

/* Dump it */

  va_start(ap, fmt);
  if (console)
    LogDoDumpBp(printf, vprintf, FALSE, bp, fmt, ap);
  if (log)
    LogDoDumpBp(logprintf, vlogprintf, TRUE, bp, fmt, ap);
  va_end(ap);
}

/*
 * LogDumpBuf()
 *
 * Dump the contents of a buffer to the log
 */

void
LogDumpBuf(int level, const u_char *buf, int count, const char *fmt, ...)
{
  int		log, console;
  va_list	ap;

/* Where to we log it? */

  log = (level & gLogOptions) || (level & LG_ALWAYS);
  console = (log && (gLogOptions & LG_CONSOLE)) || (level & LG_CONSOLE);

/* Dump it */

  va_start(ap, fmt);
  if (console)
    LogDoDumpBuf(printf, vprintf, FALSE, buf, count, fmt, ap);
  if (log)
    LogDoDumpBuf(logprintf, vlogprintf, TRUE, buf, count, fmt, ap);
}

/*
 * LogDoDumpBp()
 *
 * Dump the contents of an mbuf
 */

static void
LogDoDumpBp(int (*func)(const char *fmt, ...),
  int (*vfunc)(const char *fmt, va_list ap),
  int timestamp, Mbuf bp, const char *fmt, va_list ap)
{
  int		k, total;
  u_char	bytes[DUMP_BYTES_PER_LINE];

/* Do header */

  if (timestamp)
    LogTimeStamp(func);
  (*vfunc)(fmt, ap);
  (*func)(":\n");

/* Do data */

  for (total = 0; bp; bp = bp->next)
  {
    int	start, stop, last = 0;

    stop = bp->next ? total + bp->cnt :
		ROUNDUP(total + bp->cnt, DUMP_BYTES_PER_LINE);
    for (start = total; total < stop; )
    {
      u_int	const byte = (MBDATA(bp))[total - start];

      if (total % DUMP_BYTES_PER_LINE == 0 && timestamp)
	LogTimeStamp(func);
      if (total < start + bp->cnt)
      {
	(*func)(" %02x", byte);
	last = total % DUMP_BYTES_PER_LINE;
      }
      else
	(*func)("   ");
      bytes[total % DUMP_BYTES_PER_LINE] = byte;
      total++;
      if (total % DUMP_BYTES_PER_LINE == 0)
      {
	(*func)("  ");
	for (k = 0; k <= last; k++)
	  (*func)("%c", isgraph(bytes[k]) ? bytes[k] : '.');
	(*func)("\n");
      }
    }
  }
}

/*
 * LogDoDumpBuf()
 *
 * Dump the contents of a buffer to the log
 */

static void
LogDoDumpBuf(int (*func)(const char *fmt, ...),
  int (*vfunc)(const char *fmt, va_list ap),
  int timestamp, const u_char *buf, int count, const char *fmt, va_list ap)
{
  int	k, stop, total;

/* Do header */

  if (timestamp)
    LogTimeStamp(func);
  (*vfunc)(fmt, ap);
  (*func)(":\n");

/* Do data */

  stop = ROUNDUP(count, DUMP_BYTES_PER_LINE);
  for (total = 0; total < stop; )
  {
    if (total % DUMP_BYTES_PER_LINE == 0 && timestamp)
      LogTimeStamp(func);
    if (total < count)
      (*func)(" %02x", buf[total]);
    else
      (*func)("   ");
    total++;
    if (total % DUMP_BYTES_PER_LINE == 0)
    {
      (*func)("  ");
      for (k = total - DUMP_BYTES_PER_LINE; k < total && k < count; k++)
	(*func)("%c", isgraph(buf[k]) ? buf[k] : '.');
      (*func)("\n");
    }
  }
}

#ifndef SYSLOG_FACILITY

/*
 * LogTimeStamp()
 *
 * Print a timestamp
 */

static void
LogTimeStamp(int (*func)(const char *fmt, ...))
{
  struct tm	*ptm;
  time_t	now;

  now = time(NULL);
  ptm = localtime(&now);
  (*func)("%02d-%02d %02d:%02d:%02d ",
    ptm->tm_mon + 1, ptm->tm_mday,
    ptm->tm_hour, ptm->tm_min, ptm->tm_sec);
}

#endif

/*
 * Perror()
 */

void
Perror(const char *fmt, ...)
{
  va_list	args;
  char		buf[200];

  snprintf(buf, sizeof(buf), "mpd: ");
  va_start(args, fmt);
  vsnprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), fmt, args);
  va_end(args);
  snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf),
    ": %s", strerror(errno));
  Log(LG_ERR, ("%s", buf));
}

/*
 * logprintf()
 */

static int
logprintf(const char *fmt, ...)
{
  va_list	ap;

  va_start(ap, fmt);
  vlogprintf(fmt, ap);
  va_end(ap);
  return(0);
}

/*
 * vlogprintf()
 */

static int
vlogprintf(const char *fmt, va_list ap)
{
  static char	buf[MAX_LOG_LINE];
  int		len, eol = 0;

/* Add to current line; check for buffer overflow */

  vsnprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), fmt, ap);
  len = strlen(buf);
  if (len == sizeof(buf) - 1)
    eol = 1;	/* i guess! */
  else
    while (len && buf[len - 1] == '\n')
    {
      buf[--len] = 0;
      eol = 1;
    }

/* Wait for complete line before outputting it */

  if (*buf == 0 || !eol)
    return(0);

/* Ok, output it */

#ifdef SYSLOG_FACILITY
  syslog(SYSLOG_FACILITY|LOG_INFO, "%s\n", buf);
#else
  fprintf(logfp, "%s\n", buf);
  fflush(logfp);
#endif

/* Reset line */

  *buf = 0;
  return(0);
}

