/*
**  various utility routines for mailsend
**
**
**  Development History:
**      who                  when           why
**      muquit@muquit.com    Mar-26-2001    first cut
*/

#include "mailsend.h"
#define DAY_MIN     (24 * HOUR_MIN) /* minutes in a day */
#define HOUR_MIN    60      /* minutes in an hour */
#define MIN_SEC     60      /* seconds in a minute */


/*
** returns a positive number if the file descriptor is connected to
** console. 0 if not
*/
int isInConsole(int fd)
{
    int
        rc;
    rc=_isatty(fd);
    return (rc);
}

int isInteractive(void)
{
#ifdef WINNT
        if (isInConsole(_fileno(stdout)))
            return(1);
        else
            return(0);
#else
        if (isatty(fileno(stdout)))
            return(1);
        else
            return(0);
#endif /* ! WINNT */
 
    return(0);

}
/*
** duplicate a string. exits on failure
*/
char *xStrdup (char *string)
{
    char
        *tmp;

    if (string == (char *) NULL || *string == '\0')
        return ((char *) NULL);

    /* malloc twice as much memory.  */
    tmp = (char *) malloc ((int) strlen(string)+1);

    if (tmp == (char *) NULL)
    {
        (void) fprintf(stderr,"Error: mystrdup(): memory allocation problem\n");
        exit(0);
    }
    /* it's safe to copy this way */
    (void) strcpy(tmp, string);
    return (tmp);
}

void errorMsg(char *format,...)
{
    va_list
        args;

    va_start(args,format);
    (void) fprintf (stderr,"Error: ");
    vfprintf(stderr,format,args);
    (void) fprintf(stderr,"\n");
    va_end(args);
}

void showVerbose(char *format,...)
{
    va_list
        args;
    if (g_quiet) 
        return;

    if (g_verbose == 1)
    {
        va_start(args,format);
        vfprintf(stdout,format,args);
        (void) fflush(stdout);
        va_end(args);
    }

}

void print_info(char *format,...)
{
    va_list
        args;
    if (g_quiet) 
        return;

    va_start(args,format);
    vfprintf(stdout,format,args);
    (void) fflush(stdout);
    va_end(args);

}


/*
** Initialize TheMail structure. returns pointer to the TheMail sturcture on
** success, NULL on failure.
*/
TheMail *newTheMail(void)
{
    TheMail
        *the_mail=(TheMail *) NULL;

    the_mail=(TheMail *) malloc(sizeof(TheMail));
    if (the_mail == (TheMail *) NULL)
    {
        (void) fprintf(stderr,"initTheMail(): malloc failed\n");
        return ((TheMail *) NULL);
    }
    memset(the_mail,0,sizeof(TheMail));

    return (the_mail);
}

Address *newAddress(void)
{
    Address
        *a;

    a=(Address *) malloc(sizeof(Address));
    if (a == (Address *) NULL)
    {
        (void) fprintf(stderr," newAddress() malloc failed\n");
    }
    memset(a,0,sizeof(Address));

    return (a);
}


int validateMusts(char *from,char *to,char *smtp_server,char *helo_domain)
{
    int
        err=0;

    if (getAddressList() == NULL)
    {
        if (to == (char *) NULL)
        {
            errorMsg("No To address/es specified. Speicfy with: -t to,to,..");
            err++;
        }
    }

    if (from == (char *) NULL)
    {
        errorMsg("No From address specified. Specify with: -f");
        err++;
    }


    if (smtp_server == (char *) NULL)
    {
        errorMsg("No SMTP server address or IP specified. Specify with -smtp");
        err++;
    }


    if (helo_domain == (char *) NULL)
    {
        errorMsg("No domain specified. Specify with: -d");
        err++;
    }

    if (err)
        return (-1);

    return (0);
}

