/*
 *  TurnAwayMail.c - respond with a 421 code to specified SMTP connections
 *  Copyright 2001 Bruce Gingery all rights reserved
 *  Checked for RFC-2821 compliance 21 May 2001 - fits in the MAY's.
 *
 *  Portions of this code are subject to the copyright of 
 *    Copyright (c) 1983, 1991, 1993, 1994
 *      The Regents of the University of California.  All rights reserved.
 *
 * for those portions
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *      This product includes software developed by the University of
 *      California, Berkeley and its contributors.
 * 4. Neither the name of the University nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 */
#ifndef LINT
  char *vers =
 "@(#)$Id: turnawaymail.c,v 0.1.3 2003/03/17 08:49:20 bruce Exp $ (GMT)";
#endif

/* #define DEBUGTURNAWAY */

#include <unistd.h>
#include <sys/param.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netinet/tcp.h>
#include <time.h>
#include <sys/time.h>
#include <syslog.h>
#include <fcntl.h>

/* Maximum IP:port listen pairs */
#ifndef MAXLISTENS
#define MAXLISTENS 1
#endif

/* Maximum size of display name that we presrve */
#ifndef MAXGLOCALE
#define MAXGLOCALE 64
#endif

/* Maximum size of timestamp that we can concieve of generating */
#ifndef MAXTIMESTAMP
#define MAXTIMESTAMP 256
#endif

/* Maximum message size
   note that (E)SMTP RFCs limit the individual response size */
#ifndef MAXRMESSAGE
#define MAXRMESSAGE 2048
#endif

/* Where we write our PID tracking file */
#ifndef PIDFILELOC
#define PIDFILELOC "/var/run"
#endif

#ifndef BACKLOG
#define BACKLOG 6
#endif


char *stdmsg =
"421-%s TurnAway v0.1.3 (not) ready at %s;\r\n421-in %s.  Incoming mail services are not\r\n421-available at this IP:port.  Please use proper MX or SRV lookups.\r\n421 Your mail software MAY be broken.  Closing connection.\r\n";

       char      pidfile[MAXPATHLEN];
       char      timestamp[MAXTIMESTAMP];
       char      where[MAXGLOCALE];
       char     *names[MAXLISTENS];
       char	*ip4s[MAXLISTENS];
/* #ifdef USE_IP6
       long long ip6s[MAXLISTENS];
   #endif */
       int       ports[MAXLISTENS];
       int       socxs[MAXLISTENS];

char *
now(void)
{
  struct timeval  tv;
  struct timezone tz;
  struct tm       tm;
  gettimeofday(&tv,&tz);
  localtime_r(&tv.tv_sec,&tm);
  strftime(timestamp,MAXTIMESTAMP,"%a, %d %b %Y %T %z (%Z)",&tm);
  return timestamp;
}

int
accepted( int s, char *a, int c, u_short p )
{
   char     buf[MAXRMESSAGE];
   size_t  msgsize;
   ssize_t writesize;

   fcntl( s, F_SETFL, O_NONBLOCK );
   msgsize = snprintf(&buf[0], MAXRMESSAGE, stdmsg, names[c], now(), where);
   writesize = write( s, &buf, msgsize );
   if (writesize < 0) {
     syslog( LOG_INFO,
  "ruleset=rebuff, relay=[%s], reject=\"500 client gone\", listen=(%s [%s:%d])",
	a, names[c], ip4s[c], ports[c] );
     return -1 ;
   } else if ( msgsize == writesize ) {
     syslog( LOG_INFO,
	"ruleset=rebuff, relay=[%s], reject=\"421 %s\", listen=[%s:%d]",
	a, names[c], ip4s[c], ports[c] );
     return 0;
   } else {
     syslog( LOG_INFO,
  "ruleset=rebuff, relay=[%s], reject=\"421 (truncated)\", listen=(%s [%s:%d])",
	a, names[c], ip4s[c], ports[c] );
     return 1;
   }
   return 0;
}

