/* ** 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 */