char *askFor(char *buf,int buflen,char *label,int ask)
{

    if (label == NULL || *label == '\0')
        return (NULL);

again:
    if (isInConsole(_fileno(stdin)))
    {
        (void) fprintf(stdout,label);
        (void) fflush(stdout);
        (void) fflush(stderr);
    }

    (void) fgets(buf,buflen,stdin);
    if (*buf == '\0' || *buf == '\n')
    {
        if (ask == EMPTY_NOT_OK)
            goto again;
    }

    mutilsChopNL(buf);

    return (buf);
}

/*
** remote mailto from To
** remote mailto: from the address
**  mailto:foo => foo
** mailto: => mailto:
** mailto:x => "x"
** mailto: x => " x"
*/
char *fix_to(char *to)
{
    char
        *p=to;

    if (to == NULL || *to == '\0')
        return(p);

    /* mailto:foo */
    if (strlen(to) > 7 && mutilsStrncasecmp(to,"mailto:",7) == 0)
    {
        p=to;
        p=p+7;
        return(p);
    }

    return(p);
}

/**
 * return filepath and mime_type
 * @param str   String of the form "file,mime_type" 
 *              example: "c:\usr\local\foo.txt,text/plain"
 *              "/usr/local/foo.html,text/html"
 * @param filepath returns
 * @param fp_size  size of the buffer filepath
 * @param mime_type returns
 * @param mt_size size of the buffer mime_type
 *
 * @return 0 on success, -1 otherwise
 * 
 * Note: it will not expand "~ $" etc in the filepath
 * This function is used when a body is attached with -m flag
 *              
 */
int get_filepath_mimetype(char *str,char *filepath,int fp_size,char *mype_type,int mt_size)
{

    int
        rc=0;
    char
        *fp,
        *mt;
    if ((mt=strchr(str,ATTACHMENT_SEP)))
    {
        *mt++='\0';
    }
    else
    {
        errorMsg("Could not determine mime-type from input: %s\n",str);
        return(-1);
    }
    mutilsSafeStrcpy(mype_type,mt,mt_size);

    /* get the filepath out */
    fp=str;
    mutilsSafeStrcpy(filepath,fp,fp_size);
    return(rc);
}

void initialize_openssl(char *cipher)
{
#ifdef HAVE_OPENSSL
    static const char rnd_seed[]="my huge entropy for rng.. blah";
    SSL_CTX *ssl_ctx=(SSL_CTX *) NULL;
    SSL *ssl=NULL;
    SSL_library_init();
    SSL_load_error_strings();
    RAND_seed(rnd_seed,sizeof(rnd_seed));
    ssl_ctx=SSL_CTX_new(SSLv23_client_method());
    if (ssl_ctx == NULL)
    {
        errorMsg("Could not create SSL context\n");
        return;
    }
    if (cipher)
    {
        if (!SSL_CTX_set_cipher_list(ssl_ctx,cipher))
        {
            errorMsg("Could not set cipher list %s\n",cipher);
            return;
        }
    }
    ssl=SSL_new(ssl_ctx);
    if (ssl == NULL)
    {
        errorMsg("SSL_new() failed\n");
        return;
    }
    /* set ssl to msock's static */
    msock_set_ssl(ssl);

#endif /* HAVE_OPENSSL */
}

/**
 * Calculate Date for RFC822 Date: header
 *
 * @param when     time since unix epoch
 * @param datebuf  returns NULL terminated date string.
 *                  Example: Wed, 17 May 2006 13:55:35 -0400
 * @param bufsiz   size of buffer dates. It's callers responsibily to make sure 
 *                 datebuf contains enough space. It must be at least 64 bytes. 
 *                 Usually it'll contain 31 bytes.
 *
 * returns 0 on success, -1 on failure
 *
 * The code is adapted from postfix. 
 *
 * Changes I made:
 *  - I use fixed size buffers instead of dynamic ones.
 *  - I don't use %e to calcuate day of the week, instead I use %d. Windows strftime
 *    does not have %e.
 *  - I don't add timzone name.
 *
 * Reference: http://cr.yp.to/immhf/date.html
 */
