/*****************************************************************************/ /* In[line]Config.c Woohoo! After several false starts, finally came up up with a workable, single file, configuration mechanism. Historically, WASD uses separate files to configure global settings, authorisation, resource mapping, and virtual services. Before complexity became the norm this was workable. With multiple virtual services and sites, each requiring some customisation, it has become increasingly cumbersome maintaining service, mapping, and authorisation configurations across multiple files. So this inline configuration allows those aspects to be combined, with emphemeral (temporary) WASD configuration files generated and fed to the standard WASD configuration mechanisms. To use inline configuration create a logical name WASD_CONFIG_INLINE specifying the file prior to server startup. For example $ DEFINE /TABLE=WASD_TABLE - WASD_CONFIG_INLINE - WASD_LOCAL:CONFIG_INLINE.CONF [IncludeFile]s are inspected for indication of content; does it look like WASD_CONFIG_GLOBAL directives, WASD_CONFIG_SERVICE, WASD_CONFIG_MAP or WASD_CONFIG_AUTH directives? If not recognised an error is flagged and configuration stops. The following inline-specific directives can be used to explicitly specify a file's content. [Auth] to WASD_CONFIG_AUTH [Global] to WASD_CONFIG_GLOBAL [Map] to WASD_CONFIG_MAP [Service] to WASD_CONFIG_SERVICE These may ALSO be used to specify the content at any stage during configuration. The destination remains current until the end-of-file or next destination directive. Best not include the standard WASD_CONFIG_.. filenames, via logical or explicitly by specification. While not forbidden, this often will result in UNDEFINED BEHAVIOUR. Use inline specific file names. One exception is the WASD_CONFIG_GLOBAL defined file, which will automagically be included if not explicitly [IncludeFile]d. A variant allows a single directive to be inserted into a specific configuration file using a destination directive as a prefix, followed on the same line by the single directive. For example [Map] pass /* /wasd_root/44443/* [IncludeFile]ing a GLOBAL configuration file is optional. If not explicitly included WASD attempts to automatically include the latest non-ephemeral WASD_CONFIG_GLOBAL file before the first [Global] prefix directive. Note that the [Service] inline directive conflicts with a WASD_CONFIG_GLOBAL directive of the same name. The global directive is obsolete but also responds to [ServiceGlobal] directive :-) EXAMPLE FILE ------------ |# WASD_CONFIG_INLINE |[IncludeFile] wasd_local:inline_config_global.conf | |[IncludeFile] wasd_local:inline_config_map.conf | |[IncludeFile] wasd_local:inline_config_service.conf | |[IncludeFile] wasd_local:inline_config_auth.conf | |[Service] |[[https://*:44443]] |[Map] |pass /* /wasd_root/44443/* VERSION HISTORY --------------- 07-JUN-2022 MGD initial */ /*****************************************************************************/ #ifdef WASD_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 #include #include #include #include #include #include "wasd.h" #define WASD_MODULE "INCONFIG" #define INCLUDE_MAX 4 #define INCONFIG_EPHEMERAL "\ \n\ ##!## *************************************************\n\ ##!## ***** INLINE EPHEMERAL FILE - DO NOT EDIT *****\n\ ##!## *************************************************\n\ \n" #if WATCH_MOD #define INCONFIG_EXCISE 1 #else #define INCONFIG_EXCISE 1 #endif /******************/ /* global storage */ /******************/ static BOOL InConfigStart; BOOL InConfigGlobal, InConfigLoading; int InConfigStatus; char AuthFileName [256], GlobalFileName [256], InConfigFileName [256], MapFileName [256], ServiceFileName [256]; FILE *AuthFile, *GlobalFile, *MapFile, *ServiceFile; static FILE *AuthFileBuf, *GlobalFileBuf; *MapFileBuf, *ServiceFileBuf, *TheFile; /********************/ /* external storage */ /********************/ extern int64 HttpdTime64; extern unsigned long SysPrvMask[]; extern BOOL HttpdServerStartup; extern WATCH_STRUCT Watch; /*****************************************************************************/ /* Return a distinctive error status if the WASD_CONFIG_INLINE logical name does not exist. Otherwise initialise the inline configuration. */ int InConfigInit (char *cptr) { int status; char *sptr, *zptr; /*********/ /* begin */ /*********/ if (WATCH_MODULE(WATCH_MOD_CONFIG)) WatchThis (WATCHALL, WATCH_MOD_CONFIG, "InConfigInit()"); /* allows the check for inline ephemeral files to be disabled */ cptr = SysTrnLnm (WASD_INLINE_CHECK); if (!cptr) cptr = "1"; if (atoi (cptr)) { /* delete any inline ephemeral files still hanging around */ sys$setprv (1, &SysPrvMask, 0, 0); cptr = SysTrnLnm (CONFIG_AUTH_FILE_NAME); InConfigCheckFile (cptr); cptr = SysTrnLnm (CONFIG_GLOBAL_FILE_NAME); InConfigCheckFile (cptr); cptr = SysTrnLnm (CONFIG_MAP_FILE_NAME); InConfigCheckFile (cptr); cptr = SysTrnLnm (CONFIG_SERVICE_FILE_NAME); InConfigCheckFile (cptr); sys$setprv (0, &SysPrvMask, 0, 0); } if (!(cptr = SysTrnLnm (CONFIG_INLINE_FILE_NAME))) return (SS$_NOLOGNAM); InConfigStart = InConfigLoading = true; zptr = (sptr = InConfigFileName) + sizeof(InConfigFileName)-1; while (*cptr && sptr < zptr) *sptr++ = *cptr++; *sptr = '\0'; FaoToStdout ("%HTTPD-I-INCONFIG, from !AZ\n", InConfigFileName); status = InConfigOpen (); if (VMSnok (status)) return (status); status = InConfigLoad (); if (VMSnok (status)) return (status); return (SS$_NORMAL); } /*****************************************************************************/ /* OdsOpenReadOnly() needed fab$b_shr = FAB$M_SHRGET | FAB$M_SHRUPD to allow InConfigInit() to fopen(..."w","shr=get") otherwise FLK. Hmmm. */ int InConfigOpen () { int status; char *cptr, *sptr, *zptr; char fname [256], DateTime [32]; /*********/ /* begin */ /*********/ if (WATCH_MODULE(WATCH_MOD_CONFIG)) WatchThis (WATCHALL, WATCH_MOD_CONFIG, "InConfigOpen()"); FaoToBuffer (DateTime, sizeof(DateTime), NULL, "!20%D", &HttpdTime64); status = SS$_NORMAL; sys$setprv (1, &SysPrvMask, 0, 0); cptr = SysTrnLnm (CONFIG_AUTH_FILE_NAME); strzcpy (AuthFileName, cptr, sizeof(AuthFileName)); if (!(AuthFile = fopen (cptr, "w+", "shr=get", "fop=dlt"))) status = vaxc$errno; else if (!fgetname (AuthFile, fname, 1)) sprintf (fname, "%%X%08.08X", status = vaxc$errno); InConfigNoVersion (fname); /* add sentinal */ fprintf (AuthFile, "##!## %s %s\n", fname, DateTime); fprintf (AuthFile, "%s", INCONFIG_EPHEMERAL); if (VMSok(status)) { cptr = SysTrnLnm (CONFIG_GLOBAL_FILE_NAME); strzcpy (GlobalFileName, cptr, sizeof(GlobalFileName)); if (!(GlobalFile = fopen (cptr, "w+", "shr=get", "fop=dlt"))) status = vaxc$errno; else if (!fgetname (GlobalFile, fname, 1)) sprintf (fname, "%%X%08.08X", status = vaxc$errno); InConfigNoVersion (fname); /* add sentinal */ fprintf (GlobalFile, "##!## %s %s\n", fname, DateTime); fprintf (GlobalFile, "%s", INCONFIG_EPHEMERAL); } if (VMSok(status)) { cptr = SysTrnLnm (CONFIG_MAP_FILE_NAME); strzcpy (MapFileName, cptr, sizeof(MapFileName)); if (!(MapFile = fopen (cptr, "w+", "shr=get", "fop=dlt"))) status = vaxc$errno; else if (!fgetname (MapFile, fname, 1)) sprintf (fname, "%%X%08.08X", status = vaxc$errno); InConfigNoVersion (fname); /* add sentinal */ fprintf (MapFile, "##!## %s %s\n", fname, DateTime); fprintf (MapFile, "%s", INCONFIG_EPHEMERAL); } if (VMSok(status)) { cptr = SysTrnLnm (CONFIG_SERVICE_FILE_NAME); strzcpy (ServiceFileName, cptr, sizeof(ServiceFileName)); if (!(ServiceFile = fopen (cptr, "w+", "shr=get", "fop=dlt"))) status = vaxc$errno; else if (!fgetname (ServiceFile, fname, 1)) sprintf (fname, "%%X%08.08X", status = vaxc$errno); InConfigNoVersion (fname); /* add sentinal */ fprintf (ServiceFile, "##!## %s %s\n", fname, DateTime); fprintf (ServiceFile, "%s", INCONFIG_EPHEMERAL); } sys$setprv (0, &SysPrvMask, 0, 0); return (status); } /*****************************************************************************/ /* It is possble, under exceptional circumstances, for an ephemeral configuration file not to be closed properly (InConfigClose() not executed) and therefore to to remain undeleted. This function attempts to cleanup such files by examining the distinctive sentinal present in the first line of such a file. For each file the header is parsed for the sentinal and if present the file is deleted. This is repeated for all file name versions until it fails. In-use versions, from other instances, are locked and therefore not deleted. InConfigCheckFile() is called with SYSPRV already enabled. */ void InConfigCheckFile (char *fname) { int status; char line [INCONFIG_LINE_MAX], vname [256]; FILE *filptr; /*********/ /* begin */ /*********/ if (WATCH_MODULE(WATCH_MOD_CONFIG)) WatchThis (WATCHALL, WATCH_MOD_CONFIG, "InConfigCheckFile() !AZ", fname); for (;;) { /* if we can't open it then it's either not there or locked */ filptr = fopen (fname, "r", "shr=get"); if (!filptr) break; if (!InConfigSentinal (filptr)) break; fgetname (filptr, vname, 1); fclose (filptr); filptr = NULL; remove (fname); if (!(status = vaxc$errno)) status = SS$_NORMAL; FaoToStdout ("%HTTPD-W-INCONFIG, remove !AZ !&S\n", vname, status); if (VMSnok (status)) break; } if (filptr) fclose (filptr); } /*****************************************************************************/ /* Check the first line of the file for something that looks like ##!## WASD_CONFIG:WASD_CONFIG_GLOBAL.CONF 08-JUN-2022 22:44:55 If the file is empty, or has a first line as above, return true, else false; */ BOOL InConfigSentinal (FILE *filptr) { int status; int64 time64; char *cptr, *sptr; char line [INCONFIG_LINE_MAX]; $DESCRIPTOR (BinTimeDsc, ""); /*********/ /* begin */ /*********/ if (WATCH_MODULE(WATCH_MOD_CONFIG)) WatchThis (WATCHALL, WATCH_MOD_CONFIG, "InConfigSentinal()"); rewind (filptr); sptr = fgets (line, sizeof(line), filptr); rewind (filptr); if (!sptr) return (true); if (!MATCH6 (line, "##!## ")) return (false); /* span over file name */ for (cptr = line + 6; *cptr && !isspace(*cptr); cptr++); if (*cptr) cptr++; for (sptr = cptr; *sptr && *sptr != '\n'; sptr++); if (*sptr != '\n') return (false); /* attempt to convert what should be a VMS time string */ BinTimeDsc.dsc$a_pointer = cptr; BinTimeDsc.dsc$w_length = sptr - cptr; status = sys$bintim (&BinTimeDsc, &time64); if (VMSnok (status)) return (false); return (true); } /*****************************************************************************/ /* Read the WASD_CONFIG_INLINE configuration file, processing the appropriate section directives to the respective ephemeral configuration files. */ int InConfigLoad () { BOOL IncludeGlobal; int fcount, status, which; char *aptr, *cptr, *sptr, *zptr; char line [INCONFIG_LINE_MAX]; int lcount [INCLUDE_MAX+1]; char fname [INCLUDE_MAX+1][256]; char service [INCLUDE_MAX+1][128]; FILE *filptr [INCLUDE_MAX+1], *thebuf [INCLUDE_MAX+1]; /*********/ /* begin */ /*********/ if (WATCH_MODULE(WATCH_MOD_CONFIG)) WatchThis (WATCHALL, WATCH_MOD_CONFIG, "InConfigLoad()"); memset (lcount, 0, sizeof(lcount)); memset (fname, 0, sizeof(fname)); memset (service, 0, sizeof(service)); memset (filptr, 0, sizeof(filptr)); memset (thebuf, 0, sizeof(thebuf)); strzcpy (fname[0], InConfigFileName, sizeof(fname[0])); TheFile = NULL; InConfigGlobal = false; fcount = 0; for (;;) { sys$setprv (1, &SysPrvMask, 0, 0); filptr[fcount] = InConfigOpenFile (fname[fcount]); sys$setprv (0, &SysPrvMask, 0, 0); status = InConfigStatus; if (!filptr[fcount]) { FaoToStdout ("%HTTPD-E-INCONFIG, failed to open !AZ\n", fname[fcount]); return (status); } /* overload inline config name with one containing the version number */ if (!fcount) fgetname (filptr[fcount], InConfigFileName, 1); if ((which = InConfigIdentify (filptr[fcount])) == INCONFIG_UNKNOWN) { if (fcount) { FaoToStdout ("%HTTPD-E-INCONFIG, cannot identify !AZ\n", fname[fcount]); return (SS$_ABORT); } } switch (which) { case INCONFIG_AUTH : TheFile = AuthFile; break; case INCONFIG_GLOBAL : TheFile = GlobalFile; break; case INCONFIG_MAP : TheFile = MapFile; break; case INCONFIG_SERVICE : TheFile = ServiceFile; break; case INCONFIG_UNKNOWN : TheFile = NULL; break; default : return (SS$_BUGCHECK); } thebuf[fcount] = TheFile; if (TheFile == GlobalFile) InConfigGlobal = true; if (fcount) fprintf (TheFile, "!#+# %s line %d [IncludeFile] %s\n", fname[fcount-1], lcount[fcount-1], fname[fcount]); lcount[fcount] = 0; for (;;) { cptr = fgets (line, sizeof(line), filptr[fcount]); if (!cptr) { /* end of file */ fclose (filptr[fcount--]); if (fcount < 0) break; fprintf (TheFile, "!#-# %s line %d [IncludeFile] %s\n", fname[fcount], lcount[fcount], fname[fcount+1]); TheFile = thebuf[fcount]; IncludeGlobal = false; continue; } lcount[fcount]++; /* find the first non-white-space */ while (*cptr && ISLWS(*cptr)) cptr++; /* remove any trailing newline */ for (sptr = cptr; *sptr && *sptr != '\n'; sptr++); *sptr = '\0'; if (strsame (cptr, "[IncludeFile]", 13)) { if (fcount++ > INCLUDE_MAX) { FaoToStdout ("%HTTPD-E-INCONFIG, [IncludeFile] exceeded %d\n", INCLUDE_MAX-1); return (SS$_ABORT); } for (cptr += 13; *cptr && ISLWS(*cptr); cptr++); zptr = (sptr = fname[fcount]) + sizeof(fname[0])-1; while (*cptr && !ISLWS(*cptr) && sptr < zptr) *sptr++ = *cptr++; *sptr = '\0'; if (!fname[fcount][0]) { FaoToStdout ("%HTTPD-E-INCONFIG, no [IncludeFile] " "name !AZ at !UL\n", fname[fcount-1], lcount[fcount-1]); return (SS$_ABORT); } break; } aptr = NULL; if (strsame (cptr, "[Auth]", 6)) { for (aptr = cptr + 6; *aptr && ISLWS(*aptr); aptr++); if (*aptr) { fprintf (AuthFile, "%s\n", aptr); continue; } TheFile = AuthFile; } else if (strsame (cptr, "[Global]", 8)) { InConfigAddGlobal (fname[fcount], lcount[fcount]); for (aptr = cptr + 8; *aptr && ISLWS(*aptr); aptr++); if (*aptr) { fprintf (GlobalFile, "%s\n", aptr); continue; } TheFile = GlobalFile; } else if (strsame (cptr, "[Map]", 5)) { for (aptr = cptr + 5; *aptr && ISLWS(*aptr); aptr++); if (*aptr) { fprintf (MapFile, "%s\n", aptr); continue; } TheFile = MapFile; } else if (strsame (cptr, "[Service]", 9)) { if (TheFile != GlobalFile) { for (aptr = cptr + 9; *aptr && ISLWS(*aptr); aptr++); if (*aptr) { fprintf (ServiceFile, "%s\n", aptr); continue; } TheFile = ServiceFile; } } else if (*(USHORTPTR)cptr == '[[') { if (TheFile == ServiceFile) { zptr = (sptr = service[fcount]) + sizeof(service[0])-1; while (*cptr && !ISLWS(*cptr) && sptr < zptr) *sptr++ = *cptr++; *sptr = '\0'; fprintf (AuthFile, "%s\n", service[fcount]); fprintf (MapFile, "%s\n", service[fcount]); } else if (TheFile) fprintf (TheFile, "%s\n", line); else continue; } if (!TheFile) continue; if (aptr && !*aptr) { /* note the changed target configuration file */ fprintf (TheFile, "!#=# %s\n", cptr); } if (!aptr) fprintf (TheFile, "%s\n", line); } /* if end of original inline file */ if (fcount < 0) break; } if (!InConfigGlobal) InConfigAddGlobal ("^Z", 0); /* remove consecutive duplicate entries */ if (!(AuthFile = InConfigExcise (AuthFile))) return (InConfigStatus); if (!(MapFile = InConfigExcise (MapFile))) return (InConfigStatus); fsync (fileno(AuthFile)); fsync (fileno(GlobalFile)); fsync (fileno(MapFile)); fsync (fileno(ServiceFile)); return (SS$_NORMAL); } /*****************************************************************************/ /* Open the named file. If it opens check that it is not an ephemeral config file. This is performed by examining the first line looking for the ephemeral file sentinal. If it contains that sentinal then work back by one version each time a file contains the ephemeral file sentinal. Return a pointer to the open file, or a NULL if not/none found. Global storage InConfigStatus contains any related VMS status value. */ FILE* InConfigOpenFile (char *FileName) { int status, version; char *cptr, *sptr, *vptr, *zptr; char fname [256]; FILE *filptr; /*********/ /* begin */ /*********/ if (WATCH_MODULE(WATCH_MOD_CONFIG)) WatchThis (WATCHALL, WATCH_MOD_CONFIG, "InConfigOpenFile()"); InConfigStatus = 0; sys$setprv (1, &SysPrvMask, 0, 0); filptr = fopen (FileName, "r", "shr=get"); sys$setprv (0, &SysPrvMask, 0, 0); if (!filptr) { if (vaxc$errno != RMS$_FLK) { InConfigStatus = vaxc$errno; return (NULL); } } else if (!InConfigSentinal (filptr)) { /* does not contain the ephemeral sentinal */ return (filptr); } /* copy the file name allowing space for the appended version */ zptr = (sptr = fname) + sizeof(fname)-8; for (cptr = FileName; *cptr && sptr < zptr; *sptr++ = *cptr++); *sptr = '\0'; for (vptr = sptr; sptr > fname && *vptr != ';'; vptr--); if (sptr > fname) vptr = sptr; version = -1; filptr = NULL; for (;;) { sprintf (vptr, ";%d", version--); sys$setprv (1, &SysPrvMask, 0, 0); if (filptr) fclose(filptr); filptr = fopen (fname, "r", "shr=get"); sys$setprv (0, &SysPrvMask, 0, 0); status = vaxc$errno; if (!filptr) { if (status == RMS$_FLK) continue; if (status == RMS$_FNF) return (NULL); InConfigStatus = status; return (NULL); } if (!InConfigSentinal (filptr)) return (filptr); } InConfigStatus = SS$_BUGCHECK; return (NULL); } /*****************************************************************************/ /* Buffering the current emphemeral files, load temporary files. InConfigUnload() closes (deleting) the temporary files, restoring from buffers. */ int InConfigReload () { int status; char fname [256], DateTime [32]; /*********/ /* begin */ /*********/ if (WATCH_MODULE(WATCH_MOD_CONFIG)) WatchThis (WATCHALL, WATCH_MOD_CONFIG, "InConfigReload()"); if (!InConfigStart) return (SS$_NORMAL); InConfigGlobal = false; FaoToBuffer (DateTime, sizeof(DateTime), NULL, "!20%D", &HttpdTime64); /* buffer all the current file pointers */ AuthFileBuf = AuthFile; GlobalFileBuf = GlobalFile; MapFileBuf = MapFile; ServiceFileBuf = ServiceFile; AuthFile = GlobalFile = MapFile = ServiceFile = NULL; status = SS$_NORMAL; sys$setprv (1, &SysPrvMask, 0, 0); if (!(AuthFile = fopen (AuthFileName, "w+", "shr=get", "fop=dlt"))) status = vaxc$errno; if (VMSok(status)) if (!(GlobalFile = fopen (GlobalFileName, "w+", "shr=get", "fop=dlt"))) status = vaxc$errno; if (VMSok(status)) if (!(MapFile = fopen (MapFileName, "w+", "shr=get", "fop=dlt"))) status = vaxc$errno; if (VMSok(status)) if (!(ServiceFile = fopen (ServiceFileName, "w+", "shr=get", "fop=dlt"))) status = vaxc$errno; if (VMSnok (status)) { if (AuthFile) fclose (AuthFile); if (GlobalFile) fclose (GlobalFile); if (MapFile) fclose (MapFile); if (ServiceFile) fclose (ServiceFile); } sys$setprv (0, &SysPrvMask, 0, 0); if (VMSok (status)) { fprintf (AuthFile, "##!## %s %s\n", AuthFileName, DateTime); fprintf (AuthFile, "%s", INCONFIG_EPHEMERAL); fprintf (GlobalFile, "##!## %s %s\n", GlobalFileName, DateTime); fprintf (GlobalFile, "%s", INCONFIG_EPHEMERAL); fprintf (MapFile, "##!## %s %s\n", MapFileName, DateTime); fprintf (MapFile, "%s", INCONFIG_EPHEMERAL); fprintf (ServiceFile, "##!## %s %s\n", ServiceFileName, DateTime); fprintf (ServiceFile, "%s", INCONFIG_EPHEMERAL); status = InConfigLoad (); } if (WATCH_MODULE(WATCH_MOD_CONFIG)) WatchThis (WATCHALL, WATCH_MOD_CONFIG, "!&S", status); return (status); } /*****************************************************************************/ /* Close (deleting) the temporary emphemeral files and restore buffered files. */ void InConfigUnload () { /*********/ /* begin */ /*********/ if (WATCH_MODULE(WATCH_MOD_CONFIG)) WatchThis (WATCHALL, WATCH_MOD_CONFIG, "InConfigUnload()"); sys$setprv (1, &SysPrvMask, 0, 0); if (AuthFile) fclose (AuthFile); if (GlobalFile) fclose (GlobalFile); if (MapFile) fclose (MapFile); if (ServiceFile) fclose (ServiceFile); sys$setprv (0, &SysPrvMask, 0, 0); AuthFile = AuthFileBuf; GlobalFile = GlobalFileBuf; MapFile = MapFileBuf; ServiceFile = ServiceFileBuf; AuthFileBuf = GlobalFileBuf = MapFileBuf = ServiceFileBuf = NULL; } /*****************************************************************************/ /* Close (deleting) current emphemeral authorisation file then swap with new file. */ void InConfigKeepAuth () { /*********/ /* begin */ /*********/ if (WATCH_MODULE(WATCH_MOD_CONFIG)) WatchThis (WATCHALL, WATCH_MOD_CONFIG, "InConfigKeepAuth()"); if (!InConfigStart) return; /* close (and delete) the original ephemeral file */ sys$setprv (1, &SysPrvMask, 0, 0); fclose (AuthFileBuf); sys$setprv (0, &SysPrvMask, 0, 0); /* replace buffered with the new ephemeral file */ AuthFileBuf = AuthFile; AuthFile = NULL; } /*****************************************************************************/ /* Close (deleting) the current emphemeral mapping file then swap with new file. */ void InConfigKeepMap () { /*********/ /* begin */ /*********/ if (WATCH_MODULE(WATCH_MOD_CONFIG)) WatchThis (WATCHALL, WATCH_MOD_CONFIG, "InConfigKeepMap()"); if (!InConfigStart) return; /* close (and delete) the original ephemeral file */ sys$setprv (1, &SysPrvMask, 0, 0); fclose (MapFileBuf); sys$setprv (0, &SysPrvMask, 0, 0); /* replace buffered with the new ephemeral file */ MapFileBuf = MapFile; MapFile = NULL; } /*****************************************************************************/ /* Explicitly close the ephemeral configuration files so they are deleted with the necessary privilege enabled. */ void InConfigClose () { /*********/ /* begin */ /*********/ if (WATCH_MODULE(WATCH_MOD_CONFIG)) WatchThis (WATCHALL, WATCH_MOD_CONFIG, "InConfigClose()"); if (!InConfigStart) return; sys$setprv (1, &SysPrvMask, 0, 0); fclose (AuthFile); fclose (GlobalFile); fclose (MapFile); fclose (ServiceFile); sys$setprv (0, &SysPrvMask, 0, 0); } /*****************************************************************************/ /* If |set| true then if an inline-config startup set the |InConfigLoading| flag, otherwise reset the flag. Always return the current state of the flag. */ BOOL InConfigStartup (int set) { /*********/ /* begin */ /*********/ if (WATCH_MODULE(WATCH_MOD_CONFIG)) WatchThis (WATCHALL, WATCH_MOD_CONFIG, "InConfigStartup() !&B !&B", set, InConfigStart); if (InConfigStart && set) InConfigLoading = true; else InConfigLoading = false; return (InConfigLoading); } /*****************************************************************************/ /* Attempt to identify the (leading) configuration directives and return a pointer to the configuration file. Return a NULL pointer to indicate the configuration could not be identified. */ int InConfigIdentify (FILE *filptr) { char ch; char *aptr, *cptr; char line [INCONFIG_LINE_MAX]; /*********/ /* begin */ /*********/ if (WATCH_MODULE(WATCH_MOD_CONFIG)) WatchThis (WATCHALL, WATCH_MOD_CONFIG, "InConfigIdentify()"); rewind (filptr); /*****************/ /* authorisation */ /*****************/ while (cptr = fgets (line, sizeof(line), filptr)) { while (*cptr && *cptr != '[') cptr++; if (!*cptr) continue; /* inline directives */ if (strsame (cptr, "[Auth]", 6)) { cptr = NULL; break; } if (strsame (cptr, "[Global]", 8)) { cptr = NULL; break; } if (strsame (cptr, "[Map]", 5)) { cptr = NULL; break; } if (strsame (cptr, "[Service]", 9)) { cptr = NULL; break; }; if (*cptr == '[') { /* attempt to identify realm directives */ if (strsame (cptr, "[ACME", 5)) break; if (strsame (cptr, "[EXTERNAL", 9)) break; if (strsame (cptr, "[NONE", 5)) break; if (strsame (cptr, "[OPAQUE", 7)) break; if (strsame (cptr, "[PROMISCUOUS", 13)) break; if (strsame (cptr, "[RFC1413", 8)) break; if (strsame (cptr, "[SKELKEY", 8)) break; if (strsame (cptr, "[TOKEN", 6)) break; if (strsame (cptr, "[VMS", 4)) break; if (strsame (cptr, "[WORLD", 6)) break; if (strsame (cptr, "[X509", 5)) break; /* complex multipart realm directive */ for (aptr = cptr; *aptr != ']' && *aptr != '\"' && *aptr != ';'; aptr++); if (*aptr != ']') break; } } rewind (filptr); if (cptr && *cptr == '[') return (INCONFIG_AUTH); /**********/ /* global */ /**********/ while (cptr = fgets (line, sizeof(line), filptr)) { while (*cptr && *cptr != '[') cptr++; if (!*cptr) continue; /* inline directives */ if (strsame (cptr, "[Auth]", 6)) { cptr = NULL; break; } if (strsame (cptr, "[Global]", 8)) { cptr = NULL; break; } if (strsame (cptr, "[Map]", 5)) { cptr = NULL; break; } /* [Service] is a deprecated but current directive */ /* global directives */ if (strsame (cptr, "[InstanceMax]", 13)) break; if (strsame (cptr, "[ConnectMax]", 12)) break; if (strsame (cptr, "[Logging]", 9)) break; if (strsame (cptr, "[OpcomTarget]", 13)) break; if (strsame (cptr, "[TimeoutInput]", 14)) break; if (strsame (cptr, "[HttpProtocol]", 14)) break; if (strsame (cptr, "[SecureSocket]", 14)) break; if (strsame (cptr, "[AuthBasic]", 11)) break; if (strsame (cptr, "[Scripting]", 11)) break; if (strsame (cptr, "[DirectoryAccess]", 17)) break; if (strsame (cptr, "[AddIcon]", 9)) break; } rewind (filptr); if (cptr && *cptr == '[') return (INCONFIG_GLOBAL); /***********/ /* mapping */ /***********/ while (cptr = fgets (line, sizeof(line), filptr)) { while (*cptr && isspace(*cptr)) cptr++; if (!*cptr || *cptr == '#' || *cptr == '!') continue; /* inline directives */ if (strsame (cptr, "[Auth]", 6)) { cptr = NULL; break; } if (strsame (cptr, "[Global]", 8)) { cptr = NULL; break; } if (strsame (cptr, "[Map]", 5)) { cptr = NULL; break; } if (strsame (cptr, "[Service]", 9)) { cptr = NULL; break; } /* mapping rules */ for (aptr = cptr; *aptr && *aptr != '('; aptr++); if (*aptr) { for (aptr++; *aptr && *aptr != ')'; aptr++); if (*aptr) for (aptr++; *aptr && isspace(*aptr); aptr++); if (*aptr) cptr = aptr; } if (strsame (cptr, "exec", 4)) break; if (strsame (cptr, "fail", 4)) break; if (strsame (cptr, "map", 3)) break; if (strsame (cptr, "pass", 4)) break; if (strsame (cptr, "redirect", 8)) break; if (strsame (cptr, "script", 6)) break; if (strsame (cptr, "set", 3)) break; if (strsame (cptr, "user", 4)) break; } rewind (filptr); if (cptr && *cptr) return (INCONFIG_MAP); /***********/ /* service */ /***********/ while (cptr = fgets (line, sizeof(line), filptr)) { while (*cptr && *cptr != '[') cptr++; if (!*cptr) continue; /* inline directives */ if (strsame (cptr, "[Auth]", 6)) { cptr = NULL; break; } if (strsame (cptr, "[Global]", 8)) { cptr = NULL; break; } if (strsame (cptr, "[Map]", 5)) { cptr = NULL; break; } if (strsame (cptr, "[Service]", 9)) { cptr = NULL; break; } /* all service directives begin with ... */ if (strsame (cptr, "[Service", 8)) break; } rewind (filptr); if (cptr && *cptr == '[') return (INCONFIG_SERVICE); /********/ /* nope */ /********/ return (INCONFIG_UNKNOWN); } /*****************************************************************************/ /* If the first [IncludeFile] is not global directives or [Global] in-line directive then look for and include the latest global configuration file. */ void InConfigAddGlobal ( char *from, int lnum ) { char *sptr; char buf [256], fname [256], line [INCONFIG_LINE_MAX]; FILE *filptr; /*********/ /* begin */ /*********/ if (WATCH_MODULE(WATCH_MOD_CONFIG)) WatchThis (WATCHALL, WATCH_MOD_CONFIG, "InConfigAddGlobal()"); if (InConfigGlobal) return; InConfigGlobal = true; filptr = InConfigOpenFile (GlobalFileName); if (!filptr) { FaoToBuffer (buf, sizeof(buf), NULL, "!&S", InConfigStatus); fprintf (GlobalFile, "##### %s %s\n", GlobalFileName, buf); return; } fgetname (filptr, fname, 1); fprintf (GlobalFile, "!#+# %s line %d %s\n", fname, lnum, from); for (;;) { sptr = fgets (line, sizeof(line), filptr); if (!sptr) break; fputs (sptr, GlobalFile); } fclose (filptr); } /*****************************************************************************/ /* Revisit the (authorisation and mapping) file removing (or commenting-out) redundant virtual hosts and if-endif constructs. */ FILE* InConfigExcise (FILE *iFile) { int idx, status; char *cptr, *sptr; char fname [256], line1 [INCONFIG_LINE_MAX], line2 [INCONFIG_LINE_MAX]; FILE *oFile; fpos_t fpos2; /*********/ /* begin */ /*********/ if (WATCH_MODULE(WATCH_MOD_CONFIG)) WatchThis (WATCHALL, WATCH_MOD_CONFIG, "InConfigExcise()"); InConfigStatus = 0; fsync (fileno(iFile)); rewind (iFile); if (!fgetname (iFile, fname, 1)) { InConfigStatus = vaxc$errno; return (NULL); } InConfigNoVersion (fname); sys$setprv (1, &SysPrvMask, 0, 0); oFile = fopen (fname, "w+", "shr=get", "fop=dlt"); sys$setprv (0, &SysPrvMask, 0, 0); if (!oFile) { InConfigStatus = vaxc$errno; return (NULL); } /* read consecutive lines from the file */ for (;;) { cptr = fgets (line1, sizeof(line1), iFile); if (!cptr) break; fgetpos (iFile, &fpos2); sptr = fgets (line2, sizeof(line2), iFile); if (!sptr) { fputs (line1, oFile); break; } while (*cptr && ISLWS(*cptr)) cptr++; while (*sptr && ISLWS(*sptr)) sptr++; #if INCONFIG_EXCISE /* if not developing then excise */ if (*(USHORTPTR)cptr == '[[' && *(USHORTPTR)sptr == '[[') { fsetpos (iFile, &fpos2); continue; } if (MATCH3(cptr,"if ") && MATCH5(sptr,"endif")) continue; #else /* just comment out if developing */ if (*(USHORTPTR)cptr == '[[' && *(USHORTPTR)sptr == '[[') { line1[0] = '#'; fputs (line1, oFile); fsetpos (iFile, &fpos2); continue; } if (MATCH3(cptr,"if ") && MATCH5(sptr,"endif")) { line1[0] = line2[0] = '#'; fputs (line1, oFile); fputs (line2, oFile); continue; } #endif fsetpos (iFile, &fpos2); fputs (line1, oFile); } fsync (fileno(oFile)); rewind (oFile); /* close (and thereby delete) the (previous) file */ sys$setprv (1, &SysPrvMask, 0, 0); fclose (iFile); sys$setprv (0, &SysPrvMask, 0, 0); return (oFile); } /*****************************************************************************/ /* Remove the version number from the supplied file name. */ void InConfigNoVersion (char *fname) { char *sptr; /*********/ /* begin */ /*********/ for (sptr = fname; *sptr; sptr++); while (sptr > fname && *sptr != ';') sptr--; if (sptr > fname) *sptr = '\0'; } /*****************************************************************************/ /* Provide an annotated report of the WASD_CONFIG_INLINE configuration file. */ void InConfigReport ( REQUEST_STRUCT *rqptr, BOOL UseServerConfig ) { static char SourceFao [] = "

