/*
 *	POP Receive Mail Utility.
 */

#define __Program__ "GetMail"
#define __Version__ "1.11"

#include <Defaults.h>
#include <SMTP.h>

#include <proto/dos.h>
#include <proto/exec.h>
#include <proto/intuition.h>

#include <stdio.h>
#include <stdlib.h>
#include <strings.h>
#include <sys/dir.h>
#include <dos/stdio.h>
#include <exec/memory.h>

#define AMITCP_NEW_NAMES
//#define _OPTINLINE
#include <LocalInclude:AmiTCP/errno.h>
#include <netdb.h>

BPTR fhMailBox = NULL;
struct Remember *MemList = NULL;
BPTR Console = NULL, MyConsole = NULL;

#define OPT_TEMPLATE "MAIL=MAILFILE/K,USER/K,PASS=PASSWORD/A,HOME/K,EMAIL=EMAILADDR/K,POPHOST/K,DELETE/S,VERBOSE/S"
#define OPT_MAIL 0
#define OPT_USER 1
#define OPT_PASS 2
#define OPT_HOME 3
#define OPT_EMAILADDR 4
#define OPT_POPHOST 5
#define OPT_DELETE 6
#define OPT_VERBOSE 7
#define OPT_COUNT 8

struct RDArgs *Args;
LONG Options[OPT_COUNT];

STRPTR Mail;                // Mail file name.
STRPTR PopUser;             // Mailbox UserID.
STRPTR Password;            // Mailbox password.
STRPTR HomeDir;             // Home directory.
STRPTR EMailAddr;	          // MailBox to POP.
STRPTR POPHost;             // POP Mail Host.

int Delete;
int Verbose;


STRPTR Domain;
#define USERID_LEN 16
char UserID[USERID_LEN];
#define HOSTNAME_LEN 256
char HostName[HOSTNAME_LEN];

#define CMDBUF_SIZE 512
#define DATBUF_SIZE 1024

int hSocket = SMTP_NO_SOCKET;
struct sockaddr_in INetSocketAddr;


void __regargs Abort(STRPTR ErrorMsg, BOOL ErrorCR, int ErrorRC, LONG Error2);


void __regargs Message(STRPTR Msg, BOOL CR)
{

if (!Console)
   {
   if (Console = Output())
      MyConsole = NULL;
   else
      Console = MyConsole = Open(STDIO, MODE_READWRITE);
   
   Write(Console, "\n", 1);
   Message(Version, TRUE);
   Message("Written by Martin V Lanza.\n", TRUE);
   }

if (Msg) Write(Console, Msg, strlen(Msg));
if (CR) Write(Console, "\n", 1);

return;
}


void __regargs VerboseMsg(STRPTR Func, STRPTR Name, STRPTR Value)
{
BOOL DoLF;
static char Msg[DATBUF_SIZE * 2];

if (!Verbose) return;

sprintf(Msg, "%-16s  ", Func);

if (Name) strcat(Msg, Name);
if (Value) strcat(Msg, Value);

if (Msg[strlen(Msg)-1] == '\n')
   DoLF = FALSE;
else
   DoLF = TRUE; 

Message(Msg, DoLF);

return;
}


BOOL __regargs RecvDat(STRPTR DatBuf, int *DatLen)
{
static char Func[] = "RecvDat";

errno = 0;
if (hSocket == SMTP_NO_SOCKET) return(FALSE);
*DatLen = Recv(hSocket, DatBuf, DATBUF_SIZE - 1, 0);

if (*DatLen)
   DatBuf[*DatLen] = '\0';
else
   {
   DatBuf[0] = '\0';
   return(FALSE);
   }

VerboseMsg(Func, "Recv() ", DatBuf);

return(TRUE);
}


BOOL __regargs SendDat(STRPTR SendData)
{
static char Func[] = "SendDat";

if (hSocket == SMTP_NO_SOCKET) return(FALSE);
if (!SendData) return(TRUE);

VerboseMsg(Func, "Send() ", SendData);
if (Send(hSocket, SendData, strlen(SendData), 0) != -1) return(TRUE);
Abort("Socket message send failure.", TRUE, RETURN_FAIL, Errno());

return(FALSE);
}


