/*
 *     $Filename: PGPSendMail.c $
 *     $Revision: 1.28 $
 *     $Date: 1993/10/12 13:02:36 $
 *
 *     This is a replacement for the UUCP command >sendmail<.
 *     PGPSendMail reads the incoming message from standard input,
 *     checks if a key for the receipients is availabe and if, sends
 *     the encrypted messagt to the original sendmail for handling.
 *
 *     © Copyright 1993 Peter Simons, Germany
 *       All Rights Reserved
 *
 *     $Id: PGPSendMail.c,v 1.28 1993/10/12 13:02:36 simons Exp simons $
 *
 * ------------------------------ log history ----------------------------
 * $Log: PGPSendMail.c,v $
 * Revision 1.28  1993/10/12  13:02:36  simons
 * The confirmation of encryption can now be controlled by the
 * ConfirmEncryption keyword in the uulib:config file.
 *
 * Revision 1.27  1993/10/06  12:42:50  simons
 * The name of the program handling the mail after encryption is now
 * determined using the config-file or the appropiate env/local variable.
 *
 * Revision 1.25  1993/09/25  02:37:43  simons
 * PGPSendMail terminates immediatly, when OS 2.04 is not available.
 *
 * Revision 1.24  1993/09/24  22:53:41  simons
 * Moved TruncFile() to system.c.
 * Added call to ConfirmEncryption(), to let the user decide if the mail
 * should be encrypted or not.
 *
 * Revision 1.23  1993/09/14  22:06:02  simons
 * Thanks to my own cres.o and _main() routine, the commandline is now
 * provided to sendmail correctly.
 *
 * Revision 1.22  1993/09/14  17:28:15  simons
 * Changed code to work with the new startup-main().
 *
 * Revision 1.21  1993/09/13  17:29:49  simons
 * Minor optimizations in logfile handling.
 *
 * Revision 1.20  1993/09/08  23:08:16  simons
 * The log message "Not all receipients have..." is not debug level 1.
 *
 * Revision 1.19  1993/09/08  23:03:48  simons
 * FindKey() now calls a self-written routine stristr() instead of
 * strstr(), because the latter routine compared the strings case-
 * significant.
 *
 * Revision 1.18  1993/09/04  15:32:26  simons
 * Corrected a few typing mistakes.
 *
 * Revision 1.17  1993/09/03  14:35:08  simons
 * Added configfile support. Used keywords are UULib, Debug and LockDir.
 * LogLevel is controlled by the configfile. Default is 0.
 * Added various debug-messages.
 *
 * Revision 1.16  1993/09/03  02:05:51  simons
 * Changed style of log-messages to fit better with the uucp package.
 * Added a few log-messages.
 * Set the debug value to 255 per default. Future versions might use an
 * entry in a configfile.
 *
 * Revision 1.15  1993/09/03  01:33:35  simons
 * Added logfile support.
 * Straightened code concerning Amiga specific includes.
 *
 * Revision 1.14  1993/08/28  18:45:46  simons
 * Added alias-expansion support.
 *
 * Revision 1.13  1993/08/28  17:59:56  simons
 * PGPSendMail does not support the 'Bcc:'-line.
 *
 * Revision 1.12  1993/08/23  15:48:02  simons
 * Removed bug in ArrayToLine(): The contents of argvline had been trashed!
 * Removed some debugging output, as the routines are save now.
 *
 * Revision 1.11  1993/08/23  15:21:19  simons
 * Added a kludge to let PGPSendMail understand PGP-key-aliases. FindKey()
 * now searches for "pub " or "pub ". This may break with other versions
 * of PGP or on other systems. However, it works fine. :-)
 * Reduced the required amount of stack from 8k+ to less than 1200 byte.
 *
 * Revision 1.10  1993/08/23  13:47:36  simons
 * PGPSendMail now perfectly understands multiple line headers.
 *
 * Revision 1.9  1993/08/23  13:12:32  simons
 * Moved "PGPKey.lst"-file to "UULIB:".
 * Known bugs: - PGP aliases are not recognized! If someone has generated
 *               his key with another address than usenet, and created
 *               an alias later, he will not be found in the keyfile,
 *               because FindKey() only test lines beginning with "pub".
 *
 * Revision 1.8  1993/08/23  13:05:01  simons
 * PGPSendMail now checks wether a public key for each receipient is
 * available and sends the message unencrypted if not.
 * The commandline is now preserved, even under error-condition.
 * Known Bugs: - If the "To:" and "Cc:" lines are split over several
 *               lines, SplitAddresses() won't recognize them correctly.
 *
 * Revision 1.7  1993/08/22  17:53:02  simons
 * Known bugs: - If sendmail is called under an error-condition, the
 *               original commandline is lost. Will be fixed soon.
 *
 * Revision 1.6  1993/08/22  17:48:33  simons
 * Changed a few situations from fatal to non-fatal errors.
 *
 * Revision 1.5  1993/08/22  17:46:12  simons
 * Error conditions are handled much better. If anything goes wrong, the
 * original mail is sent to sendmail instead of the failed encrypted
 * version.
 *
 * Revision 1.4  1993/08/22  17:38:34  simons
 * PGPSendMail now calls sendmail with exactly the same parameters he
 * has been called with.
 * Known bugs: - currently does not correct the "Lines:" keyword
 *
 * Revision 1.3  1993/08/22  17:12:11  simons
 * PGPSendMail works basically.
 * Known bugs: - Aliases aren't expanded before PGPSendMail tries to
 *               determine the receipient. Hence the addresses won't be
 *               found in the keyring.
 *             - When sending (and encrypting) mail for multiple
 *               receipients, PGP does not return any warning if one of
 *               the receipients has no public key available, is just
 *               skips him. Though you should only use PGPSendmail with
 *               receipients having a key available. I'll try to modify
 *
 * Revision 1.2  1993/08/21  19:32:47  simons
 * The UUCP-message stuff is already working. I wrote a set of routines,
 * that seperate the receipients of a message out of the header and
 * skip anything but the address of them.
 * Using the address for identifying the user seems to be more reliable
 * than trying to get the realname.
 * At the moment I have to use unbuffered IO, because I have to reset
 * the position in the readfile several times, what is not supported
 * with my routines yet.
 *
 * Revision 1.1  1993/08/21  19:28:43  simons
 * Initial revision
 */



