/*
 * "$Id: log.c 6333 2007-03-12 20:49:13Z mike $"
 *
 *   Log file routines for the Common UNIX Printing System (CUPS).
 *
 *   Copyright 1997-2006 by Easy Software Products, all rights reserved.
 *
 *   These coded instructions, statements, and computer programs are the
 *   property of Easy Software Products and are protected by Federal
 *   copyright law.  Distribution and use rights are outlined in the file
 *   "LICENSE.txt" which should have been included with this file.  If this
 *   file is missing or damaged please contact Easy Software Products
 *   at:
 *
 *       Attn: CUPS Licensing Information
 *       Easy Software Products
 *       44141 Airport View Drive, Suite 204
 *       Hollywood, Maryland 20636 USA
 *
 *       Voice: (301) 373-9600
 *       EMail: cups-info@cups.org
 *         WWW: http://www.cups.org
 *
 * Contents:
 *
 *   cupsdGetDateTime() - Returns a pointer to a date/time string.
 *   cupsdLogMessage()  - Log a message to the error log file.
 *   cupsdLogPage()     - Log a page to the page log file.
 *   cupsdLogRequest()  - Log an HTTP request in Common Log Format.
 *   check_log_file()   - Open/rotate a log file if it needs it.
 */

/*
 * Include necessary headers...
 */

#include "cupsd.h"
#include <stdarg.h>
#include <syslog.h>


/*
 * Local functions...
 */

static int	check_log_file(cups_file_t **, const char *);


/*
 * 'cupsdGetDateTime()' - Returns a pointer to a date/time string.
 */

char *					/* O - Date/time string */
cupsdGetDateTime(time_t t)		/* I - Time value */
{
  struct tm	*date;			/* Date/time value */
  static time_t	last_time = -1;		/* Last time value */
  static char	s[1024];		/* Date/time string */
  static const char * const months[12] =/* Months */
		{
		  "Jan",
		  "Feb",
		  "Mar",
		  "Apr",
		  "May",
		  "Jun",
		  "Jul",
		  "Aug",
		  "Sep",
		  "Oct",
		  "Nov",
		  "Dec"
		};


  if (t != last_time)
  {
    last_time = t;

   /*
    * Get the date and time from the UNIX time value, and then format it
    * into a string.  Note that we *can't* use the strftime() function since
    * it is localized and will seriously confuse automatic programs if the
    * month names are in the wrong language!
    *
    * Also, we use the "timezone" variable that contains the current timezone
    * offset from GMT in seconds so that we are reporting local time in the
    * log files.  If you want GMT, set the TZ environment variable accordingly
    * before starting the scheduler.
    *
    * (*BSD and Darwin store the timezone offset in the tm structure)
    */

    date = localtime(&t);

    snprintf(s, sizeof(s), "[%02d/%s/%04d:%02d:%02d:%02d %+03ld%02ld]",
	     date->tm_mday, months[date->tm_mon], 1900 + date->tm_year,
	     date->tm_hour, date->tm_min, date->tm_sec,
#ifdef HAVE_TM_GMTOFF
             date->tm_gmtoff / 3600, (date->tm_gmtoff / 60) % 60);
#else
             timezone / 3600, (timezone / 60) % 60);
#endif /* HAVE_TM_GMTOFF */
  }

  return (s);
}


/*
 * 'cupsdLogMessage()' - Log a message to the error log file.
 */

