/*****************************************************************************/ /* tMAILer.c This is a drop-in replacement for the OSU TMAIL script (original authors Dick Monroe and David Jones) for the WASD environment. Written from scratch, it is intended to be backwardly compatible. Being directly CGI process based it will avoid that initial latency when a DECnet script is activated. The concept is roughly this. A form's ACTION= element specifies this script and another file, a template file, as the path. The script opens the template file and uses it's contents to parse the form's field(s), generating and sending a mail message. If there is a large number of existing OSU-based TMAIL forms/templates in use the following HTTPD$MAP rule will simply result in them being processed by TMAILER instead. map /htbin/tmail/* /cgi-bin/tmailer/* FORM EXAMPLE ------------ |
|Comments will be mailed to Web administrator: | | ... your email address (confidential) |
| |
Thankyou. |
An example template file for processing this form is shown below. TEMPLATE FILE FORMAT -------------------- The template file is plain text, and is commonly named with .TMAIL as the extension (although it could be made anything). It comprises at least two and more often three sections. The first, separated from the next by a blank line, is termed the header section and contains a version, mail message and response directives. The first line must always be the "tmail:" version, as follows: tmail: 1.1 This ensures the file is intended as a template. The next line should be the destination of the mail message. WASD-specific allows a comma-separated list of addresses. WASD-specific also allows a "To:" address to begin with a '?'. In this case a facsimile of the received message is returned to the client. Another variant allows the address to begin with a '!', suppressing actual mailing but continuing on to deliver a response to the client. Both useful for template "debugging" or demonstration purposes. to: address[,address2,address3] to: ?address to: !address An optional subject line should be provided. subject: This is the subject of the "subject:" directive Two more optional directives control the response to the request. success: URL to redirect the client to after successful mailing success-status: HTTP status code to return instead of 200 Another optional directive sets the "personal name" part of the source email address of the message which TMAILer sends: personal: personal name string If this directive is not supplied, a default name will be used based on the name of the template file. The second section of the template file (remember this is separated from the header section by a blank, or empty, line) is the body section. The text in this section (plus any expanded tag information, see below) is what is mailed to the destination address specified with the "to:" above. The third, and completely optional section, allows a specific response to be returned to the client. The command tag "[%%end]" delimits the second section from the third section. The third section should be in the format of a CGI script response. It should therefore begin with a "Content-Type: text/html", an empty line, and then a response body. TAGS ---- Tags mark points in the template where the contents of a form field or CGI variable is inserted into the text. Tags may be used anywhere within the template file. If a field or CGI variable does not exist the string "%unknown" is returned. The following characters are reserved and can be escaped by preceding them with a '[' character, as listed here: '[' escape using "[[" ']' escape using "[]" ':' escape using "[:" '?' escape using "[?" Tag information is introduced using the '[' and concluded with ']' characters. Three tag formats are supported: [field-name] the data associated with the form field name [%cgi-var-name] the contents of a CGI variable [%%command] a directive to the template processor Form field names inside tags are case-sensitive. CGI variable names are not. COMMAND TAGS ------------ Tags beginning with "%%" provide directives to the template processor. Three OSU and one WASD-specifc command tags are defined. [%%end] Indicates the end of the mail message contents. Any lines following are considered to be a CGI-compatible response to be returned to request client. It should therefore begin with a "Content-Type: text/html", an empty line, and then a response body. [%%entify] Enables conversion of HTML forbidden characters (i.e. '<', '>', '&') to the corresponding HTML entity (i.e. "<", "gt;", "&"). By default this conversion is disabled during mail message composition (i.e. (before a [%%end]) and enabled during any CGI response composition (i.e. after a [%%end]). [%%noentify] Disables conversion to HTML entities, as described above. [%%reveal] This is WASD-specific. It displays the CGI variables and the form fields and then exits, obviously for template "debugging" purposes. The CGI variables are shown with the prefixing "WWW_" which is optional when specifying tag field names. Place this immediately after the "tmail:" version directive. CONDITIONAL EXPANSION --------------------- Extensions to the tag format allows information other than the exact contents of a tag field or CGI variable to be inserted based on the contents of that field or variable! Two OSU and two WASD-specific variants are available. [field-name:string] [%cgi-var-name:string] If the form field or CGI variable contents are not null (i.e. the tag-name exists and contains a non-empty string) the string following the ':' (which may contain white-space, escaped characters, etc., but no nested tags) is inserted into the text. If the field or variable does not exist or is empty the string is not inserted. [field-name:string1::string2] [%cgi-var-name:string1::string2] This is WASD-specific. Like the simple variant this one inserts the text of string1 if the field name is not empty. However if it is empty and a "::" delimits a second string then that is inserted. [field-name?string] [%cgi-var-name?string] If the form field or CGI variable contents contain the literal "on" (for evaluating checkboxes) then the string following the '?' is inserted into the text, otherwise it is not. [field-name?string1??string2] [%cgi-var-name?string1??string2] This is WASD-specific. Like the simple variant this one inserts the text of string1 if the field name contains "on". However if it is does not and a "??" delimits a second string then that is inserted. TEMPLATE FILE EXAMPLE --------------------- The following example template file provides all three sections, header, body and response (the '|' just indicates the limits of the file): This template could be used to process the form in the example above. |tmail: 1.1 |to: WEBADMIN |subject: This is just an example (from [%SERVER_HOST])! | |This is the body of the message mailed to the above address. |The message was sent from [%HOST_ADDR] |by a client using "[%USER_AGENT]". | |The form contained a field named "comments", it's contents are: |--------------------------------------------------------------- |[comments] |--------------------------------------------------------------- | |The field "email" contains "[email]" |and [email:was::was not] completed by the client. | |This is the end of the mail message body. |What follows after the "[[%%end[]" is a CGI response. |[%%end] |Content-type: text/html | | | |The message was successfully mailed. Thankyou for your comments. | OSU NOTE! --------- Although this code has build capabilities for the OSU environment it is not intended to encroach on functionality already present in the OSU package. I have this in here merely for testing the CGILIB POSTed body processing functionality. QUALIFIERS ---------- /CHARSET= "Content-Type: text/html; charset=..." /DBUG turns on all "if (Debug)" statements /SOFTWAREID (and /VERSION) display TMAILER and CGILIB versions LOGICAL NAMES ------------- TMAILER$DBUG turns on all "if (Debug)" statements TMAILER$PARAM equivalent to (overrides) the command line parameters/qualifiers (define as a system-wide logical) BUILD DETAILS ------------- See BUILD_ONE.COM procedure. COPYRIGHT --------- Copyright (C) 1999-2021 Mark G.Daniel Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. VERSION HISTORY (update SOFTWAREVN as well!) --------------- 10-SEP-2009 MGD v1.3.4, break rather than truncate body lines at 255 23-DEC-2003 MGD v1.3.3, minor conditional mods to support IA64 28-NOV-2001 JMB v1.3.2, add 'personal' directive 01-JUL-2001 MGD v1.3.1, add 'SkipParameters' for direct OSU support 28-OCT-2000 MGD v1.3.0, use CGILIB object module, support RELAXED_ANSI compilation 12-APR-2000 MGD v1.2.1, minor changes for CGILIB 1.4 07-AUG-1999 MGD v1.2.0, use more of the CGILIB functionality 24-APR-1999 MGD v1.1.0, use CGILIB.C 31-MAR-1999 MGD v1.0.1, a couple of wrinkles 27-MAR-1999 MGD v1.0.0, initial development */ /*****************************************************************************/ #define SOFTWAREVN "1.3.4" #define SOFTWARENM "TMAILER" #ifdef __ALPHA # define SOFTWAREID SOFTWARENM " AXP-" SOFTWAREVN #endif #ifdef __ia64 # define SOFTWAREID SOFTWARENM " IA64-" SOFTWAREVN #endif #ifdef __VAX # define SOFTWAREID SOFTWARENM " VAX-" SOFTWAREVN #endif #ifdef __x86_64 # define SOFTWAREID SOFTWARENM " X86-" SOFTWAREVN #endif /* standard C header files */ #include #include #include #include #include #include #include #include /* VMS-related header files */ #include #include #include #include #define boolean int #define true 1 #define false 0 /* application related header file */ #include char CopyrightInfo [] = "Copyright (C) 2005-2009 Mark G.Daniel.\n\ This program, comes with ABSOLUTELY NO WARRANTY.\n\ This is free software, and you are welcome to redistribute it under the\n\ conditions of the GNU GENERAL PUBLIC LICENSE, version 3, or any later version.\n\ http://www.gnu.org/licenses/gpl.txt\n"; #ifndef __VAX # pragma nomember_alignment #endif #define VMSok(x) ((x) & STS$M_SUCCESS) #define VMSnok(x) (!((x) & STS$M_SUCCESS)) #define FI_LI __FILE__, __LINE__ #define TMAIL_VERSION "1.1" #define FORM_URLENCODED "application/x-www-form-urlencoded" #define UNKNOWN_TAG "%unknown" #define ISLWS(c) (c == ' ' || c == '\t') char Utility [] = "TMAILER"; boolean Debug, CommandTagEnd, CommandTagEntify; int BufferCount, CgiResponseLength, CgiResponseOffset, HeaderTmailOffset, HeaderToOffset, HeaderSubjectOffset, HeaderSuccessOffset, HeaderSuccessStatusOffset, HeaderPersonalOffset, ParsedTemplateLength, RequestDataLength, TemplateBodyLength, TemplateBodyOffset, ThereHasBeenOutput; char *BufferPtr, *CharsetPtr, *CgiContentTypePtr, *CgiEnvironmentPtr, *CgiPathInfoPtr, *CgiPathTranslatedPtr, *CgiRequestMethodPtr, *CgiResponsePtr, *CgiServerSoftwarePtr, *CliCharsetPtr, *HeaderToPtr, *HeaderSubjectPtr, *HeaderSuccessPtr, *HeaderSuccessStatusPtr, *HeaderPersonalPtr, *ParsedTemplatePtr, *RequestDataPtr, *TemplateBodyPtr; char CharsetString [64], ContentTypeCharset [64], SoftwareID [48]; /* required function prototypes */ char* ConditionalExpansion (char*, char*); char* TagCgiVar (char*, char*); char* TagFormCgiVar (char*, char*); /*****************************************************************************/ /* 'argc/argv' are only required to support OSU, in particular CgiLibOsuInit(). */ main ( int argc, char *argv[] ) { /*********/ /* begin */ /*********/ sprintf (SoftwareID, "%s (%s)", SOFTWAREID, CgiLibEnvironmentVersion()); if (getenv ("TMAILER$DBUG") != NULL) Debug = true; if (Debug) fprintf (stdout, "Content-Type: text/plain\n\n"); CgiLibEnvironmentSetDebug (Debug); GetParameters (); CgiLibEnvironmentInit (argc, argv, false); CgiLibResponseSetCharset (CliCharsetPtr); CgiLibResponseSetSoftwareID (SoftwareID); CgiLibResponseSetErrorMessage ("Reported by TMailer"); CgiEnvironmentPtr = CgiLibEnvironmentName (); ProcessRequest (); exit (SS$_NORMAL); } /*****************************************************************************/ /* Get "command-line" parameters, whether from the command-line or from a configuration symbol or logical containing the equivalent. OSU scripts have the 'method', 'url' and 'protocol' supplied as P1, P2, P3 (these being detected and used by CGILIB), and are of no interest to this function. */ GetParameters () { static char CommandLine [256]; static unsigned long Flags = 0; int status, SkipParameters; unsigned short Length; char ch; char *aptr, *cptr, *clptr, *sptr; $DESCRIPTOR (CommandLineDsc, CommandLine); /*********/ /* begin */ /*********/ if (Debug) fprintf (stdout, "GetParameters()\n"); if ((clptr = getenv ("TMAILER$PARAM")) == NULL) { /* get the entire command line following the verb */ if (VMSnok (status = lib$get_foreign (&CommandLineDsc, 0, &Length, &Flags))) exit (status); (clptr = CommandLine)[Length] = '\0'; } /* if OSU environment then skip P1, P2, P3 */ if (getenv ("WWWEXEC_RUNDOWN_STRING") != NULL) SkipParameters = 3; else SkipParameters = 0; aptr = NULL; ch = *clptr; for (;;) { if (aptr != NULL && *aptr == '/') *aptr = '\0'; if (!ch) break; *clptr = ch; if (Debug) fprintf (stdout, "clptr |%s|\n", clptr); while (*clptr && isspace(*clptr)) *clptr++ = '\0'; aptr = clptr; if (*clptr == '/') clptr++; while (*clptr && !isspace (*clptr) && *clptr != '/') { if (*clptr != '\"') { clptr++; continue; } cptr = clptr; clptr++; while (*clptr) { if (*clptr == '\"') if (*(clptr+1) == '\"') clptr++; else break; *cptr++ = *clptr++; } *cptr = '\0'; if (*clptr) clptr++; } ch = *clptr; if (*clptr) *clptr = '\0'; if (Debug) fprintf (stdout, "aptr |%s|\n", aptr); if (!*aptr) continue; if (SkipParameters) { SkipParameters--; continue; } if (strsame (aptr, "/CHARSET=", 4)) { for (cptr = aptr; *cptr && *cptr != '='; cptr++); if (*cptr) cptr++; CliCharsetPtr = cptr; continue; } if (strsame (aptr, "/DBUG", -1)) { Debug = true; continue; } if (strsame (aptr, "/SOFTWAREID", 4) || strsame (aptr, "/VERSION", 4)) { fprintf (stdout, "%%%s-I-SOFTWAREID, %s\n%s\n", Utility, SoftwareID, CopyrightInfo); exit (SS$_NORMAL); } if (*aptr == '/') { fprintf (stdout, "%%%s-E-IVQUAL, unrecognized qualifier\n \\%s\\\n", Utility, aptr+1); exit (STS$K_ERROR | STS$M_INHIB_MSG); } fprintf (stdout, "%%%s-E-MAXPARM, too many parameters\n \\%s\\\n", Utility, aptr); exit (STS$K_ERROR | STS$M_INHIB_MSG); } } /*****************************************************************************/ /* */ ProcessRequest () { int status, StatusCode, TemplateLength; char *cptr, *TemplatePtr; char PersonalName [256]; /*********/ /* begin */ /*********/ if (Debug) fprintf (stdout, "ProcessRequest()\n"); CgiServerSoftwarePtr = CgiLibVar ("WWW_SERVER_SOFTWARE"); CgiPathInfoPtr = CgiLibVar ("WWW_PATH_INFO"); CgiPathTranslatedPtr = CgiLibVar ("WWW_PATH_TRANSLATED"); status = ReadFileIntoMemory (CgiPathTranslatedPtr, &TemplatePtr, &TemplateLength); if (VMSnok (status)) { CgiLibResponseError (FI_LI, status, CgiPathInfoPtr); exit (SS$_NORMAL); } CheckTemplateVersion (TemplatePtr); if (RequestDataPtr == NULL) CgiLibReadRequestBody (&RequestDataPtr, &RequestDataLength); if (Debug) fprintf (stdout, "RequestDataPtr ...\n|%s|\n", RequestDataPtr); ParseTemplate (TemplatePtr); ResolvePointers (); if (HeaderPersonalPtr) strcpy (PersonalName, HeaderPersonalPtr); else sprintf (PersonalName, "tmailer: %s", CgiPathInfoPtr); if (HeaderToPtr[0] == '?') { /* a question mark allows viewing the message without actual mailing! */ CgiLibResponseHeader (200, "text/plain"); RevealMailMessage (PersonalName, HeaderToPtr, HeaderSubjectPtr, TemplateBodyPtr); exit (SS$_NORMAL); } else if (HeaderToPtr[0] != '!') { /* an exclamation point allows testing without actual mailing! */ MailMessage (PersonalName, HeaderToPtr, HeaderSubjectPtr, TemplateBodyPtr); } if (CgiResponsePtr == NULL) { if (HeaderSuccessStatusPtr[0]) { for (cptr = HeaderSuccessStatusPtr; *cptr && !isdigit(*cptr); cptr++); StatusCode = atoi(cptr); } if (StatusCode < 200 || StatusCode > 299) StatusCode = 200; if (HeaderSuccessPtr[0]) { /* redirection */ CgiLibResponseRedirect (HeaderSuccessPtr); } else { CgiLibResponseHeader (200, "text/html"); fprintf (stdout, "\n\ \n\ \n\ Message Mailed!\n\ \n\ \n\ Sending form data as MAIL to: %s\n\ \n\ \n", SoftwareID, HeaderToPtr); } } else fprintf (stdout, "%s", CgiResponsePtr); } /*****************************************************************************/ /* Read the request body (if POST) or the request's query string (if GET) into a single array of char (doesn't matter whether it's text or binary) pointed to by 'BufferPtr' with a length of 'BufferCount'. If the NET$LINK environment variable exists (DECnet CGI input/output) then read from that in record mode (it's DECnet after all). For DECnet an empty record (newline character only) terminates the request body, for subprocess CGI an EOF is read. */ ReadRequestDataIntoMemory ( char **BufferPtrPtr, int *DataSizePtr ) { static int BufferChunk = 1024; static int BufferCount; static char *BufferPtr; int BufferSize, ReadCount; char *cptr; /*********/ /* begin */ /*********/ if (Debug) fprintf (stdout, "ReadRequestDataIntoMemory()\n"); /* if the body has already been read then just return */ if (BufferPtr != NULL) { if (BufferPtrPtr != NULL) *BufferPtrPtr = BufferPtr; if (DataSizePtr != NULL) *DataSizePtr = BufferCount; return; } CgiRequestMethodPtr = CgiLibVar("WWW_REQUEST_METHOD"); if (strsame (CgiRequestMethodPtr, "GET", -1)) { BufferPtr = CgiLibVar("WWW_QUERY_STRING"); BufferCount = strlen (BufferPtr); if (BufferPtrPtr != NULL) *BufferPtrPtr = BufferPtr; if (DataSizePtr != NULL) *DataSizePtr = BufferCount; return; } else if (strsame (CgiRequestMethodPtr, "POST", -1)) { CgiContentTypePtr = CgiLibVar("WWW_CONTENT_TYPE"); if (!strsame (CgiContentTypePtr, FORM_URLENCODED, -1)) { CgiLibResponseError (FI_LI, 0, "Must be URL-encoded form data!"); exit (SS$_NORMAL); } } else { CgiLibResponseError (FI_LI, 0, "HTTP method must be \"GET\" or \"POST\"!"); exit (SS$_NORMAL); } CgiLibReadRequestBody (&BufferPtr, &BufferCount); if (BufferPtrPtr != NULL) *BufferPtrPtr = BufferPtr; if (DataSizePtr != NULL) *DataSizePtr = BufferCount; } /****************************************************************************/ /* Read the file contents specified by 'FileName' into memory, set the pointer at 'FileTextPtr' to the contents and the file size at 'FileSizePtr'. Returns a VMS status value that should be checked. */ int ReadFileIntoMemory ( char *Source, char **FileTextPtr, int *FileSizePtr ) { static int BytesRemaining; int status, Bytes, BufferCount, Length; char *BufferPtr, *LinePtr; FILE *FilePtr; stat_t FstatBuffer; /*********/ /* begin */ /*********/ if (Debug) fprintf (stdout, "ReadFileIntoMemory() |%s|\n", Source); if ((FilePtr = fopen (Source, "r", "shr=get", "shr=put")) == NULL) { status = vaxc$errno; if (Debug) fprintf (stdout, "fopen() %%X%08.08X\n", status); return (status); } if (fstat (fileno(FilePtr), &FstatBuffer) < 0) { status = vaxc$errno; if (Debug) fprintf (stdout, "fstat() %%X%08.08X\n", status); fclose (FilePtr); return (status); } Bytes = FstatBuffer.st_size; if (Debug) fprintf (stdout, "%d bytes\n", Bytes); /* a little margin for error ;^) */ Bytes += 32; BufferPtr = calloc (Bytes, 1); if (BufferPtr == NULL) { CgiLibResponseError (FI_LI, vaxc$errno, "calloc()"); exit (SS$_NORMAL); } BytesRemaining = Bytes; LinePtr = BufferPtr; BufferCount = 0; while (fgets (LinePtr, BytesRemaining, FilePtr) != NULL) { /** if (Debug) fprintf (stdout, "|%s|\n", LinePtr); **/ Length = strlen(LinePtr); LinePtr += Length; BufferCount += Length; BytesRemaining -= Length; } fclose (FilePtr); if (Debug) fprintf (stdout, "%d |%s|\n", BufferCount, BufferPtr); if (FileTextPtr != NULL) *FileTextPtr = BufferPtr; if (FileSizePtr != NULL) *FileSizePtr = BufferCount; return (SS$_NORMAL); } /*****************************************************************************/ /* Simply check the template file version. */ CheckTemplateVersion (char *TextPtr) { char *cptr; /*********/ /* begin */ /*********/ if (Debug) fprintf (stdout, "CheckTemplateVersion() |%*.*s|\n", 10, 10, TextPtr); for (cptr = TextPtr; *cptr && (*cptr == ' ' || *cptr == '\t'); cptr++); if (!strsame (cptr, "tmail:", 6)) { CgiLibResponseError (FI_LI, 0, "\"tmail:\"? Not a template file?"); exit (SS$_NORMAL); } cptr += 6; while (*cptr && (*cptr == ' ' || *cptr == '\t')) cptr++; if (!strsame (cptr, TMAIL_VERSION, 3)) { CgiLibResponseError (FI_LI, 0, "Template file version mismatch."); exit (SS$_NORMAL); } } /*****************************************************************************/ /* Creates a large, single, dynamically allocated, null-terminated text string based on the contents of the template file. The template is parsed from beginning to end expanding tags, etc. As the parse progresses the various components from the template header, body (mail message) and any CGI response are identified and the offsets placed in global storage. These will later be used to generate pointers and otherwise reprocess the parsed contents (by ResolvePointers()). */ ParseTemplate (char* String) { #define EXPAND_PARSE_MEMORY \ { \ Count = sptr - ParsedTemplatePtr; \ if ((ParsedTemplatePtr = \ realloc (ParsedTemplatePtr, BufferSize+BufferChunk+1)) == NULL) \ { \ CgiLibResponseError (FI_LI, vaxc$errno, "realloc()"); \ exit (SS$_NORMAL); \ } \ BufferSize += BufferChunk; \ zptr = ParsedTemplatePtr + BufferSize; \ sptr = ParsedTemplatePtr + Count; \ } /***********/ /* storage */ /***********/ static int BufferChunk = 1024; boolean InTemplateHeader; int BufferSize, Count; char *cptr, *sptr, *tptr, *zptr, *TagNameEndCharPtr; char ErrorString [256], TagName [256]; /*********/ /* begin */ /*********/ if (Debug) fprintf (stdout, "ParseTemplate() |%s|\n", String); InTemplateHeader = true; CgiResponseOffset = CgiResponseLength = HeaderToOffset = HeaderSubjectOffset = HeaderSuccessOffset = HeaderSuccessStatusOffset = HeaderPersonalOffset = ParsedTemplateLength = TemplateBodyOffset = 0; if ((sptr = ParsedTemplatePtr = calloc (BufferSize = BufferChunk, 1)) == NULL) { CgiLibResponseError (FI_LI, vaxc$errno, "calloc()"); exit (SS$_NORMAL); } zptr = ParsedTemplatePtr + BufferSize; cptr = String; while (*cptr) { if (InTemplateHeader) { if (*(unsigned short*)cptr == '\n\n') { TemplateBodyOffset = sptr - ParsedTemplatePtr + 2; InTemplateHeader = false; } else if (strsame (cptr, "Tmail:", 6)) HeaderTmailOffset = sptr - ParsedTemplatePtr + 6; else if (strsame (cptr, "To:", 3)) HeaderToOffset = sptr - ParsedTemplatePtr + 3; else if (strsame (cptr, "Subject:", 8)) HeaderSubjectOffset = sptr - ParsedTemplatePtr + 8; else /* must come before "Success:" for the obvious reason */ if (strsame (cptr, "Success-status:", 15)) HeaderSuccessStatusOffset = sptr - ParsedTemplatePtr + 15; else if (strsame (cptr, "Success:", 8)) HeaderSuccessOffset = sptr - ParsedTemplatePtr + 8; else if (strsame (cptr, "Personal:", 9)) HeaderPersonalOffset = sptr - ParsedTemplatePtr + 9; } switch (*cptr) { case '[' : /*******/ /* tag */ /*******/ if (*(unsigned short*)cptr == '[[') { /* escaped "[" */ if (sptr >= zptr) EXPAND_PARSE_MEMORY; cptr++; *sptr++ = *cptr++; continue; } if (*(unsigned short*)cptr == '[]') { /* escaped "]" */ if (sptr >= zptr) EXPAND_PARSE_MEMORY; cptr++; *sptr++ = *cptr++; continue; } cptr++; if (*(unsigned short*)cptr == '%%') { /***************/ /* command tag */ /***************/ if (strsame (cptr, "%%end]", 6)) { cptr += 6; CommandTagEnd = CommandTagEntify = true; while (*cptr && *cptr != '\n') cptr++; if (*cptr) cptr++; CgiResponseOffset = sptr - ParsedTemplatePtr; continue; } if (strsame (cptr, "%%entify]", 9)) { cptr += 9; CommandTagEntify = true; continue; } if (strsame (cptr, "%%noentify]", 11)) { cptr += 11; CommandTagEntify = false; continue; } if (strsame (cptr, "%%reveal]", 9)) { /* special case, reveal all available data */ fprintf (stdout, "%s\n\nCGI variables:\n\n", SoftwareID); fflush (stdout); system ("show symbol www_*"); fprintf (stdout, "\nForm fields:\n\n"); RevealFormFields (); exit (SS$_NORMAL); } for (sptr = cptr; *sptr && *sptr != '\n'; sptr++); *sptr = '\0'; sprintf (ErrorString, "Unknown command tag: \"[%s\"", (char*)CgiLibHtmlEscape(cptr, -1, NULL, -1)); CgiLibResponseError (FI_LI, 0, String); exit (SS$_NORMAL); } if (*cptr == '%') { /****************/ /* CGI variable */ /****************/ cptr++; cptr += GetTagName (cptr, TagName, sizeof(TagName), &TagNameEndCharPtr); tptr = TagCgiVar (TagName, TagNameEndCharPtr); while (*tptr) { if (sptr >= zptr) EXPAND_PARSE_MEMORY; *sptr++ = *tptr++; } continue; } /**************/ /* field name */ /**************/ cptr += GetTagName (cptr, TagName, sizeof(TagName), &TagNameEndCharPtr); tptr = TagFormCgiVar (TagName, TagNameEndCharPtr); while (*tptr) { if (sptr >= zptr) EXPAND_PARSE_MEMORY; *sptr++ = *tptr++; } continue; /********************/ /* just a character */ /********************/ default : if (sptr >= zptr) EXPAND_PARSE_MEMORY; *sptr++ = *cptr++; continue; } } *sptr = '\0'; ParsedTemplateLength = sptr - ParsedTemplatePtr; CgiResponseLength = ParsedTemplateLength - CgiResponseOffset; TemplateBodyLength = ParsedTemplateLength - TemplateBodyOffset - CgiResponseLength - 1; } /*****************************************************************************/ /* During the parse of the template a number of byte-count offsets into the dynamically allocated parsed text are set representing the starting locations of strings of various interests. Now the parsing and realloc()ing are over turn these into pointers to null-terminated strings. */ ResolvePointers () { char *cptr; /*********/ /* begin */ /*********/ if (Debug) fprintf (stdout, "ResolvePointers() %d %d %d %d %d %d %d %d %d\n", HeaderToOffset, HeaderSubjectOffset, HeaderSuccessOffset, HeaderSuccessStatusOffset, HeaderPersonalOffset, TemplateBodyOffset, TemplateBodyLength, CgiResponseOffset, CgiResponseLength); if (HeaderToOffset) { cptr = ParsedTemplatePtr + HeaderToOffset; while (*cptr && ISLWS(*cptr)) cptr++; HeaderToPtr = cptr; while (*cptr && *cptr != '\n') cptr++; *cptr = '\0'; } else HeaderToPtr = ""; if (HeaderSubjectOffset) { cptr = ParsedTemplatePtr + HeaderSubjectOffset; while (*cptr && ISLWS(*cptr)) cptr++; HeaderSubjectPtr = cptr; while (*cptr && *cptr != '\n') cptr++; *cptr = '\0'; } else HeaderSubjectPtr = ""; if (HeaderSuccessOffset) { cptr = ParsedTemplatePtr + HeaderSuccessOffset; while (*cptr && ISLWS(*cptr)) cptr++; HeaderSuccessPtr = cptr; while (*cptr && *cptr != '\n') cptr++; *cptr = '\0'; } else HeaderSuccessPtr = ""; if (HeaderSuccessStatusOffset) { cptr = ParsedTemplatePtr + HeaderSuccessStatusOffset; while (*cptr && ISLWS(*cptr)) cptr++; HeaderSuccessStatusPtr = cptr; while (*cptr && *cptr != '\n') cptr++; *cptr = '\0'; } else HeaderSuccessStatusPtr = ""; if (HeaderPersonalOffset) { cptr = ParsedTemplatePtr + HeaderPersonalOffset; while (*cptr && ISLWS(*cptr)) cptr++; HeaderPersonalPtr = cptr; while (*cptr && *cptr != '\n') cptr++; *cptr = '\0'; } else HeaderPersonalPtr = 0; if (TemplateBodyOffset) { TemplateBodyPtr = ParsedTemplatePtr + TemplateBodyOffset; TemplateBodyPtr[TemplateBodyLength] = '\0'; } else { CgiLibResponseError (FI_LI, 0, "No template body found."); exit (SS$_NORMAL); } if (CgiResponseOffset) CgiResponsePtr = ParsedTemplatePtr + CgiResponseOffset; else CgiResponsePtr = NULL; if (Debug) fprintf (stdout, "|%s|\n|%s|\n|%s|\n|%s|\n|%s|\n|%s|\n", HeaderToPtr, HeaderSubjectPtr, HeaderSuccessPtr, HeaderSuccessStatusPtr, TemplateBodyPtr, CgiResponsePtr); } /*****************************************************************************/ /* Parse a [...] delimited tag name from the string. Any of ':', '?' or ']' will terminate the tag name. Set the 'TagNameEndCharPtrPtr' to the address of this terminator. */ int GetTagName ( char *String, char *BufferPtr, int SizeOfBuffer, char **TagNameEndCharPtrPtr ) { char *cptr, *sptr, *zptr; char ErrorString [256]; /*********/ /* begin */ /*********/ if (Debug) fprintf (stdout, "GetTagName()\n"); zptr = (sptr = BufferPtr) + SizeOfBuffer; for (cptr = String; *cptr && *cptr != ']' && *cptr != ':' && *cptr != '?' && sptr < zptr; *sptr++ = *cptr++); { /* allow for escaped characters */ if (*(unsigned short*)cptr == '[]') cptr++; else if (*(unsigned short*)cptr == '[:') cptr++; else if (*(unsigned short*)cptr == '[?') cptr++; } if (sptr < zptr && (*cptr == ']' || *cptr == ':' || *cptr == '?')) { *sptr = '\0'; if (Debug) fprintf (stdout, "|%s|\n", BufferPtr); *TagNameEndCharPtrPtr = cptr; if (*cptr != ']') { while (*cptr && *cptr != ']') { if (*(unsigned short*)cptr == '[]') cptr++; cptr++; } } if (*cptr) cptr++; return (cptr - String); } sprintf (ErrorString, "Tag problem: \"%s\"", (char*)CgiLibHtmlEscape(String, -1, NULL, -1)); CgiLibResponseError (FI_LI, 0, ErrorString); exit (SS$_NORMAL); } /*****************************************************************************/ /* Get the CGI variable value represented by the tag name. Requires prefixing the variable name with "WWW_". Do a conditional expansion on the variable value. */ char* TagCgiVar ( char *VarName, char *TagNameEndCharPtr ) { static char WwwVarName [256] = "WWW_"; int Length; char *cptr, *sptr, *zptr; /*********/ /* begin */ /*********/ if (Debug) fprintf (stdout, "TagCgiVar() |%s| %c\n", VarName, *TagNameEndCharPtr); cptr = CgiLibVar (VarName); cptr = ConditionalExpansion (cptr, TagNameEndCharPtr); if (CommandTagEntify) { sptr = (char*)CgiLibHtmlEscape (cptr, -1, NULL, -1); free (cptr); cptr = sptr; } if (Debug) fprintf (stdout, "%s |%s|\n", WwwVarName, cptr); return (cptr); } /*****************************************************************************/ /* Get the form-URL-encoded form field value. This will have come from the query string if a "GET" request or the request body if a "POST" request. Both will the same format, a URL-encoded, null-terminated string. Do a conditional expansion on the variable value. */ char* TagFormCgiVar ( char *VarName, char *TagNameEndCharPtr ) { int Length; char *cptr, *sptr, *zptr; /*********/ /* begin */ /*********/ if (Debug) fprintf (stdout, "TagFormCgiVar() |%s| %c\n", VarName, *TagNameEndCharPtr); cptr = RequestDataPtr; for (;;) { if (!*cptr) break; for (sptr = cptr; *sptr && *sptr != '='; sptr++); if (!*sptr) { cptr = ""; break; } *sptr = '\0'; if (Debug) fprintf (stdout, "|%s|\n", cptr); /* case-sensitive compare for field names */ if (strcmp (cptr, VarName)) { *sptr++ = '='; for (cptr = sptr; *cptr && *cptr != '&'; cptr++); if (*cptr) cptr++; continue; } *sptr++ = '='; cptr = sptr; while (*sptr && *sptr != '&') sptr++; Length = sptr - cptr; if ((sptr = calloc (Length+1, 1)) == NULL) { CgiLibResponseError (FI_LI, vaxc$errno, "calloc()"); exit (SS$_NORMAL); } memcpy (sptr, cptr, Length); sptr[Length] = '\0'; /* now URL-decode in-situ */ CgiLibUrlDecode (cptr = sptr); break; } cptr = ConditionalExpansion (cptr, TagNameEndCharPtr); if (CommandTagEntify) { sptr = (char*)CgiLibHtmlEscape (cptr, -1, NULL, -1); free (cptr); cptr = sptr; } if (Debug) fprintf (stdout, "|%s|\n", cptr); return (cptr); } /*****************************************************************************/ /* Just display each form field as 'name == "value"' (for template debugging). */ RevealFormFields () { char *cptr, *sptr; /*********/ /* begin */ /*********/ if (Debug) fprintf (stdout, "RevealFormFields()\n"); cptr = RequestDataPtr; while (*cptr) { for (sptr = cptr; *sptr && *sptr != '='; sptr++) if (!*sptr) break; *sptr++ = '\0'; fprintf (stdout, " %s == \"", cptr); for (cptr = sptr; *cptr && *cptr != '&'; cptr++); if (*cptr) *cptr++ = '\0'; /* now URL-decode in-situ */ CgiLibUrlDecode (sptr); fprintf (stdout, "%s\"\n", sptr); } } /*****************************************************************************/ /* If the tag name ended with a ':' and the tag value was empty (null) then the and empty string is returned. If the tag value was non-empty the text following the ':' up to the next ']' is returned. If the tag name ended with a '?' and the tag value was "on" (case-insensitive) then the text following the '?' and up to the next ']' is returned. If not "on" an empty string is returned. */ char* ConditionalExpansion ( char *TagValue, char *TagNameEndCharPtr ) { int Length; char *cptr, *sptr, *vptr; /*********/ /* begin */ /*********/ if (Debug) fprintf (stdout, "ConditionalExpansion() |%s| %c\n", TagValue, *TagNameEndCharPtr); if (*TagNameEndCharPtr == ':') { TagNameEndCharPtr++; if (TagValue != NULL && TagValue[0]) { for (sptr = TagNameEndCharPtr; *sptr && *sptr != ']'; sptr++) { if (*(unsigned short*)sptr == '[]') sptr++; if (*(unsigned short*)sptr == '::') break; } Length = sptr - TagNameEndCharPtr; if (*(unsigned short*)sptr == '::') for (/* continue */; *sptr && *sptr != ']'; sptr++) if (*(unsigned short*)sptr == '[]') sptr++; } else { for (sptr = TagNameEndCharPtr; *sptr && *sptr != ']'; sptr++) { if (*(unsigned short*)sptr == '[]') sptr++; if (*(unsigned short*)sptr == '::') { sptr += 2; break; } } for (TagNameEndCharPtr = sptr; *sptr && *sptr != ']'; sptr++) if (*(unsigned short*)sptr == '[]') sptr++; Length = sptr - TagNameEndCharPtr; } } else if (*TagNameEndCharPtr == '?') { TagNameEndCharPtr++; if (TagValue != NULL && strsame (TagValue, "ON", -1)) { for (sptr = TagNameEndCharPtr; *sptr && *sptr != ']'; sptr++) { if (*(unsigned short*)sptr == '[]') sptr++; if (*(unsigned short*)sptr == '\?\?') break; } Length = sptr - TagNameEndCharPtr; if (*(unsigned short*)sptr == '\?\?') for (/* continue */; *sptr && *sptr != ']'; sptr++) if (*(unsigned short*)sptr == '[]') sptr++; } else { for (sptr = TagNameEndCharPtr; *sptr && *sptr != ']'; sptr++) { if (*(unsigned short*)sptr == '[]') sptr++; if (*(unsigned short*)sptr == '\?\?') { sptr += 2; break; } } for (TagNameEndCharPtr = sptr; *sptr && *sptr != ']'; sptr++) if (*(unsigned short*)sptr == '[]') sptr++; Length = sptr - TagNameEndCharPtr; } } else return (TagValue); if (Debug) fprintf (stdout, "Length: %d\n", Length); if (!Length) return (""); if ((cptr = vptr = calloc (Length+1, 1)) == NULL) { CgiLibResponseError (FI_LI, vaxc$errno, "calloc()"); exit (SS$_NORMAL); } for (sptr = TagNameEndCharPtr; *sptr && Length; *cptr++ = *sptr++) { if (*(unsigned short*)sptr == '[[' || *(unsigned short*)sptr == '[:' || *(unsigned short*)sptr == '[?' || *(unsigned short*)sptr == '[]') { if (!Length) break; Length--; sptr++; } if (!Length) break; Length--; } *cptr = '\0'; return (vptr); } /*****************************************************************************/ /* Use the VMS callable mail interface to create and send a VMS mail message. 'To' can be a list of comma-separated addresses. 'Subject' is a null-terminated string. 'Body' is a null-terminated string of '\n'-separated lines of plain text. Just truncates anything longer than 255 characters (body excluded, body records included)! */ int MailMessage ( char *PersonalName, char *To, char *Subject, char *Body ) { int status; unsigned long SendContext = 0; char *cptr, *sptr; struct { short int buf_len; short int item; void *buf_addr; unsigned short *ret_len; } BodyPartItem [] = { { 0, MAIL$_SEND_RECORD, 0, 0 }, { 0, MAIL$_NOSIGNAL, 0, 0 }, {0,0,0,0} }, PersonalNameItem [] = { { 0, MAIL$_SEND_PERS_NAME, PersonalName, 0 }, { 0, MAIL$_NOSIGNAL, 0, 0 }, {0,0,0,0} }, SendUserNameItem [] = { { 0, MAIL$_SEND_USERNAME, 0, 0 }, { 0, MAIL$_NOSIGNAL, 0, 0 }, {0,0,0,0} }, SubjectItem [] = { { 0, MAIL$_SEND_SUBJECT, Subject, 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}; /*********/ /* begin */ /*********/ if (Debug) fprintf (stdout, "MailMessage() |%s|\n|%s|\n|%s|\n", PersonalName, To, Subject); if (PersonalName != NULL && PersonalName[0]) { PersonalNameItem[0].buf_len = strlen(PersonalName); if (PersonalNameItem[0].buf_len > 255) PersonalNameItem[0].buf_len = 255; status = mail$send_begin (&SendContext, &PersonalNameItem, &NullItem); } else status = mail$send_begin (&SendContext, &NoSignalItem, &NullItem); if (VMSnok (status)) { CgiLibResponseError (FI_LI, status, "beginning message"); exit (SS$_NORMAL); } /* a single, or multiple comma-separated addresses */ cptr = To; while (*cptr) { sptr = cptr; while (*cptr && *cptr != ',') cptr++; if (*cptr) *cptr++ = '\0'; SendUserNameItem[0].buf_addr = sptr; SendUserNameItem[0].buf_len = strlen(sptr); if (SendUserNameItem[0].buf_len > 255) SendUserNameItem[0].buf_len = 255; if (Debug) fprintf (stdout, "address |%s|\n", (char*)SendUserNameItem[0].buf_addr); status = mail$send_add_address (&SendContext, &SendUserNameItem, &NullItem); if (VMSnok (status)) { CgiLibResponseError (FI_LI, status, sptr); exit (SS$_NORMAL); } } SubjectItem[0].buf_len = strlen(Subject); if (SubjectItem[0].buf_len > 255) SubjectItem[0].buf_len = 255; status = mail$send_add_attribute (&SendContext, &SubjectItem, &NullItem); if (VMSnok (status)) { CgiLibResponseError (FI_LI, status, "adding subject"); exit (SS$_NORMAL); } cptr = Body; while (*cptr) { BodyPartItem[0].buf_addr = sptr = cptr; /* break at 255 characters regardless */ while (*cptr && cptr-sptr < 255 && *cptr != '\n') cptr++; BodyPartItem[0].buf_len = cptr - sptr; if (*cptr && *cptr == '\n') cptr++; status = mail$send_add_bodypart (&SendContext, &BodyPartItem, &NullItem); if (VMSnok (status)) { CgiLibResponseError (FI_LI, status, "adding body"); exit (SS$_NORMAL); } } status = mail$send_message (&SendContext, &NoSignalItem, &NoSignalItem); if (VMSnok (status)) { CgiLibResponseError (FI_LI, status, "sending message"); exit (SS$_NORMAL); } mail$send_end (&SendContext, &NullItem, &NullItem); return (SS$_NORMAL); } /*****************************************************************************/ /* Just reveal the contents of the mail message (for template debugging). */ RevealMailMessage ( char *PersonalName, char *To, char *Subject, char *Body ) { /*********/ /* begin */ /*********/ if (Debug) fprintf (stdout, "RevealMailMessage()\n"); fprintf (stdout, "From: HTTP$SERVER \"%s\"\n\ To: %s\n\ CC:\n\ Subj: %s\n\ \n\ %s", PersonalName, To+1, Subject, Body); } /****************************************************************************/ /* Does a case-insensitive, character-by-character string compare and returns true if two strings are the same, or false if not. If a maximum number of characters are specified only those will be compared, if the entire strings should be compared then specify the number of characters as 0. */ boolean strsame ( char *sptr1, char *sptr2, int count ) { while (*sptr1 && *sptr2) { if (toupper (*sptr1++) != toupper (*sptr2++)) return (false); if (count) if (!--count) return (true); } if (*sptr1 || *sptr2) return (false); else return (true); } /*****************************************************************************/