/*****************************************************************************/ /* SendMail.c Use VMS callable Mail to send a VMS Mail message (no MIME, etc.) COPYRIGHT --------- Copyright (C) 2005-2024 Mark G.Daniel This program, comes with ABSOLUTELY NO WARRANTY. This is free software, and you are welcome to redistribute it under the conditions of the GNU GENERAL PUBLIC LICENSE, version 3, or any later version. VERSION HISTORY --------------- 06-JUL-2006 MGD SendMailAddressExtract() comma-separated processing 06-MAY-2006 MGD SendMailAddressExtract() allow blackslash (0x92) to escape a single character (at least inside a quoted string) 01-FEB-2005 MGD initial */ /*****************************************************************************/ #pragma nomember_alignment /* standard C header files */ #include #include #include #include #include /* VMS related header files */ #include #include #include #include #include #include #include #include #include /* application header file */ #include "soymail.h" #include "other.h" #include "mta.h" #include "sendmail.h" #define FI_LI __FILE__, __LINE__ /* external storage */ extern BOOL Debug, WatchEnabled; extern int VmsVersion; /* required prototypes */ int sys$fao (__unknown_params); int sys$getmsg (__unknown_params); int sys$gettim (__unknown_params); int sys$setprv (__unknown_params); /*****************************************************************************/ /* Use callable Mail to send a VMS Mail message (no MIME, etc.) */ char* SendMailMessage ( char *MailFrom, char *ToList, char *CcList, char *MailSubj, char *MailText ) { static unsigned long SysPrvMask [2] = { PRV$M_SYSPRV, 0 }; static unsigned short UserNameTypeTo = MAIL$_TO, UserNameTypeCc = MAIL$_CC; static unsigned short Length; static char ErrorString [256]; static VMS_ITEM_LIST3 BodyPartItem [] = { { 0, MAIL$_SEND_RECORD, 0, 0 }, { 0, MAIL$_NOSIGNAL, 0, 0 }, {0,0,0,0} }, FromItem [] = { { 0, MAIL$_SEND_FROM_LINE, 0, 0 }, { 0, MAIL$_NOSIGNAL, 0, 0 }, {0,0,0,0} }, SendBeginSigFileItem [] = { { 0, MAIL$_SEND_NO_SIGFILE, 0, 0 }, { 0, MAIL$_SEND_NO_PERS_NAME, 0, 0 }, { 0, MAIL$_NOSIGNAL, 0, 0 }, {0,0,0,0} }, SendBeginNoSigFileItem [] = { { 0, MAIL$_SEND_NO_PERS_NAME, 0, 0 }, { 0, MAIL$_NOSIGNAL, 0, 0 }, {0,0,0,0} }, SendToItem [] = { { 0, MAIL$_SEND_USERNAME, 0, 0 }, { sizeof(UserNameTypeTo), MAIL$_SEND_USERNAME_TYPE, &UserNameTypeTo, 0 }, { 0, MAIL$_NOSIGNAL, 0, 0 }, {0,0,0,0} }, SendCcItem [] = { { 0, MAIL$_SEND_USERNAME, 0, 0 }, { sizeof(UserNameTypeCc), MAIL$_SEND_USERNAME_TYPE, &UserNameTypeCc, 0 }, { 0, MAIL$_NOSIGNAL, 0, 0 }, {0,0,0,0} }, SubjectItem [] = { { 0, MAIL$_SEND_SUBJECT, 0, 0 }, { 0, MAIL$_NOSIGNAL, 0, 0 }, {0,0,0,0} }, NoSignalItem [] = { { 0, MAIL$_NOSIGNAL, 0, 0 }, {0,0,0,0} }, NullItem = {0,0,0,0}; int cnt, status, VmsMailStatus, WrapAt; unsigned short slen; unsigned long SendContext; unsigned long PrevPrivMask [2]; char *cptr, *sptr, *zptr; char StringBuffer [256]; $DESCRIPTOR (ErrorStringDsc, ErrorString); $DESCRIPTOR (MailAddressDsc, ""); $DESCRIPTOR (StringBufferDsc, StringBuffer); /*********/ /* begin */ /*********/ if (Debug) fprintf (stdout, "SendMailMessage()\n"); /* let's keep it within the bounds of reason! */ WrapAt = SOY_WRAP_AT; if (WrapAt && (WrapAt < 48 || WrapAt > 255)) WrapAt = SOY_WRAP_AT; SendContext = 0; /* basically to support pre-VMS6.2 where SEND_NO_SIGFILE is an error */ if (VmsVersion >= 70) { status = mail$send_begin (&SendContext, &SendBeginSigFileItem, &NullItem); if (Debug) fprintf (stdout, "mail$send_begin() %%X%08.08X\n", status); } else { status = mail$send_begin (&SendContext, &SendBeginNoSigFileItem, &NullItem); if (Debug) fprintf (stdout, "mail$send_begin() %%X%08.08X\n", status); } if (VMSnok (status)) goto VmsEmailMessageError; /* callable Mail "From:" (apparently) must be set after send_begin() */ if (MailFrom && MailFrom[0]) { if (WatchEnabled) WatchThis ("FROM {!UL}!AZ", strlen(MailFrom), MailFrom); status = sys$setprv (1, &SysPrvMask, 0, &PrevPrivMask); if (Debug) fprintf (stdout, "sys$setprv() %%X%08.08X\n", status); FromItem[0].buf_addr = MailFrom; FromItem[0].buf_len = strlen(MailFrom); if (FromItem[0].buf_len > 255) FromItem[0].buf_len = 255; status = mail$send_add_attribute (&SendContext, &FromItem, &NullItem); if (!(PrevPrivMask[0] & PRV$M_SYSPRV)) sys$setprv (0, &SysPrvMask, 0, 0); if (Debug) fprintf (stdout, "$send_add_attribute() %%X%08.08X\n", status); if (VMSnok (status)) goto VmsEmailMessageError; } /******************/ /* to address(es) */ /******************/ cptr = ToList; while (cptr && *cptr) { sptr = SendMailAddressExtract (&cptr); /* continue if no address could be parsed */ if (!sptr || !*sptr) continue; if (WatchEnabled) WatchThis ("TO {!UL}!AZ", strlen(sptr), sptr); SendToItem[0].buf_addr = sptr; SendToItem[0].buf_len = strlen(sptr); if (SendToItem[0].buf_len > 255) SendToItem[0].buf_len = 255; status = mail$send_add_address (&SendContext, &SendToItem, &NullItem); if (Debug) fprintf (stdout, "$send_add_address() %%X%08.08X\n", status); if (VMSnok (status)) { MailAddressDsc.dsc$a_pointer = SendToItem[0].buf_addr; MailAddressDsc.dsc$w_length = SendToItem[0].buf_len; goto VmsEmailMessageError; } } /******************/ /* cc address(es) */ /******************/ cptr = CcList; while (cptr && *cptr) { sptr = SendMailAddressExtract (&cptr); /* continue if no address could be parsed */ if (!sptr || !*sptr) continue; if (WatchEnabled) WatchThis ("CC {!UL}!AZ", strlen(sptr), sptr); SendCcItem[0].buf_addr = sptr; SendCcItem[0].buf_len = strlen(sptr); if (SendCcItem[0].buf_len > 255) SendCcItem[0].buf_len = 255; status = mail$send_add_address (&SendContext, &SendCcItem, &NullItem); if (Debug) fprintf (stdout, "$send_add_address() %%X%08.08X\n", status); if (VMSnok (status)) { MailAddressDsc.dsc$a_pointer = SendCcItem[0].buf_addr; MailAddressDsc.dsc$w_length = SendCcItem[0].buf_len; goto VmsEmailMessageError; } } /***********/ /* subject */ /***********/ if (MailSubj && MailSubj[0]) { if (WatchEnabled) WatchThis ("SUBJ {!UL}!AZ", strlen(MailSubj), MailSubj); SubjectItem[0].buf_addr = MailSubj; SubjectItem[0].buf_len = strlen(MailSubj); if (SubjectItem[0].buf_len > 255) SubjectItem[0].buf_len = 255; status = mail$send_add_attribute (&SendContext, &SubjectItem, &NullItem); if (Debug) fprintf (stdout, "$send_add_attribute() %%X%08.08X\n", status); if (VMSnok (status)) goto VmsEmailMessageError; } /********/ /* body */ /********/ cptr = MailText; while (cptr && *cptr) { BodyPartItem[0].buf_addr = cptr; /* only wrap non-quoted lines (those without leading '>') */ if (WrapAt && *cptr != '>') cnt = WrapAt; else cnt = 255; while (*cptr && *cptr != '\r' && *cptr != '\n' && cnt) { cptr++; cnt--; } if (!cnt && *cptr != ' ' && *cptr != '\t') { /* reached the "wrap-at" limit while in a "word", back a little */ sptr = cptr; while (*cptr && *cptr != ' ' && *cptr != '\t' && cptr > (char*)BodyPartItem[0].buf_addr) cptr--; /* if it's a continuous line of characters then just break it! */ if (cptr == BodyPartItem[0].buf_addr) cptr = sptr; } BodyPartItem[0].buf_len = cptr - (char*)BodyPartItem[0].buf_addr; if (Debug) fprintf (stdout, " %d |%*.*s|\n", BodyPartItem[0].buf_len, BodyPartItem[0].buf_len, BodyPartItem[0].buf_len, (char*)BodyPartItem[0].buf_addr); status = mail$send_add_bodypart (&SendContext, &BodyPartItem, &NullItem); if (Debug) fprintf (stdout, "$send_add_bodypart() %%X%08.08X\n", status); if (VMSnok (status)) break; /* if it was wrapped then this will absorb leading white-space */ while (*cptr && (*cptr == ' ' || *cptr == '\t')) cptr++; if (*cptr == '\r') sptr = ++cptr; if (*cptr == '\n') sptr = ++cptr; } /***************/ /* end message */ /***************/ status = mail$send_message (&SendContext, &NoSignalItem, &NoSignalItem); if (Debug) fprintf (stdout, "mail$send_message() %%X%08.08X\n", status); mail$send_end (&SendContext, &NullItem, &NullItem); return (NULL); VmsEmailMessageError: VmsMailStatus = status; mail$send_end (&SendContext, &NullItem, &NullItem); StringBufferDsc.dsc$w_length = sizeof(StringBuffer)-2; status = sys$getmsg (VmsMailStatus, &slen, &StringBufferDsc, 1, 0); if (VMSok (status)) { StringBuffer[slen++] = '.'; StringBuffer[slen] = '\0'; StringBuffer[0] = toupper(StringBuffer[0]); if (Debug) fprintf (stdout, "|%s|\n", StringBuffer); } else { sprintf (ErrorString, "SYS$GETMSG() error! %%X%08.08X", status); if (WatchEnabled) WatchThis ("ERROR !AZ", ErrorString); return (ErrorString); } if (VmsMailStatus == MAIL$_LOGLINK || VmsMailStatus == MAIL$_NOSUCHUSR || VmsMailStatus == MAIL$_PARSEFAIL || VmsMailStatus == MAIL$_USERSPEC || VmsMailStatus == MAIL$_ERRACTRNS) { StringBufferDsc.dsc$w_length = slen; status = sys$fao (&StringBufferDsc, &slen, &ErrorStringDsc, &MailAddressDsc); if (VMSok (status)) ErrorString[slen] = '\0'; else sprintf (ErrorString, "SYS$FAO() error! %%X%08.08X", status); } else strcpy (ErrorString, StringBuffer); if (WatchEnabled) WatchThis ("ERROR !AZ", ErrorString); return (ErrorString); } /*****************************************************************************/ /* Reproduce the same message content as in SendMailMessage() but into a file. See CallMailCopyFile() for a description of the sequential file format. No need to explicitly constrain the header strings to 255 characters because if they are unacceptable callable Mail would have returned an error already to we know that they are of acceptable length! */ int SendMailToFile ( char *FileName, char *FromPtr, char *ToPtr, char *CcPtr, char *SubjPtr, char *BodyPtr ) { static $DESCRIPTOR (DateTimeFaoDsc, "!%D\0"); int retval, status; int64 Time64; char *cptr, *sptr; char DateTime [32]; $DESCRIPTOR (DateTimeDsc, DateTime); FILE *fp; TEXT_DATA *tlptr; /*********/ /* begin */ /*********/ if (Debug) fprintf (stdout, "SendMailToFile()\n"); fp = fopen (FileName, "w", "rfm=var", "rat=cr", "mbc=32", "mbf=64"); if (!fp) return (vaxc$errno); sys$gettim (&Time64); sys$fao (&DateTimeFaoDsc, 0, &DateTimeDsc, &Time64); /* NOTE THE LEADING FORM-FEED! (cost me a couple of hours) */ fprintf (fp, "\f\n"); /* separate date/time using a null which tends to mask the date */ fprintf (fp, "From:\t%s%c\t%s\n", FromPtr, 0, DateTime); fprintf (fp, "To:\t%s\n", ToPtr); fprintf (fp, "CC:\t%s\n", CcPtr); retval = fprintf (fp, "Subj:\t%s\n\n", SubjPtr); cptr = BodyPtr; while (*cptr) { for (sptr = cptr; *sptr && *sptr != '\r' && *sptr != '\n'; sptr++); retval = fwrite (cptr, sptr-cptr, 1, fp); if (*sptr == '\r') sptr++; if (*sptr == '\n') sptr++; cptr = sptr; if (retval <= 0) break; } if (retval <= 0) status = vaxc$errno; else status = SS$_NORMAL; fclose (fp); return (status); } /*****************************************************************************/ /* An address list is a string containing one or more email addresses separated by newline characters. The newline character can optionally be preceded by a comma (making it look like a comma-separated list). Parse one of these addresses from the list. Return a pointer to a static buffer containing the address, an empty string if end-of-list, or a NULL if an error was encountered. Update the supplied list pointer ready to parse the next address. */ char* SendMailAddressExtract (char **ListPtrPtr) { static char abuf [256]; char *cptr, *sptr, *zptr; /*********/ /* begin */ /*********/ if (Debug) fprintf (stdout, "SendMailAddressExtract() |%s|\n", *ListPtrPtr); if (!ListPtrPtr) return (NULL); if (!(cptr = *ListPtrPtr)) return (NULL); abuf[0] = '\0'; while (*cptr) { zptr = (sptr = abuf) + sizeof(abuf); *sptr = '\0'; while (*cptr && *cptr == '\n') cptr++; if (!*cptr) break; while (*cptr && isspace(*cptr) && *cptr != ',' && *cptr != '\n') cptr++; if (!*cptr) break; if (*cptr == '\"') { cptr++; while (*cptr && *cptr != '\"' && *cptr != '\n') { if (*cptr == '\\' && *(cptr+1) && *(cptr+1) != '\n') cptr++; cptr++; } if (*cptr == '\"') cptr++; continue; } while (*cptr && !isspace(*cptr) && *cptr != ',' && sptr < zptr) *sptr++ = toupper(*cptr++); *sptr = '\0'; /* scan to the end of this list line */ while (*cptr && *cptr != '\n') cptr++; while (*cptr && *cptr == '\n') cptr++; /* if an empty line */ if (!abuf[0]) continue; break; } if (WatchEnabled) WatchThis ("ADDRESS |!AZ", abuf); /* update the pointer to the list */ *ListPtrPtr = cptr; return (abuf); } /*****************************************************************************/