/*****************************************************************************/ #ifdef COMMENTS_WITH_COMMENTS /* config.c Loads and processes the soyMAIL configuration file. This file is located using the SOYMAIL_CONFIG logical name. If the configuration file does not exist or cannot be accessed then soyMAIL just reports a fatal error. Directives and comments must begin in the first column. CONFIGURATION DIRECTIVES ------------------------ [access-log] file specification of access log [attachment-MIME-types] space-separated list of MIME types (full or leading partial) handled internally by the browser when accessing attachments (e.g. PDF, Excel, etc.) [charset-default] if not otherwise specified use this e.g. UTF-8 (default), ISO-8859-1, Windows-1252 overrides user option of same name [charsets-rtl] character sets right-to-left directionality [compose-charsets] comma-separated character sets available for selection on the message composition page [compose-contacts-after] integer, number of contacts after which the list expansion and keyword select is available [compose-contacts-size] integer, size in "em" of contacts select list [compose-edit-cols] each of these three configuration directives takes a string of space or comma (or any non-digit in fact) [compose-edit-rows] separated numbers representing the steps in each of three functions editing parameters, e.g. [compose-wrap-at] "72 80 96 132" or "72,80,96,132" [compose-user-from] ENABLE User-specified compose "From:" address field DISPLAY keyword displays but disables field edit [context-cache-expires] minutes unsued before CGIplus context cache expires [disk-quota-percent] by default begins reporting low disk quota at 85% change this to suit or set to above 100 to disable [downtime] disable the use of soyMAIL and give the specified message to users as a simple announcement [font-family-local] additional font families presented in the selector (intended for non-en language support) [greeting] "Welcome to soyMAIL - UNAUTHORISED USE IS PROHIBITED." [help-about-site] site-specific information presented on the Help 'About' page [html-editor-load] JavaScript to load and instantiate the HTML editor [image-inline] delay in mS when loading inline images default is 500 but can be adjusted to suit site [include-file] include the specified file (maximum include file depth is 2) [login-acme-doi] ACME domain of interpretation (authentication source) defaults to "VMS" [login-acme-no-restrict] ignore any login source restriction (e.g. /NONETWORK) provided authentication is OK [login-agent] external agent to perform login authentication (see description in LOGIN.C module) [login-alias] alias to username mapping (see description in LOGIN.C module) [login-alias-smtp-from] if ENABLEd use any login alias as the local part of the generated 'self' (From:) address [login-autocomplete] ENABLE to allow login page username/password form field autocompletion (default is DISABLE) [login-change-page] file-system location (VMS or C-RTL syntax) of the password change page HTML content [login-idle] number of seconds between accesses before the session is considered idle and forces the user to login again [login-language] language file used on login page [login-no-pwdexp] ignore expired passwords [login-page] file-system location (VMS or C-RTL syntax) of the login page HTML content [login-secret] string used as the key for RC4 encryption of authentication credentials (minimum 24 characters) [login-type] integer representing desired login type NETWORK=1 (default), LOCAL=3, DIALUP=4, REMOTE=5. [login-verify] number of seconds between verification of supplied authentication credentials against the authenticator [logout-realm] used with HTTP server authorization for VMS Apache this enables the logout functionality this string must be *exactly* the same as the AuthName attribute of the authorization against soyMAIL (a realm of '-' disables the logout button) [message-flags] hexadecimal number to enable MAIL$M_NEWMSG (0x01), MAIL$M_REPLIED (0x02) and MAIL$M_MARKED (0x80) flags processing by soyMAIL (bit-wise OR) [message-list-footer] site-specific information (HTML text) presented in a separate panel below the message listing [page-title] superior line in main menu panel and titles all pages [page-title1] (synonym) [page-title2] when set the text in 'page-title1' is forced left and the 'page-title2' if forced right on the page [print-header] text header for printed mail messages [print-footer] text footer for printed mail messages [private-access] allows mapping between authenticated user (CGI remote-user) and a VMS username [private-request] this request is marked as private access (overrides the /~ sentinel for private access) [public-access] permits and maps request path strings to VMS Mail e.g. /path/ /username/file/folder/ [public-no-access] VMS file specification, where public access is authorized (credentials OK) but the user does not have access (to a particular resource) return the content [public-footer] site-specific information (HTML text) presented in a separate panel below the message listing [public-options-default] allows the soyMAIL administrator to set public access 'personal option' settings (required to escape each leading '[' with a '\') [public-request] this request is marked as public access [search-control] 'full' (expensive and the default) 'header-only' (very lightweight) 'keyword-only' (no regular expressions allowed) 'none' (very cheap :-) 'priority=' set priority of message text searches 'vms-select' use VMS Mail select rather than search [site-style-sheet] if specified loads style sheet (after the theme) [SMTP-default-host] if an email address is provided without a domain part an '@' and this string is appended to create an valid (looking) RFC822 address - this disables the ability to send VMS mail! [SMTP-from-host] used when constructing the 'user@from.host.name' [SMTP-server-host] host name/address of SMTP server soyMAIL connects to for Internet mail transport (defaults to localhost) [soyMAIL-at-title] site description provided in title bar of browser window (defaults to "soyMAIL @ the.server.name") [update-last-login] keywords INTERACTIVE ('I') or NON-INTERACTIVE ('N') update the SYSUAF last login date/time with each initial soyMAIL startup (indicated by an empty state) [user-name-info] ENABLE to show current logged-in VMS username in main-menu status information, ALIAS to show login alias (if [login-alias] in use and mapped) [user-options-default] allows the soyMAIL administrator to preset some options (e.g. language) by providing options configuration text aginst this directive (required to escape each leading '[' with a '\') [user-options-override] allows the soyMAIL administrator to override user selected options by providing options configuration text against this directive (required to escape each leading '[' with a '\') [VMS-newmsg] if enabled leaves the new mail count and new message flag untouched (more like VMS command-line Mail) [VMS-occluded] ENABLE to hide the VMS-isms of soyMAIL (e.g. the VMS options, [extract] button, etc.) CONDITIONAL CONFIGURATION ------------------------- [if-CGI-] matches the value of the specifed CGI variable [if-not-CGI-] not matches the value of the specifed CGI variable [if-private] if private mail access [if-public] if public mail access [if-END] following directives are unconditionally processed [end] stop processing the configuration at this point If a match then uses the configuration directives up to the complementary [if-END]. Matching is either done by keyword or regular expression (see brief description in SEARCH.C module). Regular expression are introduced by a leading caret symbol ('^') which of course is not included in the expression. Keyword example ... [if-CGI-http_host] the.host.name [if-CGI-server_name] the.server.name [if-not-CGI-remote_user] R_J_ECUREUIL Equivalent regular expression example ... [if-CGI-http_host] ^*the\.host\.name* [if-CGI-server_name] ^*the\.server\.name* [if-not-CGI-remote_user] ^*R_J_ECUREUIL* Regular expression equivalents of these common wildcards asterisk ('*') .* question mark ('?') . percentage ('%') . A non-existant CGI variable is treated as an empty string. Non-empty and empty strings may be respectively tested for with [if-CGI-variable_name] ^.* [if-not-CGI-variable_name] ^.* Conditionals may be nested (to any depth) but every conditional *must* be closed with an [if-END]. For example: [if-private] [page-title] The Page Title [if-CGI-server_name] www.site1.com [message-list-footer] For more information on Site 1 see ... [if-end] [if-CGI-server_name] www.site2.com [message-list-footer] For more information on Site 2 see ... [if-end] [if-end] # [if-public] [page-title] The Public Page [message-list-footer] Just an public example! [public-access] /public-2005/ /WEBMASTER/MAIL/PUB2005/ [if-end] PRIVATE ACCESS -------------- By default private access is recognised with a path (excluding the script component) beginning "/~". This can be overridden using the [private-request] configuration directive. The specifies that the request should be handled as private regardless of the absence of the "/~". When this directive is used the "/~" sentinel cannot be used. The directive requires no parameter and is intended to be used within some conditional test that would indicate it is a private request. The following example shows a configuration test for the presence of a REMOTE_USER variable indicating it is a request subject to server authorization. [if-CGI-remote_user] [private-request] [end-if] This effectively directs soyMAIL to consider any request subject to authorization as a private access request. This second example shows a test based on request URI and assumes that the server has mapped the path /mail/ onto the soyMAIL executable. [if-CGI-script_name] ^^/mail$ [private-request] [end-if] The first leading caret indicates it is a regex pattern. The second a regex 'beginning of line' (the start of the request URI). Then the literal URI used to activate soyMAIL down to the regex 'end of line' dollar symbol. The [private-request] directive must be specified before the use of any directives that rely on recognition of private or public access (e.g. [if-private], [if-public], [private-access], [public-access], etc) for the obvious reason. Each [private-access] mapping has the following format // where the is the username used for authentication purposes, is a WASDism and allows the script to know against which realm the authentication occured, and is the VMS username to be used for Mail access. Any or all can be a single asterisk wildcard. The mapping '*/*/*' would map all authenticated remote usernames directly as equivalent VMS username. The mapping 'MARK/*/DANIEL' would map remote user MARK to the VMS username DANIEL. A particular username can be disabled by using a hyphen in place of the VMS username, hence the mapping 'SYSTEM/*/-' disables access to the SYSTEM account's Mail. The special-case mapping 'DANIEL/*/(POSTMASTER)' marks the remote username as a 'super-user' of soyMAIL, or Postmaster. POSTMASTER mapping must be used with a URL that begins "/~". DEDICATED ACCESS ---------------- A Web service can be dedicated to soyMAIL access. For this example it will be the host webmail.domain.name and if no URI is specified it defaults to soyMAIL. The WASD configuration would be: # soyMAIL configuration [private-request] enabled # HTTPD$MAP [[webmail.domain.name:443]] pass /soymail/-/* /wasd_root/src/soymail/* map /* /cgi-bin/soymail/~* map=once exec /cgi-bin/* /cgi-bin/* * "403 uh-uh!" [[webmail.domain.name]] redirect * https://webmail.domain.name/ PUBLIC ACCESS ------------- Each rule value has the following format /path-to-mail/ //// as in the following example /WASD-2006/ /daniel/mail/WASD_2006/ which would map the path '/WASD-2005/' to VMS username 'DANIEL', that accounts MAIL.MAI mail file, and the folder (case-sensitive) 'WASD_2005'. Alternatively, to allow mapping to any folder in a public path, with the folder of the initial access specified /path-to-mail/ ///*/ as in the following example /WASD-2006/ /daniel/mail/*WASD_2006/ with optional restriction of no access to the MAIL and NEWMAIL folders /path-to-mail/ ///*!/ as in the following example /WASD-2006/ /daniel/mail/*!WASD_2006/ AUTHORIZED PUBLIC ACCESS ------------------------ (If that's not too oxymoronic!) To require authorization for a public access path add [] after the result path and before any public folder name. /cognoscenti/ /daniel/mail/cognos/ [] Public Folder Name Now this is getting fairly esoteric ... When using AUTOGENOUS AGENT AUTHENTICATION (see LOGIN.C) and access to multiple paths needs to be controlled individually then (perhaps conditionally) add a configuration directive to cause authorization to be done with each request (yes, a bit more costly!) [login-verify] -1 Using the [public-no-access] vms:[file]specification.txt configuration directive an advisory message should be supplied noting that access has been denied. The content of this file should be an HTML page. If the usual '' of the [public-access] mapping is replaced with a VMS file specification then that soyMAIL path delivers the HTML content of that file to the user. This path can use the [] authorization token and so be both a login trigger and a list of accessable resources. [if-public] [login-verify] -1 [login-agent] @CGI-BIN:[000000]PUBAUTH.COM [public-no-access] DEVICE:[DIRECTORY]NO_ACCESS.HTML [public-access] /lists/ DEVICE:[DIRECTORY]LISTS.HTML [] /test1/ /system/mail/*/ [] A Wildcard Public (no start folder) /test2/ /system/mail/*!test/ [] A Wildcard Public (no MAIL or NEWMAIL) /test3/ /system/mail/*testing/ [] A Wildcard Public (no restriction) /test4/ /system/mail/NEWMAIL/ [] A Static Public [if-end] 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 --------------- 11-OCT-2023 MGD add [image-inline] 09-JUN-2013 MGD add [public-footer] add [site-style-sheet] 06-MAR-2010 MGD add [context-cache-expires] 19-APR-2009 MGD add [public-no-access] ConfigPublicAccess() [] authorized public token 13-NOV-2008 MGD add [VMS-newmsg] 11-AUG-2008 MGD add [include-file], [login-alias] and [login-alias-SMTP-from] 15-JUL-2008 MGD add [compose-edit-cols], [compose-edit-rows], [compose-wrap-at], [contacts-compose-after], [contacts-compose-size] and [user-name-info] 09-JUL-2008 MGD add [attachment-MIME-types] (see example in MESSAGE.C) 22-JUN-2008 MGD add [login-type] and [login-change-page] 14-JUN-2008 MGD add [compose-user-from] 15-OCT-2007 MGD add [login-agent] 03-OCT-2007 MGD ConfigSetAccess() if [login-secret] then private access 14-AUG-2007 MGD add [message-flags] 15-JUL-2007 MGD add [login-acme-no-restrict] 02-APR-2007 MGD add [access-log] 26-MAR-2007 MGD unconditionally enable [logout] if [login-secret] set 07-OCT-2006 MGD add [login-acme-doi], [login-autocomplete], [login-idle], [login-language], [login-page], [login-secret] and [login-verify] 21-JUN-2006 MGD add [disk-quota-percent] and [vms-occluded] 17-APR-2006 MGD add [update-last-login] 15-APR-2006 MGD add [private-request] and [public-request] change [if-CGI-..] and [if-not-CGI-..] so that an empty parameter acts as a boolean test for a value or not 09-APR-2006 MGD ConfigPublicAccess() modify wildcard folder to allow initial folder specification and prohibit MAIL and NEWMAIL 02-APR-2006 MGD ConfigPublicAccess() support wildcard folder 16-MAR-2006 MGD add [logout-realm] 25-FEB-2006 MGD add [greeting] 01-FEB-2005 MGD initial */ #endif /* COMMENTS_WITH_COMMENTS */ /*****************************************************************************/ #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 /* VMS related header files */ #include #include #include #include #include /* application header file */ #include "soymail.h" #include "config.h" #define FI_LI __FILE__, __LINE__ /* global storage */ #define CONFIG_INCLUDE_DEPTH 2 int ConfigFileIndex; int ConfigTextLength [CONFIG_INCLUDE_DEPTH+1]; char *ConfigTextPtr [CONFIG_INCLUDE_DEPTH+1]; CONFIG_DATA SoyMailConfig; /* external storage */ extern BOOL Debug, WatchEnabled; /*****************************************************************************/ /* Load the configuration file into memory as a series of newline-delimitted strings. Then leaving that in memory, parse the configuration directives and set the configuration data structure field. */ void ConfigLoad (REQUEST_DATA *rdptr) { BOOL WasConditional; int status, ConditionalCount, ConditionalFalseAt; char *cptr, *sptr, *NamePtr, *ValuePtr, *ErrorPtr; /*********/ /* begin */ /*********/ if (Debug) fprintf (stdout, "ConfigLoad()\n"); memset (&SoyMailConfig, 0, sizeof(SoyMailConfig)); ConfigFileIndex = 0; if (WatchEnabled) { cptr = TrnLnm("SOYMAIL_CONFIG", NULL, 0); WatchThis ("CONFIG FILE !AZ", cptr ? cptr : "logical not defined"); } status = ReadFileIntoMemory ("SOYMAIL_CONFIG", &ConfigTextPtr[ConfigFileIndex], &ConfigTextLength[ConfigFileIndex]); if (VMSnok (status)) { CgiLibResponseError (FI_LI, status, "SOYMAIL_CONFIG."); exit (SS$_NORMAL); } /* by default username/password autocomplete is disabled */ SoyMailConfig.LoginAutoComplete = FALSE; /* full search is on by default, restrict as required */ SoyMailConfig.SearchEnabled = TRUE; SoyMailConfig.SearchKeywordOnly = FALSE; SoyMailConfig.SearchHeaderOnly = FALSE; SoyMailConfig.SearchPriority = -1; /* by default soyMAIL's VMS-isms are obvious */ SoyMailConfig.VmsOccluded = FALSE; /* by default we start reporting low disk quota at 85% used */ SoyMailConfig.DiskQuotaPercent = 85; /* ten minutes by default */ SoyMailConfig.ContextCacheSeconds = 10 * 60; /* obvious ones */ SoyMailConfig.CharSetsRightToLeft = CHARSETS_COMMONLY_RTL; ConditionalCount = ConditionalFalseAt = 0; while (ConfigFileIndex || *ConfigTextPtr[ConfigFileIndex]) { cptr = ConfigTextParse (&ConfigTextPtr[ConfigFileIndex], &NamePtr, &ValuePtr); if (!cptr) { if (ConfigFileIndex--) continue; break; } /****************/ /* conditionals */ /****************/ ErrorPtr = NULL; WasConditional = FALSE; if (strsame (NamePtr, "if-CGI-", 7)) { WasConditional = TRUE; ConditionalCount++; CGIVAR (cptr, NamePtr+7); if (!cptr) cptr = ""; if (!ConditionalFalseAt) { if (!*ValuePtr) { if (!*cptr) ConditionalFalseAt = ConditionalCount; } else if (!SearchKeyword (cptr, ValuePtr, NULL)) ConditionalFalseAt = ConditionalCount; } } else if (strsame (NamePtr, "if-not-CGI-", 11)) { WasConditional = TRUE; ConditionalCount++; CGIVAR (cptr, NamePtr+11); if (!cptr) cptr = ""; if (!ConditionalFalseAt) { if (!*ValuePtr) { if (*cptr) ConditionalFalseAt = ConditionalCount; } else if (SearchKeyword (cptr, ValuePtr, NULL)) ConditionalFalseAt = ConditionalCount; } } else if (strsame (NamePtr, "if-private", -1)) { WasConditional = TRUE; ConditionalCount++; if (!ConditionalFalseAt) { ConfigSetAccess (rdptr); if (!rdptr->PrivateAccess) ConditionalFalseAt = ConditionalCount; } } else if (strsame (NamePtr, "if-public", -1)) { WasConditional = TRUE; ConditionalCount++; if (!ConditionalFalseAt) { ConfigSetAccess (rdptr); if (!rdptr->PublicAccess) ConditionalFalseAt = ConditionalCount; } } else if (strsame (NamePtr, "if-END", -1) || strsame (NamePtr, "end-if", -1) || strsame (NamePtr, "endif", -1)) { WasConditional = TRUE; if (ConditionalFalseAt == ConditionalCount) ConditionalFalseAt = 0; if (ConditionalCount > 0) ConditionalCount--; else ErrorPtr = "conditonal underflow"; } if (WatchEnabled) { if (WasConditional) if (!ConditionalFalseAt || ConditionalCount == ConditionalFalseAt) cptr = "TEST"; else cptr = "SKIP"; else if (ConditionalFalseAt) cptr = "SKIP"; else cptr = "DO"; if (!*ValuePtr) sptr = ""; else if (strchr (ValuePtr, '\n')) sptr = "\n"; else sptr = " "; WatchThis ("CONFIG !UL !AZ [!AZ]!AZ!AZ", ConditionalCount, cptr, NamePtr, sptr, ValuePtr); } if (ConditionalFalseAt || WasConditional) { if (ErrorPtr) ErrorExit (SS$_BUGCHECK, FI_LI);; continue; } if (strsame (NamePtr, "end", -1)) { if (ConfigFileIndex--) continue; break; } /*****************/ /* set directive */ /*****************/ if (strsame (NamePtr, "access-log", -1)) SoyMailConfig.AccessLogFile = ValuePtr; else if (strsame (NamePtr, "attachment-MIME-types", -1)) SoyMailConfig.AttachmentMimeTypes = ValuePtr; else if (strsame (NamePtr, "charset-default", -1)) SoyMailConfig.CharSetDefault = ValuePtr; else if (strsame (NamePtr, "charsets-rtl", -1)) SoyMailConfig.CharSetsRightToLeft = ValuePtr; else if (strsame (NamePtr, "compose-charsets", -1)) SoyMailConfig.ComposeCharSets = ValuePtr; else if (strsame (NamePtr, "compose-contacts-after", -1)) SoyMailConfig.ComposeContactsAfter = atoi(ValuePtr); else if (strsame (NamePtr, "compose-contacts-size", -1)) SoyMailConfig.ComposeContactsSize = atoi(ValuePtr); else if (strsame (NamePtr, "compose-edit-cols", -1)) SoyMailConfig.ComposeEditCols = ValuePtr; else if (strsame (NamePtr, "compose-edit-rows", -1)) SoyMailConfig.ComposeEditRows = ValuePtr; else if (strsame (NamePtr, "compose-user-from", -1)) { if (strsame (ValuePtr, "DISPLAY", -1)) SoyMailConfig.ComposeUserFrom = -1; else SoyMailConfig.ComposeUserFrom = ConfigBoolean(ValuePtr); } else if (strsame (NamePtr, "compose-wrap-at", -1)) SoyMailConfig.ComposeWrapAt = ValuePtr; else if (strsame (NamePtr, "context-cache-expires", -1)) SoyMailConfig.ContextCacheSeconds = atoi(ValuePtr)*60; else if (strsame (NamePtr, "disk-quota-percent", -1)) SoyMailConfig.DiskQuotaPercent = atoi(ValuePtr); else if (strsame (NamePtr, "downtime", -1)) SoyMailConfig.DowntimeMessage = ValuePtr; else if (strsame (NamePtr, "include-file", -1)) { if (ConfigFileIndex < CONFIG_INCLUDE_DEPTH) { ConfigFileIndex++; status = ReadFileIntoMemory (ValuePtr, &ConfigTextPtr[ConfigFileIndex], &ConfigTextLength[ConfigFileIndex]); if (VMSnok (status)) { ConfigFileIndex--; if (WatchEnabled) WatchThis ("%X!8XL !AZ", status, SysGetMsg(status)); } } else if (WatchEnabled) WatchThis ("MAX include depth !UL", CONFIG_INCLUDE_DEPTH); } else if (strsame (NamePtr, "image-inline", -1)) SoyMailConfig.ImageInline = strtol(ValuePtr, NULL, 10); else if (strsame (NamePtr, "message-flags", -1)) SoyMailConfig.MessageFlags = strtol(ValuePtr, NULL, 16); else if (strsame (NamePtr, "message-list-footer", -1)) SoyMailConfig.MessageListFooter = ValuePtr; else if (strsame (NamePtr, "font-family-local", -1)) SoyMailConfig.FontFamilyLocal = ValuePtr; else if (strsame (NamePtr, "greeting", -1)) SoyMailConfig.InitialGreeting = ValuePtr; else if (strsame (NamePtr, "help-about-site", -1)) SoyMailConfig.HelpAboutSite = ValuePtr; else if (strsame (NamePtr, "html-editor-load", -1)) SoyMailConfig.HtmlEditorLoad = ValuePtr; else if (strsame (NamePtr, "login-acme-no-restrict", -1)) SoyMailConfig.LoginAcmeNoRestrict = ConfigBoolean(ValuePtr); else if (strsame (NamePtr, "login-acme-doi", -1)) SoyMailConfig.LoginAcmeDoi = ValuePtr; else if (strsame (NamePtr, "login-agent", -1)) SoyMailConfig.LoginAgent = ValuePtr; else if (strsame (NamePtr, "login-alias", -1)) SoyMailConfig.LoginAlias = ValuePtr; else if (strsame (NamePtr, "login-alias-SMTP-from", -1)) SoyMailConfig.LoginAliasSmtpFrom = ConfigBoolean(ValuePtr); else if (strsame (NamePtr, "login-autocomplete", -1)) SoyMailConfig.LoginAutoComplete = ConfigBoolean(ValuePtr); else if (strsame (NamePtr, "login-change-page", -1)) SoyMailConfig.LoginChangePage = ValuePtr; else if (strsame (NamePtr, "login-idle", -1)) SoyMailConfig.LoginIdleSeconds = atoi(ValuePtr); else if (strsame (NamePtr, "login-language", -1)) SoyMailConfig.LoginLanguage = ValuePtr; else if (strsame (NamePtr, "login-no-pwdexp", -1)) SoyMailConfig.LoginNoPwdExp = ConfigBoolean(ValuePtr); else if (strsame (NamePtr, "login-page", -1)) SoyMailConfig.LoginPage = ValuePtr; else if (strsame (NamePtr, "login-secret", -1)) SoyMailConfig.LoginSecret = ValuePtr; else if (strsame (NamePtr, "login-type", -1)) SoyMailConfig.LoginType = atoi(ValuePtr); else if (strsame (NamePtr, "login-verify", -1)) SoyMailConfig.LoginVerifySeconds = atoi(ValuePtr); else if (strsame (NamePtr, "logout-realm", -1)) SoyMailConfig.LogoutRealm = ValuePtr; else if (strsame (NamePtr, "VMS-newmsg", -1)) SoyMailConfig.VmsNewMsg = ConfigBoolean(ValuePtr); else if (strsame (NamePtr, "page-title", -1) || strsame (NamePtr, "page-title1", -1)) SoyMailConfig.PageTitle1 = ValuePtr; else if (strsame (NamePtr, "page-title2", -1)) SoyMailConfig.PageTitle2 = ValuePtr; else if (strsame (NamePtr, "print-footer", -1)) SoyMailConfig.PrintFooter = ValuePtr; else if (strsame (NamePtr, "print-header", -1)) SoyMailConfig.PrintHeader = ValuePtr; else if (strsame (NamePtr, "private-access", -1)) SoyMailConfig.PrivateAccess = ValuePtr; else if (strsame (NamePtr, "private-request", -1)) { SoyMailConfig.PrivateRequest = TRUE; SoyMailConfig.PublicRequest = FALSE; } else if (strsame (NamePtr, "public-access", -1)) SoyMailConfig.PublicAccess = ValuePtr; else if (strsame (NamePtr, "public-footer", -1)) SoyMailConfig.PublicFooter = ValuePtr; else if (strsame (NamePtr, "public-no-access", -1)) SoyMailConfig.PublicNoAccessPage = ValuePtr; else if (strsame (NamePtr, "public-options-default", -1)) SoyMailConfig.PublicOptionsDefault = ValuePtr; else if (strsame (NamePtr, "public-request", -1)) { SoyMailConfig.PublicRequest = TRUE; SoyMailConfig.PrivateRequest = FALSE; } else if (strsame (NamePtr, "search-control", -1)) SoyMailConfig.SearchControl = ValuePtr; else if (strsame (NamePtr, "site-style-sheet", -1)) SoyMailConfig.SiteStyleSheet = ValuePtr; else if (strsame (NamePtr, "SMTP-default-host", -1)) SoyMailConfig.SmtpDefaultHost = ValuePtr; else if (strsame (NamePtr, "SMTP-from-host", -1)) SoyMailConfig.SmtpFromHost = ValuePtr; else if (strsame (NamePtr, "SMTP-server-host", -1)) SoyMailConfig.SmtpServerHost = ValuePtr; else if (strsame (NamePtr, "soyMAIL-at-title", -1)) SoyMailConfig.SoyMailAtTitle = ValuePtr; else if (strsame (NamePtr, "update-last-login", -1)) SoyMailConfig.UpdateLastLogin = ValuePtr; else if (strsame (NamePtr, "user-name-info", -1)) { if (strsame (ValuePtr, "ALIAS", -1)) SoyMailConfig.UserNameInfo = -1; else SoyMailConfig.UserNameInfo = ConfigBoolean(ValuePtr); } else if (strsame (NamePtr, "user-options-default", -1)) SoyMailConfig.UserOptionsDefault = ValuePtr; else if (strsame (NamePtr, "user-options-override", -1)) SoyMailConfig.UserOptionsOverride = ValuePtr; else if (strsame (NamePtr, "vms-occluded", -1)) SoyMailConfig.VmsOccluded = ConfigBoolean(ValuePtr); else if (WatchEnabled) WatchThis ("CONFIG ERROR [!AZ]", NamePtr); } /* in case there were no [if-private] and/or [if-public] */ ConfigSetAccess (rdptr); if (SoyMailConfig.SearchControl) { for (cptr = SoyMailConfig.SearchControl; *cptr; cptr++) *cptr = tolower(*cptr); if (strstr (SoyMailConfig.SearchControl, "full")) { SoyMailConfig.SearchEnabled = TRUE; SoyMailConfig.SearchKeywordOnly = FALSE; SoyMailConfig.SearchHeaderOnly = FALSE; } if (strstr (SoyMailConfig.SearchControl, "keyword-only")) SoyMailConfig.SearchKeywordOnly = TRUE; if (strstr (SoyMailConfig.SearchControl, "header-only")) SoyMailConfig.SearchHeaderOnly = TRUE; if (strstr (SoyMailConfig.SearchControl, "vms-select")) SoyMailConfig.SelectNotSearch = TRUE; if (cptr = strstr (SoyMailConfig.SearchControl, "priority=")) { cptr += 9; if (isdigit(*cptr)) SoyMailConfig.SearchPriority = atol(cptr); } if (strstr (SoyMailConfig.SearchControl, "none")) { SoyMailConfig.SearchEnabled = SoyMailConfig.SearchKeywordOnly = SoyMailConfig.SearchHeaderOnly = SoyMailConfig.SelectNotSearch = FALSE; } } if (SoyMailConfig.LoginSecret) { unsigned char *ucptr; /* make it a little less like ASCII data by swapping the nybles */ for (ucptr = (unsigned char*)SoyMailConfig.LoginSecret; *ucptr; ucptr++) *ucptr = ((*ucptr & 0xf0) >> 4) | ((*ucptr & 0x0f) << 4); /* check that it's a large enough secret */ if (ucptr - (unsigned char*)SoyMailConfig.LoginSecret < 24) { if (WatchEnabled) WatchThis ("CONFIG ERROR [login-secret] less than 24 characters"); ErrorExit (SS$_NOPRIV, FI_LI); } if (!SoyMailConfig.LoginLanguage) SoyMailConfig.LoginLanguage = "en"; } if (SoyMailConfig.LogoutRealm) { /* realm has been set */ if (!strcmp (SoyMailConfig.LogoutRealm, "-")) { /* setting disables logout */ SoyMailConfig.LogoutDisabled = TRUE; } } else { /* realm has not been set */ if (!CgiLibEnvironmentIsWasd() && !SoyMailConfig.LoginSecret) { /* Apache and OSU must have it set for logout to function */ SoyMailConfig.LogoutDisabled = TRUE; } } if (!SoyMailConfig.ComposeContactsAfter) SoyMailConfig.ComposeContactsAfter = 50; if (!SoyMailConfig.ComposeContactsSize) SoyMailConfig.ComposeContactsSize = 20; if (WatchEnabled) if (ConditionalCount) WatchThis ("CONFIG WARNING conditionals not balanced"); } /****************************************************************************/ /* Return true or false depedning on what contained in the parameter. */ BOOL ConfigBoolean (char *cptr) { /*********/ /* begin */ /*********/ switch (*cptr) { case '1' : return (TRUE); case '0' : return (FALSE); case '+' : return (TRUE); case '-' : return (FALSE); } if (strsame (cptr,"TRUE",-1)) return (TRUE); if (strsame (cptr,"FALSE",-1)) return (FALSE); if (strsame (cptr,"ENABLE", 6)) return (TRUE); if (strsame (cptr,"DISABLE", 7)) return (FALSE); if (strsame (cptr,"ON",-1)) return (TRUE); if (strsame (cptr,"OFF",-1)) return (FALSE); if (strsame (cptr,"YES",-1)) return (TRUE); if (strsame (cptr,"NO",-1)) return (FALSE); if (WatchEnabled) WatchThis ("CONFIG BOOLEAN? [!AZ]", cptr); return (FALSE); } /*****************************************************************************/ /* Parses the contents of a file read into memory, dividing it up into null-terminated strings with names and values. The general format for these name-value pairs is '[name] value', with values allowed to comprise one or more newline separated strings. NOTE: This function is used to parse a number of different soyMAIL files that all use this '[name] value' schema. */ char* ConfigTextParse ( char** TextPtrPtr, char** NamePtrPtr, char** ValuePtrPtr ) { #undef ISLWS #define ISLWS(ch) (ch == ' ' || ch == '\t') #define ISNL(ch) (ch == '\n') char *cptr, *sptr, *nwsptr, *NamePtr, *ValuePtr, *ErrorPtr; /*********/ /* begin */ /*********/ if (Debug) fprintf (stdout, "ConfigTextParse()\n"); if (!TextPtrPtr) ErrorExit (SS$_BUGCHECK, FI_LI); if (!NamePtrPtr) ErrorExit (SS$_BUGCHECK, FI_LI); if (!ValuePtrPtr) ErrorExit (SS$_BUGCHECK, FI_LI); cptr = *TextPtrPtr; if (!*cptr) return (NULL); NamePtr = ValuePtr = NULL; *NamePtrPtr = NamePtr; *ValuePtrPtr = ValuePtr; while (*cptr) { /* ignore leading white-space */ while (*cptr && (ISLWS(*cptr) || ISNL(*cptr))) cptr++; if (!*cptr) break; if (*cptr == '#') { while (*cptr && !ISNL(*cptr)) cptr++; continue; } sptr = cptr; while (*cptr && *cptr != ']' && !ISNL(*cptr)) { if (*cptr == '\\' && *(cptr+1) && *(cptr+1) != '\n') cptr++; cptr++; } if (*sptr != '[' || *cptr != ']') { if (WatchEnabled) WatchThis ("CONFIG PARSE ERROR !#AZ", cptr-sptr, sptr); while (*cptr && !ISNL(*cptr)) cptr++; continue; } NamePtr = ++sptr; *cptr++ = '\0'; /* scan over intervening white-space */ while (*cptr) { while (*cptr && ISLWS(*cptr)) cptr++; if (*cptr && !ISNL(*cptr)) break; if (*(cptr+1) == '[' || *(cptr+1) == '#') break; cptr++; } /* also note first non-white-space */ ValuePtr = nwsptr = sptr = cptr; /* scan to next directive */ while (*cptr) { while (*cptr && !ISNL(*cptr)) { if (*cptr == '\\' && *(cptr+1) && *(cptr+1) != '\n') cptr++; *sptr = *cptr; /* note most recent non-white-space */ if (*cptr && !ISLWS(*sptr) && !ISNL(*sptr)) nwsptr = sptr; sptr++; cptr++; } if (!*cptr) break; /* must be a newline */ if (*(cptr+1) == '[' || *(cptr+1) == '#') break; *sptr++ = *cptr++; } /* before possibly terminating over the top of any newline */ while (ISNL(*cptr)) cptr++; if (sptr > ValuePtr) { /* terminate the value */ *(nwsptr+1) = '\0'; } else ValuePtr = ""; break; } *NamePtrPtr = NamePtr; *ValuePtrPtr = ValuePtr; *TextPtrPtr = cptr; if (WatchEnabled && NamePtr && ValuePtr) { if (!*ValuePtr) sptr = ""; else if (strchr (ValuePtr, '\n')) sptr = "\n"; else sptr = " "; WatchThis ("PARSE [!AZ]!AZ!AZ", NamePtr, sptr, ValuePtr); } return (NamePtr); } /****************************************************************************/ /* Write the supplied text to the supplied file ensuring that with multi-line text any subsequent lines have the leading, configuration-reserved characters '[' and '#', escaped. Even with empty text writes at least a newline character! */ int ConfigWriteEscaped ( FILE *fp, char *TextPtr ) { int lcnt, retval; char *cptr, *eptr, *sptr; /*********/ /* begin */ /*********/ lcnt = retval = 0; cptr = sptr = TextPtr; for (;;) { while (*cptr && *cptr != '\n') cptr++; lcnt++; if (lcnt > 1 && (*sptr == '[' || *sptr == '#')) eptr = "\\"; else eptr = ""; retval += fprintf (fp, "%s%*.*s\n", eptr, cptr-sptr, cptr-sptr, sptr); if (*cptr) cptr++; if (!*cptr) break; sptr = cptr; } return (retval); } /****************************************************************************/ /* Map the private access path into the respective VMS Mail username. Returns TRUE is a mapping can be made, FALSE if not. */ BOOL ConfigPrivateAccess (REQUEST_DATA *rdptr) { char *cptr, *sptr, *zptr, *AuthRealmPtr, *MappingPtr; char AuthRealmName [64], RemoteUserName [64], VmsUserName [64]; /*********/ /* begin */ /*********/ if (WatchEnabled) { WatchThis ("PRIVATE access"); fprintf (stdout, "%s\n", SoyMailConfig.PrivateAccess ? SoyMailConfig.PrivateAccess : "(null)"); } CGIVARNULL (AuthRealmPtr, "AUTH_REALM"); cptr = SoyMailConfig.PrivateAccess; if (!cptr) cptr = ""; while (*cptr) { /* get mapped remote (authenticated) user name */ while (*cptr && ISLWS(*cptr)) cptr++; MappingPtr = cptr; zptr = (sptr = RemoteUserName) + sizeof(RemoteUserName)-1; while (*cptr && *cptr != '/' && *cptr != '\n' && !ISLWS(*cptr) && sptr < zptr) { if (*cptr == '\\' && *(cptr+1)) cptr++; *sptr++ = *cptr++; } if (sptr >= zptr) ErrorExit (SS$_BUGCHECK, FI_LI); *sptr = '\0'; /* get mapped authentication realm name (WASDism) */ while (*cptr && (ISLWS(*cptr) || *cptr == '/')) cptr++; zptr = (sptr = AuthRealmName) + sizeof(AuthRealmName)-1; while (*cptr && *cptr != '/' && *cptr != '\n' && !ISLWS(*cptr) && sptr < zptr) { if (*cptr == '\\' && *(cptr+1)) cptr++; *sptr++ = *cptr++; } if (sptr >= zptr) ErrorExit (SS$_BUGCHECK, FI_LI); *sptr = '\0'; /* get mapped VMS user name */ while (*cptr && (ISLWS(*cptr) || *cptr == '/')) cptr++; zptr = (sptr = VmsUserName) + sizeof(VmsUserName)-1; while (*cptr && *cptr != '/' && *cptr != '\n' && !ISLWS(*cptr) && sptr < zptr) { if (*cptr == '\\' && *(cptr+1)) cptr++; *sptr++ = *cptr++; } if (sptr >= zptr) ErrorExit (SS$_BUGCHECK, FI_LI); *sptr = '\0'; if (WatchEnabled) WatchThis ("PRIVATE mapping !#AZ", cptr-MappingPtr, MappingPtr); /* go to end-of-line */ while (*cptr && !ISNL(*cptr)) cptr++; while (*cptr && ISNL(*cptr)) cptr++; if (WatchEnabled) WatchThis ("PRIVATE remote !AZ !AZ", RemoteUserName, rdptr->RemoteUser); if (RemoteUserName[0] != '*' && !strsame (RemoteUserName, rdptr->RemoteUser, -1)) continue; if (WatchEnabled && AuthRealmPtr) WatchThis ("PRIVATE realm !AZ !AZ", AuthRealmName, AuthRealmPtr); if (AuthRealmPtr && AuthRealmName[0] != '*' && !strsame (AuthRealmName, AuthRealmPtr, -1)) continue; if (strsame (VmsUserName, "(POSTMASTER)", -1)) { rdptr->PostMaster = TRUE; continue; } zptr = (sptr = rdptr->UserName) + sizeof(rdptr->UserName)-1; if (VmsUserName[0] == '*') cptr = rdptr->RemoteUser; else cptr = VmsUserName; while (*cptr && sptr < zptr) *sptr++ = *cptr++; if (sptr >= zptr) ErrorExit (SS$_BUGCHECK, FI_LI); *sptr = '\0'; if (WatchEnabled) WatchThis ("PRIVATE username !AZ", rdptr->UserName); return (TRUE); } if (WatchEnabled) WatchThis ("PRIVATE failed to match"); return (FALSE); } /****************************************************************************/ /* Map the public access path into the respective VMS Mail username, mail file and mail folder. Returns TRUE is a path match is made, FALSE if not. */ BOOL ConfigPublicAccess (REQUEST_DATA *rdptr) { char *cptr, *sptr, *tptr, *zptr; char PathMatchBuffer [256]; /*********/ /* begin */ /*********/ if (WatchEnabled) { WatchThis ("PUBLIC access !AZ", rdptr->PublicAccessString); fprintf (stdout, "%s\n", SoyMailConfig.PublicAccess ? SoyMailConfig.PublicAccess : "(null)"); } cptr = SoyMailConfig.PublicAccess; if (!cptr) cptr = ""; while (*cptr) { while (*cptr && ISLWS(*cptr)) cptr++; if (*cptr == '/') cptr++; zptr = (sptr = PathMatchBuffer) + sizeof(PathMatchBuffer)-1; while (*cptr && *cptr != '/' && !ISLWS(*cptr) && !ISNL(*cptr) && sptr < zptr) { if (*cptr == '\\' && *(cptr+1)) cptr++; *sptr++ = *cptr++; } if (sptr >= zptr) ErrorExit (SS$_BUGCHECK, FI_LI); *sptr = '\0'; if (!strsame (rdptr->PublicAccessString, PathMatchBuffer, -1)) { /* no match, go to start of next rule */ if (WatchEnabled) WatchThis ("PUBLIC no !AZ", PathMatchBuffer); while (*cptr && !ISNL(*cptr)) cptr++; while (ISNL(*cptr)) cptr++; continue; } if (*cptr == '/') cptr++; while (*cptr && isspace(*cptr)) cptr++; if (*cptr == '/') { /**********************/ /* public access mail */ /**********************/ cptr++; zptr = (sptr = rdptr->UserName) + sizeof(rdptr->UserName)-1; while (*cptr && *cptr != '/' && !ISLWS(*cptr) && !ISNL(*cptr) && sptr < zptr) { if (*cptr == '\\' && *(cptr+1)) cptr++; *sptr++ = *cptr++; } if (sptr >= zptr) ErrorExit (SS$_BUGCHECK, FI_LI); *sptr = '\0'; rdptr->UserNameLength = strlen(rdptr->UserName); if (*cptr == '/') cptr++; while (*cptr && ISLWS(*cptr)) cptr++; zptr = (sptr = rdptr->MailFileName) + sizeof(rdptr->MailFileName)-1; while (*cptr && *cptr != '/' && !ISLWS(*cptr) && !ISNL(*cptr) && sptr < zptr) { if (*cptr == '\\' && *(cptr+1)) cptr++; *sptr++ = *cptr++; } if (sptr >= zptr) ErrorExit (SS$_BUGCHECK, FI_LI); *sptr = '\0'; rdptr->MailFileNameLength = strlen(rdptr->MailFileName); if (*cptr == '/') cptr++; while (*cptr && ISLWS(*cptr)) cptr++; if (*cptr == '*') { /* allowed public access to any folder */ rdptr->PublicWildcard = TRUE; cptr++; if (*cptr == '!') { /* except the MAIL and NEWMAIL folders */ rdptr->PublicNoNewMail = TRUE; cptr++; } } else { /* t'be sure t'be sure */ rdptr->PublicWildcard = FALSE; } /* use the folder specified in the directive */ zptr = (sptr = rdptr->FolderName) + sizeof(rdptr->FolderName)-1; while (*cptr && *cptr != '/' && !ISLWS(*cptr) && !ISNL(*cptr) && sptr < zptr) { if (*cptr == '\\' && *(cptr+1)) cptr++; *sptr++ = *cptr++; } if (sptr >= zptr) ErrorExit (SS$_BUGCHECK, FI_LI); *sptr = '\0'; rdptr->FolderNameLength = strlen(rdptr->FolderName); if (*cptr == '/') cptr++; } else { /**********************/ /* public access page */ /**********************/ zptr = (sptr = rdptr->PublicAccessPage) + sizeof(rdptr->PublicAccessPage)-1; while (*cptr && !ISLWS(*cptr) && !ISNL(*cptr) && sptr < zptr) *sptr++ = *cptr++; if (sptr >= zptr) ErrorExit (SS$_BUGCHECK, FI_LI); *sptr = '\0'; while (*cptr && ISLWS(*cptr)) cptr++; if (*(USHORTPTR)cptr == '[]') rdptr->PublicAuthorized = TRUE; /* end of public access page configuration */ return (TRUE); } while (*cptr && ISLWS(*cptr)) cptr++; if (*(USHORTPTR)cptr == '[]') { rdptr->PublicAuthorized = TRUE; cptr += 2; while (*cptr && ISLWS(*cptr)) cptr++; } if (!ISNL(*cptr)) { /* trailing public folder title */ zptr = (sptr = rdptr->PublicAccessTitle) + sizeof(rdptr->PublicAccessTitle)-1; while (*cptr && !ISNL(*cptr) && sptr < zptr) { if (*cptr == '\\' && *(cptr+1)) cptr++; *sptr++ = *cptr++; } *sptr = '\0'; } if (rdptr->UserNameLength && rdptr->MailFileNameLength && (rdptr->FolderNameLength || rdptr->PublicWildcard)) { if (WatchEnabled) WatchThis ("PUBLIC as \"!AZ\" \"!AZ\" \"!AZ\" \"!AZ\" !AZ", rdptr->UserName, rdptr->MailFileName, rdptr->FolderName, rdptr->PublicAccessTitle, rdptr->PublicAuthorized ? "AUTHORIZED" : "PUBLIC"); return (TRUE); } /* something wrong, go to start of next rule */ if (WatchEnabled) WatchThis ("PUBLIC error !AZ", PathMatchBuffer); while (*cptr && *cptr != '\n') cptr++; while (*cptr == '\n') cptr++; } rdptr->UserName[rdptr->UserNameLength = 0] = '\0'; rdptr->MailFileName[rdptr->MailFileNameLength = 0] = '\0'; rdptr->FolderName[rdptr->FolderNameLength = 0] = '\0'; if (WatchEnabled) WatchThis ("PUBLIC failed to match"); return (FALSE); } /****************************************************************************/ /* Determine whether the access is private or public. */ void ConfigSetAccess (REQUEST_DATA *rdptr) { /*********/ /* begin */ /*********/ if (rdptr->PrivateAccess || rdptr->PublicAccess) return; if (SoyMailConfig.PrivateRequest) rdptr->PrivateAccess = TRUE; else if (SoyMailConfig.PublicRequest) rdptr->PublicAccess = TRUE; else if (*(USHORTPTR)rdptr->CgiPathInfoPtr == '/~') rdptr->PrivateAccess = TRUE; else rdptr->PublicAccess = TRUE; if (WatchEnabled) WatchThis ("!AZ access", rdptr->PrivateAccess ? "PRIVATE" : "PUBLIC"); } /*****************************************************************************/