/**************************************************************************
 *                                                                        *
 * Sektion: Macros, Definitions, Includes, Structures                     *
 *                                                                        *
 **************************************************************************/

/************************************* Includes ***********/
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <string.h>
#include <ctype.h>

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

#include "expandalias.h"
#include "SC:Sources/uucp.lib/include/config.h"
#include "SC:Sources/uucp.lib/include/log.h"

/************************************* Defines ************/
#define PGP             "PGP -fesa <%s >%s %s +batchmode"
#define START_SENDMAIL  "%s <%s %s"
#define PGPKEYLIST      "PGPKeys.lst"

#define HEADER_ID       "X-AutoEncrypt: PGPSendMail 1.x\n"
#define BODY_ID         "Automatically encrypted message-body follows:\n\n"

#define MAX_RECEIPIENTS 256             /* max. number of receipients */
#define LINEBUFFER      1024            /* max. line length */
#define IOBUFSIZE       BUFSIZ*10       /* size of internal IO buffer */

#define CONFIRMENCRYPTION       "ConfirmEncryption"

#define my_ulog(level, name, msg) OpenLog(); ulog(level, name, msg); CloseLog();
#define my_ulog2(level, msg) OpenLog(); ulog(level, msg); CloseLog();

/************************************* Prototypes *********/
void NonFatalError(char *);
void error(char *, int);
int getline(FILE *,char []);
int CopyUntilEOF(FILE *, FILE *);
char **FindReceipients(FILE *);
char *FindHeaderEntry(FILE *, char *);
void ParseAddressLines(char **receipients);
void SplitAddressLine(char *line, char *receipients[]);
int TruncFile(char *, int);
char *ArrayToLine(char *array[]);
int FindKey(char *key, char *keylist);
int CheckKeyAvailability(char *receipients[]);
char *stristr(char *, char *);
int ConfirmEncryption(void);

/************************************* global Variables ***/
FILE *pipe1 = NULL, *pipe2 = NULL, *file = NULL;
char PIPE1[L_tmpnam], PIPE2[L_tmpnam], FILENAME[L_tmpnam];
char *argvline, *cmdbuf;

static const char __RCSId[] = "$Id: PGPSendMail.c,v 1.28 1993/10/12 13:02:36 simons Exp simons $";

