/* checkpassword authentifier against a PHPNuke user database */
/* Based largely on : */
/* Alternative checkpassword for QPopup by Jedi/Sector One <j@4u.net> */

/* Format of the configuration file is :
 * host_db:port_db:user_db:pass_db:name_db:real_login */

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

#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 <mysql/mysql.h>
#include "md5.h"
#include <syslog.h>

#ifndef LINE_MAX
# define LINE_MAX 2048
#endif

extern int errno;
extern char **environ;

char *str1e2(char *name, char *value)
{
   char *nv;
   nv = malloc(strlen(name) + strlen(value) + 2);
   if (!nv) _exit(111);
   strcpy(nv,name);
   strcat(nv,"=");
   strcat(nv,value);
   return nv;
}

char *md52char(md5_byte_t digest[16])
{
   char ctbl[16]={'0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f'};
   static char str[33];
   int i;
   
   for(i=0;i<16;i++){
      str[2*i]=ctbl[(digest[i]>>4)&0xf];
      str[2*i+1]=ctbl[digest[i]&0xf];
      }
   str[33]='\0';
   return str;
}
   
struct popconfig {
   char pw_passwd[33];
   char *pw_dir;   
   char *pw_name;      
   char *pw_shell;         
   uid_t pw_uid;
   gid_t pw_gid;
   char *db_host;
   unsigned short db_port;
   char *db_user;
   char *db_pass;
   char *db_name;
};

struct popconfig *authenticate(char *login)
{
   static char line[LINE_MAX + 1];
   static struct popconfig pc;
   struct passwd *pw;
   char *reallogin = NULL;
   char *linepnt;
   FILE *fp;
   MYSQL mysql;
   MYSQL_RES *mysql_res;
   MYSQL_ROW mysql_row;
   char query[LINE_MAX];
   static char pw_dir[LINE_MAX];
      
   if ((fp = fopen(CONFIG_FILE, "rt")) == NULL) _exit(2);
   fgets(line, LINE_MAX, fp);
   fclose(fp);
      
   if ((linepnt = strtok(line, ":")) == NULL) _exit(2);
   pc.db_host=linepnt;
   if ((linepnt = strtok(NULL, ":")) == NULL) _exit(2);
   pc.db_port = atoi(linepnt);
   if ((linepnt = strtok(NULL, ":")) == NULL) _exit(2);
   pc.db_user = linepnt;
   if ((linepnt = strtok(NULL, ":")) == NULL) _exit(2);
   pc.db_pass = linepnt;
   if ((linepnt = strtok(NULL, ":")) == NULL) _exit(2);
   pc.db_name = linepnt;
   if ((linepnt = strtok(NULL, ":")) == NULL) _exit(2);
   reallogin = linepnt;
   *(reallogin + strlen(reallogin) - 1) = '\0';

   if (reallogin == NULL) _exit(1);
   pw = getpwnam(reallogin);
   if (!pw) _exit(1); /* XXX: unfortunately getpwnam() hides temporary errors */
      
   pc.pw_name = reallogin;
   strcpy(pw_dir,pw->pw_dir);
   strcat(pw_dir,"/");
   strcat(pw_dir,login);
   strcat(pw_dir,"/");
   pc.pw_dir = pw_dir;
   pc.pw_shell = pw->pw_shell;
   pc.pw_uid = pw->pw_uid;
   pc.pw_gid = pw->pw_gid;

   mysql_init(&mysql);
   if(!mysql_real_connect(&mysql,pc.db_host,pc.db_user,pc.db_pass,pc.db_name,pc.db_port,NULL,0)) _exit(1);
   strcpy(query,"SELECT pass FROM nuke_users WHERE 1 AND uname LIKE '");
   strcat(query,login);
   strcat(query,"'");
   if(mysql_query(&mysql,query)) _exit(1);
   mysql_res=mysql_use_result(&mysql);
   mysql_row=mysql_fetch_row(mysql_res);
   strncpy(pc.pw_passwd,mysql_row[0],32);
   pc.pw_passwd[32]='\0';
   mysql_free_result(mysql_res);
   mysql_close(&mysql);   

   return &pc;
}

int main(int argc, char **argv, char *envp[])
{
   struct popconfig *pc;
   char *login;
   char *password;
   char *stored;
   char *encrypted;

   md5_state_t state;
   md5_byte_t digest[16];
                
   int i,r;
   char **newenv;
   int numenv;
   
   char *tcpremoteip;
   char *tcpremotehost;
   char *tcpremoteinfo;
   
   char up[513];
   int uplen;
   
   if (argc < 2) _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) _exit(2);
   password = up + i;
   if (i == uplen) _exit(2);
   while (up[i++]) if (i == uplen) _exit(2);
   
   md5_init(&state);
   md5_append(&state, (const md5_byte_t *)password, strlen(password));
   md5_finish(&state, digest);
   encrypted=md52char(digest);
   
   pc=authenticate(login);
   stored=pc->pw_passwd;


   if (!*stored || strcasecmp(encrypted,stored)) _exit(1);
   
   if (!pc->pw_uid) _exit(1);
   
   if (setgid(pc->pw_gid) == -1) _exit(1);
   if (setuid(pc->pw_uid) == -1) _exit(1);
   if (chdir(pc->pw_dir) == -1) _exit(111);
   
   numenv = 0;
   while (environ[numenv]) ++numenv;
   newenv = (char **) malloc((numenv + 4) * sizeof(char *));
   if (!newenv) _exit(111);
   for (i = 0;i < numenv;++i) newenv[i] = environ[i];
   newenv[numenv++] = str1e2("USER",pc->pw_name);
   newenv[numenv++] = str1e2("HOME",pc->pw_dir);
   newenv[numenv++] = str1e2("SHELL",pc->pw_shell);
   newenv[numenv] = 0;
   environ = newenv;

   for(i=0;environ[i];i++){
      if(strncmp(environ[i], "TCPREMOTEIP", 11) == 0)
         tcpremoteip=environ[i]+12;
      if(strncmp(environ[i], "TCPREMOTEHOST", 13) == 0)
         tcpremotehost=environ[i]+14;
      if(strncmp(environ[i], "TCPREMOTEINFO", 13) == 0)
         tcpremoteinfo=environ[i]+14;
      }

   syslog(LOG_AUTH | LOG_WARNING, "login %s - %s@%s [%s]",login,tcpremoteinfo,tcpremotehost,tcpremoteip);   

   execvp(argv[1],argv + 1);
   _exit(111);
   
   return 0;
}
