/*

  DBOX Module
  Copyright (C) 1994-2000 Daniel Kroening <kroening@handshake.de>

  Purpose: 

*/

#include <stdio.h>
#include <ctype.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <time.h>
#include <fcntl.h>
#include <signal.h>

#include <string>

#include <tools.h>
#include <folder.h>
#include <stringtools.h>
#include <logf.h>
#include <dboxversion.h>
#include <messageid.h>

#ifndef DBOX_STANDALONE
#include <md5.h>
#endif

#include "pop.h"
#include "dboxpop3.h"

void pop3alarm(int signal)
 {
  cout << "-ERR logout, timeout\r\n";
  exit(0);
 }

class dboxpop3t:public dboxpopt
 {
 public:
  std::string command, args;
  std::basic_string<dword> messages;
  std::basic_string<bool> deleted;
  dword lastmsg;
  std::string timestamp;

  void dolist(dword arg, bool only_parameter, bool uidl);
  bool logged_in(); 
  void dopop3();
  void parse();
  void retr_top(dword nr, const char *toparg);

  dboxpop3t();
 };

dboxpop3t::dboxpop3t():dboxpopt()
 {
  protocol="pop3";
  lastmsg=0;
 }

void dboxpop3t::parse()
 {
  const char *tptr=commandline.c_str();

  while(*tptr==' ' || *tptr=='\t') tptr++;

  command="";
  while(*tptr!=0 && *tptr!=' ' && *tptr!='\t')
    command+=*(tptr++);

  if(*tptr==' ' || *tptr=='\t')
   {
    while(*tptr==' ' || *tptr=='\t') tptr++;
    args=tptr;
   }
  else
    args="";
 }

void dboxpop3t::retr_top(dword nr, const char *toparg)
 {
  dword lines;

  //
  // get header and, if necessary, body
  //

  if(toparg==NULL)
    lines=0xffffffff;
  else
    lines=atol(toparg);

  if(toparg==NULL)
    cout << "+OK message follows\r\n";
  else
    cout << "+OK top of message follows\r\n";

  if(lines!=0)
    get_body(nr);
  else
    get_header(nr);

  // output header

  cout.write(rfcm.data, rfcm.headerlen);

  // output body

  if(lines!=0)
   {
    const char *ptr=rfcm.data+rfcm.headerlen;
    char data=0, lastchar=0;
    byte escapestatus=1; // 0=nix, 1=Zeilenanfang, 2=Zeilenanfang+'.'

    while(lines!=0 && ptr<rfcm.endofmail)
     {
      lastchar=data;

      data=*(ptr++);

      switch(escapestatus)
       {
       case 0:
        if(data=='\r' || data=='\n')
          escapestatus=1;
        break;

       case 1:
        if(data=='.')
         {
          escapestatus=2;
          cout << '.'; // Hm. Netscape macht aus ... => ..
         }
        else if(data=='\r' || data=='\n')
          escapestatus=1;
        else
          escapestatus=0;
        break;

       case 2:
        if(data=='\r' || data=='\n')
          escapestatus=1;
        else
          escapestatus=0;
        break;
       }

      if(data=='\n' && lastchar!='\r')
        cout << '\r';

      cout << data;

      if(data=='\n') lines--;
     }
  
    if(data!='\n')
      cout << "\r\n";
   }  

  cout << ".\r\n";
 }

void dboxpop3t::dolist(dword arg, bool only_parameter, bool uidl)
 {
  if(only_parameter)
   {
    if(deleted[arg-1])
     {
      cout << "-ERR message is deleted\r\n";
      return;
     }

    cout << "+OK ";
   }
  else
   {
    if(deleted[arg-1])
      return;
   }

  dword nr=messages[arg-1];
  get_folderindex(nr);

  cout << arg << " ";

  if(uidl)
   {
    cout << fi.id << "."
         << str2crc(fi.mid) << "."
         << fi.received;
   }
  else
   {
    if(!fi.flags.binary)
      cout << (fi.len+fi.headerlen);
    else
      cout << (fi.headerlen+fi.len*1377/1000);
   }

  cout << "\r\n";
 }

bool dboxpop3t::logged_in()
 {
  if(open_mail(up.name, ""))
   {
    cout << "-ERR internal error: failed to open mail folder\r\n";
    return TRUE;
   }

  if(index>=0 && header>=0)
   {
    dword idx=0;

    while(read(index, &fi, sizeof fi)==sizeof fi)
     {
      if(fi.check_version())
       {
        logf.printf("dboxpopt::login", "mailbox %s corrupted",
          up.name);

        cout << "-ERR sorry, mailbox is corrupted\r\n";
        abort();
       }

      if(!fi.flags.deleted)
        messages.append(1, idx);

      idx++;
     }

    deleted.append(messages.size(), FALSE);
   }

  return FALSE;
 }