/**************************************************************************
 *                                                                        *
 * Sektion: Hauptprogramm                                                 *
 *                                                                        *
 **************************************************************************/

int main(char *argv)
{
        extern struct ExecBase *SysBase;
        char **receipients, *dummy;
        int body_pos;

        if (SysBase->LibNode.lib_Version < 37)
                error("Kickstart 2.04 or later is required!", 20);

        if((cmdbuf = malloc(LINEBUFFER*4)) == NULL)
                error("Couldn't allocate my buffer!", 10);


        /* Determine loglevel. */

        if ((dummy = FindConfig(DEBUGNAME)) == NULL)
                LogLevel = 0;
        else
                LogLevel = atoi(dummy);


        /*
         * Copy the passed arguments into an global Variable, so
         * NonFatalError() can access them.
         */

        argvline = argv;


        /*
         * Make unique filenames for the temporary files
         * and open them.
         */

        tmpnam(PIPE1);
        tmpnam(PIPE2);
        tmpnam(FILENAME);

        if ((file = fopen(FILENAME, "w+")) == NULL)
                error("Can't open temporary file!", 10);


        /*
         * Copy standard input to a temporary file for security reasons.
         */

        if (CopyUntilEOF(stdin, file) == 0)
                error("Can't write to my temporary file!", 10);
        my_ulog(2, "PGPSendMail, original mail in '%s'", FILENAME);


        /* Find receipients to determine who to encrypt to. */

        receipients = FindReceipients(file);
        if (receipients == NULL || *receipients == NULL)
                NonFatalError("Can't determine receipient of message");
        else
                my_ulog(2, "PGPSendMail, receipients (with aliases): %s", ArrayToLine(receipients));

        receipients = ExpandAliases(receipients);
        if (receipients == NULL || *receipients == NULL)
                NonFatalError("Can't determine receipient of message");
        else
                my_ulog(2, "PGPSendMail, receipients (aliases expanded): %s", ArrayToLine(receipients));

        if ((CheckKeyAvailability(receipients)) == 0) {
                my_ulog2(1, "Not all receipients have a valid public key");
                NonFatalError("");
        }

        if ( !(dummy = FindConfig(CONFIRMENCRYPTION)) || *dummy == 'y' || *dummy == 'Y' ) {
                if (!(ConfirmEncryption()))
                        NonFatalError("Encryption denied by user");
        }


        /* Copy messagebody for encryption. */

        fseek(file, 0L, SEEK_SET);
        fflush(file);

        while((body_pos = getline(file, cmdbuf)) > 1)
                ;
        body_pos = ftell(file);

        if ((pipe1 = fopen(PIPE1, "w")) == NULL)
                NonFatalError("Can't open temporary file!");
        if (CopyUntilEOF(file, pipe1) == 0)
                NonFatalError("Can't write to my temporary file!");
        my_ulog(2, "PGPSendMail, copied message-body to '%s' for encryption", PIPE1);


        /* Encrypt message body. */

        sprintf(cmdbuf, PGP, PIPE1, PIPE2, ArrayToLine(receipients));
        my_ulog(1, "PGPSendMail, starting `%s'", cmdbuf);
        fprintf(stderr, "%s\n", cmdbuf);
        fclose(pipe1); pipe1 = NULL;
        if (system(cmdbuf) != 0)
                NonFatalError("PGP failed somehow");
        remove(PIPE1);  /* Free some memory up. */


        /* Insert encrytpted message instead of text-body */

        if ((pipe2 = fopen(PIPE2, "r")) == NULL)
                NonFatalError("Can't open temporary file!");
        my_ulog(2, "PGPSendMail, copying modified mail to '%s'", FILENAME);
        fseek(file, body_pos, SEEK_SET);
        fflush(file);
        fprintf(file, BODY_ID);
        fflush(file);
        if (CopyUntilEOF(pipe2, file) == 0)
                error("Can't write to my temporary file!", 10);
        body_pos = ftell(file);

        fclose(pipe2); pipe2 = NULL; remove(PIPE2);
        fclose(file); file = NULL;      /* Free up some memory. */

        if (TruncFile(FILENAME, body_pos) == 0)
                error("Truncating the original failed.", 10);
        my_ulog(-1, "PGPSendMail, succesfully encrypted a mail for %s", ArrayToLine(receipients));


        /* Call SendMail to handle the message. */

        sprintf(cmdbuf, START_SENDMAIL, GetConfigProgram(SENDMAIL), FILENAME, argvline);
        my_ulog(2, "PGPSendMail, starting '%s'", cmdbuf);
        body_pos = system(cmdbuf);


        /* Delete temporary files and leave. */

        remove(FILENAME);
        return body_pos;        /* Return the code, given by sendmail. */
}