\n\ \n\ \n\
Source: "!AZ"
\n\ \n\ !&@\n\ \n\
File:!AZ
!AZ!20&W
\n\
\n"; static char reduce [] = " <<<<<<<<< "; static char expand [] = " >>>>>>>>> "; static char spaces [] = " "; BOOL ConcealAfter, IncludeGlobal; int excount, fcount, status, tlcount, which; char *aptr, *cptr, *sptr, *zptr; char line [INCONFIG_LINE_MAX]; struct stat stbuf; unsigned long *vecptr; unsigned long FaoVector [16]; int lcount [INCLUDE_MAX+1]; char fname [INCLUDE_MAX+1][256]; char service [INCLUDE_MAX+1][128]; FILE *filptr [INCLUDE_MAX+1], *thebuf [INCLUDE_MAX+1]; uint64 mtime64 [INCLUDE_MAX+1]; /*********/ /* begin */ /*********/ if (WATCH_MODULE(WATCH_MOD_CONFIG)) WatchThis (WATCHALL, WATCH_MOD_CONFIG, "InConfigReport()"); memset (lcount, 0, sizeof(lcount)); memset (fname, 0, sizeof(fname)); memset (service, 0, sizeof(service)); memset (filptr, 0, sizeof(filptr)); memset (thebuf, 0, sizeof(thebuf)); memset (mtime64, 0, sizeof(mtime64)); /* conceal remaining included files after line number */ if (cptr = SysTrnLnm (WASD_INLINE_CONCEAL_AFTER)) { ConcealAfter = atoi(cptr); if (ConcealAfter <= 0) ConcealAfter = 999999; } else ConcealAfter = 20; if (UseServerConfig) { if (InConfigFileName[0]) cptr = InConfigFileName; else cptr = SysTrnLnm (CONFIG_INLINE_FILE_NAME); } else { cptr = SysTrnLnm (CONFIG_INLINE_FILE_NAME); /* development purposes only */ if (!cptr) cptr = SysTrnLnm (CONFIG_INLINE_REPORT_FILE_NAME); } if (!cptr) { rqptr->rqResponse.HttpStatus = 500; ErrorGeneral (rqptr, MsgFor(rqptr,MSG_GENERAL_DISABLED), FI_LI); AdminEnd (rqptr); return; } strzcpy (fname[0], cptr, sizeof(fname[0])); sys$setprv (1, &SysPrvMask, 0, 0); filptr[0] = InConfigOpenFile (fname[0]); if (filptr[0]) { fstat (fileno (filptr[0]), &stbuf); TimeUnix32ToVms64 (stbuf.st_mtime, &mtime64[0]); fclose (filptr[0]); } sys$setprv (0, &SysPrvMask, 0, 0); TheFile = NULL; InConfigGlobal = false; tlcount = 0; rqptr->rqResponse.PreExpired = PRE_EXPIRE_ADMIN; ResponseHeader200 (rqptr, "text/html", NULL); FaoToNet (rqptr, "\n\ \n", GenerateCspNonce(rqptr), reduce, expand); /*********/ /* title */ /*********/ AdminPageTitle (rqptr, "Inline Config Report"); vecptr = FaoVector; *vecptr++ = UseServerConfig ? "Server" : "File"; *vecptr++ = fname[0]; *vecptr++ = "\ View\ Edit"; *vecptr++ = cptr = rqptr->rqHeader.PathInfoPtr; *vecptr++ = cptr; *vecptr++ = UseServerConfig ? "Loaded" : "Revised"; *vecptr++ = &mtime64[0]; status = FaolToNet (rqptr, SourceFao, &FaoVector); if (VMSnok (status)) ErrorNoticed (rqptr, status, NULL, FI_LI); FaoToNet (rqptr, "