#ifndef DBOX_STANDALONE
std::string correct_digest(const std::string &timestamp, const std::string &password)
 {
  std::string hash_input=timestamp+password;

  unsigned char md5hash[16];
  md5_calc(md5hash, (unsigned char *)hash_input.c_str(), hash_input.size());

  std::string digest;

  for(int i=0; i<16; i++)
   {
    char hex_s[4];
    snprintf(hex_s, 3, "%02x", (unsigned char)md5hash[i]);
    digest+=hex_s;
   }

  return digest;
 }
#endif

void dboxpop3t::dopop3()
 {
  signal(SIGALRM, pop3alarm);

  timestamp="<"+gen_message_id("pop3")+">";
  cout << "+OK DBOX POP3 Server " DBOXVERSIONNR " ready";

  #ifndef DBOX_STANDALONE
  cout << " " << timestamp;
  #endif

  cout << "\r\n";

  getpeer();

  while(TRUE)
   {
    if(getcommand()) return;
    parse();

    if(stringcasecmp(command, "QUIT")==0)
     {
      if(fp.lock())
        cout << "-ERR sorry, failed to get a lock\r\n";
      else
       {
        bool crunch=FALSE;

        for(unsigned i=0; i<messages.size(); i++)
          if(deleted[i])
           {
            dword nr=messages[i];
            lseek(index, nr*sizeof(fi), SEEK_SET);
            read(index, &fi, sizeof fi);

            if(!fi.flags.deleted)
             {
              fi.flags.deleted=TRUE;
              lseek(index, nr*sizeof(fi), SEEK_SET);
              write(index, &fi, sizeof fi);
              crunch=TRUE;
             }
           }

        fp.unlock();

        cout << "+OK " << config.boxname
             << " DBOX POP Server (Version vom " DBOXVERSION ") shutdown.\r\n";

        #ifndef DBOX_STANDALONE // TODO
        if(crunch)
          docrunch(up.name);
        #endif

        return;
       }
     }
    else if(stringcasecmp(command, "USER")==0 && !transfer)
     {
      up.setname(args);
      if(up.name[0]==0)
        cout << "-ERR invalid user name\r\n";
      else
       {
        dnstring(up.name);
        cout << "+OK please send PASS command\r\n";
        #ifndef DBOX_STANDALONE
        snprintf(userlogfile, DIRLEN, "%slogfile", up.getpath());
        #endif
       }
     }
    else if(stringcasecmp(command, "PASS")==0 && !transfer)
     {
      if(up.name[0]==0)
       {
        cout << "-ERR please provide an user name first\r\n";
       }
      else
       {
        if(login(args))
         {
          cout << "-ERR invalid password, bye\r\n";
          return;
         }

        if(logged_in())
          return;

	movemail();
        cout << "+OK Welcome\r\n";
       }
     }
    else if(stringcasecmp(command, "STAT")==0 && transfer)
     {
      if(index<0)
        cout << "+OK 0 0\r\n";
      else
       {
        dword total_size=0; // estimated

        for(dword arg=1; arg<=messages.size(); arg++)
         {
          if(!deleted[arg-1])
           {
            get_folderindex(messages[arg-1]);

            if(!fi.flags.binary)
              total_size+=fi.len+fi.headerlen;
            else
              total_size+=fi.headerlen+fi.len*1377/1000; // uuencode
           }
         }

        cout << "+OK " << messages.size() << " " 
                       << total_size << "\r\n";
       }
     }
    else if(stringcasecmp(command, "LIST")==0 && transfer)
     {
      if(args=="")
       {
        cout << "+OK " << messages.size() << " messages\r\n";

        for(dword arg=1; arg<=messages.size(); arg++)
          dolist(arg, FALSE, FALSE);

        cout << ".\r\n";
       }
      else
       {
        dword arg=atoi(args.c_str());
        if(arg==0 || arg>messages.size())
          cout << "-ERR invalid message\r\n";
        else
          dolist(arg, TRUE, FALSE);
       }
     }
    else if(stringcasecmp(command, "UIDL")==0 && transfer)
     {
      if(args=="")
       {
        cout << "+OK " << messages.size() << " messages\r\n";

        for(dword arg=1; arg<=messages.size(); arg++)
          dolist(arg, FALSE, TRUE);

        cout << ".\r\n";
       }
      else
       {
        dword arg=atoi(args.c_str());
        if(arg==0 || arg>messages.size())
          cout << "-ERR invalid message\r\n";
        else
          dolist(arg, TRUE, TRUE);
       }
     }
    else if((stringcasecmp(command, "RETR")==0 ||
            stringcasecmp(command, "TOP")==0) && transfer)
     {
      const char *toparg=(char *)NULL;

      if(stringcasecmp(command, "TOP")==0)
       {
        toparg=strrchr(args.c_str(), ' ');
        if(toparg!=NULL) toparg++;
       }

      if(args=="")
        cout << "-ERR message number required!\r\n";
      else
       {
        dword arg, nr;

        arg=atoi(args.c_str());
        if(arg==0 || arg>messages.size())
          cout << "-ERR invalid message\r\n";
        else if(deleted[arg-1])
          cout << "-ERR message is deleted\r\n";
        else
         {
          if(arg>lastmsg) lastmsg=arg;
          nr=messages[arg-1];
          retr_top(nr, toparg);
         }
       }
     }
    else if(stringcasecmp(command, "DELE")==0 && transfer)
     {
      if(args=="")
        cout << "-ERR message number required!\r\n";
      else
       {
        dword arg=atoi(args.c_str());

        if(arg==0 || arg>messages.size())
          cout << "-ERR invalid message\r\n";
        else if(deleted[arg-1])
          cout << "-ERR message is deleted\r\n";
        else
         {
          if(arg>lastmsg) lastmsg=arg;
          deleted[arg-1]=TRUE;
          cout << "+OK message deleted.\r\n";
         }
       }
     }
    else if(stringcasecmp(command, "NOOP")==0 && transfer)
     {
      if(args!="")
        cout << "-ERR Uh?\r\n";
      else
        cout << "+OK Gaehn.\r\n";
     }
    else if(stringcasecmp(command, "APOP")==0)
     {
      #ifdef DBOX_STANDALONE
      cout << "-ERR sorry, no APOP supported\r\n";
      #else
      char mailbox[TEXTLEN], *tptr;
      strmaxcpy(mailbox, args.c_str(), TEXTLEN-1);
      tptr=strchr(mailbox, ' ');
      if(tptr==NULL)
        cout << "-ERR invalid paramters\r\n";
      else
       {
        *tptr=0;
        const char *digest=strchr(args.c_str(), ' ');
        if(digest!=NULL) digest++; else digest="";

        dnstring(mailbox);
        up.setname(mailbox);

        if(up.read())
         {
          logf.printf("login", "Invalid user name %s from %s", up.name, peer);
          cout << "-ERR invalid user name\r\n";
         }
        else
         {
          snprintf(userlogfile, DIRLEN, "%slogfile", up.getpath());

          const char *cryptedpassword=up|"cryptedpassword";
          if(cryptedpassword[0]==0) cryptedpassword=up|"cryptedpasswort";
          if(cryptedpassword[0]==0 || strcmp(cryptedpassword, "locked")==0)
           {
            logf.printf("login", "apop: account %s locked from %s", up.name, peer);
            cout << "-ERR account locked\r\n";
           }
          else
           {
            char password[TEXTLEN];
            uncryptpassword(cryptedpassword, password);

            if(correct_digest(timestamp, password)==digest)
             {
              logf.printf("login", "%s: correct APOP digest from %s", up.name, peer);

              if(login2())
                cout << "-ERR permission denied\r\n";
              else if(logged_in())
                return;
              else
                cout << "+OK Welcome\r\n";
             }
            else
             {
              logf.fileprintf(userlogfile, "login", 
                "wrong %s APOP digest from %s", protocol.c_str(), peer);
              logf.printf("login", "%s: wrong APOP digest from %s", up.name, peer);
              cout << "-ERR invalid password\r\n";
             }
           }
         }
       }
      #endif
     }
    else if(stringcasecmp(command, "LAST")==0 && transfer)
     {
      if(args!="")
        cout << "-ERR Uh?\r\n";
      else
        cout << "+OK " << lastmsg << "\r\n";
     }
    else if(stringcasecmp(command, "RSET")==0 && transfer)
     {
      if(args!="")
        cout << "-ERR Uh?\r\n";
      else
       {
        for(unsigned i=0; i<deleted.size(); i++)
          deleted[i]=FALSE;

        cout << "+OK\r\n";
       }
     }
    else
      cout << "-ERR Invalid command: \"" << command << "\"\r\n";

    cout.flush();
   }
 }

void dopop3()
 {
  dboxpop3t dboxpop3;

  dboxpop3.dopop3();

  #if 0
  // APOP Example from rfc1939
  cout << 
   correct_digest("<1896.697170952@dbc.mtview.ca.us>", "tanstaaf") 
   << "\n";
  #endif
 }


syntax highlighted by Code2HTML, v. 0.9.1