/**************************************************************************
 *                                                                        *
 *                              Unterprogramme                            *
 *                                                                        *
 **************************************************************************/

void NonFatalError(char *message)
{
        if (strlen(message) > 0) {
                my_ulog(-1, "PGPSendMail, %s", message);
                fprintf(stderr, "PGPSendMail: %s\n", message);
        }

        fclose(pipe1); remove(PIPE1);
        fclose(pipe2); remove(PIPE2);
        fclose(file);

        sprintf(cmdbuf, START_SENDMAIL, GetConfigProgram(SENDMAIL), FILENAME, argvline);
        my_ulog(2, "PGPSendMail, starting '%s'", cmdbuf);
        system(cmdbuf);

        remove(FILENAME);
        exit(0);
}

void error(char *message, int errorcode)
{
        my_ulog(-1, "PGPSendMail, %s", message);
        fprintf(stderr, "PGPSendMail: %s\n", message);
        exit(errorcode);
}

int getline(FILE *file, char *buffer)
{
        int c, i = 0;

        do {
                c = fgetc(file);
                *buffer = c;
                i++; buffer++;
        } while (i < LINEBUFFER && c != EOF && c != '\n');

        if (i >= LINEBUFFER)
                error("Buffer overflow!", 10);

        if (c == EOF && i == 1) {
                *(buffer-1) = '\0';
                return EOF;
        }

        if (c == '\n') {
                *buffer = '\0';
                return i;
        }

}


int CopyUntilEOF(FILE *in, FILE *out)
{
        char *buffer;
        int len;

        if ((buffer = malloc(IOBUFSIZE)) == NULL)
                return 0;

        if (in != stdin)
                lseek(fileno(in), ftell(in), 0);
        if (out != stdout)
                lseek(fileno(out), ftell(out), 0);

        while ((len = read(fileno(in), buffer, IOBUFSIZE)) > 0)
                if (write(fileno(out), buffer, len) == -1) {
                        free(buffer);
                        return 0;
                }

        free(buffer);
        return 1;
}

char **FindReceipients(FILE *fh)
{
        char **receipients, *line;

        if ((receipients = malloc(sizeof(char *[MAX_RECEIPIENTS]))) == NULL)
                NonFatalError("Couldn't allocate my buffer!");
        *receipients = NULL;

        if ((line = FindHeaderEntry(fh, "To: ")) != NULL)
                SplitAddressLine(line, receipients);
        if ((line = FindHeaderEntry(fh, "Cc: ")) != NULL)
                SplitAddressLine(line, receipients);
        ParseAddressLines(receipients);

        return receipients;
}

char *FindHeaderEntry(FILE *fh, char *entry)
{
        char *line, *line_tmp;
        int len;

        if ((line = malloc(LINEBUFFER*4)) == NULL)
                return NULL;
        fflush(file);
        len = fseek(fh, 0L, SEEK_SET);

        len = getline(fh, line);
        while (len != 1 && len != EOF) {
                if (!strnicmp(line, entry, strlen(entry))) {
                        line_tmp = line;

                        /*
                         * If the last character in the line is a ',',
                         * change the '\n' to ' ' and append the next
                         * line.
                         */

                        do {
                                line_tmp = line_tmp+len-2;
                                while (isspace(*line_tmp))
                                        line_tmp--;
                                if (*line_tmp == ',') {
                                        *(line_tmp+1) = ' ';
                                        line_tmp += 2;
                                        len = getline(fh, line_tmp);
                                }
                                else
                                        return &line[strlen(entry)];
                        } while (1);            /* Forever! */
                }
                len = getline(fh, line);
        }
        free(line);
        return NULL;
}