int __regargs SendCmd(STRPTR CmdText, STRPTR ParmText)
{
static int StrLen;
static char StrBuf[DATBUF_SIZE];

if (hSocket == SMTP_NO_SOCKET) return(0);

if (!CmdText || !*CmdText)
   *StrBuf = '\0';
else
   if (!ParmText || !*ParmText)
      sprintf(StrBuf, "%s\r\n", CmdText);
   else
      sprintf(StrBuf, "%s %s\r\n", CmdText, ParmText);

if (!SendDat(StrBuf)) return(0);
if (!RecvDat(StrBuf, &StrLen)) return(0);

if (StrBuf[0] == '-')
   if (StrBuf[1] == 'E')
      if (StrBuf[2] == 'R')
         if (StrBuf[3] == 'R')
            {
            char ErrorMsg[256];
            sprintf(ErrorMsg, "Bad %s command responce: %s", CmdText, StrBuf);
            Abort(ErrorMsg, TRUE, RETURN_FAIL, ERROR_BAD_NUMBER);
            }

// How many messages ?
if (CmdText)
   if (CmdText[0] == 'S')
      if (CmdText[1] == 'T')
         if (CmdText[2] == 'A')
            if (CmdText[3] == 'T')
               return(atol(StrBuf + 4));

// Greeting message may not fit in the buffer :-(
// Keep reading untill we get it all.
if (!CmdText)
   while (StrBuf[StrLen - 1] != '\n')
      if (!RecvDat(StrBuf, &StrLen)) break;

return(0);
}


void __regargs SocketDisconnect(void)
{
static char Func[] = "SocketDisconnect";

if (hSocket != SMTP_NO_SOCKET)
   {
   SendCmd("QUIT", NULL);
   Shutdown(hSocket, 2);
   CloseSocket(hSocket);
   hSocket = SMTP_NO_SOCKET;
   VerboseMsg(Func, "Disconnected from POP Host ", POPHost);
   }

return;
}


void __regargs CleanUp(void)
{
static char Func[] = "CleanUp";

VerboseMsg(Func, "Cleanup in progress.", NULL);

if (fhMailBox) Close(fhMailBox);

if (Args) FreeArgs(Args);

if (MemList) FreeRemember(&MemList, TRUE);

if (MyConsole)
   {
   Message("\nPress RETURN to terminate program: ", FALSE);
   FGetC(MyConsole);
   Close(MyConsole);
   }
   
return;
}


void __regargs Abort(STRPTR ErrorMsg, BOOL ErrorCR, int ErrorRC, LONG Error2)
{
static BOOL Aborting;

if (Aborting) return;
Aborting = TRUE;

if (ErrorMsg)
   {
   Message(__Program__ ": ", FALSE);
   Message(ErrorMsg, ErrorCR);
   }

SocketDisconnect();

CleanUp();

if (Error2) ((struct Process *) FindTask(NULL))->pr_Result2 = Error2;

exit(ErrorRC);
}


void __regargs SocketConnect(void)
{
struct hostent *HostAddr;
static char Func[] = "SocketConnect";

HostAddr = GetHostByName(POPHost);
if (!HostAddr)
   {
   VerboseMsg(Func, "GetHostByName() ", POPHost);
   if (h_errno == TRY_AGAIN)
      Abort("Unable to locate the POP Host, try again later.", TRUE, RETURN_FAIL, h_errno);
   else
      Abort("The POP Host is invalid.", TRUE, RETURN_FAIL, h_errno);
   }

INetSocketAddr.sin_len = sizeof(INetSocketAddr);
INetSocketAddr.sin_family = AF_INET;
INetSocketAddr.sin_port = 110; // POP
INetSocketAddr.sin_addr.s_addr = 0;

memcpy(&INetSocketAddr.sin_addr, HostAddr->h_addr, HostAddr->h_length);

hSocket = Socket(HostAddr->h_addrtype, SOCK_STREAM, 0);
if (hSocket == -1)
   {
   VerboseMsg(Func, "Socket() Failed!", NULL);
   Abort("Socket allocation failed.", TRUE, RETURN_FAIL, Errno());
   }

if (Connect(hSocket, (struct sockaddr *) &INetSocketAddr, sizeof(INetSocketAddr)) == -1)
   {
   VerboseMsg(Func, "Connect() Failed!", NULL);
   Abort("Socket connection failed.", TRUE, RETURN_FAIL, Errno());
   }

VerboseMsg(Func, "Connected to POP Host ", POPHost);

SendCmd(NULL, NULL);       // Get the greeting message.
SendCmd("USER", PopUser);  // Send UserID.
SendCmd("PASS", Password); // Send Password.
   
return;
}


BOOL __regargs GetEnvVar(STRPTR Name, STRPTR Buf, long Len)
{
static int BufLen, EnvLen;

BufLen = GetVar(Name, Buf, Len - 1, GVF_GLOBAL_ONLY);
EnvLen = IoErr();
SetIoErr(NULL);

if (BufLen <= NULL) return(FALSE);
if (BufLen != EnvLen) return(FALSE);

return(TRUE);
}