void
connections(void)
{
   struct sockaddr_in from;
   char   addr[33];
   int len = sizeof( from );
   int s,c;
   
   for (;;) {
     for ( c = 0 ; ports[c] ; c++ ) { /* FIXME
  must replace this inside loop with an FD_SET and call back */
       if (( s = accept( socxs[c], ( struct sockaddr * ) &from, &len ) ) < 0 ) {
         syslog(LOG_CRIT, "Accept failed: %m");
         return;
       }
       inet_ntop(AF_INET,&from.sin_addr.s_addr,addr,32);
       syslog(LOG_INFO,
		"rulset=turnaway, relay=[%s], clientport=%d, listen=[%s:%d]",
		addr, ntohs(from.sin_port), ip4s[c], ports[c]);
       (void)accepted( s, addr, c, from.sin_port);
       close( s );
     }
   }
   return;
}

int
startListener(int index)
{
  struct sockaddr_in addr;
  int    sockt;
  int    on = 1;

  if ((sockt = socket(PF_INET,SOCK_STREAM,0)) == -1) {
    syslog(LOG_CRIT, "Cannot open socket: %m");
    return -1;
  }
  if ((setsockopt(sockt,
		SOL_SOCKET, SO_REUSEADDR, (char *)&on, sizeof(on)) ||
       setsockopt(sockt,
		SOL_SOCKET, SO_KEEPALIVE, (char *)&on, sizeof(on))) != 0) {
    syslog(LOG_CRIT, "Cannot set socket options: %m");
    return -1;
  }
  bzero(&addr,sizeof(addr));
  addr.sin_family   = AF_INET;
  addr.sin_port     = htons(ports[index]);
  if (!inet_pton(AF_INET,ip4s[index],&addr.sin_addr.s_addr)) {
    syslog(LOG_ERR, "Cannot determine address %s, %m",ip4s[index]);
    return -1;
  }
  if (bind(sockt, ( struct sockaddr * )&addr, sizeof( addr )) < 0 ) {
    syslog(LOG_ERR, "Cannot bind address %s, %m",ip4s[index]);
    return -1;
  }
  if (listen(sockt,BACKLOG) < 0) {
    syslog(LOG_ERR, "Cannot listen on socket for %s:%d, %m",
	ip4s[index], ports[index]);
    return -1;
  }
  socxs[index] = sockt;
  return sockt;
}

void
help(const char *argv0,const char *argv1)
{
   fprintf(stderr,"Format: %s <geolocation> <IPv4[:port] name> ...\r\n",argv0);
   fprintf(stderr,"\r\n\tgeolocation\tdisplayed \"in ...\" as below\r\n");
   fprintf(stderr,
      "\tIPv4\t\tlocal IPv4 address in dot notation, and may be 0.0.0.0\r\n");
   fprintf(stderr,"\t\t\tto bind all available listen addresses on port.\r\n");
   fprintf(stderr,"\tport\t\tinteger port number 1..65535, default 25\r\n");
   fprintf(stderr,"\tname\t\tname to be used when answering.\r\n\r\n");
   fprintf(stderr,"The IPv4:port name may be one or a pair of parameters,\r\n");
   fprintf(stderr,
    "and may be repeated up to %d total to specify additional interfaces.\r\n",
	MAXLISTENS-1);
   fprintf(stderr,
  "Program rebuffs connected clients with a message much like the below.\r\n");
   fprintf(stderr,"====[sent to connecting clients]====================\r\n");
   fprintf(stderr,stdmsg,"example.com",now(),argv1);
   fprintf(stderr,"====================================================\r\n");
   exit( 1 );
}

