#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include "lock_maildrop.h"
#include "rw.h"
#include "process.h"
#include "signal.h"
#include "ssl.h"
#include "strlcpy.h"
#include "mysql.h"

#include <grp.h>
#include <pwd.h>
#include <sys/types.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <syslog.h>
#include <errno.h>
#include <stdio.h>
#include <signal.h>
#include <string.h>

#if HAVE_LIBSSL
# include <openssl/rsa.h>
# include <openssl/crypto.h>
# include <openssl/pem.h>
# include <openssl/ssl.h>
# include <openssl/err.h>
#endif

#define STATE_AUTH	1
#define STATE_TRANS	2
#define STATE_UPDATE	3

extern const char * ssl_certfile;
extern const char * ssl_keyfile;
extern const char * local_mbox;
extern char real_username[MAXLINE+1];
extern char real_maildrop[MAXLINE+1];
static char * mdl;

extern const char * mailspool;

int authenticate(char * username, char * password);
void show_uidl(int fd, char * line);

static void do_remove_lock(void) {
  do_cleanup();
  remove_lock(mdl);
}

#if ENABLE_RFC2449
static void print_capa(int fd) {
  write_line(fd,"+OK Capability list follows\r\n");
  write_line(fd,"TOP\r\n");
  write_line(fd,"UIDL\r\n");
  write_line(fd,"USER\r\n");
  write_line(fd,"EXPIRE NEVER\r\n");
  write_line(fd,"AUTH-RESP-CODE\r\n");
  write_line(fd,".\r\n");
}
#endif


static void sig_handler(int signo) {
  remove_lock(mdl);
  syslog(LOG_INFO,"%s: %u", "caught signal",signo);
  exit(EXIT_FAILURE);
}

void pop3_session(int fd) {
  int state = STATE_AUTH;
  char user[MAXLINE+1], pass[MAXLINE+1], line[MAXLINE+1];
  char * userx, * passx;
  char * maildrop;
  int modified = 0;
  int got_username = 0, got_password = 0;
  struct passwd * u_inf;
  struct group * g_inf;
  int rc;
#if HAVE_LIBSSL
  SSL_CTX * ctx = NULL;
  SSL * ssl = NULL;
  SSL_METHOD * meth = NULL;

  if (use_ssl()) {
    SSL_load_error_strings();
    SSLeay_add_ssl_algorithms();
    meth = SSLv23_server_method();
    ctx = SSL_CTX_new(meth);
    if (ctx==NULL) {
      syslog(LOG_ERR,"%s","SSL: failed to create new context");
      exit(EXIT_FAILURE);
    }

    if (SSL_CTX_use_certificate_file(ctx,ssl_certfile,SSL_FILETYPE_PEM) <= 0) {
      syslog(LOG_ERR,"%s: %s","SSL: failed to use certificate file",ssl_certfile);
      exit(EXIT_FAILURE);
    }

    if (SSL_CTX_use_PrivateKey_file(ctx,ssl_keyfile,SSL_FILETYPE_PEM) <= 0) {
      syslog(LOG_ERR,"%s: %s","SSL: failed to use key file",ssl_keyfile);
      exit(EXIT_FAILURE);
    }

    ssl = SSL_new(ctx);
    if (ssl==NULL) {
      syslog(LOG_ERR,"%s","SSL: failed to get new handle");
      exit(EXIT_FAILURE);
    }
    set_ssl_handle(ssl);

    SSL_set_fd(ssl,fd);
    rc = SSL_accept(ssl);
    if (rc==-1) {
      syslog(LOG_ERR,"%s","SSL: failed to accept connection");
      exit(EXIT_FAILURE);
    }
  }
#endif

  /* I'm ready */
  write_line(fd, "+OK\r\n");

  do {
    read_line(fd,line,MAXLINE);
    if (strncasecmp(line,"QUIT",4)==0) {
      write_line(fd,"+OK\r\n");
      exit(EXIT_SUCCESS);
    } else if (strncasecmp(line,"USER ",5)==0) {
      strlcpy(user,line,sizeof(user));
      if (!got_password) {
        write_line(fd,"+OK\r\n");
      }
      got_username = 1;
    } else if (strncasecmp(line,"PASS ",5)==0) {
      strlcpy(pass,line,sizeof(pass));
      if (!got_username) {
        write_line(fd,"+OK\r\n");
      }
      got_password = 1;
#if ENABLE_RFC2449
    } else if (strncasecmp(line,"CAPA",4)==0) {
      print_capa(fd);
#endif
    } else {
      write_line(fd,"-ERR\r\n");
    }
  } while (!got_username || !got_password);

#define clean_up(str) { \
    char * x; \
    if ((x=memchr(str,'\r',strlen(str))) != NULL) { \
      *x = '\0'; \
    } else if ((x=memchr(str,'\n',strlen(str))) != NULL) { \
      *x = '\0'; \
    } \
  }

  /*
   * we both have username and password, we can now
   * authenticate.
   */
  if ((rc=authenticate(user+5,pass+5))<=0) {
    if (rc==0) {
      write_line(fd,"-ERR [AUTH] authentication failed\r\n");
    } else if (rc==-1) {
      write_line(fd,"-ERR [SYS/TEMP] temporary resource problem\r\n");
    } else {
      write_line(fd,"-ERR unknown error\r\n");
    }
    clean_up(user);
    syslog(LOG_WARNING,"%s ('%.16s') return value %d","login failed",user+5,rc);
    exit(EXIT_FAILURE);
  
  }

  clean_up(user);
  syslog( LOG_INFO, "Authenticated %s", user+5 );
  userx = user + 5;
  clean_up(pass);
  passx = pass + 5;

  /* we've reached transaction state */
  state = STATE_TRANS;

  if (real_username[0] != 0) {
    userx = real_username;
  }