void SplitAddressLine(char *line, char *receipients[])
{
        int brackets, i;

        if (line == NULL || receipients == NULL)
                return;

        while (*receipients != NULL)
                receipients++;

        *receipients++ = line;

        for (i = 0, brackets = 0; line[i] != '\n'; i++) {
                if (line[i] == '(' && (i == 0 || isspace(line[i-1])))
                        brackets = 1;
                if (line[i] == ')' && (line[i+1] == ',' || line[i+1] == '\n' || isspace(line[i+1])))
                        brackets = 0;
                if (line[i] == ',' && brackets == 0) {
                        line[i++] = '\0';
                        while (isspace(line[i]))
                                i++;
                        *receipients++ = &line[i];
                }
        }
        line[i] = '\0';
        *receipients = NULL;
}


void ParseAddressLines(char **receipients)
{
        char line[LINEBUFFER];
        int brackets, i, z;

        while (*receipients != NULL) {
                strcpy(line, *receipients);
                for (i = 0, z = 0, brackets = 0; line[i] != '\0'; i++) {
                        if (line[i] == '(' && (i == 0 || isspace(line[i-1])))
                                brackets++;
                        if (line[i] == ')' && (line[i+1] == '\0' || isspace(line[i+1])))
                                brackets--;
                        if (line[i] == '<' && brackets == 0) {
                                i++;
                                while (1) {             /* Forever! */
                                        if (line[i] == '>' && (line[i+1] == '\0' || isspace(line[i+1])))
                                                break;
                                        (*receipients)[z++] = line[i++];
                                }
                                (*receipients)[z] = '\0';
                                break;
                        }
                }
                if (z == 0) {   /* No <address> occured. */
                        i = 0;
                        while (!isspace((*receipients)[i]))
                                i++;
                        (*receipients)[i] = '\0';
                }
                receipients++;
        }
}


char *ArrayToLine(char *array[])
{
        char *line;

        if ((line = malloc(LINEBUFFER)) == NULL)
                return NULL;
        line[0] = '\0';

        while (*array != NULL) {
                strcat(line, *array);
                strcat(line, " ");
                array++;
        }
        return line;
}

int CheckKeyAvailability(char *receipients[])
{
        FILE *fh;
        char *keylist, *filename;
        int filelength;

        if (*receipients == NULL)
                return 0;               /* No receipient found! */

        filename = MakeConfigPath(UULIB, PGPKEYLIST);
        LockFile(filename);

        if ((fh = fopen(filename, "r")) == NULL) {
                UnLockFile(filename);
                return 0;               /* Couldn't open keylist! */
        }
        fseek(fh, 0L, SEEK_END); filelength = ftell(fh);
                                        /* Determine filelength. */


        /* Allocate buffer and read keylist. */

        if (keylist = malloc(filelength+1)) {
                lseek(fileno(fh), 0, 0);
                if ((read(fileno(fh), keylist, filelength)) == -1) {
                        fclose(fh);     /* Couldn't read keylist. */
                        UnLockFile(filename);
                        free(keylist);
                        return 0;
                }
                else {          /* Read went fine. */
                        fclose(fh);
                        UnLockFile(filename);
                        keylist[filelength] = '\0';

                        while (*receipients != NULL) {
                                if (FindKey(*receipients, keylist) == 0) {
                                        free(keylist);
                                        my_ulog(1, "PGPSendMail, No key available for <%s>", *receipients);
                                        return 0;       /* Key is not in keylist. */
                                }
                                receipients++;
                        }
                }
        }
        free(keylist);
        return 1;                       /* Everything is fine. */
}

int FindKey(char *key, char *keylist)
{
        char line[LINEBUFFER];
        int i;

        while (*keylist != '\0') {

                /* Is this a valid key-line? */

                if (!strnicmp(keylist, "pub ", strlen("pub "))
                        || !strnicmp(keylist, "    ", strlen("    ")))
                {
                        for(i = 0; keylist[i] != '\n'; i++)
                                line[i] = keylist[i];
                        line[i] = '\0';
                        if (stristr(line, key))
                                return 1;
                }


                /* Find end of line or end of buffer. */

                while (*keylist != '\n' && *keylist != '\0')
                        keylist++;
                keylist++;
        }
        return 0;
}

char *stristr(char *line, char *key)
{
        int i, cmplen = strlen(key), end = strlen(line)-cmplen;

        for (i = 0; i <= end; i++)
                if (strnicmp(&line[i], key, cmplen) == NULL)
                        return &line[i];
        return NULL;
}

