/* Alternative checkpassword for QPopup by Jedi/Sector One <j@4u.net> 
 *
 * Format of the configuration file is :
 * pop_login:crypted_password:real_login:path
 *
 * Syslog logging added by Paul Gregg <pgregg@tibus.net> Dec 1997
 * compile by: gcc -o checkpoppasswd checkpoppasswd.c -lcrypt
*/


#define CONFIG_FILE "/var/qmail/users/poppasswd"

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

#define  BUFLEN 2048

#ifndef LINE_MAX
# define LINE_MAX 2048
#endif

#ifndef LOGIN_MAX
# define LOGIN_MAX 16
#endif

extern int errno;

extern char *crypt();
extern char *malloc();
extern char **environ;

char up[513];
int uplen;

   char remotehost[BUFLEN]="\0";
   char remoteip[BUFLEN]="\0";
   char remoteinfo[BUFLEN]="unknown\0";


char *str1e2(char *name, char *value)
{
   char *nv;
   nv = malloc(strlen(name) + strlen(value) + 2);
   if (!nv) {
     syslog(LOG_AUTH | LOG_WARNING, "pop3checkpasswd: exit 111 at point 1");
     _exit(111);
   }
   strcpy(nv,name);
   strcat(nv,"=");
   strcat(nv,value);
   return nv;
}

struct newpw {
   char *pw_passwd;
   char *pw_dir;   
   char *pw_name;      
   char *pw_shell;         
   uid_t pw_uid;
   gid_t pw_gid;
   char *pw_real;
} *newgetpwnam(char *newlogin)
{
   static char line[LINE_MAX + 1];
   static struct newpw npw;
   struct passwd *pw;
   char *tlogin = NULL;
   FILE *fp;
   
   if ((fp = fopen(CONFIG_FILE, "rt")) == NULL) {
     syslog(LOG_AUTH | LOG_WARNING, "pop3checkpasswd: exit 2 at point 2");
     _exit(2);
   }
   while (fgets(line, LINE_MAX, fp) != NULL) {
      char *linepnt;
      
      if ((linepnt = strchr(line, '\n')) != NULL) {
         *linepnt = 0;
      }
      if ((linepnt = strtok(line, ":")) == NULL) {
        syslog(LOG_AUTH | LOG_WARNING, "pop3checkpasswd: exit 2 at point 3");
        _exit(2);
      }
      if (strcmp(linepnt, newlogin) == 0) {
         npw.pw_real = linepnt;
         if ((linepnt = strtok(NULL, ":")) == NULL) {
           syslog(LOG_AUTH | LOG_WARNING, "pop3checkpasswd: exit 2 at point 4");
           _exit(2);
         }
         npw.pw_passwd = linepnt;
         if ((linepnt = strtok(NULL, ":")) == NULL) {
           syslog(LOG_AUTH | LOG_WARNING, "pop3checkpasswd: exit 2 at point 5");
           _exit(2);
         }
         *(linepnt - 1) = 0;
         tlogin = linepnt;
         if ((linepnt = strtok(NULL, ":")) == NULL) {
           syslog(LOG_AUTH | LOG_WARNING, "pop3checkpasswd: exit 2 at point 6");
           _exit(2);
         }
         npw.pw_dir = linepnt;
         break;
      }
   }   
   fclose(fp);
   
   if (tlogin == NULL) {
     /*syslog(LOG_AUTH | LOG_WARNING, "pop3checkpasswd: exit 1 at point 7");*/
     syslog(LOG_AUTH | LOG_WARNING, "pop3checkpasswd: FAILED: %s - no such user from %s@%s [%s]", newlogin, remoteinfo, remotehost, remoteip);
     sleep(2);
     _exit(1);
   }
   pw = getpwnam(tlogin);
   if (!pw) {
     syslog(LOG_AUTH | LOG_WARNING, "pop3checkpasswd: exit 1 at point 8");
     _exit(1); /* XXX: unfortunately getpwnam() hides temporary errors */
   }
      
   npw.pw_name = tlogin;
   npw.pw_shell = pw->pw_shell;
   npw.pw_uid = pw->pw_uid;
   npw.pw_gid = pw->pw_gid;
   
   return &npw;
}

