/*****************************************************************************/ /* mta.c An elementary, (semi-)standalone SMTP Message Transport Agent (MTA). It uses the TCP/IP Services QIO interface. It connects to the specified SMTP server host ('HostName') at port 25 (defaults to LOCALHOST) and conducts an RFC821/2821 dialog to originate a pre-formatted RFC822/2822 message. The destination addresses ('AddrTo') should be a null-terminated string containing one or more newline-terminated RFC822/2822 addresses acceptble to the SMTP server. If the header text is NULL it will generate an elementary RFC822/2822 mail header. The body text should be either a null-terminated string ('BodyText') or a linked list of such texts ('HeaderList' and 'BodyList', see TEXT_DATA_STRUCT below), acceptable to an SMTP message agent. It returns a NULL if the message was given to the SMTP server without detected error, or a pointer to static storage containing an error message (the most commonly the response of the SMTP server). This module is designed to be able to be plucked from it's current code context and dropped into somewhere else. It *should* be usable on all versions of VMS at least from V6.0 and later. 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 --------------- 08-JAN-2023 MGD MtaMsgToFile() change in fwrite() behaviour with at least VSI C V7.4-001 on OpenVMS IA64 V8.4-2L3 meaning fwrite("",0,1,fp) fails to write a record 09-JUL-2011 MGD MtaMsgToFile() accomodate addresses containing hyphens 11-MAY-2008 MGD MtaMsgSmtpToServer() allow but do not RCPT any address "(whatever)"@whatever (with BCC allows non-disclosure) 07-NOV-2007 MGD MtaMsgSmtpToServer() improve error detection and reporting 15-APR-2007 MGD MtaHostNameByAddr() convenience function 04-FEB-2007 MGD bugfix; MtaAddressExtract() *cptr assign instead of equality 12-JUN-2006 MGD modifications to MtaRfcBinDate() and MtaTimezone() to allow binary time and timezone to be passed as parameters 06-MAY-2006 MGD bugfix; AddressIsRfc() RFC2822 only allows the double quotation character (0x34) to quote bugfix; AddressIsRfc() and MtaAddressExtract() allow backslash (0x92) to escape a single character bugfix; MtaMsgSmtpToServer() thanks Hunter Goatley 21-APR-2006 MGD MtaMsgSmtpToServer() allow a port number to be specified with the SMTP server host (i.e. "the.host.name:25") 21-MAR-2006 MGD MtaSmtpServerPoll() to provide MTA details for watch 15-MAR-2006 MGD use MtaAddressExtract() for SMTP "MAIL from:<..>" address 01-FEB-2005 MGD initial */ /*****************************************************************************/ #ifdef SOYMAIL_VMS_V7 #undef _VMS_V6_SOURCE #define _VMS_V6_SOURCE #undef __VMS_VER #define __VMS_VER 70000000 #undef __CRTL_VER #define __CRTL_VER 70000000 #endif /* standard C header files */ #include #include #include #include #include #include #include #include /* VMS related header files */ #include #include #include #include #include #include #include #include /* Internet-related header files */ #include #include #include /* application header files */ #include "cgilib.h" /** HP C V7.1-015 on OpenVMS Alpha V8.3 reports the following (apparently) because of a nesting error in RESOLV.H due to the __CRTL_VER constraint that WASD applications are built with. This does not happen with Compaq C V6.4-005 on OpenVMS Alpha V7.3-2 for instance. Just avoid the error by pretending the header file is already loaded. Doesn't *seem* to cause side-effects. #pragma __member_alignment __restore ........^ %CC-W-ALIGNPOP, This "restore" has underflowed the member alignment's stack. No corresponding "save" was found. at line number 451 in module RESOLV of text library SYS$COMMON:[SYSLIB]DECC$RTLD **/ #define __RESOLV_LOADED 1 #include /* application specific */ #include "mta.h" /* define required values from UCX$INETDEF.H (Multinet does not supply one) */ #define INET_PROTYP$C_STREAM 1 #define INETACP$C_TRANS 2 #define INETACP_FUNC$C_GETHOSTBYNAME 1 #define INETACP_FUNC$C_GETHOSTBYADDR 2 #define UCX$C_AF_INET 2 #define UCX$C_DSC_ALL 2 #define UCX$C_FULL_DUPLEX_CLOSE 8192 #define UCX$C_REUSEADDR 4 #define UCX$C_SOCK_NAME 4 #define UCX$C_SOCKOPT 1 #define UCX$C_TCP 6 #define RMS$_RNF 98994 #define VMSok(x) ((x) & STS$M_SUCCESS) #define VMSnok(x) !(((x) & STS$M_SUCCESS)) #define FI_LI __FILE__, __LINE__ struct AnIOsb { unsigned short Status; unsigned short Count; unsigned long Unused; } IOsb; $DESCRIPTOR (InetDeviceDsc, "UCX$DEVICE"); int OptionEnabled = 1; struct { unsigned short Length; unsigned short Parameter; void *Address; } ReuseAddress = { sizeof(OptionEnabled), UCX$C_REUSEADDR, &OptionEnabled }, ReuseAddressSocketOption = { sizeof(ReuseAddress), UCX$C_SOCKOPT, &ReuseAddress }; unsigned short TcpSocket [3] = { UCX$C_TCP, INET_PROTYP$C_STREAM, UCX$C_AF_INET }; /*************/ /* text list */ /*************/ typedef struct TEXT_DATA_STRUCT { void *NextPtr; char *TextPtr; int TextSize; int TextLength; } TEXT_DATA; #define TEXT_DATA_SIZEOF (sizeof(void*)+sizeof(char*)+sizeof(int)+sizeof(int)) #define TEXT_DATA_CALLOC(tlptr,size) \ tlptr = (struct TEXT_DATA_STRUCT*) CgiLibVeeMemCalloc (TEXT_DATA_SIZEOF+size); \ if (tlptr) tlptr->TextSize = size; #define TEXT_DATA_NEXT(tlptr) (((struct TEXT_DATA_STRUCT*)tlptr)->NextPtr) #define TEXT_DATA_TEXT(tlptr) (((struct TEXT_DATA_STRUCT*)tlptr)->TextPtr) #define TEXT_DATA_SIZE(tlptr) (((struct TEXT_DATA_STRUCT*)tlptr)->TextSize) #define TEXT_DATA_LENGTH(tlptr) (((struct TEXT_DATA_STRUCT*)tlptr)->TextLength) /* if the text follows immediately on from the structure */ #define TEXT_DATA_SELF(tlptr) ((char*)(tlptr+TEXT_DATA_SIZEOF)) /********************/ /* external storage */ /********************/ int WatchThis (char*, ...); int WatchDump (char*, int); extern int Debug, WatchEnabled; int ErrorExit (int, char*, int); /*****************************************************************************/ /* An elementary, standalone SMTP Message Transport Agent (MTA). */ char* MtaMsgSmtpToServer ( char *HostName, char *AddrFrom, char *AddrTo, char *HeaderText, void *HeaderList, char *BodyText, void *BodyList ) { static char ErrorBuffer [1024+1], ReadBuffer [1024+1]; int len, status, AddressCount, HeaderLength, ReplyCode, SectionCount, SmtpPort = 25, InsideData = 0; unsigned short SmtpChannel, TwoCharBuffer; unsigned long BinTime [2]; char *aptr, *cptr, *sptr, *zptr; char LocalHostName [128], SmtpHostName [128], WriteBuffer [1024+1]; TEXT_DATA *tlptr; TEXT_DATA DummyTextList; /*********/ /* begin */ /*********/ if (Debug) fprintf (stdout, "MtaMsgSmtpToServer()\n"); if (gethostname (LocalHostName, sizeof(LocalHostName))) strcpy (LocalHostName, "localhost"); if (!HostName) HostName = LocalHostName; zptr = (sptr = SmtpHostName) + sizeof(SmtpHostName)-1; for (cptr = HostName; *cptr && *cptr != ':' && sptr < zptr; *sptr++ = *cptr++); *sptr = '\0'; if (*cptr) { cptr++; if (isdigit(*cptr)) SmtpPort = atoi(cptr); } status = MtaOpenNet (&SmtpChannel, SmtpHostName, SmtpPort, NULL); if (VMSnok (status)) { sprintf (ReadBuffer, "%s", MtaSysGetMsg(status)); return (ReadBuffer); } ReplyCode = MtaWriteSmtpRead (SmtpChannel, NULL, 0, ReadBuffer, sizeof(ReadBuffer), 1); if (ReplyCode != 220) { sys$dassgn (SmtpChannel); return (ReadBuffer); } len = sprintf (WriteBuffer, "HELO %s\r\n", HostName); ReplyCode = MtaWriteSmtpRead (SmtpChannel, WriteBuffer, len, ReadBuffer, sizeof(ReadBuffer), 1); if (ReplyCode != 250) { MtaWriteSmtpRead (SmtpChannel, "QUIT\r\n", -1, NULL, 0, 0); sys$dassgn (SmtpChannel); return (ReadBuffer); } cptr = AddrFrom; sptr = MtaAddressExtract (&cptr); len = sprintf (WriteBuffer, "MAIL from:<%s>\r\n", sptr); ReplyCode = MtaWriteSmtpRead (SmtpChannel, WriteBuffer, len, ReadBuffer, sizeof(ReadBuffer), 1); if (ReplyCode != 250) { MtaWriteSmtpRead (SmtpChannel, "QUIT\r\n", -1, NULL, 0, 0); sys$dassgn (SmtpChannel); for (zptr = ReadBuffer; *zptr && *zptr != '\r' && *zptr != '\n'; zptr++); *zptr = '\0'; sprintf (ErrorBuffer, "%s: %s", ReadBuffer, AddrFrom); return (ErrorBuffer); } cptr = AddrTo; while (*cptr) { sptr = MtaAddressExtract (&cptr); /* continue if no address could be parsed */ if (!sptr || !*sptr) continue; /* if an "(undisclosed)" address just continue */ if (*(USHORTPTR)sptr == '\"(') { for (aptr = sptr+2; *aptr && *aptr != ')'; aptr++); if (*(USHORTPTR)aptr == ')\"') continue; } len = sprintf (WriteBuffer, "RCPT to:<%s>\r\n", sptr); ReplyCode = MtaWriteSmtpRead (SmtpChannel, WriteBuffer, len, ReadBuffer, sizeof(ReadBuffer), 1); if (ReplyCode != 250) { MtaWriteSmtpRead (SmtpChannel, "QUIT\r\n", -1, NULL, 0, 0); sys$dassgn (SmtpChannel); for (zptr = ReadBuffer; *zptr && *zptr != '\r' && *zptr != '\n'; zptr++); *zptr = '\0'; sprintf (ErrorBuffer, "%s: %s", ReadBuffer, sptr); return (ErrorBuffer); } } ReplyCode = MtaWriteSmtpRead (SmtpChannel, "DATA\r\n", 6, ReadBuffer, sizeof(ReadBuffer), 1); if (ReplyCode != 354) { MtaWriteSmtpRead (SmtpChannel, "QUIT\r\n", -1, NULL, 0, 0); sys$dassgn (SmtpChannel); return (ReadBuffer); } if (!HeaderList) { if (!HeaderText || !strchr(HeaderText, '\n')) { /* construct a primitive RFC822 header */ sys$gettim (&BinTime); /* create a minimal RFC822 header for the message */ len = sprintf (WriteBuffer, "Message-ID: <%08.08X%08.08X@%s>\r\n\ Date: %s\r\n\ Subject: %s\r\n\ From: %s\r\n\ To: ", BinTime[0], BinTime[1], LocalHostName, MtaRfcBinDate(NULL,NULL), HeaderText ? HeaderText : "(none)", AddrFrom); zptr = (sptr = WriteBuffer) + sizeof(WriteBuffer) - len - 1; sptr += len; AddressCount = 0; cptr = AddrTo; while (*cptr) { aptr = cptr; /* now find the start of the/any next line */ while (*cptr && *cptr != '\r' && *cptr != '\n') cptr++; if (aptr < cptr) { if (AddressCount++ && sptr < zptr) *sptr++ = ' '; while (aptr < cptr && sptr < zptr) *sptr++ = *aptr++; if (sptr < zptr) *sptr++ = '\r'; if (sptr < zptr) *sptr++ = '\n'; } while (*cptr && (*cptr == '\r' || *cptr == '\n')) cptr++; } if (sptr < zptr) *sptr++ = '\r'; if (sptr < zptr) *sptr++ = '\n'; *sptr = '\0'; HeaderText = WriteBuffer; HeaderLength = sptr - HeaderText; if (Debug) fprintf (stdout, "%d |%s|\n", HeaderLength, HeaderText); } else HeaderLength = strlen(HeaderText); } if (!(tlptr = HeaderList)) { /* fake a body text list */ tlptr = &DummyTextList; TEXT_DATA_TEXT(tlptr) = HeaderText; TEXT_DATA_SIZE(tlptr) = TEXT_DATA_LENGTH(tlptr) = HeaderLength; TEXT_DATA_NEXT(tlptr) = NULL; } while (tlptr) { cptr = TEXT_DATA_TEXT(tlptr); len = TEXT_DATA_LENGTH(tlptr); tlptr = TEXT_DATA_NEXT(tlptr); if (!cptr) continue; ReplyCode = MtaWriteSmtpRead (SmtpChannel, cptr, len, NULL, 0, 0); if (ReplyCode & 0x8000000) { /* high bit set indicates a VMS status (bit kludgy I'll admit) */ sys$dassgn (SmtpChannel); sprintf (ReadBuffer, "%s", MtaSysGetMsg(ReplyCode & 0x7fffffff)); return (ReadBuffer); } } if (!(tlptr = BodyList)) { /* fake a body text list */ tlptr = &DummyTextList; TEXT_DATA_TEXT(tlptr) = BodyText ? BodyText : ""; TEXT_DATA_SIZE(tlptr) = TEXT_DATA_LENGTH(tlptr) = strlen(TEXT_DATA_TEXT(tlptr)); TEXT_DATA_NEXT(tlptr) = NULL; } while (tlptr) { cptr = TEXT_DATA_TEXT(tlptr); tlptr = TEXT_DATA_NEXT(tlptr); if (!cptr) continue; while (*cptr) { for (sptr = cptr; *sptr && *sptr != '\r' && *sptr != '\n'; sptr++); TwoCharBuffer = *(USHORTPTR)sptr; *(USHORTPTR)sptr = '\r\n'; if (*cptr == '.') { if (sptr-cptr+2 > sizeof(WriteBuffer)-1) ErrorExit (SS$_BUGCHECK, FI_LI); WriteBuffer[0] = '.'; memcpy (WriteBuffer+1, cptr, sptr-cptr+2); ReplyCode = MtaWriteSmtpRead (SmtpChannel, WriteBuffer, sptr-cptr+3, NULL, 0, 0); } else ReplyCode = MtaWriteSmtpRead (SmtpChannel, cptr, sptr-cptr+2, NULL, 0, 0); if (ReplyCode & 0x8000000) { /* high bit set indicates a VMS status (bit kludgy I'll admit) */ sys$dassgn (SmtpChannel); sprintf (ReadBuffer, "%s", MtaSysGetMsg(ReplyCode & 0x7fffffff)); return (ReadBuffer); } *(USHORTPTR)sptr = TwoCharBuffer; cptr = sptr; if (*cptr == '\r') cptr++; if (*cptr == '\n') cptr++; } } ReplyCode = MtaWriteSmtpRead (SmtpChannel, ".\r\n", 3, ReadBuffer, sizeof(ReadBuffer), 1); MtaWriteSmtpRead (SmtpChannel, "QUIT\r\n", -1, NULL, 0, 0); sys$dassgn (SmtpChannel); if (ReplyCode != 250) return (ReadBuffer); return (NULL); } /*****************************************************************************/ /* Reproduce the same message content as in MtaMsgToSmtp() but into a file. See CallMailCopyFile() for a description of the sequential file format. Constrain the header strings used in the VMS format message to provide a maximum of 255 characters otherwise CallMailCopyFile() will barf. 09-JUL-2011 Note: Christoph Gartmann reported a host name change from imunbio to ie-freiburg resulted in the sent items folder stopping working and/or strange dates appearing in the VMS Mail message header(s). soyMAIL's "Sent Items" facility works as described in CallMailCopyFile() and it appears that the mail$message_copy() parse of the "From:" line confuses a hyphen in an address with a date component! Solution is to fudge a mail transport to allowing quoting of the address. This is only done when a hyphen is detected. */ int MtaMsgToFile ( char *FileName, char *FromPtr, char *ToPtr, char *CcPtr, char *SubjPtr, void *HeaderList, void *BodyList ) { static $DESCRIPTOR (DateTimeFaoDsc, "!%D\0"); int retval, status; int64 Time64; char *cptr, *sptr, *zptr; char Cc255 [256], DateTime [32], FromEsc [256], Subj255 [256], To255 [256]; $DESCRIPTOR (DateTimeDsc, DateTime); FILE *fp; TEXT_DATA *tlptr; /*********/ /* begin */ /*********/ if (Debug) fprintf (stdout, "MtaMsgToFile()\n"); if (!HeaderList || !BodyList) ErrorExit (SS$_BUGCHECK, FI_LI); /* need to limit to a VMS Mail acceptable 255 */ zptr = (sptr = To255) + sizeof(To255)-1; for (cptr = ToPtr; *cptr && *cptr != '\n' && sptr < zptr; *sptr++ = *cptr++); *sptr = '\0'; /* need to limit to a VMS Mail acceptable 255 */ zptr = (sptr = Cc255) + sizeof(Cc255)-1; for (cptr = CcPtr; *cptr && *cptr != '\n' && sptr < zptr; *sptr++ = *cptr++); *sptr = '\0'; /* need to limit to a VMS Mail acceptable 255 */ zptr = (sptr = Subj255) + sizeof(Subj255)-1; for (cptr = SubjPtr; *cptr && sptr < zptr; *sptr++ = *cptr++); *sptr = '\0'; 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"); /* hyphens are confusing (see note above) */ if (strchr (FromPtr, '-')) { /* one for the null, one for the trailing quote, one elbow-room */ zptr = (sptr = FromEsc) + sizeof(FromEsc)-3; for (cptr = "SOYMAIL%\""; *cptr && sptr < zptr; *sptr++ = *cptr++); /* escape existing quotes by doubling-up */ for (cptr = FromPtr; *cptr && sptr < zptr; *sptr++ = *cptr++) if (*cptr == '\"') *sptr++ = '\"'; *sptr++ = '\"'; *sptr = '\0'; FromPtr = FromEsc; } /* 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", To255); fprintf (fp, "CC:\t%s\n", Cc255); retval = fprintf (fp, "Subj:\t%s\n\n", Subj255); /* start with the header list ... */ tlptr = HeaderList; while (tlptr) { cptr = TEXT_DATA_TEXT(tlptr); tlptr = TEXT_DATA_NEXT(tlptr); if (!tlptr) { /* ... and when that's exhausted continue with the body */ tlptr = BodyList; BodyList = NULL; } if (!cptr) continue; while (*cptr) { for (sptr = cptr; *sptr && *sptr != '\r' && *sptr != '\n'; sptr++); if (sptr - cptr) retval = fwrite (cptr, sptr-cptr, 1, fp); else retval = fwrite ("\n", 1, 1, fp); if (*sptr == '\r') sptr++; if (*sptr == '\n') sptr++; cptr = sptr; if (retval <= 0) break; } if (retval <= 0) break; } if (retval <= 0) status = vaxc$errno; else status = SS$_NORMAL; fclose (fp); return (status); } /*****************************************************************************/ /* Connect to the specified SMTP server to get it's announcement string into the watch output. */ void MtaSmtpServerPoll (char *HostName) { int status, SmtpPort = 25; unsigned short SmtpChannel; char *cptr, *sptr, *zptr; char LocalHostName [128], SmtpHostName [128], ReadBuffer [255+1]; /*********/ /* begin */ /*********/ if (Debug) fprintf (stdout, "MtaSmtpServerPoll()\n"); if (gethostname (LocalHostName, sizeof(LocalHostName))) strcpy (LocalHostName, "localhost"); if (!HostName) HostName = LocalHostName; zptr = (sptr = SmtpHostName) + sizeof(SmtpHostName)-1; for (cptr = HostName; *cptr && *cptr != ':' && sptr < zptr; *sptr++ = *cptr++); *sptr = '\0'; if (*cptr) { cptr++; if (isdigit(*cptr)) SmtpPort = atoi(cptr); } if (WatchEnabled) WatchThis ("MTA poll ..."); status = MtaOpenNet (&SmtpChannel, SmtpHostName, SmtpPort, NULL); if (VMSnok (status)) { if (WatchEnabled) WatchThis ("%X!8XL !AZ", status, MtaSysGetMsg(status)); return; } MtaWriteSmtpRead (SmtpChannel, NULL, 0, ReadBuffer, sizeof(ReadBuffer), 1); sys$qiow (0, SmtpChannel, IO$_WRITEVBLK, 0, 0, 0, "QUIT\r\n", 6, 0, 0, 0, 0); sys$dassgn (SmtpChannel); } /****************************************************************************/ /* Does two things. First, optionlly write a string to the SMTP server. If 'DataPtr' is NULL this is not done. Second, reads an optional reply line from the server. It returns the three digit SMTP status code as an integer. */ int MtaWriteSmtpRead ( unsigned short SmtpChannel, char *DataPtr, int DataLength, char *DataBuffer, int SizeOfDataBuffer, int ExpectReply ) { int status, clen, ReadCount; char *cptr; char ReadBuffer [512+1]; /*********/ /* begin */ /*********/ if (Debug) fprintf (stdout, "MtaWriteSmtpRead() %d %*.*s\n", ExpectReply, DataLength, DataLength, DataPtr); if (DataPtr) { if (DataLength <= 0) DataLength = strlen(DataPtr); status = MtaWriteNet (SmtpChannel, DataPtr, DataLength); /* high bit set indicates a VMS status (bit kludgy I'll admit) */ if (VMSnok (status)) return (status | 0x8000000); } if (!ExpectReply) return (200); if (DataBuffer) { cptr = DataBuffer; clen = SizeOfDataBuffer; } else { cptr = ReadBuffer; clen = sizeof(ReadBuffer); } status = MtaReadNet (SmtpChannel, cptr, clen, &ReadCount, 1); /* high bit set indicates a VMS status (bit kludgy I'll admit) */ if (VMSnok (status)) { cptr = MtaSysGetMsg (status); if (DataBuffer && strlen (cptr) < SizeOfDataBuffer) strcpy (DataBuffer, cptr); return (status | 0x8000000); } return (atoi(cptr)); } /****************************************************************************/ /* Open a "socket" connect to the specific host name and port. If 'IpAddressPtr' is not NULL then set where it points to the 32 IP address of the remote system. */ MtaOpenNet ( unsigned short *ChannelPtr, char *HostName, int PortNumber, int *IpAddressPtr ) { int status, IpAddress; unsigned short Channel; char *cptr; struct AnIOsb IOsb; struct sockaddr_in SocketName; struct hostent *HostEntryPtr; struct { unsigned long Length; void *Address; int *LengthPtr; } SocketNameItem = { sizeof(SocketName), &SocketName, 0 }; /*********/ /* begin */ /*********/ if (Debug) fprintf (stdout, "MtaOpenNet() %s:%d\n", HostName, PortNumber); if (WatchEnabled) WatchThis ("SMTP >> !AZ:!UL", HostName, PortNumber); if (isdigit((cptr = HostName)[0])) while (*cptr && (isdigit(*cptr) || *cptr == '.')) cptr++; if (!*cptr) { /*************************/ /* address "131.185.2.4" */ /*************************/ if ((IpAddress = inet_addr (HostName)) == -1) return (vaxc$errno); } else { /***************************/ /* address "the.host.name" */ /***************************/ if (!(HostEntryPtr = gethostbyname (HostName))) return (SS$_NOSUCHNODE); memcpy (&IpAddress, HostEntryPtr->h_addr, 4); } /* assign a channel to the internet template device */ if (VMSnok (status = sys$assign (&InetDeviceDsc, &Channel, 0, 0))) return (status); /* make the channel a TCP, connection-oriented socket */ status = sys$qiow (0, Channel, IO$_SETMODE, &IOsb, 0, 0, &TcpSocket, 0, 0, 0, 0, 0); if (Debug) fprintf (stdout, "sys$qiow() %%X%08.08X IOsb.Status %%X%08.08X\n", status, IOsb.Status); if (VMSok (status) && VMSnok (IOsb.Status)) status = IOsb.Status; if (VMSnok (status)) return (status); SocketName.sin_family = AF_INET; SocketName.sin_port = htons (PortNumber); memcpy (&SocketName.sin_addr.s_addr, &IpAddress, 4); if (IpAddressPtr) memcpy (IpAddressPtr, &IpAddress, 4); status = sys$qiow (0, Channel, IO$_ACCESS, &IOsb, 0, 0, 0, 0, &SocketNameItem, 0, 0, 0); if (VMSok (status) && VMSnok (IOsb.Status)) status = IOsb.Status; if (VMSok (status)) *ChannelPtr = Channel; else { *ChannelPtr = 0; if (WatchEnabled) WatchThis ("SMTP >> %X!8XL", status); } return (status); } /****************************************************************************/ /* Read from the specific "socket" into the the supplied buffer. If 'AsText' is true the allow for and supply a terminating null character. If 'ReadCountPtr' is not NULL then set it's location to the number of bytes read. If read 'AsText' the read buffer is adjusted to allow for a terminating null. */ int MtaReadNet ( unsigned short Channel, char *DataBuffer, int SizeOfDataBuffer, int *ReadCountPtr, int AsText ) { int status; char *cptr; struct AnIOsb IOsb; /*********/ /* begin */ /*********/ if (Debug) fprintf (stdout, "MtaReadNet() %d %d\n", SizeOfDataBuffer, AsText); if (AsText) SizeOfDataBuffer--; status = sys$qiow (0, Channel, IO$_READVBLK, &IOsb, 0, 0, DataBuffer, SizeOfDataBuffer, 0, 0, 0, 0); if (Debug) fprintf (stdout, "sys$qiow() status %%X%08.08X IOsb.Status %%X%08.08X %d bytes\n", status, IOsb.Status, IOsb.Count); if (VMSok (status) && VMSnok (IOsb.Status)) status = IOsb.Status; if (VMSok (status)) { if (AsText) { char *cptr, *sptr, *zptr; DataBuffer[IOsb.Count] = '\0'; zptr = (cptr = DataBuffer) + IOsb.Count; while (cptr < zptr && *cptr != '\r' && *cptr != '\n') cptr++; sptr = cptr; if (cptr < zptr) cptr++; if (cptr < zptr) cptr++; if (WatchEnabled) WatchThis ("SMTP << {!UL-!UL}!#AZ", IOsb.Count, cptr-sptr, IOsb.Count-(cptr-sptr), DataBuffer); } if (ReadCountPtr) *ReadCountPtr = IOsb.Count; } else if (ReadCountPtr) { *ReadCountPtr = 0; if (WatchEnabled) WatchThis ("SMTP << %X!8XL", status); } return (status); } /****************************************************************************/ /* Write the supplied data to the specific "socket". If 'DataLength' is -1 then consider the data to be a null-terminated string and get it's length using strlen(). */ int MtaWriteNet ( unsigned short Channel, char *DataPtr, int DataLength ) { int status; struct AnIOsb IOsb; /*********/ /* begin */ /*********/ if (Debug) fprintf (stdout, "MtaWriteNet() %d |%*.*s|\n", DataLength, DataLength, DataLength, DataPtr); if (DataLength == -1) DataLength = strlen(DataPtr); if (WatchEnabled) { char *cptr, *sptr, *zptr; zptr = (cptr = DataPtr) + DataLength; while (cptr < zptr && *cptr != '\r' && *cptr != '\n') cptr++; sptr = cptr; if (cptr < zptr) cptr++; if (cptr < zptr) cptr++; WatchThis ("SMTP >> {!UL-!UL}!#AZ", DataLength, cptr-sptr, DataLength-(cptr-sptr), DataPtr); } status = sys$qiow (0, Channel, IO$_WRITEVBLK, &IOsb, 0, 0, DataPtr, DataLength, 0, 0, 0, 0); if (Debug) fprintf (stdout, "sys$qiow() %%X%08.08X IOsb.Status %%X%08.08X\n", status, IOsb.Status); if (VMSok (status) && VMSnok (IOsb.Status)) status = IOsb.Status; if (WatchEnabled) WatchThis ("SMTP >> %X!8XL", status); 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* MtaAddressExtract (char **ListPtrPtr) { static char abuf [256]; char *cptr, *lptr, *sptr, *zptr, *StartOfLinePtr; /*********/ /* begin */ /*********/ if (Debug) fprintf (stdout, "MtaAddressExtract() |%s|\n", *ListPtrPtr); if (!ListPtrPtr) return (NULL); if (!(lptr = *ListPtrPtr)) return (NULL); abuf[0] = '\0'; while (*lptr) { zptr = (sptr = abuf) + sizeof(abuf); *sptr = '\0'; while (*lptr && *lptr == '\n') lptr++; if (!*lptr) break; StartOfLinePtr = lptr; while (*lptr && *lptr != '\"' && *lptr != '@' && *lptr != '\n') { if (*lptr == '\\' && *(lptr+1) && *(lptr+1) != '\n') lptr++; lptr++; } if (!*lptr) break; if (*lptr == '\"') { cptr = lptr; lptr++; while (*lptr && *lptr != '\"' && *lptr != '\n') { if (*lptr == '\\' && *(lptr+1) && *(lptr+1) != '\n') lptr++; lptr++; } if (*lptr == '\"') lptr++; /* if it's not a quoted local part */ if (*lptr && *lptr != '@' && *lptr != '\n') continue; } else { /* scan back to get the start of the local part */ cptr = lptr; while (cptr > StartOfLinePtr) { if (isspace(*cptr)) { if (cptr > StartOfLinePtr) { if (*(cptr-1) != '\\') break; } else break; } cptr--; } while (*cptr && isspace(*cptr)) { if (*cptr == '\\' && *(cptr+1) && *(cptr+1) != '\n') cptr++; cptr++; } } if (*cptr == '<') cptr++; while (cptr < lptr && sptr < zptr) *sptr++ = *cptr++; while (*lptr && !isspace(*lptr) && sptr < zptr) *sptr++ = *lptr++; /* allow for comma-separated list of addresses */ if (sptr > abuf && *(sptr-1) == ',') sptr--; if (sptr > abuf && *(sptr-1) == '>') sptr--; if (sptr >= zptr) { abuf[0] = '!'; sptr = abuf + sizeof(abuf)-1; } *sptr = '\0'; /* scan to the end of this list line */ while (*lptr && *lptr != '\n') lptr++; while (*lptr && *lptr == '\n') lptr++; /* if an empty line */ if (!abuf[0]) continue; break; } if (WatchEnabled) WatchThis ("ADDRESS |!AZ", abuf); /* update the pointer to the list */ *ListPtrPtr = lptr; return (abuf); } /*****************************************************************************/ /* This function is included in MTA.C for pure convenience. It just confines the instances of the warning issue described immediately above the #include netdb.h above. */ char* MtaHostNameByAddr (char *IpAddrStr) { int IpAddr; char *cptr; struct hostent *HostEntryPtr; /*********/ /* begin */ /*********/ if (Debug) fprintf (stdout, "MtaHostNameByAddr()\n"); IpAddr = inet_addr (IpAddrStr); if (IpAddr == -1) return (NULL); HostEntryPtr = gethostbyaddr (&IpAddr, 4, AF_INET); if (!HostEntryPtr) return (NULL); cptr = CgiLibVeeMemCalloc (strlen(HostEntryPtr->h_name)+1); if (cptr) strcpy (cptr, HostEntryPtr->h_name); return (cptr); } /*****************************************************************************/ /* Return a pointer to an allocated string in the RFC/Unix-style format (e.g. "31 Mar 2005 20:35:16 +0930") for the supplied VMS binary time (current if NULL). */ char* MtaRfcBinDate ( unsigned long *Time64Ptr, char *TzDiffPtr ) { static char *DayName[] = { "", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun" }; static char *MonName[] = { "", "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" }; int len; char RfcDateString [64]; char *sptr; unsigned short NumTime [7]; unsigned long DayOfWeek; /*********/ /* begin */ /*********/ if (Debug) fprintf (stdout, "MtaRfcBinDate()\n"); sys$numtim (&NumTime, Time64Ptr); lib$day_of_week (Time64Ptr, &DayOfWeek); len = sprintf (RfcDateString, "%s, %02d %s %4d %02d:%02d:%02d %s", DayName[DayOfWeek], NumTime[2], MonName[NumTime[1]], NumTime[0], NumTime[3], NumTime[4], NumTime[5], MtaTimezone(TzDiffPtr)); sptr = CgiLibVeeMemCalloc (len+1); memcpy (sptr, RfcDateString, len); return (sptr); } /*****************************************************************************/ /* The SYS$TIMEZONE_DIFFERENTIAL logical contains the number of seconds offset from GMT (UTC) as a positive (ahead) or negative (behind) number. Return a pointer to a string containg "+hhmm" or "-hhmm" string equivalent. */ char* MtaTimezone (char *TzDiffPtr) { static char RfcTimezone [8], SysTimeDiff [8]; static unsigned short Length; static $DESCRIPTOR (TimezoneLogicalNameDsc, "SYS$TIMEZONE_DIFFERENTIAL"); static $DESCRIPTOR (LnmSystemDsc, "LNM$SYSTEM"); static struct { short int buf_len; short int item; void *buf_addr; unsigned short *ret_len; } LnmItems [] = { { sizeof(SysTimeDiff)-1, LNM$_STRING, SysTimeDiff, &Length }, { 0,0,0,0 } }; int status; long hh, mm, secs; char *cptr; /*********/ /* begin */ /*********/ if (Debug) fprintf (stdout, "MtaTimezone()\n"); if (!TzDiffPtr) { status = sys$trnlnm (0, &LnmSystemDsc, &TimezoneLogicalNameDsc, 0, &LnmItems); if (VMSnok (status)) return ("-0000"); SysTimeDiff[Length] = '\0'; TzDiffPtr = SysTimeDiff; } secs = atol(TzDiffPtr); if (secs < 0) { secs = -secs; cptr = "-"; } else cptr = "+"; if (secs > 43200 || secs < -43200) return ("+0000"); hh = secs / 3600; mm = (secs - hh * 3600) / 60; sprintf (RfcTimezone, "%s%02d%02d", cptr, hh, mm); return (RfcTimezone); } /*****************************************************************************/ /* */ char* MtaSysGetMsg (int StatusValue) { static char StatusMsg [256]; int status; unsigned short Length; char *cptr; $DESCRIPTOR (StatusMsgDsc, StatusMsg); /*********/ /* begin */ /*********/ if (Debug) fprintf (stdout, "MtaSysGetMsg() %%X%08.08X\n", StatusValue); status = sys$getmsg (StatusValue, &Length, &StatusMsgDsc, 1, 0); if (!(status & 1)) exit (status); StatusMsg[Length] = '\0'; StatusMsg[0] = toupper(StatusMsg[0]); if (cptr = strstr (StatusMsg, "!/!_")) *(ULONGPTR)cptr = '. '; return (StatusMsg); } /****************************************************************************/