view src/listen.c @ 0:08114f7dcc23 0.2.21

this is masqmail-0.2.21 from oliver kurth
author meillo@marmaro.de
date Fri, 26 Sep 2008 17:05:23 +0200
parents
children 26e34ae9a3e3
line wrap: on
line source

/*  MasqMail
    Copyright (C) 1999/2000 Oliver Kurth

    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.
*/

#include "masqmail.h"
#include <sys/wait.h>
#include <sys/types.h>

static int volatile sighup_seen = 0;

static
void sighup_handler(int sig)
{
  sighup_seen = 1;
  signal(SIGHUP, sighup_handler);
}

static
void sigchld_handler(int sig)
{
  pid_t pid;
  int status;
  
  pid = waitpid(0, &status, 0);
  if(pid > 0){
    if(WEXITSTATUS(status) != EXIT_SUCCESS)
      logwrite(LOG_WARNING, "process %d exited with %d\n",
	       pid, WEXITSTATUS(status));
    if(WIFSIGNALED(status))
      logwrite(LOG_WARNING,
	       "process with pid %d got signal: %d\n",
	       pid, WTERMSIG(status));
  }
  signal(SIGCHLD, sigchld_handler);
}

#ifdef ENABLE_SMTP_SERVER
void accept_connect(int listen_sock, int sock, struct sockaddr_in* sock_addr)
{
  pid_t pid;
  int dup_sock = dup(sock);
  FILE *out, *in;
  gchar *rem_host;
  gchar *ident = NULL;

  rem_host = g_strdup(inet_ntoa(sock_addr->sin_addr));
#ifdef ENABLE_IDENT
  {
    gchar *id = NULL;
    if((id = (gchar *)ident_id(sock, 60))){
      ident = g_strdup(id);
    }
    logwrite(LOG_NOTICE, "connect from host %s, port %hd ident=%s\n",
	     rem_host,
	     ntohs (sock_addr->sin_port),
	     ident ? ident : "(unknown)");
  }
#else
  logwrite(LOG_NOTICE, "connect from host %s, port %hd\n",
	 rem_host,
	 ntohs (sock_addr->sin_port));
#endif

  // start child for connection:
  signal(SIGCHLD, sigchld_handler);
  pid = fork();
  if(pid == 0){
    close(listen_sock);
    out = fdopen(sock, "w");
    in = fdopen(dup_sock, "r");
    
    smtp_in(in, out, rem_host, ident);

    _exit(EXIT_SUCCESS);
  }else if(pid < 0){
    logwrite(LOG_WARNING, "could not fork for incoming smtp connection: %s\n",
	     strerror(errno));
  }

#ifdef ENABLE_IDENT
  if(ident != NULL) g_free(ident);
#endif

  close(sock);
  close(dup_sock);
}
#endif /*ifdef ENABLE_SMTP_SERVER*/

void listen_port(GList *iface_list, gint qival, char *argv[])
{
  int i;
  fd_set active_fd_set, read_fd_set;
  struct timeval tm;
  time_t time_before, time_now;
  struct sockaddr_in clientname;
  size_t size;
  GList *node, *node_next;
  int sel_ret;

  /* Create the sockets and set them up to accept connections. */
  FD_ZERO (&active_fd_set);
#ifdef ENABLE_SMTP_SERVER
  for(node = g_list_first(iface_list);
      node;
      node = node_next){
    interface *iface = (interface *)(node->data);
    int sock;

    node_next=g_list_next(node);
    if ((sock = make_server_socket (iface))<0){
      iface_list= g_list_remove_link(iface_list, node);
      g_list_free_1(node);
      continue;
    }
    if (listen (sock, 1) < 0){
      logwrite(LOG_ALERT, "listen: (terminating): %s\n", strerror(errno));
      exit (EXIT_FAILURE);
    }
    logwrite(LOG_NOTICE, "listening on interface %s:%d\n",
	     iface->address, iface->port);
    DEBUG(5) debugf("sock = %d\n", sock);
    FD_SET (sock, &active_fd_set);
  }
#endif

  /* setup handler for HUP signal: */
  signal(SIGHUP, sighup_handler);
  signal(SIGCHLD, sigchld_handler);

  /* now that we have our socket(s),
     we can give up root privileges */
  if(!conf.run_as_user){
    if(setegid(conf.mail_gid) != 0){
      logwrite(LOG_ALERT, "could not change gid to %d: %s\n",
	       conf.mail_gid, strerror(errno));
      exit(EXIT_FAILURE);
    }
    if(seteuid(conf.mail_uid) != 0){
      logwrite(LOG_ALERT, "could not change uid to %d: %s\n",
	       conf.mail_uid, strerror(errno));
      exit(EXIT_FAILURE);
    }
  }

  /*  sel_ret = 0;*/
  time(&time_before);
  time_before -= qival;
  sel_ret = -1;

  while (1){

    /* if we were interrupted by an incoming connection (or a signal)
       we have to recalculate the time until the next queue run should
       occur. select may put a value into tm, but doc for select() says
       we should not use it.*/
    if(qival > 0){
      time(&time_now);
      if(sel_ret == 0){ /* we are either just starting or did a queue run */
	tm.tv_sec = qival;
	tm.tv_usec = 0;
	time_before = time_now;
      }else{
	tm.tv_sec = qival - (time_now - time_before);
	tm.tv_usec = 0;

	/* race condition, very unlikely (but possible): */
	if(tm.tv_sec < 0)
	  tm.tv_sec = 0;
      }
    }
    /* Block until input arrives on one or more active sockets,
       or signal arrives,
       or queuing interval time elapsed (if qival > 0) */
    read_fd_set = active_fd_set;
    if ((sel_ret = select(FD_SETSIZE, &read_fd_set, NULL, NULL,
			  qival > 0 ? &tm : NULL)) < 0){
      if(errno != EINTR){
	logwrite(LOG_ALERT, "select: (terminating): %s\n", strerror(errno));
	exit (EXIT_FAILURE);
      }else{
	if(sighup_seen){
	  logwrite(LOG_NOTICE, "HUP signal received. Restarting daemon\n");

	  for(i = 0; i < FD_SETSIZE; i++)
	    if(FD_ISSET(i, &active_fd_set))
	      close(i);

	  execv(argv[0], &(argv[0]));
	  logwrite(LOG_ALERT, "restarting failed: %s\n", strerror(errno));
	  exit(EXIT_FAILURE);
	}
      }
    }
    else if(sel_ret > 0){
#ifdef ENABLE_SMTP_SERVER
      for(i = 0; i < FD_SETSIZE; i++){
	if (FD_ISSET (i, &read_fd_set)){
	  int sock = i;
	  int new;
	  size = sizeof (clientname);
	  new = accept (sock,
			(struct sockaddr *) &clientname,
			&size);
	  if (new < 0){
	    logwrite(LOG_ALERT, "accept: (ignoring): %s\n",
		     strerror(errno));
	  }else
	    accept_connect(sock, new, &clientname);
	}
      }
#else
      ;
#endif
    }else{
      /* If select returns 0, the interval time has elapsed.
	 We start a new queue runner process */
      int pid;
      signal(SIGCHLD, sigchld_handler);
      if((pid = fork()) == 0){
	queue_run();

	_exit(EXIT_SUCCESS);
      }
      else if(pid < 0){
	logwrite(LOG_ALERT, "could not fork for queue run");
      }
    }
  }
}