int
main(int argc, char **argv)
{
  int a,c,hascolon,hasspace;
  char *v,*l,*e,*p;
  FILE *fp;
  pid_t launchpid = getpid();

  /* zero out global storage */
  bzero(timestamp,sizeof(timestamp));
  bzero(where,    sizeof(where));
  bzero(names,    sizeof(names));
  bzero(socxs,	  sizeof(socxs));
  bzero(ports,    sizeof(ports));
  bzero(ip4s,     sizeof(ip4s));

#ifdef DEBUGTURNAWAY
  fprintf(stdout,"PID: %d\nargv0: %s\n",launchpid,argv[0]);
  for ( c = 1 ; c < argc ; c++ ) fprintf(stdout,"argv[%d]: %s\n",c,argv[c]);
  fflush(stdout);
#endif

  /* trim path from argv[0] for more readable help() output */
  p = NULL;
  for (e = argv[0] ; *e ; e++ ) {
    if ( *e == '/') {
      p = e;
      p++;
    }
  }
  if (p == NULL) {
    p = argv[0];
  }
  if (argc < 2) {
    help(p,"Somecity, Earth");
  } else {
    if (!strcmp("-h",argv[1]) || !strcmp("--help",argv[1])) {
       help(p,"Somecity, Earth");
    }
  }

  /* Okay, now presume we're gonna run... */
  openlog(p, LOG_PID | LOG_NOWAIT, LOG_MAIL);

  /* roll my own strncpy */
  v = where;
  l = argv[1];

  for ( c = 0 ; *l > '\0' && c < MAXGLOCALE ; c++) {
    *v++ = *l++;
    if (c >= MAXGLOCALE - 1) where[MAXGLOCALE - 1] = '\0';
  }
  a = 2;
  c = 0;

  /* Now parse repeatable arguments */
  while (argc > a && c < MAXLISTENS) {
    l = NULL;
    v = NULL;
    hascolon = 0;
    hasspace = 0;
    ip4s[c] = e = argv[a];

    while (*e) {

      if (*e == ':') {
        hascolon = 1;
        v = e;
        *v++ = '\0';
        e++;
      } else if (*e == ' ') {
        hasspace = 1;
        l = e;
        *l++ = '\0';
        break;
      } else {
        e++;
      }

      if (!hasspace) {
        l = argv[++a];
      }
    }
    if (l == NULL) {
      syslog(LOG_ERR, "Error - cannot read name for IP #%d: %s;exiting.",
		c+1, argv[1] );
        exit( 2 );
    }
    names[c] = l;

    if (v == NULL) {
       ports[c] = 25;
    } else {
      ports[c] = atoi(v);
    }
    if (startListener(c) < 0) {
        syslog(LOG_ERR,
		"Error - cannot listen for IP #%d: %s:%d (%s); exiting.",     
		c+1, ip4s[c], ports[c], l);
        exit( 3 );
    }
    syslog(LOG_INFO,"Listening on %s:%d for %s",ip4s[c],ports[c],l);
#ifdef DEBUGTURNAWAY
    fprintf(stdout,"Listening on %s:%d for %s\n",ip4s[c],ports[c],l);
    fflush(stdout);
#endif

    a++;
    c++;
  }

#ifdef DEBUGTURNAWAY
      fprintf(stdout,"%d listeners\n",c);
      fflush(stderr);
#endif

  if (c == 0) {
    syslog(LOG_ERR,"No listen address + name specified");
    exit( 4 );
  }

  if (daemon(0,0) < 0) {
    syslog(LOG_WARNING,"Unable to daemon myself: %m; exiting.");
    exit( 5 );
  }

  if (setlogin("") < 0) {
    syslog(LOG_WARNING, "Cannot clear logname: %m; continuing.");
  }

  snprintf(pidfile,MAXPATHLEN,"%s/%s.pid",PIDFILELOC,p);
  if ((fp = fopen(pidfile,"w")) != NULL) {
    fprintf(fp, "%u\n", getpid());
    fclose(fp);
  } else {
    syslog(LOG_ERR, "%s: %m, exiting.", pidfile); 
    exit( 6 );
  }
  if (setregid(65533,65533) == -1) {
    syslog(LOG_ERR, "Cannot set group to nogroup: %m; continuing.");
  }
  if (setreuid(65534,65534) == -1) {
    syslog(LOG_ERR, "Cannot set user to nobody: %m; continuing.");
    if (geteuid() == 0) {
      syslog(LOG_CRIT, "Did not shed root privilege: %m; exiting.");
      exit( 8 );
    }
  }
  
  syslog(LOG_INFO, "Replaces [%d] for daemon mode, uid=(%s [%d/%d]),",
	launchpid,getlogin(),getuid(),geteuid());
  connections();
  return 0;
}