int					/* O - 1 on success, 0 on error */
cupsdLogMessage(int        level,	/* I - Log level */
                const char *message,	/* I - printf-style message string */
	        ...)			/* I - Additional args as needed */
{
  int			len;		/* Length of message */
  va_list		ap;		/* Argument pointer */
  static const char	levels[] =	/* Log levels... */
		{
		  ' ',
		  'X',
		  'A',
		  'C',
		  'E',
		  'W',
		  'N',
		  'I',
		  'D',
		  'd'
		};
#ifdef HAVE_VSYSLOG
  static const int syslevels[] =	/* SYSLOG levels... */
		{
		  0,
		  LOG_EMERG,
		  LOG_ALERT,
		  LOG_CRIT,
		  LOG_ERR,
		  LOG_WARNING,
		  LOG_NOTICE,
		  LOG_INFO,
		  LOG_DEBUG,
		  LOG_DEBUG
		};
#endif /* HAVE_VSYSLOG */
  static int	linesize = 0;		/* Size of line for output file */
  static char	*line = NULL;		/* Line for output file */


 /*
  * See if we want to log this message...
  */

  if (level > LogLevel || !ErrorLog)
    return (1);

#ifdef HAVE_VSYSLOG
 /*
  * See if we are logging errors via syslog...
  */

  if (!strcmp(ErrorLog, "syslog"))
  {
    va_start(ap, message);
    vsyslog(syslevels[level], message, ap);
    va_end(ap);

    return (1);
  }
#endif /* HAVE_VSYSLOG */

 /*
  * Not using syslog; check the log file...
  */

  if (!check_log_file(&ErrorFile, ErrorLog))
    return (0);

 /*
  * Print the log level and date/time...
  */

  cupsFilePrintf(ErrorFile, "%c %s ", levels[level], cupsdGetDateTime(time(NULL)));

 /*
  * Allocate the line buffer as needed...
  */

  if (!linesize)
  {
    linesize = 8192;
    line     = malloc(linesize);

    if (!line)
    {
      cupsFilePrintf(ErrorFile,
                     "ERROR: Unable to allocate memory for line - %s\n",
                     strerror(errno));
      cupsFileFlush(ErrorFile);

      return (0);
    }
  }

 /*
  * Format the log message...
  */

  va_start(ap, message);
  len = vsnprintf(line, linesize, message, ap);
  va_end(ap);

 /*
  * Resize the buffer as needed...
  */

  if (len >= linesize)
  {
    len ++;

    if (len < 8192)
      len = 8192;
    else if (len > 65536)
      len = 65536;

    line = realloc(line, len);

    if (line)
      linesize = len;
    else
    {
      cupsFilePrintf(ErrorFile,
                     "ERROR: Unable to allocate memory for line - %s\n",
                     strerror(errno));
      cupsFileFlush(ErrorFile);

      return (0);
    }

    va_start(ap, message);
    len = vsnprintf(line, linesize, message, ap);
    va_end(ap);
  }

  if (len >= linesize)
    len = linesize - 1;

 /*
  * Then the log message...
  */

  cupsFilePuts(ErrorFile, line);

 /*
  * Then a newline...
  */

  if (len > 0 && line[len - 1] != '\n')
    cupsFilePutChar(ErrorFile, '\n');

 /*
  * Flush the line to the file and return...
  */

  cupsFileFlush(ErrorFile);

  return (1);
}


/*
 * 'cupsdLogPage()' - Log a page to the page log file.
 */

int					/* O - 1 on success, 0 on error */
cupsdLogPage(cupsd_job_t *job,		/* I - Job being printed */
             const char  *page)		/* I - Page being printed */
{
  ipp_attribute_t *billing,		/* job-billing attribute */
                  *hostname;		/* job-originating-host-name attribute */


  billing  = ippFindAttribute(job->attrs, "job-billing", IPP_TAG_ZERO);
  hostname = ippFindAttribute(job->attrs, "job-originating-host-name",
                              IPP_TAG_ZERO);

#ifdef HAVE_VSYSLOG
 /*
  * See if we are logging pages via syslog...
  */

  if (!strcmp(PageLog, "syslog"))
  {
    syslog(LOG_INFO, "PAGE %s %s %d %s %s %s", job->printer->name,
           job->username ? job->username : "-",
           job->id, page, billing ? billing->values[0].string.text : "-",
           hostname->values[0].string.text);

    return (1);
  }
#endif /* HAVE_VSYSLOG */

 /*
  * Not using syslog; check the log file...
  */

  if (!check_log_file(&PageFile, PageLog))
    return (0);

 /*
  * Print a page log entry of the form:
  *
  *    printer job-id user [DD/MON/YYYY:HH:MM:SS +TTTT] page num-copies \
  *        billing hostname
  */

  cupsFilePrintf(PageFile, "%s %s %d %s %s %s %s\n", job->printer->name,
        	 job->username ? job->username : "-",
        	 job->id, cupsdGetDateTime(time(NULL)), page,
		 billing ? billing->values[0].string.text : "-",
        	 hostname->values[0].string.text);
  cupsFileFlush(PageFile);

  return (1);
}


/*
 * 'cupsdLogRequest()' - Log an HTTP request in Common Log Format.
 */