int main(int argc, char **argv, char *envp[])
{
   struct newpw *pw;
   char *login;
   char *password;
   char *stored;
   char *encrypted;
   int r;
   int i;
   char **newenv;
   int numenv;
   char **me;
   char *constr;
   int z;

/*
   strcpy(remoteinfo, "");
   (remoteinfo)[0]='\0';
*/
   for(me=envp;*me;me++) {
     long ptr;
     ptr = (long) *(me);
/*
	syslog(LOG_AUTH | LOG_WARNING, "pop3checkpasswd: %s", *me);
*/
     if (strncmp(*me, "TCPREMOTEIP", 11) == 0) {
	ptr += 12;
	strcpy(remoteip, (char*)(ptr));
     }

     if (strncmp(*me, "TCPREMOTEHOST", 13) == 0) {
	ptr += 14;
	strcpy(remotehost, (char*)(ptr));
     }

     if (strncmp(*me, "TCPREMOTEINFO", 13) == 0) {
	ptr += 14;
	strcpy(remoteinfo, (char*)(ptr));
     }
   }
/*
syslog(LOG_AUTH | LOG_WARNING, "POP3 Host: %s", remotehost);
syslog(LOG_AUTH | LOG_WARNING, "POP3 IP: %s", remoteip);
syslog(LOG_AUTH | LOG_WARNING, "POP3 Info: %s", remoteinfo);
*/

   /* sleep(2); */
/*
   sprintf (constr, "POP3 connect from %s@%s [%s]", remoteinfo, remotehost, remoteip);
   syslog(LOG_AUTH | LOG_WARNING, "pop3checkpasswd: %s", *constr);
   if (1 == 0) {
	syslog(LOG_AUTH | LOG_WARNING, "POP3 connect from %s@%s [%s]", *remoteinfo, *remotehost, *remoteip);
   } else {
*/
	syslog(LOG_AUTH | LOG_WARNING, "POP3 connect from %s@%s [%s]", remoteinfo, remotehost, remoteip);
/*
   }
*/

   
   if (argc < 2) {
     syslog(LOG_AUTH | LOG_WARNING, "pop3checkpasswd: exit 2 at point 9");
     _exit(2);
   }
   
   uplen = 0;
   for (;;) {
      do {
        r = read(3,up + uplen,sizeof(up) - uplen);
      } while ((r == -1) && (errno == EINTR));
      if (r == -1) _exit(111);
      if (r == 0) break;
      uplen += r;
      if (uplen >= sizeof(up)) _exit(1);
   }
   
   close(3);
   
   i = 0;
   login = up + i;
   while (up[i++])
     if (i == uplen) {
        syslog(LOG_AUTH | LOG_WARNING, "pop3checkpasswd: exit 2 at point 10");
       _exit(2);
     }

/*
	syslog(LOG_AUTH | LOG_WARNING, "POP3 user %s connected from %s@%s [%s]", login, remoteinfo, remotehost, remoteip);
*/

   password = up + i;
   if (i == uplen) {
     syslog(LOG_AUTH | LOG_WARNING, "pop3checkpasswd: exit 2 at point 11");
     _exit(2);
   }
   while (up[i++])
     if (i == uplen) {
       syslog(LOG_AUTH | LOG_WARNING, "pop3checkpasswd: exit 2 at point 12");
       _exit(2);
     }
   
   pw = newgetpwnam(login);
   if (!pw) {
     syslog(LOG_AUTH | LOG_WARNING, "pop3checkpasswd: exit 1 at point 13");
     _exit(1); 
   }
   stored = pw->pw_passwd;
   
   encrypted = crypt(password,stored);
   
   
   if (!*stored || strcmp(encrypted,stored)) {
     syslog(LOG_AUTH | LOG_WARNING, "pop3checkpasswd: FAILED: %s [%s] - password incorrect (%s) from %s@%s [%s]", login, pw->pw_real, password, remoteinfo, remotehost, remoteip);
/*
     syslog(LOG_AUTH | LOG_WARNING, "pop3checkpasswd: FAILED: %s - password incorrect (%s)", pw->pw_real, password);
*/
     sleep(2); /* pgregg sleep to 1)allow syslog to work and 2)hinder pop3 brute force hacks */
     /* pgregg - clear input username/passwd stack */
     for (i = 0;i < sizeof(up);++i) up[i] = 0;
     _exit(1);
   }

   for (i = 0;i < sizeof(up);++i) up[i] = 0;
   
   if (!pw->pw_uid) {
     syslog(LOG_AUTH | LOG_WARNING, "pop3checkpasswd: exit 1 at point 15");
     _exit(1);
   }
   
   /* pgregg - always use 888
   if (setgid(888) == -1) { */
   if (setgid(pw->pw_gid) == -1) {
     syslog(LOG_AUTH | LOG_WARNING, "pop3checkpasswd: exit 1 at point 16"); _exit(1);
   }
   if (setuid(pw->pw_uid) == -1) {
     syslog(LOG_AUTH | LOG_WARNING, "pop3checkpasswd: exit 1 at point 17"); _exit(1);
   }
   if (chdir(pw->pw_dir) == -1) {
     /*
     syslog(LOG_AUTH | LOG_WARNING, "pop3checkpasswd: exit 1 at point 18"); _exit(111);
      */
     syslog(LOG_AUTH | LOG_WARNING, "pop3checkpasswd: FAILED: %s - unable to change to Mail directory: %s", pw->pw_real, pw->pw_dir);_exit(111);
   }

   if (getuid() < 500) {
     syslog(LOG_AUTH | LOG_WARNING, "pop3checkpasswd: DENIED: POP3 user's UID < 500"); _exit(1);
   }

   
   numenv = 0;
   while (environ[numenv]) ++numenv;
   newenv = (char **) malloc((numenv + 4) * sizeof(char *));
   if (!newenv) {
     syslog(LOG_AUTH | LOG_WARNING, "pop3checkpasswd: exit 111 at point 20 (%s)", argv[1]);
     _exit(111);
   }
   for (i = 0;i < numenv;++i) newenv[i] = environ[i];
   newenv[numenv++] = str1e2("USER",pw->pw_name);
   newenv[numenv++] = str1e2("HOME",pw->pw_dir);
   newenv[numenv++] = str1e2("SHELL",pw->pw_shell);
   newenv[numenv] = 0;
   environ = newenv;
   
/*
   syslog(LOG_AUTH | LOG_WARNING, "pop3checkpasswd: POP3user %s : %s logged in.", pw->pw_real, pw->pw_dir);
*/
  syslog(LOG_AUTH | LOG_WARNING, "pop3checkpasswd: POP3 user %s : %s logged in from %s@%s [%s]", pw->pw_real, pw->pw_dir, remoteinfo, remotehost, remoteip);
   execvp(argv[1],argv + 1);
   _exit(111);
   
   return 0;
}

