Logo Search packages:      
Sourcecode: zmailer version File versions  Download package

pop-proxy.c

/*
 * pop-proxy   --  POP3 proxy for Sonera Corporation (Finland)
 *             (ex name: Telecom Finland) internet services
 *
 * Written by Matti Aarnio <matti.aarnio@sonera.fi> 1998/March
 *
 *  Copyright Sonera Corporation
 *
 *  This has been merged into ZMailer sources because:
 *  - Sonera Corp uses ZMailer at POP-mailbox operations
 *  - There are integration items in between these message-store
 *    access systems (POP and IMAP proxies) and outbound smtp services
 *    in form of using authenticated POP or IMAP access as temporary
 *    key for successfull acceptance of smtp traffic for outbound relay
 *    from any address in the known netaddress universe.
 *  The integration is not complete (into autoconfig), because
 *  our low-level libraries are not so easily autoconfigurable :-/
 *
 */
/*
 * TODO:
 * - Do more complex protocol recognition phase so that USER+PASS
 *   commands are both recognized, and are reported back to user
 *   thru code that analyzes WHAT the server really said. That way
 *   this proxy can recognize, when the USER+PASS has been accepted.
 *   If the PASS is not accepted, QUIT the server, and humm around
 *   locally asking for a new USER.  If the PASS is accepted, only
 *   then do 
 * - Log successfull connections to SMTP server's IP-source opening
 *   system for temporary acceptance of outbound SMTP relaying.
 * - SSLeay tunneling integration
 */

#include <stdio.h>
#include <stdlib.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include <errno.h>
#include <fcntl.h>
#include <setjmp.h>
#include <signal.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <string.h>
#include <arpa/inet.h>
#include <syslog.h>

#ifdef USE_AA0          /* Sonera Corp. backend db access v-1 */
#include "aa0client.h"
#else
#ifdef USE_AA_ARB /* Sonera Corp. backend db access v-2 */
#include "arblib.h"
#else
#endif
#endif

int serverport = 10110;
int timeout    = 10*60; /* The default timeout for select and alarm() */
int debug      = 0;

time_t t0 = 0;
long pop_in_bytes = 0;
long pop_out_bytes = 0;
char *username = "<NOT-LOGGED-IN>";
char *popserver = "<NOT-LOGGED-IN>";
#ifdef LOGFAC
const int facility = LOGFAC;
#else
const int facility = LOG_MAIL;
#endif

struct sockaddr_in raddr;

void syslog_stats(int rc)
{
  time_t t1 = time(NULL);
  openlog("pop3-proxy", LOG_PID, facility);
  syslog(LOG_INFO, "from=%s fromport=%d user=%s host=%s  duration=%d  char-in=%ld char-out=%ld  rc=%d",
       inet_ntoa(raddr.sin_addr), ntohs(raddr.sin_port),
       username, popserver, (int)(t1 - t0), pop_in_bytes, pop_out_bytes, rc);
}


void sigalrm(int sig)
{
  if (debug) printf("*** SIGALRM !!! Timeout death...\r\n");
  syslog_stats(90);
  _exit(90);
}