BOOL __regargs GetEMailAddr(void)
{
int Len;
BPTR fhFile;
char strName[MAXNAMLEN + 1];
char Func[] = "GetEMailAddr";

strcpy(strName, HomeDir);
AddPart(strName, ".EMailAddr", MAXNAMLEN);

if (!(fhFile = Open(strName, MODE_OLDFILE)))
   {
   VerboseMsg(Func, strName, " could not be opened.");
   return(FALSE);
   }

VerboseMsg(Func, strName, " successfully opened.");
FGets(fhFile, EMailAddr, USERID_LEN + HOSTNAME_LEN);

if (IoErr())
   {
   VerboseMsg(Func, "Read failure on ", strName);
   Close(fhFile);
   return(FALSE);
   }

Close(fhFile);

Len = strlen(EMailAddr);
if (!Len) return(FALSE);

while (--Len)
   {
   if (EMailAddr[Len] != ' ')
      if (EMailAddr[Len] != '\r')
         if (EMailAddr[Len] != '\n')
            break;
            
   EMailAddr[Len] = '\0';
   }

return(TRUE);
}


void __regargs InitProg(void)
{
char Func[] = "InitProg";

memset(Options, NULL, sizeof(LONG) * OPT_COUNT);
Args = ReadArgs(OPT_TEMPLATE, Options, NULL);
if (!Args)
   {
   PrintFault(IoErr(), NULL);
	Abort("Argument Template is " OPT_TEMPLATE, TRUE, RETURN_FAIL, ERROR_BAD_TEMPLATE);
   }

Mail = (STRPTR) Options[OPT_MAIL];
PopUser = (STRPTR) Options[OPT_USER];
Password = (STRPTR) Options[OPT_PASS];
HomeDir = (STRPTR) Options[OPT_HOME];
EMailAddr = (STRPTR) Options[OPT_EMAILADDR];
POPHost = (STRPTR) Options[OPT_POPHOST];
Delete = Options[OPT_DELETE];
Verbose = Options[OPT_VERBOSE];

if (!GetEnvVar("USER", UserID, USERID_LEN))
   Abort("ENV variable 'USER' not found.", TRUE, RETURN_FAIL, NULL);
 
if (!GetEnvVar("HOSTNAME", HostName, HOSTNAME_LEN))
   Abort("ENV variable 'HOSTNAME' not found.", TRUE, RETURN_FAIL, NULL);

Domain = HostName;
while (*Domain && *Domain != '.') Domain++;
if (*Domain) Domain++;

if (!Mail)
   {
   Mail = AllocRemember(&MemList, MAXNAMLEN + 1, MEMF_ANY | MEMF_CLEAR);
   if (!Mail) Abort("Not enough free memory.", TRUE, RETURN_FAIL, ERROR_NO_FREE_STORE);
   strcpy(Mail, "MailBox.InWard");
   }

if (!HomeDir)
   {
   HomeDir = AllocRemember(&MemList, MAXNAMLEN + 1, MEMF_ANY | MEMF_CLEAR);
   if (!HomeDir) Abort("Not enough free memory.", TRUE, RETURN_FAIL, ERROR_NO_FREE_STORE);
   if (!GetEnvVar("HOME", HomeDir, MAXNAMLEN))
      Abort("'HOME' not found.", TRUE, RETURN_FAIL, NULL);
   }

if (!PopUser) PopUser = UserID;
      
if (stricmp(PopUser, UserID) != 0)
   {
   struct passwd *UserList;
   if ((UserList = getpwnam(PopUser)))
      strcpy(HomeDir, UserList->pw_dir);
   }

if (!POPHost)
   {
   POPHost = AllocRemember(&MemList, HOSTNAME_LEN, MEMF_ANY | MEMF_CLEAR);
   if (!POPHost) Abort("Not enough free memory.", TRUE, RETURN_FAIL, ERROR_NO_FREE_STORE);
   if (!GetEnvVar("POPHOST", POPHost, HOSTNAME_LEN))
      {
      struct hostent *HostAddr;
      if (HostAddr = GetHostByName("POPHOST"))
         strcpy(POPHost, HostAddr->h_name);
      else
         {
         strcpy(POPHost, Domain);
         VerboseMsg(Func, "POPHost name not provided, assumed ", POPHost);
         }
      }
   }
      
if (!EMailAddr)
   {
   EMailAddr = AllocRemember(&MemList, USERID_LEN + HOSTNAME_LEN, MEMF_ANY | MEMF_CLEAR);
   if (!EMailAddr) Abort("Not enough free memory.", TRUE, RETURN_FAIL, ERROR_NO_FREE_STORE);
   if (!GetEnvVar("EMAILADDR", EMailAddr, USERID_LEN + HOSTNAME_LEN))
      if (!GetEMailAddr())
         {
         sprintf(EMailAddr, "%s@%s", UserID, POPHost);
         VerboseMsg(Func, "EMailAddr not provided, assumed ", EMailAddr);
         }
   }

if (Verbose)
   {
   VerboseMsg(Func, "User=", PopUser);
   VerboseMsg(Func, "Password=", Password);
   VerboseMsg(Func, "Home=", HomeDir);
   VerboseMsg(Func, "EMailAddr=", EMailAddr);
   VerboseMsg(Func, "POPHost=", POPHost);
   Delete ? VerboseMsg(Func, "Delete=", "TRUE") : VerboseMsg(Func, "Delete=", "FALSE");
   Verbose ? VerboseMsg(Func, "Verbose=", "TRUE") : VerboseMsg(Func, "Verbose=", "FALSE");
   }

return;
}


