/*****************************************************************************/ /* newmail.c New mail checking and notification. Contains a neat little experiment in Ajax http://ajaxpatterns.org/ which has turned out quite satisfactorily (see NewMailScriptRefresh()). COPYRIGHT --------- Copyright (C) 2005-2024 Mark G.Daniel This program, comes with ABSOLUTELY NO WARRANTY. This is free software, and you are welcome to redistribute it under the conditions of the GNU GENERAL PUBLIC LICENSE, version 3, or any later version. VERSION HISTORY --------------- 09-JUN-2013 MGD NewMailRefreshXMLHttpRequest() and NewMailXMLHttpRequest() provide a more modern new mail check at the server [check-newmail-ajax] reverts to the older IFRAME check audio alert WAVs obsoleted in favour of MP3s 27-MAR-2011 MGD NewMailScriptAjax() refresh if message count decreases 14-FEB-2010 MGD NewMailAt() 28-OCT-2008 MGD NewMailLoginFail() 31-APR-2008 MGD NewMailScriptRefresh() add 'beeps' for browsers/environments not supporting WAVs 10-FEB-2007 MGD Purveyor support removed 30-DEC-2006 MGD NewMailScriptRefresh() and NewMailScriptAjax() cater for MSIE initial IFRAME load to avoid disruptive initial 'check new mail' message in status panel 06-OCT-2006 MGD propagate login credential cookie 20-JUN-2006 MGD CallMailUserDiskQuota() and MessageDiskQuotaUsed() notifies the client with each folder open once disk quota reaches the percentage specified by default or configuration 25-MAR-2006 MGD NewMailPersonalYouGotMail() small accomodation for Purveyor 23-MAR-2006 MGD 'checking for new mail' uses setWorking() progress dots 01-FEB-2005 MGD initial */ /*****************************************************************************/ #ifdef SOYMAIL_VMS_V7 #undef _VMS_V6_SOURCE #define _VMS_V6_SOURCE #undef __VMS_VER #define __VMS_VER 70000000 #undef __CRTL_VER #define __CRTL_VER 70000000 #endif #pragma nomember_alignment /* standard C header files */ #include #include #include #include #include #include #include #include #include #include /* VMS related header files */ #include #include /* application header file */ #include #include #include #include #include #include #include #include #include #define FI_LI __FILE__, __LINE__ /* prototypes */ int sys$gettim (__unknown_params); int lib$sub_times (__unknown_params); /* external storage */ extern BOOL Debug, WatchEnabled; extern char LocalHostName[]; extern CONFIG_DATA SoyMailConfig; extern VMS_MAIL_USER VmsMailUser; /*****************************************************************************/ /* Called by MainMenuRequest() after a [new] button or by the .submit() of NewMailScriptAjax() with NEWMAIL_REFRESH non-empty. */ void NewMailMessage (REQUEST_DATA *rdptr) { int cnt, idx, status; int *midptr; char *cptr; USER_OPTIONS *uoptr; REQUEST_DATA *sdptr; VMS_MAIL_MSG *vmptr; VMS_MAIL_USER *muptr; /*********/ /* begin */ /*********/ if (WatchEnabled) WatchThis ("NewMailMessage()"); muptr = &VmsMailUser; vmptr = &muptr->VmsMailMsg; uoptr = &rdptr->UserOptions; sdptr = (REQUEST_DATA*)rdptr->StateDataPtr; CallMailGoToNewMail (muptr); strcpy (rdptr->MailFileName, "MAIL"); rdptr->MailFileNameLength = 4; strcpy (rdptr->FolderName, "NEWMAIL"); rdptr->FolderNameLength = 7; if (VMSok (muptr->MailVmsStatus)) { if (sdptr->NewMailValid) { /* newmail count in the state data is valid */ if (rdptr->NewMessages > sdptr->NewMessages) { rdptr->NewMailNoticed = TRUE; if (uoptr->YouGotMail[0]) rdptr->NewMailPlay = TRUE; if (uoptr->YouGotMailAt[0] && uoptr->YouGotMailAt[0] != '#' && uoptr->YouGotMailAt[0] != '!') rdptr->NewMailAt = TRUE; } else if (sdptr->NewMailNoticed) { CGIVARNULL (cptr, "FORM_NEWMAIL_REFRESH"); if (cptr && *cptr == 'N') rdptr->NewMailNoticed = TRUE; else rdptr->NewMailNoticed = FALSE; rdptr->NewMailPlay = rdptr->NewMailAt = FALSE; } } if (rdptr->NewMailNoticed) { StatusMessage (FI_LI, -1, " %s", LangFor("n_e_w_m_a_i_l")); if (rdptr->NewMailAt) NewMailAt (rdptr); } else { char FolderOpened [512]; status = CallMailUserDiskQuota (muptr); if (VMSnok (status)) StatusMessage (FI_LI, 1, "DISK-QUOTA: %s.", SysGetMsg(status)); else { sprintf (FolderOpened, LangFor("folder_opened"), HTML_ESCAPE(rdptr->FolderName), vmptr->MessageSelectedCount); StatusMessage (FI_LI, 0, "%s%s", FolderOpened, MessageDiskQuotaUsed(rdptr)); } } } else StatusMessage (FI_LI, 1, "MAIL: %s.", SysGetMsg(muptr->MailVmsStatus)); MessageFolderPage (rdptr); } /*****************************************************************************/ /* Does what NewMailScriptRefresh() did with handcrafted Ajax, checks for new mail message on the server, using the (now) better established XMLHttpRequest() JavaScript object. */ char* NewMailRefreshXMLHttpRequest (REQUEST_DATA *rdptr) { USER_OPTIONS *uoptr; /*********/ /* begin */ /*********/ if (WatchEnabled) WatchThis ("NewMailRefreshXMLHttpRequest()"); if (!NewMailInclude (rdptr)) return (NULL); uoptr = &rdptr->UserOptions; /* note that the newmail count is valid (used by NewMailmessage()) */ rdptr->NewMailValid = TRUE; fprintf (stdout, "\n", stdout); if (uoptr->CheckNewMailAjax) return (NewMailScriptRefresh(rdptr)); return ("refreshNewMail(soy_CheckNewMailMins);"); } /*****************************************************************************/ /* Just like pulling teeth! I started off with the (currently) recommended method of using s for the audio but had too many issues getting it to work satisfactorily across Mozilla, MSIE and Opera. I have gone to the deprecated - but obviously more widely supported and uniform - . Also needs to be supported for the [listen] button in the user options page. Place the required HTML and JavaScript infrastructure in place on the message listing page for the NEWMAIL folder. This infrastruture implements the new mail checking and audio as well as textual notification. There are an number of variants used for the refresh here: o full refresh - where the full folder message listing page is refreshed to check for any new mail (fairly expensive). The is a checkbox on the options page alongside the check newmail minutes for switching to this behaviour (as a fallback for untested browser behaviours). o Ajax 1 - for everything but MSIE; where an IFRAME is used as a background engine to 'poll' soyMAIL for an increase in the new mail count. When there is no new mail it just returns a small HTML reposne with JavaScript code to refresh the new mail status information. When new mail is detected (actually any change in the new message count) it returns an HTML response containing JavaScript code to submit the form of the parent soyMAIL window causing the page with the listing to refresh. This is quite lightweight compared to the full refresh method. o Ajax 2 - a minor variant for MSIE (what else? :-) which complains about 'secure and non-secure' items on a secure (SSL accessed) page. Ajax 1 suppresses an initial (and unnecessary) poll when a NEWMAIL message listing page is requested by not putting any src= in that frame's HTML. Then at the first scheduled poll uses JavaScript and the DOM to dynamiclly change the IFRAME document location and reload. MSIE complains about that. There is a second checkbox on the options page alongside the check newmail minutes for switching to this behaviour (as a fallback for untested browser behaviours). This has been tested against Win32 versions of Firefox 1.5, Mozilla 1.7, Opera 8.5 and MSIE 6.0. */ char* NewMailScriptRefresh (REQUEST_DATA *rdptr) { char *cptr, *sptr, *zptr; USER_OPTIONS *uoptr; /*********/ /* begin */ /*********/ if (Debug) fprintf (stdout, "NewMailScriptRefresh()\n"); if (!NewMailInclude (rdptr)) return (NULL); uoptr = &rdptr->UserOptions; if (rdptr->UserAgent == SOY_USER_AGENT_MSIE) fprintf (stdout, "\n", rdptr->FormAction); else fprintf (stdout, "\n"); return ("refreshNewMail(soy_CheckNewMailMins);"); } /*****************************************************************************/ /* Respond to the XMLHttpRequest() from the client enquiring about new mail. The request query string is tested early in request handling and if it contains a number this function is called. The number in the query string is the current (at the browser) new mail count. A comparison is made to the user's new mail count. */ void NewMailXMLHttpRequest (REQUEST_DATA *rdptr) { int NewMailCount; USER_OPTIONS *uoptr; /*********/ /* begin */ /*********/ if (Debug) fprintf (stdout, "NewMailXMLHttpRequest()\n"); uoptr = &rdptr->UserOptions; if (uoptr->CheckNewMailAjax) { NewMailScriptAjax(rdptr); return; } NewMailCount = atoi(rdptr->CgiQueryStringPtr); if (rdptr->NewMessages > NewMailCount) /* number of messages has increased */ CgiLibResponseHeader (202, "application/x-soyMAIL", "Content-Length: 0\n%s", rdptr->LoginSetCookiePtr); else if (rdptr->NewMessages < NewMailCount) /* number of messages has decreased (e.g. deleted by another agent) */ CgiLibResponseHeader (205, "application/x-soyMAIL", "Content-Length: 0\n%s", rdptr->LoginSetCookiePtr); else /* number of messages has remained the same */ CgiLibResponseHeader (204, "application/x-soyMAIL", "Content-Length: 0\n%s", rdptr->LoginSetCookiePtr); } /*****************************************************************************/ /* This is the workhorse function for the Ajax-style new mail checker. The request query string is tested early in request handling and if it contains a number this function is called. The number in the query string is the current (at the browser) new mail count. A comparison is made to the user's new mail count. If different the small HTML page returned contains JavaScript to refresh the IFRAME's parent (reload the entire page using the
submit() method), the main soyMAIL message listing window. If the new mail count has not changed (usually increased) then just a similarly small HTML page containing JavaScript to refresh the new mail status information. Neat huh?!! */ void NewMailScriptAjax (REQUEST_DATA *rdptr) { int NewMailCount; /*********/ /* begin */ /*********/ if (Debug) fprintf (stdout, "NewMailScriptAjax()\n"); CgiLibResponseHeader (200, "text/html", "Script-Control: X-content-encoding-gzip=0\n%s", rdptr->LoginSetCookiePtr); /* it's the AJAX IFRAME requesting a newmail count */ NewMailCount = atoi(rdptr->CgiQueryStringPtr); if (NewMailCount == 999999) { /* magic number for ... */ fprintf (stdout, "\n"); } else if (rdptr->NewMessages < NewMailCount) { /* number of messages has decreased (e.g. deleted by another agent) */ fprintf (stdout, "\n"); } else if (rdptr->NewMessages > NewMailCount) { /* number of messages has increased */ fprintf (stdout, "\n"); } else { fprintf (stdout, "\n"); } } /*****************************************************************************/ /* An Ajax-style new mail checker request has failed to login. Return a response that will cause the parent window to refresh (and thereby result in the login page to be presented). */ void NewMailLoginFail (REQUEST_DATA *rdptr) { USER_OPTIONS *uoptr; /*********/ /* begin */ /*********/ if (Debug) fprintf (stdout, "NewMailLoginFail()\n"); uoptr = &rdptr->UserOptions; if (uoptr->CheckNewMailAjax) { /* old-fashioned Ajax :-) */ CgiLibResponseHeader (200, "text/html", "Script-Control: X-content-encoding-gzip=0\n%s", rdptr->LoginSetCookiePtr); fprintf (stdout, "\n"); } else { /* XMLHttpRequest() */ CgiLibResponseHeader (403, "application/x-soyMAIL", "Content-Length: 0\n%s", rdptr->LoginSetCookiePtr); } } /*****************************************************************************/ /* Returns the personalised mail notification audio content stored in the user' mail directory. For network efficiency, reduced latency, and overall better performance keep track of the modification datum of the file being served by this function. */ void NewMailPersonalYouGotMail (REQUEST_DATA *rdptr) { int fd, retval, status, DataLength; char *cptr, *fnptr, *DataPtr; char LastModifiedGmt [32]; stat_t StatBuffer; USER_OPTIONS *uoptr; REQUEST_DATA *sdptr; VMS_MAIL_USER *muptr; /*********/ /* begin */ /*********/ if (Debug) fprintf (stdout, "NewMailPersonalYouGotMail()\n"); muptr = &VmsMailUser; uoptr = &rdptr->UserOptions; sdptr = (REQUEST_DATA*)rdptr->StateDataPtr; fnptr = CallMailFileIn (muptr, NULL, SOY_YOUGOTMAIL); if (WatchEnabled) WatchThis ("YOUGOTMAIL !AZ", fnptr); if (stat (fnptr, &StatBuffer)) ErrorExit (vaxc$errno, FI_LI); GmtBinTimeToString (LastModifiedGmt, NULL, StatBuffer.st_mtime); CGIVARNULL (cptr, "HTTP_IF_MODIFIED_SINCE"); if (cptr) { if (strsame (cptr, LastModifiedGmt, -1)) { /* not modified */ CgiLibResponseHeader (304, NULL, "Content-Length: 0\n"); return; } } fd = open (fnptr, O_RDONLY, 0, "ctx=stm", "shr=get"); if (!fd) ErrorExit (vaxc$errno, FI_LI); DataPtr = CgiLibVeeMemCalloc (StatBuffer.st_size); if (!DataPtr) ErrorExit (vaxc$errno, FI_LI); DataLength = read (fd, DataPtr, StatBuffer.st_size); if (DataLength <= 0) ErrorExit (vaxc$errno, FI_LI); close (fd); /* http://www.garykessler.net/library/file_sigs.html */ /* MP3 magic hex 49 44 33 or hex ff fb */ if (!(DataPtr[0] == 0xff && (DataPtr[1] & 0xf6) > 0xf0 && (DataPtr[2] & 0xf0) != 0xf0)) ErrorExit (SS$_BUGCHECK, FI_LI); CgiLibResponseHeader (200, "audio/mpeg", "Content-Length: %d\n\ Expires: %s\n\ Last-Modified: %s\n\ %s\ Script-Control: X-content-encoding-gzip=0\n", StatBuffer.st_size, LastModifiedGmt, LastModifiedGmt, rdptr->LoginSetCookiePtr); fwrite (DataPtr, DataLength, 1, stdout); CgiLibVeeMemFree (DataPtr); } /*****************************************************************************/ /* Notify of new mail by sending an advisary mail message (hopefully elsewhere). */ void NewMailAt (REQUEST_DATA *rdptr) { int NewMessageCount; char *cptr, *sptr, *zptr, *SelfPtr, *SmtpServerPtr; char MsgText [256], UpCaseRemoteUser [256]; REQUEST_DATA *sdptr; USER_OPTIONS *uoptr; /*********/ /* begin */ /*********/ if (WatchEnabled) WatchThis ("NewMailAt()"); uoptr = &rdptr->UserOptions; sdptr = (REQUEST_DATA*)rdptr->StateDataPtr; NewMessageCount = rdptr->NewMessages - sdptr->NewMessages; cptr = LangFor("option_soy_new_soymail_messages"); if (AddressIsRfc(uoptr->YouGotMailAt)) { SmtpServerPtr = SoyMailConfig.SmtpServerHost; if (!SmtpServerPtr) SmtpServerPtr = "localhost"; SelfPtr = ComposeSelfAddress (rdptr); sprintf (MsgText, "%d %s %s", NewMessageCount, cptr, SelfPtr); cptr = MtaMsgSmtpToServer (SmtpServerPtr, SelfPtr, uoptr->YouGotMailAt, MsgText, NULL, MsgText, NULL); if (cptr) StatusMessage (FI_LI, 1, "SMTP: %s", HTML_ESCAPE(cptr)); } else { /* autogenous authentication login may mean this is in lower-case */ zptr = (sptr = UpCaseRemoteUser) + sizeof(UpCaseRemoteUser)-1; for (cptr = rdptr->RemoteUser; *cptr && sptr < zptr; *sptr++ = toupper(*cptr++)); *sptr = '\0'; sprintf (MsgText, "%d %s %s@%s", NewMessageCount, cptr, UpCaseRemoteUser, LocalHostName); cptr = SendMailMessage (UpCaseRemoteUser, uoptr->YouGotMailAt, NULL, MsgText, MsgText); if (cptr) StatusMessage (FI_LI, 1, "MAIL: %s", HTML_ESCAPE(cptr)); } } /*****************************************************************************/ /* All good reasons not to include the NEWMAIL refresh functionality. */ BOOL NewMailInclude (REQUEST_DATA *rdptr) { /*********/ /* begin */ /*********/ if (Debug) fprintf (stdout, "NewMailInclude()\n"); if (rdptr->ThisPageIdent != PAGE_FOLDER) return (FALSE); if (!rdptr->UserOptions.CheckNewMailMins) return (FALSE); if (rdptr->SearchData.InProgress) return (FALSE); if (strcmp (rdptr->MailFileName, "MAIL") || strcmp (rdptr->FolderName, "NEWMAIL")) return (FALSE); return (TRUE); } /*****************************************************************************/