int user_to_server_fd(char *username)
{
      /* XXXX: lookup from AA0/ARB, connect there, and
         issue "USER nnnn\r\n" command.  Return fd.
         In case of failure to find server, to connect to
         it, return -1 (and close possibly open socket) */

      int fd = -1;
      int rc, i;
      struct sockaddr_in sin;
      char keybuf[200];
      char aa0result[9000];
      char *hname, *s;
      struct hostent *hp;
#define userread aa0result
      int userrdspc, userrdcnt;
#define userwrite aa0result
      int userwrspc, userwrcnt;

#ifdef USE_AA0
      AA0DB *aa0db;

      if (aa_locate("MBOX", &aa0db, "QUERY", 0) == 0) {
        if (debug) printf("** backend db access error 1\r\n");
        return -1; /* No database ? */
      }

      sprintf(keybuf,"MBOX:%.60s_ipro", username);
      rc = -1;
      for (i = 0; i < 3; ++i) {
        rc = aa0get(aa0db, keybuf, aa0result);
        if (rc == AA0RET_OK)
          break;
      }
      aa0close(aa0db);
      if (rc != AA0RET_OK) {
        if (debug) printf("** backend db access error 2\r\n");
        return -1;
      }
#else
#ifdef USE_AA_ARB
      sprintf(keybuf,"%.60s_ipro", username);
      for (i = 0; i < 3; ++i) {
        rc = arbget ("mbox", keybuf, aa0result, sizeof (aa0result));
        if (rc == AAS_SUCCESS)
          break;
      }
      closearb();
      if (rc != AAS_SUCCESS) {
        if (debug) printf("** backend db access error 2\r\n");
        return -1;
      }
#else
# error "Neither AA0 nor ARB in use ??!!  What database do you use ?"
#endif
#endif

#if defined(USE_AA0) || defined(USE_AA_ARB)
      /* Data in AA0RESULT buffer, scan for the 7th ":" separated field */
      s = aa0result;
      for (i = 0; i < 6; ++i) {
        if (s != NULL)
          s = strchr(s+1,':');
      }
      if (s) {
        ++s;
      } else {
        if (debug) printf("** backend db access error - bad data\r\n");
        return -1;
      }
      hname = s;
      s = strchr(s,':');
      if (s) *s = '\0';
#else
# error "Neither AA0 nor ARB in use ??!!  What database do you use ?"
#endif

      /* Now 'hname' has the hostname */
      if (*hname == 0) {
        if (debug) printf("** backend db access error - bad data\r\n");
        return -1; /* Invalid! */
      }

      hp = gethostbyname(hname);
      if (!hp) {
        if (debug) printf("** backend db access error - bad data\r\n");
        return -1;
      }

      popserver = strdup(hname);

      fd = socket(AF_INET, SOCK_STREAM, 0);
      if (fd < 0) {
        if (debug) printf("** Proxy failed to create socket ???\r\n");
        return -1;
      }
      memset(&sin, 0, sizeof(sin));
      sin.sin_family = AF_INET;
      sin.sin_port   = htons(serverport);
      memcpy(&sin.sin_addr.s_addr,hp->h_addr,4); /* TCP/IP */

      if (connect(fd, (struct sockaddr *) &sin, sizeof(sin)) < 0) {
        if (debug) printf("** connect to real server (%s:%d) failed: %s\r\n",
                      inet_ntoa(sin.sin_addr), serverport,
                      strerror(errno));
        return -1;
      }

      /* Now we have successfull connect -- to somewhere */

      /* We pull in stuff from there until \n, and then
         send there string:  "USER username\r\n" */

      userrdspc = userrdcnt = 0;
      *userread = 0;
      for (;;) {
        rc = read(fd, userread+userrdspc, sizeof(userread)-userrdspc-1);
        /* We use VERY DANGEROUS assumptions here, but this protocol
           is supposedly half-duplex at this moment anyway.. */
        if (rc < 0) {
          /* Hmm.. */
          if (errno == EINTR)
            continue; /* Again... */
          /* XXX: HUH!? */
        }
        if (rc == 0) {
          /* EOF -- damn..  Ah well. */
          syslog_stats(22);
          exit(22);
        }
        userrdspc += rc;
        userread[userrdspc] = 0; /* Make sure there is \0 */
        if (memchr(userread + userrdspc - rc, '\n', rc) != NULL)
          break; /* Found \n there, great.. */
      }

      /* Now build the output, and write it! */
      
      userwrspc = sprintf(userwrite, "USER %.200s\r\n", username);
      userwrcnt = 0;

      while (userwrspc > userwrcnt) {
        rc = write(fd, userwrite + userwrcnt, userwrspc - userwrcnt);
        if (rc < 0) {
          rc = 0;
          /* XX: which  errno  values may happen ? */
          if (errno == EPIPE) {
            syslog_stats(33);
            exit(0);
          }
        }
        userwrcnt += rc;
      }

      /* It is written!  We are free to continue with PROXY operation */

      return fd;

#undef userread
#undef userwrite
}