\n\ \n\
");

   /**********/
   /* report */
   /**********/

   excount = fcount = 0;
   for (;;)
   {
      sys$setprv (1, &SysPrvMask, 0, 0);
      filptr[fcount] = InConfigOpenFile (fname[fcount]);
      sys$setprv (0, &SysPrvMask, 0, 0);
      status = InConfigStatus;
      if (!filptr[fcount])
      {
         FaoToNet (rqptr,
"Failed to open !AZ !&S\n",
                   fname[fcount], status);
         break;
      }

      if (fstat (fileno (filptr[fcount]), &stbuf) < 0)
      {
         status = InConfigStatus = vaxc$errno;
         FaoToNet (rqptr,
"Failed to fstat() !AZ !&S\n",
                   fname[fcount], status);
         break;
      }
      TimeUnix32ToVms64 (stbuf.st_mtime, &mtime64[fcount]);

      if ((which = InConfigIdentify (filptr[fcount])) == INCONFIG_UNKNOWN)
      {
         if (fcount)
         {
            FaoToNet (rqptr,
"Cannot identify !AZ !&S\n",
                      fname[fcount], SS$_ABORT);
            break;
         }
      }

      switch (which)
      {
         case INCONFIG_AUTH : cptr = "AUTH"; break;
         case INCONFIG_GLOBAL : cptr = "GLOBAL"; break;
         case INCONFIG_MAP : cptr = "MAP"; break;
         case INCONFIG_SERVICE : cptr = "SERVICE"; break;
         case INCONFIG_UNKNOWN : cptr = ""; break;
         default : cptr = "BUGCHECK";
      }
      if (*cptr)
         FaoToNet (rqptr,
"!4ZL !1ZL !4ZL!#AZ [!AZ]\n",
                   tlcount, fcount+1, lcount[fcount], fcount*2, spaces, cptr);

      if (which == INCONFIG_GLOBAL) InConfigGlobal = true;

      status = SS$_NORMAL;
      lcount[fcount] = 0;
      for (;;)
      {
         if (VMSnok (status)) break;
         cptr = fgets (line, sizeof(line), filptr[fcount]);
         if (!cptr)
         {
            if (fcount > 0 && lcount[fcount] >= ConcealAfter)
            {
               FaoToNet (rqptr,
"\
\
!&;AZ!#AZ
\ ", excount, excount, reduce, fcount*2, spaces, excount); } fclose (filptr[fcount--]); if (fcount < 0) break; if (WATCH_MODULE(WATCH_MOD_CONFIG)) WatchThis (WATCHALL, WATCH_MOD_CONFIG, "!UL !AZ", fcount, fname[fcount]); TheFile = thebuf[fcount]; IncludeGlobal = false; continue; } tlcount++; lcount[fcount]++; if (fcount && lcount[fcount] == ConcealAfter + 1) { excount++; FaoToNet (rqptr, "\ \ !&;AZ!#AZ
\n\
", excount, excount, expand, fcount*2, spaces, excount); } FaoToNet (rqptr, "!4ZL !1ZL !4ZL!#AZ ", tlcount, fcount+1, lcount[fcount], fcount*2, spaces); /* find the first non-white-space */ while (*cptr && ISLWS(*cptr)) cptr++; /* remove any trailing newline */ for (sptr = cptr; *sptr && *sptr != '\n'; sptr++); *sptr = '\0'; if (strsame (cptr, "[IncludeFile]", 13)) { if (fcount++ > INCLUDE_MAX) { FaoToNet (rqptr, "[IncludeFile] exceeded !UL\n", fname[fcount]); status = SS$_ABORT; break; } for (cptr += 13; *cptr && ISLWS(*cptr); cptr++); zptr = (sptr = fname[fcount]) + sizeof(fname[0])-1; while (*cptr && !ISLWS(*cptr) && sptr < zptr) *sptr++ = *cptr++; *sptr = '\0'; if (!fname[fcount][0]) { FaoToNet (rqptr, "[IncludeFile] not specified !AZ at !UL\n", fname[fcount-1], lcount[fcount-1]); status = SS$_ABORT; break; } FaoToNet (rqptr, "!AZ line !UL [IncludeFile] !AZ\n", fname[fcount-1], lcount[fcount-1], fname[fcount]); break; } aptr = NULL; if (strsame (cptr, "[Auth]", 6)) { for (aptr = cptr + 6; *aptr && ISLWS(*aptr); aptr++); if (*aptr) { FaoToNet (rqptr, "!&;AZ\n", cptr); continue; } which = INCONFIG_AUTH; FaoToNet (rqptr, "!&;AZ\n", cptr); continue; } if (strsame (cptr, "[Global]", 8)) { InConfigAddGlobal (fname[fcount], lcount[fcount]); for (aptr = cptr + 8; *aptr && ISLWS(*aptr); aptr++); if (*aptr) { FaoToNet (rqptr, "!&;AZ\n", cptr); continue; } which = INCONFIG_GLOBAL; FaoToNet (rqptr, "!&;AZ\n", cptr); continue; } if (strsame (cptr, "[Map]", 5)) { for (aptr = cptr + 5; *aptr && ISLWS(*aptr); aptr++); if (*aptr) { FaoToNet (rqptr, "!&;AZ\n", cptr); continue; } which = INCONFIG_MAP; FaoToNet (rqptr, "!&;AZ\n", cptr); continue; } if (strsame (cptr, "[Service]", 9)) { if (which != INCONFIG_GLOBAL) { for (aptr = cptr + 9; *aptr && ISLWS(*aptr); aptr++); if (*aptr) { FaoToNet (rqptr, "!&;AZ\n", cptr); continue; } which = INCONFIG_SERVICE; FaoToNet (rqptr, "!&;AZ\n", cptr); continue; } } if (*(USHORTPTR)cptr == '[[') { if (strsame (cptr, "[[WASD", 6)) { FaoToNet (rqptr, "!&;AZ\n", cptr); continue; } if (which == INCONFIG_SERVICE) { FaoToNet (rqptr, "!&;AZ\n", cptr); zptr = (sptr = service[fcount]) + sizeof(service[0])-1; while (*cptr && !ISLWS(*cptr) && sptr < zptr) *sptr++ = *cptr++; *sptr = '\0'; continue; } FaoToNet (rqptr, "!&;AZ\n", cptr); continue; } if (*cptr == '#' || *(USHORTPTR)cptr == '!#') { FaoToNet (rqptr, "!&;AZ\n", cptr); continue; } FaoToNet (rqptr, "!&;AZ\n", cptr); } if (VMSnok (status)) break; if (fcount < 0) break; } FaoToNet (rqptr, "----\n\
\n\
\n"); AdminEnd (rqptr); } /*****************************************************************************/