#ifndef HAVE_LIBMYSQLCLIENT
  u_inf = getpwnam(userx);
#else
  u_inf = getMpwnam( userx ); /* getMpwnam() first checks getpwnam() anyway */
#endif /* HAVE_LIBMYSQLCLIENT */
 
  if (u_inf==NULL) {
    syslog(LOG_ERR,"%s: %s","UID lookup failed",strerror(errno));
    write_line(fd,"-ERR [SYS/TEMP] user no longer exists\r\n");
    exit(EXIT_FAILURE);
  }

  if (real_maildrop[0] != 0) {
    maildrop = real_maildrop;
  } else {
    if (local_mbox) {
      size_t md_len = (size_t)strlen(u_inf->pw_dir)+strlen(local_mbox)+2;
      maildrop = alloca(md_len);
      if (maildrop) {
        snprintf(maildrop,md_len,"%s/%s",u_inf->pw_dir,local_mbox);
      } else {
        syslog(LOG_ERR,"%s: %s","alloca() failed",strerror(errno));
        write_line(fd,"-ERR [SYS/TEMP] unable to allocate enough memory\r\n");
        exit(EXIT_FAILURE);
      }
    } else {
      size_t md_len = (size_t)strlen(mailspool)+strlen(userx)+1;
      maildrop = alloca(md_len);
      if (maildrop) {
        snprintf(maildrop,md_len,"%s%s",mailspool,userx);
      } else {
        syslog(LOG_ERR,"%s: %s","alloca() failed",strerror(errno));
      }
    }
  }

  mdl = maildrop;

  g_inf = getgrnam("mail");
  if (g_inf==NULL) {
    syslog(LOG_ERR,"%s","group 'mail' not found");
    write_line(fd,"-ERR [SYS/TEMP] group 'mail' not found\r\n");
    exit(EXIT_FAILURE);
  }
  if (setegid(g_inf->gr_gid)!=0 && real_username[0] == 0) {
    syslog(LOG_ERR,"%s: %u: %s","setegid() failed",g_inf->gr_gid,strerror(errno));
    write_line(fd,"-ERR [SYS/TEMP] failed to join 'mail' group (setegid)\r\n");
    exit(EXIT_FAILURE);
  }

  if (setgid(g_inf->gr_gid)!=0 && real_username[0] == 0) {
    syslog(LOG_ERR,"%s: %u: %s","setgid() failed",g_inf->gr_gid,strerror(errno));
    write_line(fd,"-ERR [SYS/TEMP] failed to join 'mail' group (setgid)\r\n");
    exit(EXIT_FAILURE);
  }

  if ((rc=lock_maildrop(maildrop,u_inf->pw_uid,g_inf->gr_gid))<=0) {
    syslog(LOG_ERR,"%s: %s: %s","failed to lock maildrop",maildrop,strerror(errno));
    if (rc==-1) {
      write_line(fd,"-ERR [SYS/TEMP] failed to lock maildrop\r\n");
    } else {
      write_line(fd,"-ERR [IN-USE] failed to lock maildrop\r\n");
    }
    exit(EXIT_FAILURE);
  }

  if (setuid(u_inf->pw_uid)!=0) {
    syslog(LOG_ERR,"%s: %u: %s","setuid() failed",u_inf->pw_uid,strerror(errno));
    write_line(fd,"-ERR [SYS/TEMP] failed to set user identity\r\n");
    do_remove_lock();
    exit(EXIT_FAILURE);
  }

  if (seteuid(u_inf->pw_uid)!=0) {
    syslog(LOG_ERR,"%s: %u: %s","seteuid() failed",u_inf->pw_uid,strerror(errno));
    write_line(fd,"-ERR [SYS/TEMP] failed to set effective user identity\r\n");
    do_remove_lock();
    exit(EXIT_FAILURE);
  }

  if (atexit(do_remove_lock)!=0) {
    syslog(LOG_WARNING,"%s: %s","atexit() failed; lock files may fail to expire",strerror(errno));
  }

  Signal(SIGPIPE,sig_handler);

  if (process_mails(maildrop)==0) {
    write_line(fd,"-ERR [SYS/PERM] failed to scan maildrop contents\r\n");
    do_cleanup();
    do_remove_lock();
    exit(EXIT_FAILURE);
  }

  /* authenticated and maildrop locked
   */
  write_line(fd,"+OK\r\n");

  read_line(fd,line,MAXLINE);
  while (strncasecmp(line,"QUIT",4)!=0) {
    if (strncasecmp(line,"STAT",4)==0) {
      long msg, size;
      get_stats(&msg,&size);
      snprintf(line,MAXLINE,"+OK %ld %ld\r\n",msg,size);
      write_line(fd,line);
    } else if (strncasecmp(line,"LIST",4)==0) {
      do_list(fd,line); /* parses itself */
    } else if (strncasecmp(line,"RETR ",5)==0) {
      retrieve_msg(fd,line); /* parses itself */
    } else if (strncasecmp(line,"DELE ",5)==0) {
      delete_msg(fd,line); /* parses itself */
      modified = 1;
    } else if (strncasecmp(line,"NOOP",4)==0) {
      write_line(fd,"+OK\r\n");
      /* do nothing */
    } else if (strncasecmp(line,"RSET",4)==0) {
      reset_msg();
      modified = 0;
      write_line(fd,"+OK\r\n");
    } else if (strncasecmp(line,"TOP ",4)==0) {
      show_top_msg(fd,line);
    } else if (strncasecmp(line,"UIDL",4)==0) {
      show_uidl(fd,line+4);
#if ENABLE_RFC2449
    } else if (strncasecmp(line,"CAPA",4)==0) {
      print_capa(fd);
#endif
    } else {
      write_line(fd,"-ERR command not understood\r\n");
    }
    read_line(fd,line,MAXLINE);
  }

  write_line(fd,"+OK\r\n");
  close(fd);
#if HAVE_LIBSSL
  if (use_ssl()) {
    SSL_free(ssl);
    SSL_CTX_free(ctx);
  }
#endif

  if (modified) {
    do_update(maildrop);
  }
  do_cleanup();
  remove_lock(maildrop);
}


syntax highlighted by Code2HTML, v. 0.9.1