int					/* O - 1 on success, 0 on error */
cupsdLogRequest(cupsd_client_t *con,	/* I - Request to log */
                http_status_t  code)	/* I - Response code */
{
  static const char * const states[] =	/* HTTP client states... */
		{
		  "WAITING",
		  "OPTIONS",
		  "GET",
		  "GET",
		  "HEAD",
		  "POST",
		  "POST",
		  "POST",
		  "PUT",
		  "PUT",
		  "DELETE",
		  "TRACE",
		  "CLOSE",
		  "STATUS"
		};


#ifdef HAVE_VSYSLOG
 /*
  * See if we are logging accesses via syslog...
  */

  if (!strcmp(AccessLog, "syslog"))
  {
    syslog(LOG_INFO,
           "REQUEST %s - %s \"%s %s HTTP/%d.%d\" %d " CUPS_LLFMT " %s %s\n",
           con->http.hostname, con->username[0] != '\0' ? con->username : "-",
	   states[con->operation], con->uri,
	   con->http.version / 100, con->http.version % 100,
	   code, CUPS_LLCAST con->bytes,
	   con->request ?
	       ippOpString(con->request->request.op.operation_id) : "-",
	   con->response ?
	       ippErrorString(con->response->request.status.status_code) : "-");

    return (1);
  }
#endif /* HAVE_VSYSLOG */

 /*
  * Not using syslog; check the log file...
  */

  if (!check_log_file(&AccessFile, AccessLog))
    return (0);

 /*
  * Write a log of the request in "common log format"...
  */

  cupsFilePrintf(AccessFile,
                 "%s - %s %s \"%s %s HTTP/%d.%d\" %d " CUPS_LLFMT " %s %s\n",
        	 con->http.hostname, con->username[0] != '\0' ? con->username : "-",
		 cupsdGetDateTime(con->start), states[con->operation], con->uri,
		 con->http.version / 100, con->http.version % 100,
		 code, CUPS_LLCAST con->bytes,
		 con->request ?
		     ippOpString(con->request->request.op.operation_id) : "-",
		 con->response ?
		     ippErrorString(con->response->request.status.status_code) :
		     "-");

  cupsFileFlush(AccessFile);

  return (1);
}


/*
 * 'check_log_file()' - Open/rotate a log file if it needs it.
 */

static int				/* O  - 1 if log file open */
check_log_file(cups_file_t **lf,	/* IO - Log file */
	       const char  *logname)	/* I  - Log filename */
{
  char		backname[1024],		/* Backup log filename */
		filename[1024],		/* Formatted log filename */
		*ptr;			/* Pointer into filename */
  const char	*logptr;		/* Pointer into log filename */


 /*
  * See if we have a log file to check...
  */

  if (!lf || !logname || !logname[0])
    return (1);

 /*
  * Format the filename as needed...
  */

  if (!*lf ||
      (strncmp(logname, "/dev/", 5) && cupsFileTell(*lf) > MaxLogSize &&
       MaxLogSize > 0))
  {
   /*
    * Handle format strings...
    */

    filename[sizeof(filename) - 1] = '\0';

    if (logname[0] != '/')
    {
      strlcpy(filename, ServerRoot, sizeof(filename));
      strlcat(filename, "/", sizeof(filename));
    }
    else
      filename[0] = '\0';

    for (logptr = logname, ptr = filename + strlen(filename);
         *logptr && ptr < (filename + sizeof(filename) - 1);
	 logptr ++)
      if (*logptr == '%')
      {
       /*
        * Format spec...
	*/

        logptr ++;
	if (*logptr == 's')
	{
	 /*
	  * Insert the server name...
	  */

	  strlcpy(ptr, ServerName, sizeof(filename) - (ptr - filename));
	  ptr += strlen(ptr);
	}
        else
	{
	 /*
	  * Otherwise just insert the character...
	  */

	  *ptr++ = *logptr;
	}
      }
      else
	*ptr++ = *logptr;

    *ptr = '\0';
  }

 /*
  * See if the log file is open...
  */

  if (!*lf)
  {
   /*
    * Nope, open the log file...
    */

    if ((*lf = cupsFileOpen(filename, "a")) == NULL)
    {
      syslog(LOG_ERR, "Unable to open log file \"%s\" - %s", filename,
             strerror(errno));

      return (0);
    }

    if (strncmp(filename, "/dev/", 5))
    {
     /*
      * Change ownership and permissions of non-device logs...
      */

      fchown(cupsFileNumber(*lf), RunUser, Group);
      fchmod(cupsFileNumber(*lf), LogFilePerm);
    }
  }

 /*
  * Do we need to rotate the log?
  */

  if (strncmp(logname, "/dev/", 5) && cupsFileTell(*lf) > MaxLogSize &&
      MaxLogSize > 0)
  {
   /*
    * Rotate log file...
    */

    cupsFileClose(*lf);

    strcpy(backname, filename);
    strlcat(backname, ".O", sizeof(backname));

    unlink(backname);
    rename(filename, backname);

    if ((*lf = cupsFileOpen(filename, "a")) == NULL)
    {
      syslog(LOG_ERR, "Unable to open log file \"%s\" - %s", filename,
             strerror(errno));

      return (0);
    }

   /*
    * Change ownership and permissions of non-device logs...
    */

    fchown(cupsFileNumber(*lf), RunUser, Group);
    fchmod(cupsFileNumber(*lf), LogFilePerm);
  }

  return (1);
}


/*
 * End of "$Id: log.c 6333 2007-03-12 20:49:13Z mike $".
 */
