#ifdef HAVE_CONFIG_H
# include "config.h"
#endif

#include <pwd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#ifdef HAVE_SHADOW_H
#include <shadow.h>
#endif
#include <unistd.h>
#include <fcntl.h>
#include <syslog.h>
#include <errno.h>

#ifdef HAVE_CRYPT_H
#include <crypt.h>
#endif

#include "strlcpy.h"

extern const char * authfile;
char real_username[MAXLINE+1];
char real_maildrop[MAXLINE+1];
extern int use_pop3_allow_deny;

/* user_in_file() and is_user_allowed() by Lukasz Luzar
 * modified by Andreas Krennmair
 */
static int user_in_file(char * user, char * path) {
  int fd;
  size_t userlen;
  off_t len;
  char * buffer, * ptr;
  struct stat sb;

  /* sanity check */
  if (user==NULL || path==NULL) {
    return 0;
  }

  userlen = strlen(user);

  fd = open(path,O_RDONLY);
  if (fd < 0) {
    return 0;
  }
  if (0!=fstat(fd,&sb)) {
    close(fd);
    return 0;
  }
  len = sb.st_size;
  buffer = alloca(len+1);

  /* sanity check */
  if (buffer==NULL) {
    return 0;
  }

  if (read(fd,buffer,(size_t)len)!=(ssize_t)len) {
    close(fd);
    return 0;
  }
  if (0!=close(fd)) {
    return 0;
  }

  buffer[len] = '\0';

  ptr = buffer;
  while (*ptr != '\0') {

    /* omit whitespaces */
    while (*ptr == '\t' || *ptr == ' ') {
      ptr++;
    }

    if (*ptr == '\0') {
      break;
    }

    if ((*ptr!='#') && (strncmp(ptr,user,userlen)==0) &&
        (ptr[userlen] == ' ' || ptr[userlen] == '\t' || ptr[userlen] == '\n')) {
      return 1;
    }

    while (*ptr != '\0' && *ptr != '\n') {
      ptr++;
    }

    if (*ptr == '\n') {
      ptr++;
    }
  }
  return 0;
}

static int is_user_allowed(char * user) {
  int allow, deny;

  switch (user_in_file(user,"/etc/pop3.allow")) {
    case 0:
      allow = 0;
      break;
    case 1:
      allow = 1;
      break;
    default: /* user_in_file() must not return anything else! */
      return 0;
  }

  switch (user_in_file(user,"/etc/pop3.deny")) {
    case 0:
      deny = 0;
      break;
    case 1:
      deny = 1;
      break;
    default: /* dito */
      return 0;
  }

  if ((allow == 0 && deny == 0) || (allow == 1 && deny == 0)) {
    return 1;
  }
  return 0;
}

int authenticate(char * username, char * password) {
  char user[MAXLINE+1], pass[MAXLINE+1];
  char linebuf[MAXLINE+1];
  char * ptr;
  char * sys_pw;
  char * crp;
  struct passwd * u;
#ifdef HAVE_SHADOW_H
  struct spwd * s;
#endif
  int len;
  FILE * fptr;

  /* sanity checks */
  if (username==NULL || password==NULL) {
    return 0;
  }


  real_username[0] = '\0';
  real_maildrop[0] = '\0';

  /* extract username */
  crp = index(username,'\r');
  if (crp==NULL) {
    crp = index(username,'\n');
    if (crp==NULL) {
      crp = username + strlen(username);
    }
  }
  len = crp - username;
  if (len+1>sizeof(user)) {
    len = sizeof(user)-1;
  }
  memset(user,0,sizeof(user));
  strlcpy(user,username,len+1);

  /* extract password */
  crp = index(password,'\r');
  if (crp==NULL) {
    crp = index(password,'\n');
    if (crp==NULL) {
      crp = password + strlen(password);
    }
  }
  len = crp - password;
  if (len+1>sizeof(pass)) {
    len = sizeof(pass)-1;
  }
  memset(pass,0,sizeof(pass));
  strlcpy(pass,password,len+1);

  /* check /etc/pop3.{allow,deny} */
  if (0!=use_pop3_allow_deny && 0==is_user_allowed(user)) {
    return 0;
  }

  /*
   * Text-file authentication
   */
  if (authfile != NULL) {
    fptr = fopen(authfile, "r");
    if (fptr == NULL) {
      syslog(LOG_ERR,"%s: %s: %s","failed to read auth file",authfile,strerror(errno));
      return 0;
    }
    while ((0!=ferror(fptr)) && (0!=feof(fptr))) {
      linebuf[0] = '\0';
      if (fgets(linebuf, sizeof(linebuf), fptr) == NULL) {
        fclose(fptr);
        return 0;
      }
      ptr = strtok(linebuf, ":");
      if (ptr == NULL) continue;
      if (strcmp(user, ptr) != 0) continue;
      ptr = strtok(NULL, ":");
      if (ptr == NULL) {
        fclose(fptr);
        return 0;
      }
      if (strcmp(pass, ptr) != 0) {
        fclose(fptr);
        return 0;
      }

      /*
       * At this point we've authenticated, but we now need to find out what
       * Unix username to use and what maildrop file to read
       */

      ptr = strtok(NULL, ":");
      if (ptr == NULL) {
        fclose(fptr);
        return 0;
      }

      strlcpy(real_username, ptr, sizeof(real_username));

      ptr = strtok(NULL, ":\n");
      if (ptr == NULL) {
        real_username[0] = '\0';
        fclose(fptr);
        return 0;
      }

      fclose(fptr);
      strlcpy(real_maildrop, ptr, sizeof(real_maildrop));

      return 1;
    }
    fclose(fptr);
    return 0;
  }

  u = getpwnam(user);

  if (u == NULL || u->pw_passwd == NULL) {
    return 0;
  }

  /* handle shadowed passwd files */
#if HAVE_SHADOW_H
  if (strcmp(u->pw_passwd,"x")==0) {
    s = getspnam(user);
    sys_pw = s->sp_pwdp;
  } else {
    sys_pw = u->pw_passwd;
  }
#else
  sys_pw = u->pw_passwd;
#endif

  if (strcmp(sys_pw,crypt(pass,sys_pw))==0) {
    return 1;
  }
  return 0;
}