int rfc822_date(time_t when,char *datebuf,int bufsiz)
{
    char
        ts1[32],
        ts2[32];

    struct tm
        *lt;

    struct tm
        gmt;

    int
        gmtoff;

    if (bufsiz < 64)
    {
        (void) fprintf(stderr,"buffer size of date must be > 31, it is %d\n",bufsiz);
        return(-1);
    }

    memset(datebuf,0,bufsiz);
    memset(ts1,0,sizeof(ts1));
    memset(ts2,0,sizeof(ts2));

    gmt=*gmtime(&when);
    lt=localtime(&when);

    gmtoff = (lt->tm_hour - gmt.tm_hour) * HOUR_MIN + lt->tm_min - gmt.tm_min;
    if (lt->tm_year < gmt.tm_year)
    gmtoff -= DAY_MIN;
    else if (lt->tm_year > gmt.tm_year)
    gmtoff += DAY_MIN;
    else if (lt->tm_yday < gmt.tm_yday)
    gmtoff -= DAY_MIN;
    else if (lt->tm_yday > gmt.tm_yday)
    gmtoff += DAY_MIN;
    if (lt->tm_sec <= gmt.tm_sec - MIN_SEC)
    gmtoff -= 1;
    else if (lt->tm_sec >= gmt.tm_sec + MIN_SEC)
    gmtoff += 1;

    /*
    ** windows strftime does not have %e, so I'll use %d instead.
    ** day 1 - 9 can be written as 01 - 09 
    */
    (void) strftime(ts1,sizeof(ts1)-1,"%a, %d %b %Y %H:%M:%S ",lt);
    if (gmtoff < -DAY_MIN || gmtoff > DAY_MIN)
    {
        (void) fprintf(stderr,"UTC time offset %d is larger than one day",gmtoff);
        return(-1);
    }

    (void) snprintf(ts2,sizeof(ts2)-1,"%+03d%02d",
            (int) (gmtoff / HOUR_MIN),(int) (abs(gmtoff) % HOUR_MIN));

    /* put everything in the buffer. it's usually 31 bytes */
    (void) snprintf(datebuf,bufsiz,"%s%s",ts1,ts2);

    /* I will not add timezone name, it's not required */

    return(0);
}

/*
** return 0 on success, -1 on failure
*/
int guess_file_type(char *file,unsigned int *flag)
{
    char
        buf[BUFSIZ];

    int
        i,
        c,
        cr_found=0,
        nbytes,
        buflen;

    FILE
        *fp=NULL;

    *flag=0;

    buflen=sizeof(buf)-1;
    fp=fopen(file,"rb");
    if (fp == NULL)
    {
        errorMsg("Could not open file %s for reading (%s)\n",
                file,ERR_STR);
        return(-1);
    }
    while(!feof(fp))
    {
        nbytes=fread(buf,1,buflen,fp);
        if ((nbytes != buflen) && ferror(fp))
        {
            errorMsg("Read error in guess_file_type\n");
            goto ExitProcessing;
        }
        for (i=0; i < nbytes; i++)
        {
            c=buf[i];
            if (cr_found)
            {
                cr_found=0;
                if (c == '\n')
                    continue;
            }
            if (c == '\r')
            {
                cr_found=1;
                *flag |= FILE_TYPE_DOS;
            }
            else if (c == '\n')
            {
                *flag |= FILE_TYPE_UNIX;
            }
            else if ((c < 32 || c > 126) && c != '\t')
            {
                *flag |= FILE_TYPE_BINARY;
                (void) fclose(fp);
                return(0);
            }
        }
    }

    if (fp)
        (void) fclose(fp);
    return(0);
ExitProcessing:
    if (fp)
        (void) fclose(fp);
    return(-1);
    
}