void server(int fd, int issock)
{
      /* I/O buffers:                     */
      char userread[512];
      char servread[512];
      char *userwrite = servread;
      /* How much data in those buffers:  */
      int userrdcnt = 0, userrdspc = 0;
      int userwrcnt = 0, userwrspc = 0;
      int servrdcnt = 0, servrdspc = 0;
      int rc;

      int serverfd = -1;
      int quitnow  = 0;
      int highfd;

      userwrspc = sprintf(userwrite, "+OK POP3 proxy\r\n");
      userwrcnt = 0; 

      while (serverfd < 0) {
        /* Pull in commandlines, recognize some commands,
           and do processing */
        char command[20+1], param[500+1];

        alarm(timeout);

        while (userwrspc > userwrcnt) {
          rc = write(fd, userwrite + userwrcnt, userwrspc - userwrcnt);
          if (rc < 0) {
            rc = 0;
            /* XX: which  errno  values may happen ? */
            if (errno == EPIPE) {
            syslog_stats(55);
            exit(0);
            }
          }
          userwrcnt += rc;
        }
        userwrspc = userwrcnt = 0;
        /* Ok, output buffer written */

        if (quitnow) {
          syslog_stats(0);
          exit(0);
        }

        /* Now we live a bit dangerously, and presume the input
           to be line oriented.  The danger being, the input
           MAY get more than line's data if the remote sends
           input in a burst -- "USER nnn\r\nPASS mmm\r\n"
           without waiting for +OK/-ERR in between. */

        userrdspc = userrdcnt = 0;
        *userread = 0;
        for (;;) {
          rc = read(fd, userread+userrdspc, sizeof(userread)-userrdspc-1);
          /* We use VERY DANGEROUS assumptions here, but this protocol
             is supposedly half-duplex at this moment anyway.. */
          if (rc < 0) {
            /* Hmm.. */
            if (errno == EINTR)
            continue; /* Again... */
            /* XXX: HUH!? */
          }
          if (rc == 0) {
            /* EOF -- damn..  Ah well. */
            syslog_stats(20);
            exit(20);
          }
          userrdspc += rc;
          userread[userrdspc] = 0; /* Make sure there is \0 */
          if (memchr(userread + userrdspc - rc, '\n', rc) != NULL)
            break; /* Found \n there, great.. */
        }
        userrdspc = userrdcnt = 0;

        *command = 0;
        *param   = 0;
        sscanf(userread, "%20s %500[^\r\n]", command, param);

        if (strcasecmp(command,"user") == 0) {
          /* Talk with user's socket to pull in "USER xxxx" */
          serverfd = user_to_server_fd(param);
          username = strdup(param);
          if (serverfd < 0) {
            /* No success, claim such anyway.. */
            userwrspc = sprintf(userwrite, "+OK Give PASS\r\n");
            userwrcnt = 0;
            continue;
          }
        } else if (strcasecmp(command,"pass")==0) {
          userwrspc = sprintf(userwrite, "-ERR Sorry, something failed, bad username, password mismatch\r\n");
          userwrcnt = 0;
        } else if (strcasecmp(command,"quit")==0) {
          userwrspc = sprintf(userwrite, "+OK Bye bye\r\n");
          userwrcnt = 0;
          quitnow = 1;
        } else if (strcasecmp(command,"debug")==0) {
          userwrspc = sprintf(userwrite, "*** DEBUG TURNED ON, POP-PROTOCOL BREAKS\r\n");
          userwrcnt = 0;
          debug = 1;
        } else {
          userwrspc = sprintf(userwrite, "-ERR Don't know that command, I am Proxy\r\n");
          userwrcnt = 0; 
        }

      } /* Server acquisition loop ends */

      alarm(0);

      /* Now we have a server on  serverfd */

      fcntl(fd,       F_SETFL, fcntl(fd,       F_GETFL, 0) | O_NONBLOCK);
      fcntl(serverfd, F_SETFL, fcntl(serverfd, F_GETFL, 0) | O_NONBLOCK);

      /* Copy from  serverfd to fd, and the other way.
         Exit if either yields EOF */

      highfd = fd;
      if (serverfd > highfd)
        highfd = serverfd;
      ++highfd;

      for (;;) {

        /* Do we need timeout monitoring ?
           Probably not, but one never knows, thus we rig
           an alarm of 10 minutes at each select call.
           If it expires, we close both sockets, and exit. */

        struct timeval tv;
        fd_set rdset, wrset;

        FD_ZERO(&rdset); FD_ZERO(&wrset);
        tv.tv_sec = timeout; tv.tv_usec = 0; /* 10 minutes */

        if (userrdspc > 0) /* fd -> serverfd */
          FD_SET(serverfd, &wrset); /* Things to write */
        else
          FD_SET(fd, &rdset); /* Just watch for read then */

        if (servrdspc > 0) /* serverfd -> fd */
          FD_SET(fd, &wrset); /* Things to write */
        else
          FD_SET(serverfd, &rdset); /* Just watch for read then */


        rc = select(highfd, &rdset, &wrset, NULL, &tv);
        if (rc == 0) {
          /* Timeout! */
          close(fd);
          close(serverfd);
          syslog_stats(40);
          exit(40);
        }
        if (rc < 0) {
          if (errno == EINTR)
            continue;
          /* Other errs ?? */
          continue;
        }
        if (FD_ISSET(fd, &rdset)) { /* READ: <-- user */
          /* To read from user */
          rc = read(fd, userread, sizeof(userread));
          if (rc == 0) { /* EOF ! */
            syslog_stats(60);
            exit(60);
          }
          if (rc < 0) {
            rc = 0;
          }
          userrdcnt = 0;
          userrdspc = rc;
        }
        if (FD_ISSET(serverfd, &rdset)) { /* READ: <-- server */
          /* To read from server */
          rc = read(serverfd, servread, sizeof(servread));
          if (rc == 0) { /* EOF ! */
            syslog_stats(61);
            exit(61);
          }
          if (rc < 0) {
            rc = 0;
          }
          pop_out_bytes += rc;
          servrdcnt = 0;
          servrdspc = rc;
        }
        if (FD_ISSET(fd, &wrset)) { /* WRITE: server -> user */
          /* We have stuff to write, and space for it */
          rc = write(fd, servread + servrdcnt, servrdspc - servrdcnt);
          if (rc < 0) {
            rc = 0;
            /* Errs ? */
            if (errno == EPIPE) { /* Closed */
            close(fd);
            close(serverfd);
            syslog_stats(62);
            exit(62);
            }
          }
          servrdcnt += rc;
          if (servrdcnt >= servrdspc)
            servrdcnt = servrdspc = 0;
        }
        if (FD_ISSET(serverfd, &wrset)) { /* WRITE: user -> server */
          /* We have stuff to write, and space for it */
          rc = write(serverfd, userread + userrdcnt, userrdspc - userrdcnt);
          if (rc < 0) {
            rc = 0;
            /* Errs ? */
            if (errno == EPIPE) { /* Closed */
            close(fd);
            close(serverfd);
            syslog_stats(64);
            exit(64);
            }
          }
          userrdcnt += rc;
          pop_in_bytes += rc;
          if (userrdcnt >= userrdspc)
            userrdcnt = userrdspc = 0;
        }
      }
      /* NOT REACHED */
}

