/*****************************************************************************

  POPular -- A POP3 server and proxy for large mail systems

  $Id: daemon.c,v 1.10 2003/11/24 19:23:30 sqrt Exp $

  http://www.remote.org/jochen/mail/popular/

******************************************************************************

  Copyright (C) 1999-2003  Jochen Topf <jochen@remote.org>

  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

*****************************************************************************/

#if HAVE_CONFIG_H
# include <config.h>
#endif

#include "popular.h"


/*****************************************************************************

  daemonize()

*****************************************************************************/
void
daemonize(const char *prg)
{
  switch (fork()) {
    case 0:     /* child */
      break;
    case -1:    /* error */
      fprintf(stderr, "%s: fork failed: %s\n", prg, strerror(errno));
      exit(RCODE_ERR);
    default:    /* parent */
      exit(RCODE_OK);
  }

  (void) close(0);
  (void) close(1);
  (void) close(2);
  (void) chdir("/");
  (void) setsid();
}


/*****************************************************************************

  set_keepalive()

  Set TCP keepalive on a socket.

*****************************************************************************/
int
set_keepalive(int socket)
{
  int one=1;
  return setsockopt(socket, SOL_SOCKET, SO_KEEPALIVE, &one, sizeof(int));
}


/*****************************************************************************

  prepare_statefile()

*****************************************************************************/
void *
prepare_statefile(const char *dir, const char *templ, const char *link, int size, int *ofd)
{
  char fn[strlen(dir) + strlen(templ) + 10];
  char ln[strlen(dir) + strlen(link) + 10];
  caddr_t mp;
  int fd;
  char sbuf[1] = "";
  char *p;

  strlcpy(fn, dir, sizeof(fn));
  strlcat(fn, "/", sizeof(fn));
  snprintf(fn+strlen(fn), sizeof(fn)-strlen(fn), templ, getpid());

  fd = open(fn, O_RDWR|O_CREAT, 0644);
  if (fd == -1) {
    /* XLOG-DOC:SOS:0135:open_statefile
     * Open of the state file failed. The program will be terminated. */
    xlog_printf(xlog_sos, 0x0135, "open_statefile filename='%s' errno=%d errmsg='%s'", fn, errno, strerror(errno));
    exit(RCODE_ERR);
  }

  if (lseek(fd, size, SEEK_SET) == -1) {
    /* XLOG-DOC:SOS:0136:seek_statefile
     * Seeking inside the state file failed. The program will be
     * terminated. Try deleting the state file before restarting. */
    xlog_printf(xlog_sos, 0x0136, "seek_statefile errno=%d errmsg='%s'", errno, strerror(errno));
    exit(RCODE_ERR);
  }

  if (rel_write(fd, sbuf, 1) != 1) {
    /* XLOG-DOC:SOS:0137:write_statefile
     * Writing to state file failed. The program will be terminated.
     * Try deleting the state file before restarting. */
    xlog_printf(xlog_sos, 0x0137, "write_statefile errno=%d errmsg='%s'", errno, strerror(errno));
    exit(RCODE_ERR);
  }

  mp = mmap(NULL, size, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
  if (mp == (caddr_t) -1) {
    /* XLOG-DOC:SOS:0138:mmap_statefile
     * Mmapping the state file failed. The program will be terminated.
     * Try deleting the state file before restarting. */
    xlog_printf(xlog_sos, 0x0138, "mmap_statefile errno=%d errmsg='%s'", errno, strerror(errno));
    exit(RCODE_ERR);
  }

  memset(mp, 0, size);

  strlcpy(ln, dir,  sizeof(ln));
  strlcat(ln, "/",  sizeof(ln));
  strlcat(ln, link, sizeof(ln));
  p = strrchr(fn, '/');

  (void)unlink(ln);
  if (symlink(++p, ln) < 0) {
    /* XLOG-DOC:ADM:0139:symlink_statefile_failed
     * The symlinking of the real state file to its symbolical name
     * failed. The server will still work, but the pstatus program
     * will not find the state file. */
    xlog_printf(xlog_adm, 0x0139, "symlink_statefile_failed linkname='%s' name='%s' errno=%d errmsg='%s'", p, ln, errno, strerror(errno));
  }

  if (ofd) *ofd = fd;
  return (void *)mp;
}


/*****************************************************************************
 
  unlink_in_rundir()

*****************************************************************************/
void
unlink_in_rundir(const char *dir, const char *templ, const char *link)
{
  char fn[strlen(dir) + max(strlen(templ), strlen(link)) + 10];

  strlcpy(fn, dir, sizeof(fn));
  strlcat(fn, "/", sizeof(fn));
  snprintf(fn+strlen(fn), sizeof(fn)-strlen(fn), templ, getpid());

  if (unlink(fn) < 0) {
    /* XLOG-DOC:ADM:0141:unlink_file_in_rundir_failed
     * An unlink() operation in the run directory failed. The file can
     * be a pid file, the state file or the control socket. */
    xlog_printf(xlog_adm, 0x0141, "unlink_file_in_rundir_failed filename='%s' errno=%d errmsg='%s'", fn, errno, strerror(errno));
    return;
  }

  strlcpy(fn, dir,  sizeof(fn));
  strlcat(fn, "/",  sizeof(fn));
  strlcat(fn, link, sizeof(fn));
  if (access(fn, F_OK) != 0) {
    if (unlink(fn) < 0) {
      /* XLOG-DOC:ADM:0142:unlink_link_in_rundir_failed
       * An unlink() operation in the run directory failed. The filename is
       * a link to either a pid file, a state file or a control socket. */
      xlog_printf(xlog_adm, 0x0142, "unlink_link_in_rundir_failed filename='%s' errno=%d errmsg='%s'", fn, errno, strerror(errno));
    }
  }

  return;
}


/*****************************************************************************
 
  write_pidfile()

*****************************************************************************/
void
write_pidfile(const char *dir, const char *templ, const char *link)
{
  char fn[strlen(dir) + strlen(templ) + 10];
  char ln[strlen(dir) + strlen(link) + 10];
  int fd;
  char buf[10];
  char *p;

  strlcpy(fn, dir, sizeof(fn));
  strlcat(fn, "/", sizeof(fn));
  snprintf(fn+strlen(fn), sizeof(fn)-strlen(fn), templ, getpid());

  fd = open(fn, O_WRONLY|O_CREAT, 0644);
  if (fd < 0) {
    /* XLOG-DOC:ADM:0140:pidfile_open_failed
     * The opening of the pidfile failed. The server will continue to run. */
    xlog_printf(xlog_adm, 0x0140, "pidfile_open_failed file='%s' errno=%d errmsg='%s'", fn, errno, strerror(errno));
    return;
  }

  snprintf(buf, sizeof(buf), "%d\n", getpid());
  rel_write(fd, buf, strlen(buf));
  close(fd);

  strlcpy(ln, dir,  sizeof(ln));
  strlcat(ln, "/",  sizeof(ln));
  strlcat(ln, link, sizeof(ln));
  p = strrchr(fn, '/');

  (void)unlink(ln);
  if (symlink(++p, ln) < 0) {
    /* XLOG-DOC:ADM:008d:pidfile_symlink_failed
     * The symlink to the pidfile failed. The server will continue to run. */
    xlog_printf(xlog_adm, 0x008d, "pidfile_symlink_failed file='%s' link='%s' errno=%d errmsg='%s'", p, ln, errno, strerror(errno));
  }
}


/*****************************************************************************
 
  open_ctrl_socket()

*****************************************************************************/
int
open_ctrl_socket(const char *dir, const char *templ, const char *link, mode_t mask)
{
  struct sockaddr_un sockun;
  char ln[strlen(dir) + strlen(link) + 10];
  mode_t oldmask;
  int fd;
  char *p;

  fd = socket(AF_UNIX, SOCK_DGRAM, 0);
  if (fd < 0) {
    /* XLOG-DOC:SOS:013a:control_socket_open_failed
     * Opening of the UNIX domain control socket failed. The program will be
     * terminated. */
    xlog_printf(xlog_sos, 0x013a, "control_socket_open_failed errno=%d errmsg='%s'", errno, strerror(errno));
    exit(RCODE_ERR);
  }

  memset(&sockun, 0, sizeof(sockun));
  sockun.sun_family = AF_UNIX;
  strlcpy(sockun.sun_path, dir, sizeof(sockun.sun_path));
  strlcat(sockun.sun_path, "/", sizeof(sockun.sun_path));
  snprintf(sockun.sun_path+strlen(sockun.sun_path), sizeof(sockun.sun_path)-strlen(sockun.sun_path), templ, getpid());

  oldmask = umask(mask);
  if (bind(fd, (struct sockaddr *) &sockun, sizeof(sockun)) < 0) {
    /* XLOG-DOC:SOS:013b:control_bind_failed
     * Binding the UNIX domain control socket failed. The program will be
     * terminated. */
    xlog_printf(xlog_sos, 0x013b, "control_bind_failed socket='%s' errno=%d errmsg='%s'", sockun.sun_path, errno, strerror(errno));
    exit(RCODE_ERR);
  }
  (void) umask(oldmask);

  strlcpy(ln, dir,  sizeof(ln));
  strlcat(ln, "/",  sizeof(ln));
  strlcat(ln, link, sizeof(ln));
  p = strrchr(sockun.sun_path, '/');

  (void)unlink(ln);
  if (symlink(++p, ln) != 0) {
    /* XLOG-DOC:SOS:013c:control_symlink_failed
     * Linking the UNIX domain control socket to the canonical name failed.
     * The programm will be terminated. */
    xlog_printf(xlog_sos, 0x013c, "control_symlink_failed socket='%s' linkname='%s' errno=%d errmsg='%s'", sockun.sun_path, link, errno, strerror(errno));
    exit(RCODE_ERR);
  }

  return fd;
}


/*****************************************************************************

  load_ok()

  Checks wether the current load is smaller than the given <maxload>. If
  <maxload> is 0, the check always returns true.

*****************************************************************************/
int
load_ok(int maxload)
{
  static char *statenames[] = { "OK", "WARN", "MAX" };
  static int state=0;
  int oldstate = state;
  long load;

  if (maxload == 0) return 1;

  load = get_load();
  if (load < 0) return 1;

  switch (state) {
    case 0:
      if (load >= maxload * 9 / 10) state=1;
      if (load >= maxload) state=2;
      break;
    case 1:
      if (load < maxload * 8 / 10) state=0;
      if (load >= maxload) state=2;
      break;
    case 2:
      if (load < maxload * 9 / 10) state=1;
      if (load < maxload * 8 / 10) state=0;
      break;
    default:
      /* XLOG-DOC:BUG:0171:illegal_load_state
       * The state in sess_load() has an illegal value. This can never happen.
       * The server keeps going but might log strange things. */
      xlog_printf(xlog_bug, 0x0171, "illegal_load_state");
      return state != 2;
  }

  /* XLOG-DOC:ADM:0172:load_state
   * The load state is printed. This can be one of 'OK', 'WARN', 'MAX'.
   * See the documentation for the exact meaning of these messages. */
  if (oldstate != state) xlog_printf(xlog_adm, 0x0172, "load_state state=%s load=%ld", statenames[state], load);
  return state != 2;
}


/*****************************************************************************

  sess_ok()

*****************************************************************************/
int
sess_ok(int used, int max, const char *dir, const char *file)
{
  static char *statenames[] = { "OK", "WARN", "MAX" };
  static int state=0;
  static int flagfile=0;
  int oldstate = state;

  if (flagfile == 1 && used < max-1) {
    char buf[MAXBUF];
    flagfile = 0;
    strlcpy(buf, dir, sizeof(buf));
    strlcat(buf, "/", sizeof(buf));
    strlcat(buf, file, sizeof(buf));
    if (unlink(buf) != 0) {
      /* XLOG-DOC:SOS:0174:unlink_failed
       * Unlink of a flag file failed. */
      xlog_printf(xlog_sos, 0x0174, "unlink_failed file='%s/%s' errno=%d errmsg='%s'", dir, file, errno, strerror(errno));
    }
  }

  if (flagfile == 0 && used >= max && dir && file) {
    flagfile = 1;
    if (touch(dir, file) != 0) {
      /* XLOG-DOC:SOS:0173:touch_failed
       * Creating or 'touching' of a flag file failed. */
      xlog_printf(xlog_sos, 0x0173, "touch_failed file='%s/%s' errno=%d errmsg='%s'", dir, file, errno, strerror(errno));
    }
  }

  switch (state) {
    case 0:
      if (used >= max * 9 / 10) state=1;
      if (used >= max) state=2;
      break;
    case 1:
      if (used < max * 8 / 10) state=0;
      if (used >= max) state=2;
      break;
    case 2:
      if (used < max * 9 / 10) state=1;
      break;
    default:
      /* XLOG-DOC:BUG:016f:illegal_session_state
       * The state in sess_ok() has an illegal value. This can never happen.
       * The server keeps going but might log strange things. */
      xlog_printf(xlog_bug, 0x016f, "illegal_session_state");
      return used < max;
  }

  /* XLOG-DOC:ADM:0170:session_state
   * The session state is printed. This can be one of 'OK', 'WARN', 'MAX'.
   * See the documentation for the exact meaning of these messages. */
  if (oldstate != state) xlog_printf(xlog_adm, 0x0170, "session_state state=%s", statenames[state]);
  return used < max;
}


/*****************************************************************************

  reapchildren()

  Reap all deceased children, logging the event only if they died an
  unnatural death. The function freesess will be called with the pid
  of any dead child.

*****************************************************************************/
void
reapchildren(void (*freesess)(pid_t))
{
  pid_t pid;
  int status;

  while ((pid = waitpid(-1, &status, WNOHANG)) > 0) {
    if (WIFEXITED(status) && WEXITSTATUS(status)) {
      /* XLOG-DOC:ADM:0020:child_died
       * A child of pserv or pproxy died with non-zero exit code. See the
       * log entries of the child for more details about what happend. */
      xlog_printf(xlog_adm, 0x0020, "child_died pid=%d status=%d", pid, WEXITSTATUS(status));
    } else if (WIFSIGNALED(status)) {
      if (WTERMSIG(status) == SIGALRM) {
	/* XLOG-DOC:ERR:017c:child_died_timeout
	 * A child of pserv or pproxy died because of an ALRM signal. This
	 * means that 'sessiontimeout' (or, for pproxy, 'authtimeout') ran
	 * out. */
	xlog_printf(xlog_err, 0x017c, "child_died_timeout pid=%d", pid);
      } else {
	/* XLOG-DOC:SOS:0021:child_died_signal
	 * A child of pserv or pproxy died because of a signal. This can
	 * either mean that there is a bug in the program of the system
	 * has big problems (like running out of memory). */
	xlog_printf(xlog_sos, 0x0021, "child_died_signal pid=%d signal=%d", pid, WTERMSIG(status));
      }
    }
    (*freesess)(pid);
  }
}


/** THE END *****************************************************************/


syntax highlighted by Code2HTML, v. 0.9.1