void __regargs OpenMailBox(void)
{
char strName[MAXNAMLEN + 1];
char strError[MAXNAMLEN + 32];
char Func[] = "OpenMailBox";

strcpy(strName, HomeDir);
AddPart(strName, Mail, MAXNAMLEN);
fhMailBox = Open(strName, MODE_READWRITE);

if (fhMailBox)
   {
   VerboseMsg(Func, strName, " successfully opened.");
   if (!SetVBuf(fhMailBox, NULL, BUF_FULL, 4096))
      VerboseMsg(Func, strName, " will be buffered.");
   }
else
   {
   sprintf(strError, "Unable to open mail file %s !", strName);
   Abort(strError, TRUE, RETURN_FAIL, IoErr());
   }

if (Seek(fhMailBox, 0, OFFSET_END) == DOSTRUE)
   Abort("Seek to end of mail file failed.", TRUE, RETURN_FAIL, IoErr());

return;
}


int __regargs POPMail(void)
{
int Count;
int MsgInx;
BOOL MsgEnd;
char MsgNum[6];
int StrLen;
char Func[] = "POPMail";
STRPTR StrPtr, LinePtr;
char StrBuf[DATBUF_SIZE];
char LineBuf[DATBUF_SIZE];
char StrDay[LEN_DATSTRING];
char StrDate[LEN_DATSTRING];
char StrTime[LEN_DATSTRING];
struct DateTime TheDateTime;

Count = SendCmd("STAT", NULL);
if (!Count) return(RETURN_OK);

OpenMailBox();

MsgInx = 1;
while(MsgInx <= Count)
   {
   if (Verbose)
      {
      sprintf(StrBuf, "Reading message %ld of %ld.", MsgInx, Count);
      VerboseMsg(Func, StrBuf, NULL);
      }
   
   // Day, Date & Time.
   DateStamp(&TheDateTime.dat_Stamp);
   TheDateTime.dat_Format = FORMAT_DOS;
   TheDateTime.dat_Flags = NULL;
   TheDateTime.dat_StrDay = StrDay;
   TheDateTime.dat_StrDate = StrDate;
   TheDateTime.dat_StrTime = StrTime;
   DateToStr(&TheDateTime);

   sprintf(StrBuf, "From %s on %s, the %s at %s\n", EMailAddr, StrDay, StrDate, StrTime);
   if (FPuts(fhMailBox, StrBuf) == DOSTRUE)
      Abort("Write Error on mail file.", TRUE, RETURN_FAIL, IoErr());

   sprintf(MsgNum, "%ld", MsgInx);

   MsgEnd = FALSE;
   LinePtr = LineBuf;
   SendCmd("RETR", MsgNum);
   while(RecvDat(StrBuf, &StrLen))
      {
      StrPtr = StrBuf;

      while (*StrPtr)
         {
         if (*StrPtr != '\r') *(LinePtr++) = *StrPtr;

         if (*(StrPtr++) != '\n') continue;

         *LinePtr = '\0';
         LinePtr = LineBuf;
          
         if (LineBuf[0] == '.')
            {
            if (LineBuf[1] == '\n')
               {
               MsgEnd = TRUE;
               break;
               }
                 
            if (LineBuf[1] == '.')
               if (LineBuf[2] == '\n')
                  LinePtr = &LineBuf[1];
            }
                           
         VerboseMsg(Func, "Writing to MailFile: ", LinePtr);
         if (FPuts(fhMailBox, LinePtr) == DOSTRUE)
            Abort("Write Error on mail file.", TRUE, RETURN_FAIL, IoErr());
                  
         LinePtr = LineBuf;
         }
      
      if (MsgEnd) break;
      }

   if (Delete)
      {
      VerboseMsg(Func, "Deleting message ", MsgNum);
      SendCmd("DELE", MsgNum);
      }
   
   MsgInx++;
   }
 
VerboseMsg(Func, "Closing mail file.", NULL);
Close(fhMailBox);
fhMailBox = NULL;

return(RETURN_WARN);
}


int __regargs main(int argc, char **argv)
{
int RC;

if (!argc) exit(RETURN_FAIL);

InitProg();

SocketConnect();

RC = POPMail();

SocketDisconnect();

CleanUp();
                 
return(RC);
}