int main(int argc, char *argv[])
{
      int c;
      int raddrlen;
      int issock = 1;

      setvbuf(stdout, NULL, _IONBF, 0);
      setvbuf(stderr, NULL, _IONBF, 0);
      setvbuf(stdin,  NULL, _IONBF, 0);

      while ((c = getopt(argc,argv,"p:T:?")) != EOF) {
        /* Reserved options:
            -S            This is SSLeay POP3 proxy
            -P host:port  Where to report successfull connects for
                          temporary opening SMTP acceptance for
                      outbound SMTP relaying.
        */
        switch (c) {
        case 'p':
          serverport = atoi(optarg);
          break;
        case 'T':
          timeout = atoi(optarg); /* In minutes, value range: 1 .. 30 */
          if (timeout < 1)
            timeout = 1;
          if (timeout > 30)
            timeout = 30;
          timeout = timeout * 60;
          break;
        case '?':
          break;
        default:
          break;
        }
      }

      time(&t0);

      raddrlen = sizeof(raddr);
      if (getpeername(0, (struct sockaddr *) & raddr, &raddrlen) < 0) {
        issock = 0;
        memset(&raddr, 0, sizeof(raddr));
        /* In theory: (errno == ENOTSOCK)  when not socket.. */
      }

      signal(SIGPIPE, SIG_IGN); /* socket remote close can cause SIGPIPE
                             to be fed back to the socket fd ... */
      signal(SIGALRM, sigalrm);

      server(0, issock);
      return 0;
}

Generated by  Doxygen 1.6.0   Back to index