/* * CGI script for sending mail, driven by a template file that inserts * form fields at specified spots. * * Usage: * Create HTML form that specifies this script as the action with the * template file as the path_info following the script name. * * Template file format: * Template file consists of a header followed by the body section, the * end of header is indicated by a blank line. At any point in the * file you can specify a form field name surrounded by square brackets * and the corresponding field from the form will be substituted. * * Template header fields: * To: address * Subject: string * Success: url * * Fail: url * * AUTHORS: * * Dick Munroe Original CGIMAILTO program. * Acorn Software, Inc. * munroe@acornsw.com * * David Jones * Ohio State University * * CREATION DATE: 04-Dec-94 * Date: 13-JUN-1996 * Revised: 16-JUN-1996 * Revised: 30-MAY-1997 */ #if defined(__ALPHA) /* Some structures needed by mail are not aligned */ #pragma nomember_alignment #endif /** MGD **/ #if defined(__ia64) #pragma nomember_alignment #endif #include #include #include "cgilib.h" struct ItemList { short int length, code; char *buffer; long returnLength; }; #include #include #include #include #include #include #include #include #include #include /* * Prototypes for callable mail API. */ extern unsigned long mail$send_begin() ; extern unsigned long mail$send_add_attribute() ; extern unsigned long mail$send_add_address() ; extern unsigned long mail$send_add_bodypart() ; extern unsigned long mail$send_message() ; extern unsigned long mail$send_end() ; /* * Miscellaneous data structures used to store form data and text lines. */ struct field_def { struct field_def *next; char *key, *value; int klen; }; typedef struct field_def *field_def_p; struct scanctx { char *body; char *subline; }; struct outrec { struct outrec *next; int length; char data[1]; /* variable length actually allocated */ }; static int ent_flag = 0; /* if true, entify chars "<>&" in expansions */ /* * Forward function references. */ static int scan_body ( struct scanctx *context, field_def_p fields, char *buffer, int bufsize, int *length ); static char *expand_line ( char *, field_def_p ); static char *reformat_value ( char *source, char *format, int fmt_len ); static char *entify_string ( char *source ); static field_def_p form_data(); static void send_fail ( char *stsline, char *reason, void *arg ) { char fmt[256]; sprintf(fmt,"context-type: text/plain\nstatus: %s\n\nSend failed: %s\n", stsline, reason ); cgi_printf(fmt,arg,arg); exit(1); } /****************************************************************************/ /* Main routine. */ int main ( int argc, char **argv ) { char buffer[1024], body_line[256]; char *template_name, *template_file, *template; char *method, *personal_name, *t_body, *expanded, *cp ; struct scanctx context; struct ItemList itemList[10], nullItemList = {0, 0, 0, 0} ; unsigned long sendContext = 0 ; char *to, *subj, *version, *success, *failure, *line; FILE *tfile; field_def_p field_list, fld; struct outrec rec_list, *last_rec, *cur_rec; int i, j, status, length, t_used, t_alloc, succ_sts; /* * Initialize CGI and load content into memory. */ status = cgi_init ( argc, argv ); if ( (status&1) == 0 ) return status; field_list = form_data(); if ( !field_list ) return 1; /* * Get template file name and attempt to open. */ template_name = cgi_info ( "PATH_INFO" ); template_file = cgi_info ( "PATH_TRANSLATED" ); if ( !template_name || !template_file ) send_fail ( "500 bad template", "Bad template name (%s)\n", template_name ? template_name : "???" ); tfile = fopen ( template_file, "r" ); if ( !tfile ) send_fail ( "500 open failure", "open failure on template file (%s)", template_file ); /* * Read template data into memory. */ t_alloc = 8192; template = malloc(t_alloc); t_used = 0; while (0 < (length=fread(&template[t_used], 1, t_alloc - t_used, tfile))) { t_used += length; if ( t_used >= t_alloc ) { t_alloc = t_alloc + 20000; if ( t_alloc > 100000 ) send_fail ( "500 error", "template file too big (%s)", template_name ); template = realloc ( template, t_alloc ); } } fclose ( tfile ); /* * Parse header out of template, check that first line is version. */ t_body = version = (char *) 0; for ( i = 0; i < t_used; i++ ) if ( template[i] == '\n' ) { if ( !version ) { /* First line must be "tmail" */ for ( j = 0; template[j] && template[j] != ':'; j++ ) template[j] = tolower(template[j]); if ( 0 != strncmp(template,"tmail:", 6) ) send_fail ( "500 invalid template", "first line of template must be tmail: header", 0 ); version = &template[7]; } if ( template[i+1] == '\n' ) { template[i+1] = '\0'; t_body = &template[i+2]; break; } } if ( !t_body ) send_fail ( "500 invalid template", "Invalid template format - no body", 0 ); /* * Parse header lines. */ to = subj = success = failure = (char *) 0; succ_sts = 0; for ( i = 0, line = template; template[i]; i++ ) { if (template[i] == '\n') { template[i] = '\0'; for ( j = 0; line[j] && line[j] != ':'; j++ ) line[j] = tolower(line[j]); if ( strncmp(line,"to:",3)==0 ) to = expand_line(&line[3],field_list); else if ( strncmp(line,"subject:",8) == 0 ) subj = expand_line(&line[8], field_list); else if ( strncmp(line,"success:",8) == 0 ) success = expand_line ( &line[8], field_list ); else if ( strncmp(line,"success-status:",15) == 0 ) succ_sts = atoi(&line[15]); else if ( strncmp(line,"failure:",8) == 0 ) failure = expand_line ( &line[8], field_list ); line = &template[i+1]; } } if ( !to ) send_fail ( "500 invalid template", "invalid template file header - no to: line", 0) ; /* * Parse the body lines and generate list of records. */ context.body = t_body; context.subline = (char *) 0; for ( last_rec = &rec_list; ; last_rec = cur_rec ) { status = scan_body (&context, field_list, body_line, sizeof(body_line)-1, &length ); if ( status > 0 ) { /* * Add record to list. */ cur_rec = (struct outrec *) malloc (sizeof(struct outrec)+length); last_rec->next = cur_rec; if ( !cur_rec ) return 0; cur_rec->next = (struct outrec *) 0; cur_rec->length = length; memcpy ( cur_rec->data, body_line, length ); cur_rec->data[length] = '\0'; /* not really necessary */ } else if ( status == 0 ) { break; /* status == 0 ==> EOD */ } else { /* Error */ return 1; } } /* * Initiate mail message, construct a personal name for the * message from the path used to invoke it. */ personal_name = malloc ( strlen(template_name) + 20 ); sprintf ( personal_name, "tmail: %s", template_name ); i = -1; itemList[++i].length = strlen(personal_name); itemList[i].code = MAIL$_SEND_PERS_NAME ; itemList[i].buffer = personal_name; itemList[i].returnLength = 0 ; itemList[++i].length = 0 ; itemList[i].code = 0 ; status = mail$send_begin ( &sendContext, &itemList, &nullItemList) ; /* Set up to construct the mail context. */ if (!$VMS_STATUS_SUCCESS(status)) send_fail ( "500 mail error", "MAIL$SEND_BEGIN returned %%X%0X",(void *) status) ; /* ** Now build up the attributes of the message. */ i = -1 ; while ( isspace(*to) && *to ) to++; /* trim leading spaced */ itemList[++i].length = strlen(to) ; itemList[i].code = MAIL$_SEND_TO_LINE ; itemList[i].buffer = to ; itemList[i].returnLength = 0 ; if ( !subj ) subj = "Mail from tmail script"; while ( isspace(*subj) && *subj ) subj++; /* trim leading spaced */ itemList[++i].length = strlen(subj); itemList[i].code = MAIL$_SEND_SUBJECT ; itemList[i].buffer = subj; itemList[i].returnLength = 0 ; itemList[++i].length = 0 ; itemList[i].code = 0 ; status = mail$send_add_attribute ( &sendContext, &itemList, &nullItemList) ; /* Add the the message header. */ if (!$VMS_STATUS_SUCCESS(status)) send_fail ( "500 mail error", "MAIL$SEND_ADD_ATTRIBUTE returned %%X%0X,\ncheck template's subject: line", (void *) status ); /* * Set destination for to: header line. */ i = -1 ; itemList[++i].length = strlen(to) ; itemList[i].code = MAIL$_SEND_USERNAME ; itemList[i].buffer = to ; itemList[i].returnLength = 0 ; itemList[++i] = nullItemList ; /* The itemlist describes an address of a receipient. */ status = mail$send_add_address ( &sendContext, &itemList, &nullItemList) ; /* Add the sender of the message to the list of receipients. */ if (!$VMS_STATUS_SUCCESS(status)) send_fail ( "500 mail error", "MAIL$SEND_ADD_ADDRESS returned %%X%0X,\ncheck template's to: header line", (void *) status); /* * Send the items. */ itemList[0].code = MAIL$_SEND_RECORD; itemList[0].returnLength = 0; itemList[1] = nullItemList; for ( cur_rec = rec_list.next; cur_rec; cur_rec = cur_rec->next ) { itemList[0].length = cur_rec->length; itemList[0].buffer = cur_rec->data; status = mail$send_add_bodypart (&sendContext, &itemList, &nullItemList) ; if ( !$VMS_STATUS_SUCCESS(status) ) send_fail ( "500 mail error", "MAIL$SEND_ADD_BODYPART returned %%X%0X", (void *) status) ; } /* * Message complete, queue to delivery system. */ status = mail$send_message ( &sendContext, &nullItemList, &nullItemList) ; /* Send the message. */ if (!$VMS_STATUS_SUCCESS(status)) send_fail ( "500 mail error", "MAIL$SEND_MESSAGE returned %%X%0X", (void *) status) ; status = mail$send_end ( &sendContext, &nullItemList, &nullItemList) ; /* Set up to construct the mail context. */ if (!$VMS_STATUS_SUCCESS(status)) send_fail ( "500 mail error", "MAIL$SEND_END returned %%X%0X", (void *) status); if ( success ) { cgi_printf ( "location: %s\n\n", success ); } else if ( context.body[0] ) { /* * Non-null body remaining means it holds data to return, assume * it includes CGI headers. */ ent_flag = 1; while ( scan_body (&context, field_list, body_line, sizeof(body_line)-1, &length ) > 0 ) { cgi_printf ( "%s\n", body_line ); } } else { cgi_printf("content-type: text/plain\n"); /* CGI header */ if ( (succ_sts < 200) || (succ_sts > 299) ) succ_sts = 200; cgi_printf("status: %d mail delivered\n\n", succ_sts ); cgi_printf("Sending form data as MAIL to: %s\n",to); cgi_printf("Send Succeeded: submitted form data mailed\n") ; } return 1; } /*****************************************************************************/ /* Scan template body for tags and expand from form fields data, return * result in line-oriented fashion. */ static int scan_body ( struct scanctx *context, field_def_p fields, char *buffer, int bufsize, int *length ) { int i; /* * Load next subline if none currently. */ if ( !context->subline ) { /* scan next body line. */ for ( i = 0; context->body[i] != '\n'; i++ ) { if ( !context->body[i] ) return 0; /* no more body */ } context->body[i] = '\0'; context->subline = expand_line ( context->body, fields ); context->body[i] = '\n'; context->body = &context->body[i+1]; if ( !context->subline ) return 0; } /* * parse data out of subline. */ for ( i = 0; i < bufsize; i++ ) { buffer[i] = context->subline[i]; if ( buffer[i] == '\n' ) { context->subline = &context->subline[i+1]; break; } else if ( context->subline[i] == '\r' ) { if ( context->subline[i+1] == '\n' ) context->subline = &context->subline[i+2]; else context->subline = &context->subline[i+1]; break; } else if ( !context->subline[i] ) { context->subline += i; break; } } buffer[i] = '\0'; *length = i; if ( i >= bufsize ) context->subline = &context->subline[i]; if ( context->subline[0] == '\0' ) { /* Done processing subline, force load of next */ context->subline = (char *) 0; } return 1; } /****************************************************************************/ /* Convert escaped characters in string */ static int htmlStrcpy ( char *out, char *in) { int i, j; for ( j=0; *in; in++) { if (*in == '%') { int xxx ; if ( (in[1]=='\0') || (in[2]=='\0') ) break; /* too short */ sscanf (&in[1], "%02x", &xxx) ; out[j++] = (char) xxx ; in += 2 ; } else { out[j++] = *in; } } return j; } /****************************************************************************/ /* Read form data and parse into list of key/value pairs. Plus-to-space * conversion is performed but escaped chars are not converted. * Full CGI responses are generated on error. */ static field_def_p form_data() { char *method, *content_data, *cp; field_def_p field_list, fld; int i, content_length, status; /* * Determine what form data is being delivered by client. */ method = cgi_info ( "REQUEST_METHOD" ); if ( 0 == strcmp ( method, "POST" ) ) { /* * Data was posted. */ content_length = atoi(cgi_info ("CONTENT_LENGTH")); if (content_length == 0) send_fail ( "500 missing content", "No data in CONTENT_LENGTH (%s)", cgi_info("CONTENT_LENGTH") ); content_data = (char *) malloc(sizeof(char)*(content_length+1)); status = cgi_read(content_data, content_length); if (status == 0) send_fail ( "500 missing content", "No data from cgi_read", 0 ); content_data[content_length] = '\0'; } else if ( 0 == strcmp ( method, "GET" ) ) { /* * Form method was GET, data is supplied as query string. */ cp = cgi_info ( "QUERY_STRING" ); if ( !cp ) send_fail ( "500 missing content", "no data from cgi_read\n", 0 ) ; if ( *cp == '?' ) cp++; content_length = strlen ( cp ); content_data = (char *) malloc ( content_length + 1 ); strcpy ( content_data, cp ); /* printf("GET Content: '%s' l: %d\n", content_data, content_length);*/ } else { send_fail("501 unsupported method","Method %s is unsupported", method ); } /* * Parse the content into key/value pairs. */ field_list = (field_def_p) 0; if ( content_data[0] != '&' ) { fld = (field_def_p) malloc ( sizeof(struct field_def) ); fld->next = field_list; fld->key = content_data; fld->klen = 0; field_list = fld; } for ( i = 0; i < content_length; i++ ) if ( content_data[i] == '&' ) { fld = (field_def_p) malloc ( sizeof(struct field_def) ); fld->next = field_list; fld->key = &content_data[i+1]; fld->klen = 0; content_data[i] = '\0'; field_list = fld; } for ( fld = field_list; fld; fld = fld->next ) { fld->value = ""; for ( cp = fld->key, i = 0; cp[i]; i++ ) { if ( cp[i] == '=' ) { cp[i] = '\0'; fld->klen = i; fld->value = &cp[i+1]; } } for ( cp = fld->value; *cp; cp++ ) if ( *cp == '+' ) *cp = ' '; /* printf("Parsed key '%s' = '%s'\n", fld->key, fld->value ); */ } return field_list; } /*****************************************************************************/ /* Scan line for tags ([tag-name]) and replace with the corresponding value * from flist or cgi_info for that tag. */ static char *expand_line ( char *line, field_def_p flist ) { char *new; field_def_p fld; int i, j, k, size, length, lbrack, rbrack; /* * Allocate buffer to hold result, use size+256 as initial guess. */ size = 256 + strlen ( line ); new = malloc ( size ); /* * Scan string for tags. */ for ( i = j = 0; line[i]; i++ ) { if ( line[i] == '[' ) { /* * Start of tag detected */ if ( line[i+1] == '[' ) { /* Treat "[[" specially: insert single "[" */ k = 1; if ( j+1 >= size ) { size += 256; new = realloc ( new, size ); } new[j++] = '['; } else if ( line[i+1] == ']' ) { /* Treat [] specially: insert single "]" */ k = 1; if ( j+1 >= size ) { size += 256; new = realloc ( new, size ); } new[j++] = ']'; } else for ( k = 1; line[i+k]; k++) if ( line[i+k] == ']' ) { /* * Found end of tag. */ int saved_j = j; if ( line[i+1] == '%' && (k < 65) && (k > 1) ) { /* * string following % is CGI environment variable * unless it is %end. */ char varname[64], *var; strncpy ( varname, &line[i+2], k-2 ); varname[k-2] = '\0'; if ( varname[0] == '%' ) { /* special variable */ int l; for (l=0; varname[l]; l++) varname[l] = tolower(varname[l]); length = 0; if ( strcmp ( varname, "%end" ) == 0 ) { /* Magic tag to force EOD */ free ( new ); return (char *) 0; } else if ( strcmp ( varname, "%noentify" ) == 0 ) { ent_flag = 0; } else if ( strcmp ( varname, "%entify" ) == 0 ) { ent_flag = 1; } } else { /* CGI variable */ var = cgi_info ( varname ); if ( !var ) var = "%unknown"; if ( ent_flag ) var = entify_string ( var ); length = strlen(var); if ( length+j+1 >= size ) { size = j + length + 256; new = realloc ( new, size ); } strcpy ( &new[j], var ); } j += length; } else { /* * Check tag for format specifiers: * tag:string value is string or null * tag?string value is string if field 'on' */ int klen, fmt_flag; for (klen=fmt_flag=0; klennext) if (fld->klen==klen ) { if ( strncmp(&line[i+1],fld->key,klen) == 0 ) { char *value; value = fld->value; if ( fmt_flag ) value = reformat_value ( value, &line[i+klen+1], k-1-klen ); length = strlen ( value ); if ( length + j+1 >= size ) { size = j + length + 256; new = realloc ( new, size ); } length = htmlStrcpy ( &new[j], value ); if ( ent_flag ) { /* Convert s */ value = entify_string(&new[j]); if ( value != &new[j] ) { length = strlen ( value ); if ( length + j+1 >= size ) { size = j + length + 256; new = realloc ( new, size ); } strcpy ( &new[j], value ); } } j += length; if ( fmt_flag ) break; /* only expand once */ } } } if ( saved_j = j ) if ( strncmp("required-",&line[i+1],9)==0 ) { /* * Required field was missing, aobrt with error message. */ send_fail ( "500 error", "Required field not supplied: %s", &line[i] ); } break; } if ( line[i+k] != '\0' ) i += k; } else { /* * Add character not part of tags to output. */ if ( j+1 >= size ) { size = size + 256; new = realloc ( new, size ); } new[j++] = line[i]; } } /* * Ensure result is terminated and return in to caller. */ new[j] = '\0'; return new; } /****************************************************************************/ /* Substitute value according to format string. */ static char *reformat_value ( char *value, char *format, int fmt_len ) { char *result; /* * Perform tests based upon first character of format string, * returning empty string on test failure. */ result = ""; if ( *format == '?' ) { /* test value for 'on' */ if ( tolower(value[0]) != 'o' ) return result; if ( tolower(value[1]) != 'n' ) return result; if ( value[2] != '\0' ) return result; } else if ( *format == ':' ) { /* test for non-null */ if ( !*value ) return result; } /* * Copy chars in format string after 1st to result string. */ result = malloc ( fmt_len ); if ( !result ) return ""; strncpy ( result, &format[1], fmt_len-1 ); result[fmt_len-1] = '\0'; return result; } /****************************************************************************/ /* Convert strings containing punctuation characters into escaped strings. */ static char *entify_string ( char *source ) { int i, j,brack_count; char *dest; dest = source; brack_count = 0; for ( i = 0; source[i]; i++ ) { if ( (source[i] == '<') || (source[i] == '>') || (source[i] == '&') ) brack_count++; } if ( brack_count > 0 ) { dest = malloc ( i + brack_count*4 + 1 ); if ( !dest ) return source; for ( i = j = 0; source[i]; i++ ) { if ( source[i] == '<' ) { dest[j++] = '&'; dest[j++] = 'l'; dest[j++] = 't'; dest[j++] = ';'; } else if ( source[i] == '>' ) { dest[j++] = '&'; dest[j++] = 'g'; dest[j++] = 't'; dest[j++] = ';'; } else if ( source[i] == '&' ) { dest[j++] = '&'; dest[j++] = 'a'; dest[j++] = 'm'; dest[j++] = 'p'; dest[j++] = ';'; } else dest[j++] = source[i]; } dest[j] = '\0'; } return dest; }