/*****************************************************************************/ /* request.c COPYRIGHT --------- Copyright (C) 2005-2016 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 --------------- 17-JUL-2012 MGD bugfix; RequestPrivate() reset state if username changes 27-MAR-2011 MGD bugfix; RequestVmsMailContext() CGIplus new mail detection 12-DEC-2007 MGD bugfix; RequestPrivate() and RequestParsePath() allow for GET request URI without /~ 30-SEP-2007 MGD RequestPrivate() and RequestPublic() when [user-options-override] then OptionsParse() it modify path parsing to allow [private-request] flagged requests to employ non-script access URLs 13-APR-2007 MGD RequestAccessLog() provide combined access log 16-MAR-2007 MGD RequestVmsMailContext() recallable 17-APR-2006 MGD RequestLastLogin() optionally sets private access SYSUAF last-login at first (initial GET) access 15-APR-2006 MGD configuration [private-request] overrides the default path prefix /~ for private access and requires changes to the way private access is detected and the form action path 12-APR-2006 MGD bugfix; RequestPublic() parsing folder with GET 09-APR-2006 MGD RequestPublic() support public wildcard folder configuration with MAIL and NEWMAIL prohibition 26-MAR-2006 MGD RequestPrivate() Purveyor (1.2 at least) requires a full URL to redirect (during logout) 16-MAR-2006 MGD RequestPrivate() revised logout support 11-MAR-2006 MGD RequestPrivate() no longer include the username when generating the form action URI unless POSTMASTER facility in use (this reduces the leakage of potentially sensitive information through the "Referer:.." header) bugfix; RequestPrivate() do not upper-case username when parsing path as it can interfere with VMS external auth 18-FEB-2006 MGD RequestPrivate() refine [logout] mechanism to improve overall behaviour and avoid hole if MSIE [cancel]s 16-FEB-2006 MGD better handle MAIL$_NOSUCHUSER from CallMailGetUserProfile() 30-JAN-2006 MGD bugfix; RequestParsePath() attachment names can contain '/' 01-FEB-2005 MGD initial */ /*****************************************************************************/ #ifdef SOYMAIL_VMS_V6 #undef _VMS_V6_SOURCE #define _VMS_V6_SOURCE #undef __VMS_VER #define __VMS_VER 60000000 #undef __CRTL_VER #define __CRTL_VER 60000000 #endif #ifndef __VAX # pragma nomember_alignment #endif /* standard C header files */ #include #include #include #include #include #include #include #include /* VMS related header files */ #include #include #include #include #include #include /* application specific */ #include "soymail.h" #include "attach.h" #include "callmail.h" #include "cereal.h" #include "cgilib.h" #include "compose.h" #include "config.h" #include "lang.h" #include "login.h" #include "mainmenu.h" #include "message.h" #include "mta.h" #include "newmail.h" #include "options.h" #include "other.h" #include "public.h" #include "request.h" #define FI_LI __FILE__, __LINE__ #define MAIL$_NOSUCHUSER 8290346 /* can't use a language-specific message here, language not loaded yet */ #define STATE_ERROR "Inconsistent state data (%s). Restarting session." /********************/ /* external storage */ /********************/ extern BOOL Debug, WatchEnabled; extern int SoyMailVolumeStruct; extern char CallMailNoSuchUser[], DocType[], SoftwareVn[]; extern unsigned long CurrentVmsBinTime[]; extern unsigned short CurrentVmsNumTime[]; extern CONFIG_DATA SoyMailConfig; extern VMS_MAIL_USER VmsMailUser; /* required prototypes */ int sys$setuai (__unknown_params); /****************************************************************************/ /* Process a private (authenticated) request. */ RequestPrivate (REQUEST_DATA *rdptr) { int status; unsigned long CurrentSeconds; char *cptr, *sptr, *zptr; char ch; char PathUserName [64]; USER_OPTIONS *uoptr; REQUEST_DATA *sdptr; VMS_MAIL_USER *muptr; /*********/ /* begin */ /*********/ if (WatchEnabled) WatchThis ("RequestPrivate()"); uoptr = &rdptr->UserOptions; muptr = &VmsMailUser; /* if not subject to authorization */ if (!rdptr->RemoteUser[0]) ErrorExit (SS$_NOPRIV, FI_LI); /* map the authenticated username to establish the VMS username */ if (!ConfigPrivateAccess (rdptr)) ErrorExit (SS$_NOPRIV, FI_LI); /* get any username from the request path */ cptr = rdptr->CgiPathInfoPtr; if (WatchEnabled) WatchThis ("PATH_INFO: !AZ", cptr); if (*cptr == '/' && *(cptr+1) && *(cptr+1) != '/' && !isalnum(*(cptr+1))) { /* path-specified username (postmaster) must begin "/~" or equivalent */ ch = *++cptr; while (*cptr && *cptr == ch) cptr++; zptr = (sptr = PathUserName) + sizeof(PathUserName)-1; while (*cptr && *cptr != '/' && sptr < zptr) *sptr++ = *cptr++; if (sptr >= zptr) ErrorExit (SS$_BUGCHECK, FI_LI); *sptr = '\0'; if (WatchEnabled) WatchThis ("PathUserName: !AZ", PathUserName); /* if the path username is not the same as the mapped VMS username */ if (PathUserName[0] && !strsame (PathUserName, rdptr->UserName, -1)) { /* if was not mapped as a postmaster */ if (!rdptr->PostMaster) ErrorExit (SS$_NOPRIV, FI_LI); /* replace with the path-specified username */ zptr = (sptr = rdptr->UserName) + sizeof(rdptr->UserName)-1; for (cptr = PathUserName; *cptr && sptr < zptr; *sptr++ = *cptr++); if (sptr >= zptr) ErrorExit (SS$_BUGCHECK, FI_LI); *sptr = '\0'; rdptr->UserNameLength = sptr - rdptr->UserName; } else rdptr->PostMaster = FALSE; } else if (SoyMailConfig.PrivateRequest || SoyMailConfig.LoginSecret) rdptr->PostMaster = FALSE; else ErrorExit (SS$_BUGCHECK, FI_LI); RequestStateRestore (rdptr); sdptr = (REQUEST_DATA*)rdptr->StateDataPtr; if (sdptr->UserName[0] && !strsame (sdptr->UserName, rdptr->UserName, -1)) { /* username has changed (login page) reset state data */ memset (sdptr, 0, sizeof(REQUEST_DATA)); } /* propagate */ rdptr->PrevPageIdent = sdptr->ThisPageIdent; /* if the personal options were not included in the state data */ if (sdptr->UserOptions.OptionsLoaded) memcpy (&rdptr->UserOptions, &sdptr->UserOptions, sizeof(rdptr->UserOptions)); else OptionsLoadSoy (rdptr); /* and if required override some options */ if (cptr = SoyMailConfig.UserOptionsOverride) { /* duplicate because OptionsParse() munges the string */ sptr = CgiLibVeeMemCalloc (strlen(cptr)+1); if (!sptr) ErrorExit (vaxc$errno, FI_LI); strcpy (sptr, cptr); OptionsParse (&rdptr->UserOptions, sptr); OptionsMunge (&rdptr->UserOptions); } /* an important flag to set in the mail user structure */ if (uoptr->VmsNewMsg > 0) muptr->VmsNewMsg = TRUE; else if (uoptr->VmsNewMsg < 0) muptr->VmsNewMsg = FALSE; else muptr->VmsNewMsg = SoyMailConfig.VmsNewMsg; LangLoad (rdptr->UserOptions.Language); if (!uoptr->CharSet[0]) strcpy (uoptr->CharSet, LangFor("lang_charset")); /* these must be set after the language is loaded */ if (!uoptr->FolderDraftItems[0]) strzcpy (uoptr->FolderDraftItems, LangFor("folder_draft_items"), sizeof(uoptr->FolderDraftItems)); if (!uoptr->FolderWasteItems[0]) strzcpy (uoptr->FolderWasteItems, LangFor("folder_waste_items"), sizeof(uoptr->FolderWasteItems)); /* propagate (any) previous search parameters from request to request */ SearchPropagate (rdptr); /* check for the logout sentinal in the path */ if (!strcmp (rdptr->CgiQueryStringPtr, SOY_LOGOUT_SENTINAL+1)) { if (SoyMailConfig.LogoutDisabled) CgiLibResponseError (FI_LI, 0, LangFor("logout_disabled")); else { fprintf (stdout, "Status: 401 Authentication cancelled!\n"); if (SoyMailConfig.LogoutRealm) { CGIVARNULL (cptr, "AUTH_TYPE"); if (!cptr) cptr = "basic"; fprintf (stdout, "WWW-Authenticate: %s realm=\"%s\"\n", cptr, SoyMailConfig.LogoutRealm); } /* belt, braces and a piece of string */ fprintf (stdout, "Content-Type: text/html\n\ \n\ %s\n\ \n\ \n\ \n\ \n\ \n\ %s\n\ \n\ \n", DocType, rdptr->CgiRequestUriPtr, LangFor("logout_not")); } return; } RequestPrivateDefaults (rdptr); if (!strcmp (rdptr->CgiRequestMethodPtr, "GET")) { RequestParsePath (rdptr); if (rdptr->CgiQueryStringPtr[0]) { if (isdigit(rdptr->CgiQueryStringPtr[0])) { NewMailXMLHttpRequest (rdptr); return; } if (!strcmp (rdptr->CgiQueryStringPtr, "yougotmail.mp3")) { NewMailPersonalYouGotMail (rdptr); return; } if (!strcmp (rdptr->CgiQueryStringPtr, "export.ldif")) { ContactsExport (rdptr); return; } if (!strncmp (rdptr->CgiQueryStringPtr, "mailto:", 7)) { ComposePageRequest (rdptr); return; } if (!strncmp (rdptr->CgiQueryStringPtr, "view:", 5)) { AttachView (rdptr, rdptr->CgiQueryStringPtr+5); return; } if (!strncmp (rdptr->CgiQueryStringPtr, "preview", 7)) { MessagePreView (rdptr); return; } } if (rdptr->AttachmentNameLength) { /* get an attachment or other part */ RequestVmsMailContext (rdptr); if (!MessagePart (rdptr)) CgiLibResponseError (FI_LI, 0, LangFor("part_not_found")); return; } /* only performed for the initial GET */ if (SoyMailConfig.UpdateLastLogin) RequestLastLogin (rdptr); } if (rdptr->PrevPageIdent == PAGE_HELP) { HelpPageRequest (rdptr); return; } RequestVmsMailContext (rdptr); if (!MainMenuRequest (rdptr)) { /* not handled directly by the main menu */ switch (rdptr->PrevPageIdent) { case PAGE_FOLDER : MessagePageFolderRequest (rdptr); break; case PAGE_MESSAGE : MessagePageMsgRequest (rdptr); break; case PAGE_SEARCH : SearchPageRequest (rdptr); break; case PAGE_OPTIONS : OptionsPageRequest (rdptr); break; case PAGE_CONTACTS : ContactsPageRequest (rdptr); break; case PAGE_COMPOSE : ComposePageRequest (rdptr); break; default : MessageFolderPage (rdptr); } } } /****************************************************************************/ /* After preparing request data according to what button has been clicked (etc.) this function is called to complete any request parameters that have not been established by the calling code but are, or may be, necessary for appropriate request processing. Processing order is important as later parameters can override earlier. */ BOOL RequestPrivateDefaults (REQUEST_DATA *rdptr) { char *cptr, *sptr, *zptr; char FileFolderDest [256]; VMS_MAIL_USER *muptr; USER_OPTIONS *uoptr; REQUEST_DATA *sdptr; /*********/ /* begin */ /*********/ if (WatchEnabled) WatchThis ("RequestPrivateDefaults()"); uoptr = &rdptr->UserOptions; sdptr = (REQUEST_DATA*)rdptr->StateDataPtr; muptr = &VmsMailUser; /****************/ /* file/folder */ /****************/ /* first check if a folder button has been selected */ CGIVARNULL (cptr, "FORM_FOLDER_BTN"); if (cptr) { /* although a little out-of-order look for message checkbox(es) */ if (MessageIdList (rdptr)) { /* yes there are, stay within the current folder */ CGIVARNULL (cptr, "FORM_FOLDER_LIST_OPEN"); if (cptr && cptr[0]) RequestParseFileFolder (rdptr, cptr); } else { /* parse the folder button */ RequestParseFileFolder (rdptr, cptr); if (!rdptr->MailFileName[0]) { /* folder (folder) buttons have no leading mail file */ CGIVARNULL (cptr, "FORM_FOLDER_BTN_MAILFILE"); if (cptr && cptr[0]) { zptr = (sptr = rdptr->MailFileName) + sizeof(rdptr->MailFileName)-1; while (*cptr && sptr < zptr) *sptr++ = *cptr++; *sptr = '\0'; strcpy (rdptr->MailFileName, sdptr->MailFileName); rdptr->MailFileNameLength = sptr - rdptr->MailFileName; } } } } else { CGIVARNULL (cptr, "FORM_FOLDER_LIST_OPEN"); if (cptr && cptr[0]) RequestParseFileFolder (rdptr, cptr); } if (uoptr->AllMailFiles) { zptr = (sptr = FileFolderDest) + sizeof(FileFolderDest)-1; CGIVAR (cptr, "FORM_FILE_TEXT_DEST"); while (*cptr && isspace(*cptr)) cptr++; while (*cptr && sptr < zptr) *sptr++ = *cptr++; if (sptr > FileFolderDest && sptr < zptr) *sptr++ = '/'; CGIVAR (cptr, "FORM_FOLDER_TEXT_DEST"); while (*cptr && isspace(*cptr)) cptr++; while (*cptr && sptr < zptr) *sptr++ = *cptr++; if (sptr >= zptr) ErrorExit (SS$_BUGCHECK, FI_LI); *sptr = '\0'; cptr = FileFolderDest; } else { CGIVAR (cptr, "FORM_FOLDER_TEXT_DEST"); while (*cptr && isspace(*cptr)) cptr++; } /* when entering file/folder manually (creating?) refresh the lists */ if (*cptr) FolderListReset (rdptr); if (!*cptr) CGIVAR (cptr, "FORM_FOLDER_LIST_DEST"); if (*cptr) RequestParseDestFileFolder (rdptr, cptr); /****************************/ /* basic request parameters */ /****************************/ if (!rdptr->MailFileName[0]) { strcpy (rdptr->MailFileName, "MAIL"); rdptr->MailFileNameLength = strlen(rdptr->MailFileName); } if (!rdptr->DestMailFileName[0]) { strcpy (rdptr->DestMailFileName, rdptr->MailFileName); rdptr->DestMailFileNameLength = strlen(rdptr->DestMailFileName); } if (!rdptr->FolderName[0]) { strcpy (rdptr->FolderName, "NEWMAIL"); rdptr->FolderNameLength = strlen(rdptr->FolderName); } if (!rdptr->DestFolderName[0]) { strcpy (rdptr->DestFolderName, rdptr->FolderName); rdptr->DestFolderNameLength = rdptr->FolderNameLength; } /*********************/ /* refresh any lists */ /*********************/ if (WatchEnabled) { WatchThis ("MAIL file !AZ", rdptr->MailFileName); WatchThis ("MAIL folder !AZ", rdptr->FolderName); WatchThis ("DEST file !AZ", rdptr->DestMailFileName); WatchThis ("DEST folder !AZ", rdptr->DestFolderName); } /* sentinals and triggers to refresh the lists */ if (strcmp (rdptr->MailFileName, sdptr->MailFileName)) FolderListReset (rdptr); else if (strcmp (rdptr->DestMailFileName, sdptr->DestMailFileName)) FolderListReset (rdptr); else if (LangSame ("(none)", rdptr->FolderName)) FolderListReset (rdptr); else { unsigned long TimeSeconds; time (&TimeSeconds); if (TimeSeconds - rdptr->FolderRefreshSeconds > FOLDER_REFRESH_SECONDS) FolderListReset (rdptr); } if (Debug) fprintf (stdout, "final |%s|%s|%s|%s|%s|%s|%d|\n", rdptr->UserName, rdptr->MailFileName, rdptr->DestMailFileName, rdptr->FolderName, rdptr->DestFolderName, rdptr->AttachmentName, rdptr->MessageId); /****************************/ /* create the
action */ /****************************/ zptr = (sptr = rdptr->FormAction) + sizeof(rdptr->FormAction)-1; if (strsame (rdptr->CgiRequestUriPtr, rdptr->CgiScriptNamePtr, strlen(rdptr->CgiScriptNamePtr))) { cptr = rdptr->CgiScriptNamePtr; while (*cptr && sptr < zptr) *sptr++ = *cptr++; if (sptr < zptr) *sptr++ = '/'; if (!SoyMailConfig.PrivateRequest && sptr < zptr) *sptr++ = '~'; if (strcmp (rdptr->UserName, rdptr->RemoteUser)) for (cptr = rdptr->UserName; *cptr && sptr < zptr; *sptr++ = *cptr++); } else { cptr = rdptr->CgiRequestUriPtr; if (*(USHORTPTR)cptr == '/\0') cptr = ""; while (*cptr && sptr < zptr) *sptr++ = *cptr++; } if (sptr >= zptr) ErrorExit (SS$_BUGCHECK, FI_LI); *sptr = '\0'; return (TRUE); } /****************************************************************************/ /* Parse the mail file (optional) and folder names from a string in the format '/' or '/' or '' */ void RequestParseFileFolder ( REQUEST_DATA *rdptr, char *FileFolderString ) { int SlashCount; char *cptr, *sptr, *zptr; USER_OPTIONS *uoptr; /*********/ /* begin */ /*********/ if (WatchEnabled) WatchThis ("RequestParseFileFolder() !AZ", FileFolderString); uoptr = &rdptr->UserOptions; cptr = FileFolderString; if (*cptr == '/') cptr++; SlashCount = 0; for (sptr = cptr; *sptr; sptr++) if (*sptr == '/') SlashCount++; if (SlashCount) { /* format is '/' or '/' */ zptr = (sptr = rdptr->MailFileName) + sizeof(rdptr->MailFileName)-1; while (*cptr && sptr < zptr) { if (*cptr == '/') SlashCount--; if (!SlashCount) break; *sptr++ = *cptr++; } *sptr = '\0'; if (uoptr->PmdfImap) { /* With the PMDF IMAP user option enabled a mail file can only be "MAIL" (callable Mail) or something else (PMDF IMAP). Append a trailing slash (the only character we can guarantee unused in a folder name) to indicate it's a PMDF IMAP file if not "MAIL". */ if (strcmp (rdptr->MailFileName, "MAIL")) if (sptr < zptr) { *sptr++ = '/'; *sptr = '\0'; } } if (sptr >= zptr) ErrorExit (SS$_BUGCHECK, FI_LI); rdptr->MailFileNameLength = sptr - rdptr->MailFileName; /* if file name with trailing slash default to the "MAIL" folder */ if (*cptr) cptr++; if (!*cptr) cptr = "MAIL"; zptr = (sptr = rdptr->FolderName) + sizeof(rdptr->FolderName)-1; while (*cptr && sptr < zptr) *sptr++ = *cptr++; *sptr = '\0'; if (sptr >= zptr) ErrorExit (SS$_BUGCHECK, FI_LI); rdptr->FolderNameLength = sptr - rdptr->FolderName; if (WatchEnabled) WatchThis ("FILE/FOLDER !AZ/!AZ", rdptr->MailFileName, rdptr->FolderName); } else { /* just contains a folder name */ strcpy (rdptr->MailFileName, "MAIL"); rdptr->MailFileNameLength = 4; zptr = (sptr = rdptr->FolderName) + sizeof(rdptr->FolderName)-1; while (*cptr && sptr < zptr) *sptr++ = *cptr++; if (sptr >= zptr) ErrorExit (SS$_BUGCHECK, FI_LI); *sptr = '\0'; rdptr->FolderNameLength = sptr - rdptr->FolderName; if (WatchEnabled) WatchThis ("(FILE/)FOLDER !AZ/!AZ", rdptr->MailFileName, rdptr->FolderName); } } /****************************************************************************/ /* Parse the destination mail file (optional) and folder names from a string in the format '/' or '/' or '' */ void RequestParseDestFileFolder ( REQUEST_DATA *rdptr, char *FileFolderString ) { int SlashCount; char *cptr, *sptr, *zptr; USER_OPTIONS *uoptr; /*********/ /* begin */ /*********/ if (WatchEnabled) WatchThis ("RequestParseDestFileFolder() !AZ", FileFolderString); uoptr = &rdptr->UserOptions; cptr = FileFolderString; if (*cptr == '/') cptr++; SlashCount = 0; for (sptr = cptr; *sptr; sptr++) if (*sptr == '/') SlashCount++; if (SlashCount) { /* format is '/' or '/' */ zptr = (sptr = rdptr->DestMailFileName) + sizeof(rdptr->DestMailFileName)-1; while (*cptr && sptr < zptr) { if (*cptr == '/') SlashCount--; if (!SlashCount) break; *sptr++ = *cptr++; } *sptr = '\0'; if (uoptr->PmdfImap) { /* seet comment in RequestParseFileFolder() above */ if (strcmp (rdptr->DestMailFileName, "MAIL")) if (sptr < zptr) { *sptr++ = '/'; *sptr = '\0'; } } if (sptr >= zptr) ErrorExit (SS$_BUGCHECK, FI_LI); rdptr->DestMailFileNameLength = sptr - rdptr->DestMailFileName; /* if file name with trailing slash default to the MAIL folder */ if (*cptr) cptr++; if (!*cptr) cptr = "MAIL"; zptr = (sptr = rdptr->DestFolderName) + sizeof(rdptr->DestFolderName)-1; while (*cptr && sptr < zptr) *sptr++ = *cptr++; if (sptr >= zptr) ErrorExit (SS$_BUGCHECK, FI_LI); *sptr = '\0'; rdptr->DestFolderNameLength = sptr - rdptr->DestFolderName; if (WatchEnabled) WatchThis ("DEST FILE/FOLDER !AZ/!AZ", rdptr->DestMailFileName, rdptr->DestFolderName); } else { /* just contains a folder name */ strcpy (rdptr->DestMailFileName, "MAIL"); rdptr->DestMailFileNameLength = 4; zptr = (sptr = rdptr->DestFolderName) + sizeof(rdptr->DestFolderName)-1; while (*cptr && sptr < zptr) *sptr++ = *cptr++; if (sptr >= zptr) ErrorExit (SS$_BUGCHECK, FI_LI); *sptr = '\0'; rdptr->DestFolderNameLength = sptr - rdptr->DestFolderName; /* now, is there a mail file being specified by the list? */ CGIVARNULL (cptr, "FORM_FOLDER_LIST_DEST"); if (cptr && cptr[0]) { if (*cptr == '/') cptr++; for (sptr = cptr; *sptr && *sptr != '/' && *sptr != '<'; sptr++); if (*sptr && *sptr == '/') { zptr = (sptr = rdptr->DestMailFileName) + sizeof(rdptr->DestMailFileName)-1; while (*cptr && *cptr != '/' && sptr < zptr) *sptr++ = *cptr++; if (sptr >= zptr) ErrorExit (SS$_BUGCHECK, FI_LI); *sptr = '\0'; rdptr->DestMailFileNameLength = sptr - rdptr->DestMailFileName; } } if (WatchEnabled) WatchThis ("DEST (FILE/)FOLDER !AZ/!AZ", rdptr->DestMailFileName, rdptr->DestFolderName); } } /****************************************************************************/ /* Parse the request path for '/username/file/folder/message/attachment.name'. This should be done last so that any path-supplied components (e.g. username) override any form supplied components. */ BOOL RequestParsePath (REQUEST_DATA *rdptr) { char *cptr, *sptr, *zptr; char ch; /*********/ /* begin */ /*********/ if (WatchEnabled) WatchThis ("RequestParsePath()"); cptr = rdptr->CgiPathInfoPtr; if (*cptr == '/' && *(cptr+1) && *(cptr+1) != '/' && !isalnum(*(cptr+1))) { /* path-specified username (postmaster) must begin "/~" or equivalent */ ch = *++cptr; while (*cptr && *cptr == ch) cptr++; if (isalnum(*cptr)) { zptr = (sptr = rdptr->UserName) + sizeof(rdptr->UserName)-1; while (*cptr && *cptr != '/' && sptr < zptr) *sptr++ = *cptr++; if (sptr >= zptr) ErrorExit (SS$_BUGCHECK, FI_LI); *sptr = '\0'; rdptr->UserNameLength = strlen(rdptr->UserName); } } else if (SoyMailConfig.PrivateRequest || SoyMailConfig.LoginSecret) { /* just skip over any largely irrelevant username component */ for (cptr++; *cptr && *cptr != '/'; *cptr++); } else ErrorExit (SS$_BUGCHECK, FI_LI); if (*cptr == '/') { cptr++; zptr = (sptr = rdptr->MailFileName) + sizeof(rdptr->MailFileName)-1; while (*cptr && *cptr != '/' && sptr < zptr) *sptr++ = *cptr++; if (sptr >= zptr) ErrorExit (SS$_BUGCHECK, FI_LI); *sptr = '\0'; rdptr->MailFileNameLength = strlen(rdptr->MailFileName); } if (*cptr == '/') { cptr++; zptr = (sptr = rdptr->FolderName) + sizeof(rdptr->FolderName)-1; while (*cptr && *cptr != '/' && sptr < zptr) *sptr++ = *cptr++; if (sptr >= zptr) ErrorExit (SS$_BUGCHECK, FI_LI); *sptr = '\0'; rdptr->FolderNameLength = strlen(rdptr->FolderName); } if (*cptr == '/') { cptr++; if (isdigit(*cptr)) { rdptr->MessageId = atoi(cptr); while (*cptr && isdigit(*cptr)) cptr++; if (*cptr == '.') { cptr++; if (isdigit(*cptr)) { rdptr->MessageHash = atoi(cptr); while (*cptr && isdigit(*cptr)) cptr++; } } } } if (*cptr == '/') { cptr++; zptr = (sptr = rdptr->AttachPartName) + sizeof(rdptr->AttachPartName)-1; while (*cptr && *cptr != '/' && sptr < zptr) *sptr++ = *cptr++; if (sptr >= zptr) ErrorExit (SS$_BUGCHECK, FI_LI); *sptr = '\0'; rdptr->AttachPartNameLength = sptr - rdptr->AttachPartName; } if (*cptr == '/') { cptr++; zptr = (sptr = rdptr->AttachmentName) + sizeof(rdptr->AttachmentName)-1; /* right to the end of the path */ while (*cptr && sptr < zptr) *sptr++ = *cptr++; if (sptr >= zptr) ErrorExit (SS$_BUGCHECK, FI_LI); *sptr = '\0'; rdptr->AttachmentNameLength = sptr - rdptr->AttachmentName; } if (WatchEnabled) WatchThis ("PARSED /!AZ/!AZ/!AZ/!UL.!UL/!AZ/!AZ", rdptr->UserName, rdptr->MailFileName, rdptr->FolderName, rdptr->MessageId, rdptr->MessageHash, rdptr->AttachPartName, rdptr->AttachmentName); return (TRUE); } /****************************************************************************/ /* Process a public (non-authenticated) request. */ RequestPublic (REQUEST_DATA *rdptr) { char *cptr, *sptr, *zptr; USER_OPTIONS *uoptr; REQUEST_DATA *sdptr; VMS_MAIL_USER *muptr; /*********/ /* begin */ /*********/ if (WatchEnabled) WatchThis ("RequestPublic()"); /* if not subject to authorization - and should be! */ if (!rdptr->RemoteUser[0] && rdptr->PublicAuthorized) ErrorExit (SS$_NOPRIV, FI_LI); muptr = &VmsMailUser; uoptr = &rdptr->UserOptions; RequestStateRestore (rdptr); sdptr = (REQUEST_DATA*)rdptr->StateDataPtr; /* propagate */ rdptr->PrevPageIdent = sdptr->ThisPageIdent; /* if the 'personal' options were not included in the state data */ if (sdptr->UserOptions.OptionsLoaded) memcpy (&rdptr->UserOptions, &sdptr->UserOptions, sizeof(rdptr->UserOptions)); else OptionsPublicGet (rdptr); /* and if required override some options */ if (cptr = SoyMailConfig.UserOptionsOverride) { /* duplicate because OptionsParse() munges the string */ sptr = CgiLibVeeMemCalloc (strlen(cptr)+1); if (!sptr) ErrorExit (vaxc$errno, FI_LI); strcpy (sptr, cptr); OptionsParse (&rdptr->UserOptions, sptr); OptionsMunge (&rdptr->UserOptions); } LangLoad (rdptr->UserOptions.Language); if (!uoptr->CharSet[0]) strcpy (uoptr->CharSet, LangFor("lang_charset")); /* propagate (any) previous search parameters from request to request */ SearchPropagate (rdptr); RequestPublicDefaults (rdptr); if (rdptr->PublicAuthorized) { CGIVARNULL (cptr, "FORM_MAIN_BTN"); if (cptr && LangSame ("logout", cptr)) { /**********/ /* logout */ /**********/ if (SoyMailConfig.LogoutDisabled) { StatusMessage (FI_LI, 1, LangFor("logout_disabled")); return (FALSE); } if (SoyMailConfig.LoginSecret) { /* using soyMAIL's autogenous authentication, force re-login */ LoginPage (rdptr, "", NULL, 0); RequestAccessLog (NULL, "logout"); } else { strcat (rdptr->FormAction, SOY_LOGOUT_SENTINAL); CgiLibResponseRedirect (rdptr->FormAction); } return (TRUE); } } if (!strcmp (rdptr->CgiRequestMethodPtr, "GET")) { /**************/ /* GET method */ /**************/ /* check if there is attachment detail here */ cptr = rdptr->CgiPathInfoPtr; if (*cptr == '/') cptr++; while (*cptr && *cptr != '/') cptr++; if (*cptr == '/') cptr++; if (*cptr && rdptr->PublicWildcard) { /* includes the folder name */ zptr = (sptr = rdptr->FolderName) + sizeof(rdptr->FolderName)-1; while (*cptr && *cptr != '/' && sptr < zptr) *sptr++ = *cptr++; if (sptr >= zptr) ErrorExit (SS$_BUGCHECK, FI_LI); *sptr = '\0'; rdptr->FolderNameLength = strlen(rdptr->FolderName); if (*cptr == '/') cptr++; } if (*cptr) { if (!isdigit(*cptr)) ErrorExit (SS$_BUGCHECK, FI_LI); rdptr->MessageId = atoi(cptr); while (isdigit(*cptr)) cptr++; if (*cptr == '.') { cptr++; if (isdigit(*cptr)) { rdptr->MessageHash = atoi(cptr); while (*cptr && isdigit(*cptr)) cptr++; } } } if (*cptr == '/') { cptr++; zptr = (sptr = rdptr->AttachPartName) + sizeof(rdptr->AttachPartName)-1; while (*cptr && *cptr != '/' && sptr < zptr) *sptr++ = *cptr++; if (sptr >= zptr) ErrorExit (SS$_BUGCHECK, FI_LI); *sptr = '\0'; rdptr->AttachPartNameLength = sptr - rdptr->AttachPartName; } if (*cptr == '/') { cptr++; zptr = (sptr = rdptr->AttachmentName) + sizeof(rdptr->AttachmentName)-1; while (*cptr && sptr < zptr) *sptr++ = *cptr++; if (sptr >= zptr) ErrorExit (SS$_BUGCHECK, FI_LI); *sptr = '\0'; rdptr->AttachmentNameLength = sptr - rdptr->AttachmentName; } if (WatchEnabled) WatchThis ("PARSED /!AZ/!UL.!UL/!AZ/!AZ", rdptr->FolderName, rdptr->MessageId, rdptr->MessageHash, rdptr->AttachPartName, rdptr->AttachmentName); if (rdptr->AttachmentNameLength) { /***********************************/ /* get an attachment or other part */ /***********************************/ if (rdptr->PublicWildcard && rdptr->PublicNoNewMail && (!strcmp (rdptr->FolderName, "MAIL") || !strcmp (rdptr->FolderName, "NEWMAIL"))) ErrorExit (SS$_NOPRIV, FI_LI); RequestVmsMailContext (rdptr); if (!MessagePart (rdptr)) CgiLibResponseError (FI_LI, 0, LangFor("part_not_found")); return (TRUE); } } if (rdptr->PublicWildcard && rdptr->PublicNoNewMail && (!strcmp (rdptr->FolderName, "MAIL") || !strcmp (rdptr->FolderName, "NEWMAIL"))) ErrorExit (SS$_NOPRIV, FI_LI); RequestVmsMailContext (rdptr); if (rdptr->PrevPageIdent == PAGE_MESSAGE || rdptr->MessageId) PublicMessageRequest (rdptr); else PublicFolderRequest (rdptr); } /****************************************************************************/ /* Public defaults. Processing order is important as later parameters can override earlier. */ BOOL RequestPublicDefaults (REQUEST_DATA *rdptr) { char *cptr, *sptr, *zptr; VMS_MAIL_USER *muptr; /*********/ /* begin */ /*********/ if (WatchEnabled) WatchThis ("RequestPublicDefaults()"); muptr = &VmsMailUser; if (rdptr->PublicWildcard) { /*******************/ /* wildcard public */ /*******************/ /* folder selection (change) will propagate search, [open] doesn't */ CGIVARNULL (cptr, "FORM_MAIN_BTN"); if (cptr && *cptr) rdptr->SearchData.InProgress = FALSE; CGIVARNULL (cptr, "FORM_FOLDER_LIST_OPEN"); if (cptr && cptr[0]) { zptr = (sptr = rdptr->FolderName) + sizeof(rdptr->FolderName)-1; while (*cptr && sptr < zptr) *sptr++ = *cptr++; *sptr = '\0'; rdptr->FolderNameLength = sptr - rdptr->FolderName; } } /****************************/ /* basic request parameters */ /****************************/ if (!rdptr->MailFileName[0]) { strcpy (rdptr->MailFileName, "MAIL"); rdptr->MailFileNameLength = strlen(rdptr->MailFileName); } if (!rdptr->FolderName[0]) { strcpy (rdptr->FolderName, "NEWMAIL"); rdptr->FolderNameLength = strlen(rdptr->FolderName); } if (Debug) fprintf (stdout, "final |%s|%s|%s|%s|%d|\n", rdptr->UserName, rdptr->MailFileName, rdptr->FolderName, rdptr->AttachmentName, rdptr->MessageId); /****************************/ /* create the action */ /****************************/ zptr = (sptr = rdptr->FormAction) + sizeof(rdptr->FormAction)-1; cptr = rdptr->CgiScriptNamePtr; while (*cptr && sptr < zptr) *sptr++ = *cptr++; cptr = rdptr->CgiPathInfoPtr; if (*cptr == '/' && sptr < zptr) *sptr++ = *cptr++; while (*cptr && *cptr != '/' && sptr < zptr) *sptr++ = *cptr++; if (*cptr == '/' && sptr < zptr) *sptr++ = *cptr++; if (sptr >= zptr) ErrorExit (SS$_BUGCHECK, FI_LI); *sptr = '\0'; return (TRUE); } /****************************************************************************/ /* Call VMS Mail to establish user context. */ void RequestVmsMailContext (REQUEST_DATA *rdptr) { VMS_MAIL_USER *muptr; /*********/ /* begin */ /*********/ if (WatchEnabled) WatchThis ("RequestVmsMailContext()"); muptr = &VmsMailUser; if (!muptr->UserName[0]) { strcpy (muptr->UserName, rdptr->UserName); muptr->UserNameLength = strlen(muptr->UserName); CallMailGetUserProfile (muptr); if (VMSnok (muptr->MailVmsStatus)) { /* pretty fundamental error */ if (WatchEnabled) WatchThis ("%X!8XL", muptr->MailVmsStatus); if (muptr->MailVmsStatus == MAIL$_NOSUCHUSER) { CgiLibResponseError (FI_LI, 0, CallMailNoSuchUser); exit (SS$_NORMAL); } else ErrorExit (muptr->MailVmsStatus, FI_LI); } if (WatchEnabled) WatchThis ("!AZ !AZ !AZ", muptr->UserName, muptr->VmsMailFileName, muptr->VmsMailFolderName); } rdptr->NewMessages = muptr->VmsMailNewMessages; strcpy (muptr->VmsMailFileName, rdptr->MailFileName); muptr->VmsMailFileNameLength = rdptr->MailFileNameLength; strcpy (muptr->VmsMailFolderName, rdptr->FolderName); muptr->VmsMailFolderNameLength = rdptr->FolderNameLength; if (rdptr->DestMailFileNameLength) { strcpy (muptr->VmsMailDestFileName, rdptr->DestMailFileName); muptr->VmsMailDestFileNameLength = rdptr->DestMailFileNameLength; } else { strcpy (muptr->VmsMailDestFileName, rdptr->MailFileName); muptr->VmsMailDestFileNameLength = rdptr->MailFileNameLength; } strcpy (muptr->VmsMailDestFolderName, rdptr->DestFolderName); muptr->VmsMailDestFolderNameLength = rdptr->DestFolderNameLength; SoyMailVolumeStruct = CallMailVolumeStruct (muptr->VmsMailFullDirectory); } /****************************************************************************/ /* Set the SYSUAF last-login datum. */ void RequestLastLogin (REQUEST_DATA *rdptr) { static struct ItemList3Struct { unsigned short buf_len; unsigned short item; void *buf_addr; void *ret_len; } LastLoginI_Item [] = { { 8, UAI$_LASTLOGIN_I, &CurrentVmsBinTime, 0 }, { 0, 0, 0, 0 } }, LastLoginN_Item [] = { { 8, UAI$_LASTLOGIN_N, &CurrentVmsBinTime, 0 }, { 0, 0, 0, 0 } }; int status; $DESCRIPTOR (UserNameDsc, ""); /*********/ /* begin */ /*********/ if (WatchEnabled) WatchThis ("UAI$_LASTLOGIN_!AZ !AZ !%D", toupper(SoyMailConfig.UpdateLastLogin[0]) == 'I' ? "I" : "N", rdptr->UserName, &CurrentVmsBinTime); UserNameDsc.dsc$a_pointer = rdptr->UserName; UserNameDsc.dsc$w_length = rdptr->UserNameLength; if (toupper(SoyMailConfig.UpdateLastLogin[0]) == 'I') status = sys$setuai (0, 0, &UserNameDsc, &LastLoginI_Item, 0, 0, 0); else status = sys$setuai (0, 0, &UserNameDsc, &LastLoginN_Item, 0, 0, 0); if (VMSnok (status)) ErrorExit (status, FI_LI); } /*****************************************************************************/ /* Serialize the private state data. */ void RequestPrivateStateSave (REQUEST_DATA *rdptr) { char *flptr, *ffptr, *rsptr; VMS_MAIL_USER *muptr; /*********/ /* begin */ /*********/ if (Debug) fprintf (stdout, "RequestPrivateStateSave()\n"); muptr = &VmsMailUser; rdptr->StateSaveCount++; memcpy (rdptr->StateVersion, SoftwareVn, sizeof(rdptr->StateVersion)); rsptr = CerealDataOut (rdptr, sizeof(REQUEST_DATA)); if (muptr->DestFolderListPtr) ffptr = CerealDataOut (muptr->DestFolderListPtr, muptr->DestFolderListLength); else ffptr = ""; if (muptr->MailFolderListPtr) flptr = CerealDataOut (muptr->MailFolderListPtr, muptr->MailFolderListLength); else flptr = ""; fprintf (stdout, "\n\ \n\ \n", rsptr, ffptr, flptr); } /*****************************************************************************/ /* Serialize the public state data. */ void RequestPublicStateSave (REQUEST_DATA *rdptr) { char *rsptr; /*********/ /* begin */ /*********/ if (Debug) fprintf (stdout, "RequestPublicStateSave()\n"); /* reset possibly sensitive information in the request structure */ memset (rdptr->UserName, 0, sizeof(rdptr->UserName)); if (!rdptr->PublicWildcard) { memset (rdptr->MailFileName, 0, sizeof(rdptr->MailFileName)); memset (rdptr->FolderName, 0, sizeof(rdptr->FolderName)); } rdptr->StateSaveCount++; memcpy (rdptr->StateVersion, SoftwareVn, sizeof(rdptr->StateVersion)); rsptr = CerealDataOut (rdptr, sizeof(REQUEST_DATA)); fprintf (stdout, "\n", rsptr); } /*****************************************************************************/ /* Allocate a dynamic state (request) structure. Attempt to get data from the CGI request state variable. */ BOOL RequestStateRestore (REQUEST_DATA *rdptr) { int dlen, retval; char *cptr, *zptr; REQUEST_DATA *sdptr; VMS_MAIL_USER *muptr; /*********/ /* begin */ /*********/ if (Debug) fprintf (stdout, "RequestStateRestore()\n"); muptr = &VmsMailUser; /*****************/ /* request state */ /*****************/ rdptr->StateDataPtr = sdptr = CgiLibVeeMemCalloc (sizeof(REQUEST_DATA)); CGIVARNULL (cptr, "FORM_STATE_REQUEST"); if (!cptr) return (FALSE); retval = CerealDataIn (cptr, rdptr->StateDataPtr, sizeof(REQUEST_DATA)); if (Debug) fprintf (stdout, "%d\n", retval); if (!retval) { StatusMessage (FI_LI, 1, STATE_ERROR, "corruption"); return (FALSE); } if (memcmp (((REQUEST_DATA*)rdptr->StateDataPtr)->StateVersion, SoftwareVn, sizeof((REQUEST_DATA*)rdptr->StateDataPtr)->StateVersion)) { StatusMessage (FI_LI, 1, STATE_ERROR, "version"); memset (rdptr->StateDataPtr, 0, sizeof(REQUEST_DATA)); return (FALSE); } if (WatchEnabled) { WatchThis ("STATE DATA UserName {!UL}!AZ", strlen(sdptr->UserName), sdptr->UserName); WatchThis ("STATE DATA MailFileName {!UL}!AZ", strlen(sdptr->MailFileName), sdptr->MailFileName); WatchThis ("STATE DATA DestMailFileName {!UL}!AZ", strlen(sdptr->DestMailFileName), sdptr->DestMailFileName); WatchThis ("STATE DATA FolderName {!UL}!AZ", strlen(sdptr->FolderName), sdptr->FolderName); WatchThis ("STATE DATA DestFolderName {!UL}!AZ", strlen(sdptr->DestFolderName), sdptr->DestFolderName); WatchThis ("STATE DATA AttachmentName {!UL}!AZ", strlen(sdptr->AttachmentName), sdptr->AttachmentName); WatchThis ("STATE DATA FolderMessageCount !UL", sdptr->FolderMessageCount); WatchThis ("STATE DATA MessageId !UL", sdptr->MessageId); WatchThis ("STATE DATA RangeIdStart !UL", sdptr->RangeIdStart); WatchThis ("STATE DATA RangeIdCount !UL", sdptr->RangeIdCount); WatchThis ("STATE DATA \'search\' !UL !UL !UL !UL \ !AZ !AZ !AZ !AZ !AZ !AZ", sdptr->SearchData.DateScope, sdptr->SearchData.DateDay, sdptr->SearchData.DateMonth, sdptr->SearchData.DateYear, sdptr->SearchData.From, sdptr->SearchData.To, sdptr->SearchData.Cc, sdptr->SearchData.Subject, sdptr->SearchData.Message, sdptr->SearchData.Attachment); } /**************************/ /* mail file/folder state */ /**************************/ CGIVARNULL (cptr, "FORM_STATE_DESTIN"); if (!cptr) return (FALSE); dlen = CerealDataIn (cptr, NULL, 0); if (dlen) { muptr->DestFolderListPtr = CgiLibVeeMemCalloc (dlen+8); muptr->DestFolderListSize = muptr->DestFolderListLength = dlen; dlen = CerealDataIn (cptr, muptr->DestFolderListPtr, dlen); if (Debug) fprintf (stdout, "filefold:%d\n", dlen); /* calculate the number of null-terminated items in the list */ zptr = muptr->DestFolderListPtr + muptr->DestFolderListLength; cptr = muptr->DestFolderListPtr; muptr->DestFolderListCount = 0; while (cptr < zptr) if (!*cptr++) muptr->DestFolderListCount++; if (WatchEnabled) WatchThis ("STATE MAILFILE !UL", muptr->DestFolderListCount); } /****************/ /* folder state */ /****************/ CGIVARNULL (cptr, "FORM_STATE_FOLDER"); if (!cptr) return (FALSE); dlen = CerealDataIn (cptr, NULL, 0); if (dlen) { muptr->MailFolderListPtr = CgiLibVeeMemCalloc (dlen+8); muptr->MailFolderListSize = muptr->MailFolderListLength = dlen; dlen = CerealDataIn (cptr, muptr->MailFolderListPtr, dlen); if (Debug) fprintf (stdout, "folder:%d\n", dlen); /* calculate the number of null-terminated items in the list */ zptr = muptr->MailFolderListPtr + muptr->MailFolderListLength; cptr = muptr->MailFolderListPtr; muptr->MailFolderListCount = 0; while (cptr < zptr) if (!*cptr++) muptr->MailFolderListCount++; if (WatchEnabled) WatchThis ("STATE FOLDER !UL", muptr->MailFolderListCount); } return (TRUE); } /*****************************************************************************/ /* This function appends access log records to the file specified by [access-log] configuration directive. The log is designed supplement (not supplant) the server access log with authenticated username data for soyMAIL autogenous authentication (data that is no longer available in the server access log because it is no longer performing the authentication). It is in 'combined' (NCSA) format so as to make it amenable to processing with a log analysis tool if desired. Datum for request 'response bytes' is always zero. The 'Codicil' parameter provides additional information about the nature of the request, in particular login/log-fail/logout data. It can be identified by a delimitting exclamation mark and then text following the requet URI. This helps supplement the HTTP status which will never indicate 401 for a failed login attempt (it not being HTTP authentication and all). The log file name can be fully specified by [access-log] or optionally can include up to three C-RTL conversion sequences to introduce components of the date into the log file name. For example; HT_LOGS:SOYMAIL_%04d.LOG introduces the year into the name, HT_LOGS:SOYMAIL_%04d%02d.LOG the year and month, and HT_LOGS:SOYMAIL_%04d%02d%02d.LOG the year, month and day. This allows access log size to be managed more effectively for busy sites, and/or log files to be periodically rotated. (It's a bit rude and crude but there we go.) The function is designed to be called twice. At the start of request processing to ensure that the log file is accessable (indicated by 'rdptr' being NULL). If not then the request is not processed. Then at the conclusion of the request where the actual access log entry is written. The log file is not kept open between these two calls so as to minimise the period the file is actually open and therefore locked, which on a busy site could be indefinitely. There is an effective trade-off against the extra file-system activity. Combined format: 'client rfc1413 auth-user date/time request status bytes referer user-agent' */ int RequestAccessLog ( REQUEST_DATA *rdptr, char *CodicilPtr ) { static char *MonName[] = { "", "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" }; static char CodicilBuffer [64], LogFileName [256]; int cnt, status, HttpStatus; char *cptr, *HttpRefererPtr, *HttpUserAgentPtr, *RemoteHostPtr, *ServerProtocolPtr, *UserNamePtr; FILE *alptr; /*********/ /* begin */ /*********/ if (Debug) fprintf (stdout, "RequestAccessLog()\n"); if (!SoyMailConfig.AccessLogFile) return (SS$_NORMAL); if (CodicilPtr) { strcpy (CodicilBuffer, "!"); strcat (CodicilBuffer, CodicilPtr); if (!rdptr) return (SS$_NORMAL); } if (!LogFileName[0]) { /* rudimentary sanity check - up to three "%d"s */ cnt = 0; for (cptr = SoyMailConfig.AccessLogFile; *cptr; cptr++) { if (*cptr == '%') { cptr++; while (isdigit(*cptr)) cptr++; if (*cptr != 'd') break; if (++cnt > 3) break; } } if (*cptr) return (RMS$_SYN); if (cnt) sprintf (LogFileName, SoyMailConfig.AccessLogFile, CurrentVmsNumTime[0], CurrentVmsNumTime[1], CurrentVmsNumTime[2]); else strcpy (LogFileName, SoyMailConfig.AccessLogFile); } /* if it's locked for any reason then retry */ for (cnt = 0; cnt < SOYMAIL_ACCESS_LOG_ATTEMPTS; cnt++) { /* record mode is required to support concurrent writers */ alptr = fopen (LogFileName, "a", "ctx=rec", "shr=put"); if (alptr) break; status = vaxc$errno; if (status != RMS$_FLK) break; sleep (1); } /* if failed to open the log file */ if (!alptr) return (status); if (!rdptr) { /* just testing that the log file is accessable */ fclose (alptr); return (SS$_NORMAL); } CGIVARNULL (RemoteHostPtr, "REMOTE_HOST"); if (!RemoteHostPtr) CGIVARNULL (RemoteHostPtr, "REMOTE_ADDR"); if (!RemoteHostPtr) RemoteHostPtr = "?"; if (rdptr->RemoteUser[0]) /* authenticated username */ UserNamePtr = rdptr->RemoteUser; else /* attempt to obtain any supplied login page username form field */ CGIVARNULL (UserNamePtr, "FORM_LOGIN_USERNAME"); if (!UserNamePtr || !*UserNamePtr) UserNamePtr = "-"; CGIVARNULL (ServerProtocolPtr, "SERVER_PROTOCOL"); if (!ServerProtocolPtr) ServerProtocolPtr = "?"; HttpStatus = CgiLibResponseGetStatus (); CGIVARNULL (HttpRefererPtr, "HTTP_REFERER"); /* referer legitimately can be absent */ if (!HttpRefererPtr) HttpRefererPtr = ""; CGIVARNULL (HttpUserAgentPtr, "HTTP_USER_AGENT"); /* user agent legitimately can be absent */ if (!HttpUserAgentPtr) HttpUserAgentPtr = ""; fprintf (alptr, "%s - %s [%02d/%s/%4d:%02d:%02d:%02d %s] \ \"%s %s%s %s\" %03d 0 \"%s\" \"%s\"\n", RemoteHostPtr, UserNamePtr, CurrentVmsNumTime[2], MonName[CurrentVmsNumTime[1]], CurrentVmsNumTime[0], CurrentVmsNumTime[3], CurrentVmsNumTime[4], CurrentVmsNumTime[5], MtaTimezone(NULL), rdptr->CgiRequestMethodPtr, rdptr->CgiRequestUriPtr, CodicilBuffer, ServerProtocolPtr, HttpStatus, HttpRefererPtr, HttpUserAgentPtr); fclose (alptr); return (SS$_NORMAL); } /*****************************************************************************/