static int unix2dos(FILE *ifp,FILE *ofp)
{
    int
        n,
        j,
        k,
        c,
        c2;
    char
        buf2[BUFSIZ+1],
        buf[BUFSIZ+1];
    if (ifp == NULL || ofp == NULL)
        return(-1);
#ifdef WINNT
        _setmode(_fileno(ofp),_O_BINARY);
#endif /* WINNT */
        while(!feof(ifp))
        {
            n=fread(buf,1,BUFSIZ,ifp);
            (void) fprintf(stderr,"n=\"%d\"\n",n);
            k=0;
           for (j=0; j < n; j++)
            {
                c=buf[j];
                if (c == '\n')
                {
                    (void) fprintf(stderr,"j=%d\n",j);
                    buf2[k++]='\r';
                }
                buf2[k++]=c;
            }
            c2=fwrite(buf2,sizeof(char),k,ofp);
            if (c2 != k)
            {
                perror("fwrite");
                return(1);
            }
        }
}


#ifdef HAVE_OPENSSL
void print_cert_info(SSL *ssl)
{
    X509
        *cert=NULL;
    X509_NAME
        *sub=NULL,
        *issuer=NULL;
    char
        buf[BUFSIZ];

    if (!g_verbose)
        return;

    print_info("Cipher: %s\n",SSL_get_cipher(ssl));
    (void) fprintf(stdout,"Certificate information:\n");
    cert=SSL_get_peer_certificate(ssl);
    if (cert == NULL)
    {
        errorMsg("No peer certificate found\n");
        return;
    }
    /* get subject name */
    sub=X509_get_subject_name(cert);
    if (sub == NULL)
    {
        errorMsg("Could not get subject name in certificate\n");
    }
    X509_NAME_oneline(sub,buf,sizeof(buf)-1);
    (void) fprintf(stdout,"Subject: %s\n",buf);

    issuer=X509_get_issuer_name(cert);
    if (issuer == NULL)
    {
        errorMsg("Could not get issuer name in certificate\n");
    }
    X509_NAME_oneline(issuer,buf,sizeof(buf)-1);
    (void) fprintf(stdout,"Issuer: %s\n",buf);
    (void) fflush(stdout);
}

/*
** params:
**  challenge:   base64 encoded challenge
**  user         username
**  seceret      password
**
** Returns: NULL terminated base64 encoded CRAM-MD5 material on success, 
** NULL on failure
*/
char *encode_cram_md5(char *challenge,char *user,char *secret)
{
    unsigned char
        *data;

    unsigned long
        data_len;

    unsigned char
        hmac_md5[16];

    HMAC_CTX
        ctx;

    const EVP_MD
        *md5=NULL;

    unsigned int
        hmac_len;

    int
        i;

    char
        *b64;

    unsigned long
        b64len=0;

    char
        hex[33],
        buf[BUFSIZ];
    if (challenge == NULL || *challenge == '\0' ||
        user == NULL || *user == '\0' ||
        secret == NULL || *secret == '\0')
        return(NULL);

    OpenSSL_add_all_digests();

    /* decode the challenge */
    data=mutils_decode_base64(challenge,strlen(challenge),&data_len);
    if (data == NULL)
    {
        errorMsg("Could not base64 decode CRAM-MD5 challenge: %s",challenge);
        return(NULL);
    }

    /* take HMAC-MD5 of the challenge*/
    md5=EVP_get_digestbyname("md5");
    HMAC_CTX_init(&ctx);
    HMAC_Init(&ctx,secret,strlen(secret),md5);
    HMAC_Update(&ctx,data,data_len);
    HMAC_Final(&ctx,hmac_md5,&hmac_len);

    /* convert the digest to hex */
    memset(hex,0,sizeof(hex));
    for (i=0; i < 16; i++)
    {
        (void) sprintf(hex+2*i,"%02x",hmac_md5[i]);
    }

    (void) snprintf(buf,sizeof(buf)-1,"%s %s",user,hex);
    /* base64 encode "user hex_digest" */
    b64=mutils_encode_base64(buf,strlen(buf),&b64len);
    if (b64len <= 0)
        return(NULL);
    /* mutils_encode_base64 adds CRLF */
    if (b64len > 2)
        b64[b64len-2]='\0';
    return(b64);
}
#else

char *encode_cram_md5(char *challenge,char *user,char *secret)
{
        errorMsg("Must be compiled with OpenSSL in order to get CRAM-MD5 support\n");
    return(NULL);
}
#endif /* HAVE_OPENSSL */
