/*****************************************************************************/ #ifdef COMMENTS_WITH_COMMENTS /* DAVweb.c This package is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; version 3 of the License, or any later version. > This package is distributed in the hope that it will be useful, > but WITHOUT ANY WARRANTY; without even the implied warranty of > MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the > GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. Web-based Distributed Authoring and (not) Versioning for the WASD package. Effective WASD WebDAV file-space (without significant naming constraints) relies on being hosted on ODS-5 volumes. Behaviour hosting file-space on ODS-2 volumes is untested (though possible provided file naming is constrained to ODS-2 conventions). WASD WebDAV methods and request headers, etc., are also propagated to the scripting environment and so functionality may be implemented using CGI, CGIplus or RTE based applications. WASD proxy-serving supports WebDAV methods, header fields, etc. Generally WebDAV clients are applications other than browsers and so response bodies with human-readable error explanations are unnecessary and consume bandwidth to no good purpose, and so not provided. File-systems are notoriously latent components relative to the rest of the system (more so with VMS). Any operation to collections (directories) are not going to be atomic and for large collections requiring many sub-operations the potential for the process to be interrupted or otherwise disturbed are enornmous. File-systems are not databases amenable to extensive ACID operations. WASD takes out its own VMS DLM locks on resources (files and directories) before beginning any WebDAV operation, and these prevent conflict with other WASD WebDAV operations on the same system or cluster, but RMS does not use these nor does WASD use RMS locks (except when actually modifying the file-system of course), and so there is potential for interactions between the two domains (in common with general file-system actvities). WASD WebDAV deliberately does not try to block file-system actions from other processing (except where RMS locks/blocks). It's own DLM locking is purely for internal purposes. In addition each file under WebDAV management has the potential for an associated but independent META data file. This of course means for every DAV-specific resource file activity there is at least a file-system action to check for a META data file and for some actions such as COPY the potential for an associated but entirely independent file operation. Of course WebDAV was not intended or designed as a general file-system protocol but one for distributed management of somewhat restricted collections of Web-related resources and so in context probably works well enough. See sections below on file-system operation method restrictions. If using WebDAV in any serious fashion the likes of $ HTTPD/DO=RESTART=NOW during server WebDav file-system modifications is a recipe for inconsistency and/or corruption! In other words; ALL CARE BUT NO RESPONSIBILITY TAKEN! See GPL above. REFERENCES ---------- http://webdav.org/ http://en.wikipedia.org/wiki/Webdav http://tools.ietf.org/html/rfc4918 http://tools.ietf.org/html/rfc4331 (quota) http://tools.ietf.org/html/rfc2518 (obsolete) WebDAV: Next-Generation Collaborative Web Authoring Lisa Dusseault, 2003 ISBN: 0130652083 Using Expat by Clark Cooper http://en.wikipedia.org/wiki/Expat_(XML) http://www.xml.com/pub/a/1999/09/expat/index.html http://www.xml.com/lpt/a/47 DEVELOPMENT CLIENT TOOLS ------------------------ http://www.webdavsystem.com/server/access/ DAVExplorer - a Java-based GUI Explorer-style file navigation tool http://www.davexplorer.org/ cadaver - a command-line WebDAV client for *x http://www.webdav.org/cadaver/ davfs2 - a mountable WebDAV file-system for Linux http://savannah.nongnu.org/projects/davfs2 The WebDAV URL handling of KDE 4.2 Dolphin (v1.2) http://www.webdavsystem.com/server/access/konqueror (yup, I know!) (KDE+Dolphin is one of the best-behaved WebDAV clients I have encountered!) The WebDAV URL handling of Gnome Nautilus (gvfs/1.2.2) http://www.webdavsystem.com/server/access/gnome_nautilus *** THE RECENT (08,09 at least) GNOME/GVFS/NAUTILUS HAVE A LOT OF ENTRIES IN BUGZILLA - DO NOT EXPECT IT TO BEHAVE ITSELF - this has been my experience *** Windows Explorer - and the associated mini-director, et.al., on XP (not Vista). See "Microsoft Miscellanea" below. *** AND PARTICULARLY EFFECTIVE FOR THE WINDOWS PLATFORM *** WebDrive "FTP Client" for Windows XP (try before you buy) http://www.webdrive.com/ METHODS SUPPORTED ----------------- A list of WebDAV methods, what WASD does with them, and any limitations or restrictions. Issues of atomicity with the manipulation of file-system trees containing numbers of individual files makes strict RFC 4918 compliance difficult. COPY - reproduces both single resources (files) and collections (directory trees). Currently will overwrite files (if specified by the request) but will respond 209 (Conflict) if it would overwrite a tree. DELETE - deletes files and directory trees. GET - just the vanilla HTTP/1.1 behaviour. HEAD - ditto. LOCK - see WEBDAV LOCKING below. MKCOL - create a directory. MOVE - moves (rename or copy) a file or a directory tree. Currently will 'overwrite' files (if specified by the request) but will respond 209 (Conflict) if it would overwrite a tree. OPTIONS - if WebDAV is enabled and available for the path this reports the WebDAV extension methods. PROPFIND - retrieves the requested file characteristics, DAV lock status and 'dead' properties for individual files, a directory and it's child files, or a directory tree. PROPPATCH - set and remove 'dead' meta-data properties (see DAVMETA.C). PUT - against a WebDAV resource behaves a little differently to historical WASD implementation of PUT. See the PUT.C module. UNLOCK - see WEBDAV LOCKING below. COPY RESTRICTIONS ----------------- Currently does not comply with the overwrite:T directive for collections (does so for files). Will not currently preemptively delete the existing tree. It returns a 209 (Conflict) response instead. COPY does not maintain collection consistent URL namespace if a member resource cannot be moved as required by RFC4918. It should maintain the source subtree completely uncopied. Instead it is best-effort and continues copying resources until exhausted. This is consistent with file-system behaviour. The RFC4918 requirement, while not impossible, is fraught with issues inside a file-system. DELETE RESTRICTIONS ------------------- Deletion of collections is particularly fraught with issues for a file-system. In userland it is almost impossible to predetermine if an individual file in a directory tree is going to resist deletion (due to locking, protections, etc) and in kernel land it's probably no easier. It leaves the undeleted tree hierachy (resource ancestors) intact. This is RFC4918 compliant however! So, in the case of WASD WebDAV it's just best-effort and if something down the tree won't disappear, it just reports the failure in the 207 response and carries merrily on through the tree regardless. This IS acceptable WebDAV server behaviour! MOVE RESTRICTIONS ----------------- Currently does not comply with the overwrite:T directive for collections (does so for files). Will not currently pre-emptively delete the existing tree. It returns a 209 (Conflict) response instead. MOVE first attempts to rename the file or directory. This is reasonably efficient, especially for directory trees but obviously only suitable for a target on the same disk volume. If a rename failure is due to a different device it falls back to using a COPY then DELETE in two separate phases. Needless-to-say this is hardly atomic and can lead to inconsistencies between source and target. MOVE does not maintain collection consistent URL namespace if a member resource cannot be moved as required by RFC4918. It should maintain the source subtree unmoved. Instead it is best-effort and continues moving resources until exhausted. This is consistent with file-system behaviour. The RFC4918 requirement, while not impossible, is fraught with issues inside a file-system. IF: RESTRICTIONS ---------------- The conditional "If:" request header field does not have full RFC 4918 support. If currently implements lock token and etag token processing with parenthetical OR and NOT processing. For unsupported features WATCH reports that the header was not understood and always returns an abort status. WebDAV "If:" processing is an extrodinarily complex kludge for on-the-fly decision making by the server and much of what I have read indicates most clients only ever use extremely simple conditions anyway. GLOBAL CONFIGURATION -------------------- WebDAV can be enabled and locking disabled with a WebDAV 1 compliance. # HTTPD$CONFIG [WebDAV] enabled [WebDAVlocking] enabled [WebDAVlockTimeoutDefault] hh:mm:ss [WebDAVlockTimeoutMax] hh:mm:ss [WebDAVlockCollectionDepth] integer [WebDAVquota] enabled [WebDAVmetaDir] string FILE-SYSTEM ACCESS ------------------ Is controlled using the mapping rules: WEBDAV=PROFILE access using request SYSUAF security profile WEBDAV=WRITE unconditional permission to read/write WEBDAV=READ unconditional permission to read WEBDAV=ACCESS access using server account permissions All access by WebDAV operations MUST HAVE AT LEAST ONE SET against the path. If access is permitted by one of the above settings SYSPRV is enabled to allow that access using the server account. Therefore files and directories should have a SYSTEM:READ+WRITE+EXECUTE+DELETE protection or equivalent ACL permissions, or the access may fail totally or in some part of a supposedly atomic action. These file-system access settings are applied in the order listed above. That is, if a path successively has one or more of the above settings applied during rule processing, when it comes to applying those access controls, SYSUAF profile is applied, then if no profile SETing access to read/write, then to read-only, then access via the server account. FILE-SYSTEM AUTHORISATION ------------------------- All access by WebDAV operations MUST HAVE AT LEAST ONE SET against the path. All WebDAV access is a combination of HTTPD$MAP path setting and HTTPD$AUTH authorisation permissions. The least permissive of the two overrides the more. These is the test-bench environment used during development: # HTTPD$MAP pass /dweb/* /dweb/* ods=5 webdav=write webdav=nowinprop redirect /dweb /dweb/ # HTTPD$AUTH ["KLAATU"=WASD_VMS_RW=id] /dweb/* r+w Note that WebDAV read/write access is a combination of the mapping and the authorisation rule (mapping WEBDAV=READ overrides authorisation read+write). Expect complications with Microsoft environments. For test-benching you could avoid authorisation issues completely with: # HTTPD$AUTH [world] /dweb/* r+w SUMMARY OF WEBDAV SET RULES --------------------------- WEBDAV=[NO]HIDDEN WebDAV (and directory) listing do not display hidden files WEBDAV=[NO]LOCK allow/apply WebDAV locking to this path WEBDAV=[NO]PROFILE WebDAV access according to SYSUAF profile WEBDAV=[NO]PROP allow/apply WebDAV 'dead' property(ies) to this path WEBDAV=[NO]PUT=LOCK a resource must be locked before a PUT is allowed WEBDAV=[NO]READ WebDAV methods allowed read this tree WEBDAV=[NO]SERVER WebDAV access as server account (best effort) WEBDAV=[NO]WINPROP when NOWINPROP windows properties are ignored and emulated WEBDAV=[NO]WRITE WebDAV methods allowed write to this path (implied read) WEBDAV=LOCK=TIMEOUT=DEFAULT= hh:mm:ss WEBDAV=LOCK=TIMEOUT=MAX= hh:mm:ss WEBDAV=META=DIR= subdirectory or full directory for metadata files WEBDAV LOCKING -------------- For efficiency and functionality considerations WebDAV locking may be enabled and disabled (default) as global functionality using the HTTPD$CONFIG [WebDAVlocking] directive. Additionally the WEBVDAV=[NO]LOCKING path SETing can configure this on a per-path basis. In common with RFC 4918 WASD WebDAV locking controls only write access. Both exclusive and shared locks are provided. This includes methods such as DELETE, LOCK, MKCOL, MOVE, PROPATCH, PUT, UNLOCK. WASD WebDAV locking checks parent collections to a configurable depth. HTTPD$CONFIG directive [WebDAVlockCollectionDepth] where the default (0 or 1) checks only WebDAV locking on files, 2 WebDAV locking on the parent directory, 3 on the grandparent, 4 the great-grandparent, etc. Of course each level can add significant latency (and expense) to some operations. For more information on locking operation and implementation details see the DAVLOCK.C module and for meta-data in general the DAVMETA.C module. VMS DLM LOCKING --------------- WASD uses VMS locking to queue and arbitrate access to WebDAV resources and meta-files. Two lock modes are employed; 'exclusive', when changes are to be made to the resource or it's meta-data, and 'concurrent read', when resource and/or meta-data are only to be read. Concurrent read locks are compatible, but an exclusive queued against a resource currently being read waits, as does a read against a current exclusive. MUNGING TYPELESS DIRECTORIES ---------------------------- Microsoft WebDAV mini-redirector (and others) insists on requesting directories without a trailing slash thereby making them difficult to distinguish from type-less files. With WASD WebDAV a typeless file with the same name as a directory will be hidden by the directory for such a client because the directory will always be checked for first. An added complication is a directory such as /whatever/This%20is.a.Test which on ODS-5 will map to WHATEVER:[000000]This^_is^.a.Test cannot simply be transformed into WHATEVER:[000000]This^_is^.a.Test.DIR to check it's existance, it must be completely 'remapped' via /whatever/This%20is.a.Test/ into a directory specification WHATEVER:[This^_is^.a^.Test] and from that the underlying directory file WHATEVER:[000000]This^_is^.a^.Test.DIR may be derived. Because of these complications it is always desirable for a client to request directories using a trailing slash! Sigh. DREAMWEAVER SILLINESS --------------------- Dreamwever 8 (at least, the only version I have access to) insists on using a URI with a trailing "/./" occasionally (I'm guessing to specify the "current" directory - c.f. "/../", or "parent" syntax). Just absorb this internally using a mapping internal redirect along the lines of redirect /webdav/**/./ /webdav/*/ MICROSOFT WEBDAV MINI-REDIR --------------------------- Microsoft approach WebDAV in their own inimitable fashion. Hence Microsoft agents, considering their ubiquity, including their mini-redirector are specifically looked for and functionality modified to accomodate them. MICROSOFT MISCELLANEA --------------------- A cornucopia of of minor and major annoyances! Some general references: http://greenbytes.de/tech/webdav/webdav-redirector-list.html http://greenbytes.de/tech/webdav/webfolder-client-list.html http://www.zorched.net/2006/03/01/more-webdav-tips-tricks-and-bugs/ http://www.webdavsystem.com/server/documentation/troubleshooting http://www.webdavsystem.com/documentation/troubleshooting http://code.google.com/p/sabredav/wiki/Windows http://ulihansen.kicks-ass.net/aero/webdav/ http://chapters.marssociety.org/webdav/ Command-line network configuration: C:\whatever> NET USE Z: http://the.host.name/folder/ C:\whatever> NET USE Z: /DELETE 1) Mapping Microsoft agents (at least) seem to request the server OPTIONS of the server root regardless of any path provided with the NET USE or other network drive mapping employed. To selectively map such a request into a path that has WebDAV enabled on it (and will therefore respond with the DAV-related options) use a conditional redirect rule. For example if (webdav:) if (request-method:OPTIONS) redirect / /dav-path/ endif or if only required for MS agents then something more specific if (webdav:MSagent) if (request-method:OPTIONS) redirect / /dav-path/ endif Subsequent rules will probably be required to map typeless directory requests to the actual directory required. redirect /dav-path /dav-path/ pass /dav-path/* /dav_root/* webdav=read 2) FrontPage Extensions Requests containing paths /_vti_inf.html and /_vti_bin/* are related to FrontPage protocol discovery probing. They can be adequately handled using a mapping rule lsuch as the following: pass /_vti_* "404 Not an MS platform!" 3) OPTIONS header "MS-Author-Via: DAV" http://msdn2.microsoft.com/en-us/library/ms691698.aspx If the server's response does not contain an MS-Author-Via header, the OLE DB Provider for Internet Publishing loads the WEC and WebDAV protocol drivers one at a time (WEC first, WebDAV second) and asks them, "Do you know how to handle this URL?", specifying the exact URL passed in by the client. The first protocol which responds "yes" is selected. If neither protocol driver responds "yes" then the method which triggered the automatic driver selection (usually IBindResource::Bind) fails with an OLE DB Provider for Internet Publishing specific error code IPP_E_SERVERTYPE_NOT_SUPPORTED. 4) Repairing broken XP Web Folders http://chapters.marssociety.org/webdav/ Some Windows XP machines have a broken Web Folders installation. Microsoft includes a Web Folders repair utility built in to Windows to correct the problem. Use the following steps to fix the problem: 1. Click on the "Start" menu in the lower left corner, and select "Run..." 2. Type in "webfldrs.msi" and click the "OK" button. 3. Click on the "Select reinstall mode" button. 4. Select *ALL* of the checkboxes *except* for the second one ("Reinstall only if file is missing"). 5. Click on the "OK" button. 6. Click on the "Reinstall" button. 7. After the reinstallation is complete, reboot the computer. 5) Adding a port number to the webfolder-address Attach the port-number (80 by default) to the http-address you enter into the field of the "My Network Places"-assistant. As you can see in the following image and the linked screenshot, this will force Windows XP to use the "Microsoft Data Access Internet Publishing Provider DAV 1.1" mechanism instead of "Microsoft-WebDAV-MiniRedir/5.1.2600". 6) Adding a number-sign ("#") to the webfolder-address It is also possible to add the number sign # to the http-address you enter into the field of the "My Network Places"-assistant. As you can see in the following image and the linked screenshot, this will also force Windows XP to use the "Microsoft Data Access Internet Publishing Provider DAV 1.1" mechanism instead of "Microsoft-WebDAV-MiniRedir/5.1.2600". http://the.host.name/folder# 7) Force Windows XP to use Basic Authentication There is a third way to get this working from the client-site. As described in the Microsoft Knowledge Base, Article ID: 841215, Windows XP disables "Basic Auth" in his "Microsoft-WebDAV-MiniRedir/5.1.2600"-mechanism by default for security reasons. See description below. 8) Avoiding Microsoft Property Clutter The MS WebDAV implementation (XP at least) sets properties corresponding to Win32 file characteristics against all files it writes. Much of these are timestamps already corresponding to existing live data. Sat, 29 Sep 2007 13:06:33 GMT Fri, 19 Jun 2009 20:28:48 GMT Fri, 19 Jun 2009 20:28:48 GMT 00000020 To avoid this *.*__wasdav; meta-data file clutter map the path with a WEBDAV=NOWINPROP setting. WASD WebDAV absorbs the associated PROPATCH and emulates these from existing live data in PROPFINDs. 9) Error 0x800700DF: The file size exceeds the limit allowed and cannot be saved (per JPP) "In my case I try to copy file over WEBDAV to WEB Client connection e.g. I have mapped drive to web site. file is about 70MB I can copy small files from the same WEBDav folder." HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\WebClient\Parameters 1. Right click on the FileSizeLimitInBytes and click Modify 2. Click on Decimal 3. In the Value data box, type 4294967295, and then click OK. Note this sets the maximum you can download from the Webdav to 4 gig at one time, I havent figured out how to make it unlimited so if you want to download more you need to split it up. http://social.answers.microsoft.com/Forums/en/xphardware/thread/d208bba6-920c-4639-bd45-f345f462934f MICROSOFT XP EXPLORER BASIC AUTHENTICATION ------------------------------------------ http://www.microsoft.com/technet/prodtechnol/winxppro/maintain/sp2netwk.mspx http://support.microsoft.com/kb/841215 (Vista & XP SP2) You can enable BasicAuth by adding the following registry key and setting it to a non-zero value: HKEY_LOCAL_MACHINE\SYSTEM \CurrentControlSet\Services\WebClient\Parameters\UseBasicAuth (DWORD) If you delete the registry key or set it to 0, the behavior reverts to the default, or disabling the use of BasicAuth. Disabling Basic Authentication over a clear channel: Because the DAVRdr is part of the remote file system stack, a computer is open to attack whenever an attempt is made to remotely access files. Although the threat to other applications that use the Internet APIs is less severe than it is for the DAVRdr, a similar attack is possible whenever an application (or the user) attempts to access a URL. For this reason, WinInet is exposing the mechanism by which the DAVRdr disables BasicAuth to other users of the Internet APIs. With Windows XP Service Pack 2, there are two ways to block the use of Basic Authentication over clear (or unencrypted) channels: Create the following registry key and set it to a non-zero value. HKEY_CURRENT_USER\SOFTWARE\Microsoft\Windows\CurrentVersion \InternetSettings\DisableBasicOverClearChannel (DWORD) This prevents WININET from attempting to use BasicAuth unless the channel is secured (HTTPS or SSL). The application can disable the use of BasicAuth for its connections by setting the AUTH_FLAG_DISABLE_BASIC_CLEARCHANNEL flag (0x4) in the value supplied in the call to InternetSetOption using INTERNET_OPTION_AUTH_FLAGS. *** AND THEN RESTART WINDOWS *** VERSION HISTORY --------------- 01-AUG-2020 MGD DavWebRequest() remove after RequestParseExecute() fix 14-MAY-2020 MGD DavWebRequest() remove requirement for logical name WASD_HTTP2_WEBDAV after WebDAV over HTTP/2 tested 12-SEP-2018 MGD bugfix; significant refactor of locking 04-AUG-2018 MGD bugfix; DavWebMicrosoftDetect() before ->WebDavTaskPtr 13-APR-2018 MGD DavWebRundown() explicitly aborts WebDAV processing bugfix; DavWebRequest() no DavWebEnd() until task set up 19-NOV-2017 MGD (almost) always DavWebEnd() not RequestEnd() 06-MAY-2015 MGD bugfix; DavWebDestination() URI and URL (Total Commander) 29-APR-2015 MGD DavWebRequest() allow bodies with any and no Content-Type: then in DavWebRequest2() check for XML in the body content 28-JUN-2014 MGD metadata subdirectory or independent directory e.g. [.^.dav] and DKA0:[WASDDAV] (see DAVMAETA.C) via WASD_CONFIG_GLOBAL [WebDAVmetaDir] directive or WASD_CONFIG_MAP webdav=meta=dir= path SETing DavLockUrnUuidToken() move from opaque to urn:uuid token (OS X 10.9 started having issues with "opaquetoken"s) PutWebDavBegin() migrate into DavWebPutBegin() bugfix; DavWebCreateDir() set SYSPRV access, propagate rest 29-MAR-2012 MGD bugfix; DavWebMicrosoftMunge2() just the header field 09-JUN-2010 MGD bugfix; DavWebDestination() et al '^[', '^.' and '^]' 15-MAY-2010 JPP bugfix; DavWebSlashlessMunge() enable SYSPRV while calling OdsFileExists() 02-SEP-2009 MGD bugfix; scanning backwards through extended file spec 29-AUG-2009 MGD DavWebMultiStatus() simplify HREF generation 24-JUN-2009 MGD bugfix; DavWebEnqueue() EnqName memcpy() offset 31-DEC-2006 MGD initial */ #endif /* COMMENTS_WITH_COMMENTS */ /*****************************************************************************/ #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 #include "wasd.h" #include "davweb.h" #define WASD_MODULE "DAVWEB" #ifndef WASD_WEBDAV #define WASD_WEBDAV 1 #endif /******************/ /* global storage */ /******************/ BOOL WebDavEnabled, WebDavHttp2Enabled, WebDavLockingEnabled, WebDavQuotaEnabled; int WebDavLockCollectionDepth; WebDavLockTimeoutDefaultSeconds, WebDavLockTimeoutMaxSeconds; /********************/ /* external storage */ /********************/ extern int EfnWait, EfnNoWait; extern int ToLowerCase[], ToUpperCase[]; extern unsigned long SysLckMask[], SysPrvMask[]; extern char ErrorSanityCheck[]; extern ACCOUNTING_STRUCT *AccountingPtr; extern CONFIG_STRUCT Config; extern LIST_HEAD RequestList; extern WATCH_STRUCT Watch; /*****************************************************************************/ /* */ DavWebInit () { /*********/ /* begin */ /*********/ if (WATCH_MODULE(WATCH_MOD_WEBDAV)) WatchThis (WATCHALL, WATCH_MOD_WEBDAV, "DavWebInit()"); if (Config.cfWebDav.DavEnabled) { WebDavLockTimeoutDefaultSeconds = Config.cfWebDav.LockTimeoutDefaultSeconds; if (!WebDavLockTimeoutDefaultSeconds) WebDavLockTimeoutDefaultSeconds = WEBDAV_LOCK_TIMEOUT_DEFAULT; WebDavLockTimeoutMaxSeconds = Config.cfWebDav.LockTimeoutMaxSeconds; if (!WebDavLockTimeoutMaxSeconds) WebDavLockTimeoutMaxSeconds = WEBDAV_LOCK_TIMEOUT_MAX; WebDavLockCollectionDepth = Config.cfWebDav.LockCollectionDepth; if (!WebDavLockCollectionDepth) WebDavLockCollectionDepth = 1; WebDavEnabled = true; WebDavLockingEnabled = Config.cfWebDav.LockingEnabled; WebDavQuotaEnabled = Config.cfWebDav.QuotaEnabled; InstanceGblSecSetLong (&AccountingPtr->WebDavEnabled, true); WebDavHttp2Enabled = (SysTrnLnm("WASD_HTTP2_WEBDAV") != NULL); FaoToStdout ("%HTTPD-I-WEBDAV, enabled; locking !AZ; quota !AZ\n", WebDavLockingEnabled ? "enabled" : "disabled", WebDavQuotaEnabled ? "enabled" : "disabled"); DavXmlInit (); return; } InstanceGblSecSetLong (&AccountingPtr->WebDavEnabled, false); FaoToStdout ("%HTTPD-I-WEBDAV, disabled\n"); } /*****************************************************************************/ /* Begin processing WebDAV-specific request. This function can be called twice. The second call is after any request body ("text/xml" properties for the WebDAV request) has been completely read (see BodyReadBegin() below). */ DavWebRequest (REQUEST_STRUCT *rqptr) { int status; char *cptr, *sptr, *zptr; WEBDAV_TASK *tkptr; /*********/ /* begin */ /*********/ if (WATCHMOD (rqptr, WATCH_MOD_WEBDAV)) WatchThis (WATCHITM(rqptr), WATCH_MOD_WEBDAV, "DavWebRequest() !AZ nam$m_wildcard:!&B", rqptr->ParseOds.ExpFileName, (rqptr->ParseOds.Nam_fnb & NAM$M_WILDCARD)); if (!(tkptr = rqptr->WebDavTaskPtr)) { InstanceGblSecIncrLong (&AccountingPtr->DoWebDavCount); /* ensure WebDAV is mapped to access this path */ if (!(rqptr->rqPathSet.WebDavServer || rqptr->rqPathSet.WebDavProfile || rqptr->rqPathSet.WebDavRead || rqptr->rqPathSet.WebDavWrite)) { rqptr->WebDavMethod = rqptr->WebDavRequest = rqptr->WhiffOfWebDav = false; DavWebResponse (rqptr, 403, 0, "PATH is not mapped for WebDAV access", FI_LI); RequestEnd (rqptr); return; } /* set up the task structure */ tkptr = rqptr->WebDavTaskPtr = VmGetHeap (rqptr, sizeof(WEBDAV_TASK)); OdsStructInit (&tkptr->DestOds, false); OdsStructInit (&tkptr->SearchOds, false); /* if globally enabled and not disabled for the path */ if (Config.cfWebDav.LockingEnabled && !rqptr->rqPathSet.WebDavNoLock) tkptr->LockingEnabled = true; /* just in case we decide to introduce a SETing for this */ tkptr->QuotaEnabled = Config.cfWebDav.LockingEnabled; if (WATCHING (rqptr, WATCH_WEBDAV)) WatchThis (WATCHITM(rqptr), WATCH_WEBDAV, "AGENT !AZ", rqptr->rqHeader.UserAgentPtr ? rqptr->rqHeader.UserAgentPtr : "?"); if (rqptr->rqPathSet.WebDavMetaDirPtr) tkptr->MetaFileDirPtr = rqptr->rqPathSet.WebDavMetaDirPtr; else if (Config.cfWebDav.MetaFileDirectoryLength) tkptr->MetaFileDirPtr = Config.cfWebDav.MetaFileDirectory; else tkptr->MetaFileDirPtr = NULL; } if (rqptr->ParseOds.Nam_fnb & NAM$M_WILDCARD) { DavWebResponse (rqptr, 400, RMS$_WLD, NULL, FI_LI); DavWebEnd (rqptr); return; } /* see if it corresponds to an actual directory name */ status = DavWebSlashlessMunge (rqptr, true); if (VMSnok (status)) { DavWebResponse (rqptr, 500, status, NULL, FI_LI); DavWebEnd (rqptr); return; } if (status == SS$_SUPERSEDE) tkptr->SlashlessDir = tkptr->IsDirectory = true; else if (status == SS$_NOTMODIFIED) tkptr->IsDirectory = true; if (rqptr->rqHeader.Method == HTTP_METHOD_PUT) { /* PUT handles it's own body processing */ if (tkptr->IsDirectory) { DavWebResponse (rqptr, 405, 0, "cannot PUT a collection", FI_LI); DavWebEnd (rqptr); } else PutBegin (rqptr, &DavWebEnd); return; } if (rqptr->rqHeader.ContentLength) { /* read all of request body */ BodyReadBegin (rqptr, &DavWebRequest2, &BodyProcessReadAll); return; } DavWebRequest2 (rqptr); } /*****************************************************************************/ /* */ DavWebRequest2 (REQUEST_STRUCT *rqptr) { int status; char *cptr, *zptr; STR_DSC_AUTO (XmlTextDsc); WEBDAV_TASK *tkptr; /*********/ /* begin */ /*********/ if (WATCHMOD (rqptr, WATCH_MOD_WEBDAV)) WatchThis (WATCHITM(rqptr), WATCH_MOD_WEBDAV, "DavWebRequest2()"); tkptr = rqptr->WebDavTaskPtr; if (rqptr->rqBody.DataPtr) { if (rqptr->rqBody.DataStatus != SS$_ENDOFFILE) { DavWebEnd (rqptr); return; } /* examine the content for what looks like XML */ zptr = (cptr = rqptr->rqBody.DataPtr) + rqptr->rqBody.DataCount; while (isspace(*cptr) && cptr < zptr) cptr++; if (cptr >= zptr-6 || !MATCH6 (cptr, "rqBody.DataCount) len--; while (rqptr->rqBody.DataPtr[len] == '\n' && len) len--; if (len) len++; WatchThis (WATCHITM(rqptr), WATCH_WEBDAV, "REQUEST XML"); WatchDataFormatted ("!#AZ\n", len, rqptr->rqBody.DataPtr); } StrDscThis (NULL, &XmlTextDsc, rqptr->rqBody.DataPtr, rqptr->rqBody.DataCount); status = DavXmlParseText (rqptr, &XmlTextDsc); if (VMSnok (status)) { /* "out of memory" is a fatal error */ if (status == LIB$_INSVIRMEM) ErrorExitVmsStatus (status, "DavWebEnd()", FI_LI); DavWebEnd (rqptr); return; } } if (rqptr->rqHeader.WebDavDepthPtr) { /* sanity check the depth field */ if (rqptr->rqHeader.WebDavDepth != WEBDAV_DEPTH_ZERO && rqptr->rqHeader.WebDavDepth != WEBDAV_DEPTH_ONE && rqptr->rqHeader.WebDavDepth != WEBDAV_DEPTH_INFINITY) { if (WATCHING (rqptr, WATCH_WEBDAV)) WatchThis (WATCHITM(rqptr), WATCH_WEBDAV, "DEPTH? !UL", rqptr->rqHeader.WebDavDepth); rqptr->rqResponse.HttpStatus = 500; ErrorVmsStatus (rqptr, SS$_ABORT, FI_LI); DavWebEnd (rqptr); return; } tkptr->ToDepth = rqptr->rqHeader.WebDavDepth; } else tkptr->ToDepth = WEBDAV_DEPTH_INFINITY; if (rqptr->rqHeader.WebDavOverwritePtr) tkptr->CopyData.MoveOverwrite = rqptr->rqHeader.WebDavOverwrite; else tkptr->CopyData.MoveOverwrite = true; if (DavMetaDir (rqptr, &rqptr->ParseOds)) { ErrorVmsStatus (rqptr, RMS$_DNF, FI_LI); DavWebEnd (rqptr); return; } switch (rqptr->rqHeader.Method) { case HTTP_METHOD_WEBDAV_COPY : DavCopyBegin (rqptr); return; case HTTP_METHOD_DELETE : DavDeleteBegin (rqptr); return; case HTTP_METHOD_WEBDAV_LOCK : if (!tkptr->LockingEnabled) { /* if it gets this far the path is SET disabled */ DavWebResponse (rqptr, 501, 0, "Locking disabled", FI_LI); DavWebEnd (rqptr); return; } DavLockBegin (rqptr); return; case HTTP_METHOD_WEBDAV_MKCOL : DavWebMkCol (rqptr); return; case HTTP_METHOD_WEBDAV_MOVE : DavMoveBegin (rqptr); return; case HTTP_METHOD_WEBDAV_PROPFIND : DavPropBegin (rqptr); return; case HTTP_METHOD_WEBDAV_PROPPATCH : DavPropPatchBegin (rqptr); return; case HTTP_METHOD_WEBDAV_UNLOCK : if (!tkptr->LockingEnabled) { /* if it gets this far the path is SET disabled */ DavWebResponse (rqptr, 501, 0, "Locking disabled", FI_LI); DavWebEnd (rqptr); return; } DavUnLockBegin (rqptr); return; default : DavWebResponse (rqptr, 501, 0, "unknown WebDAV method", FI_LI); DavWebEnd (rqptr); } } /*****************************************************************************/ /* Begin WebDAV-specific PUT processing. This function will be called multiple times as asynchronous testing of the DLM and meta locking occurs. */ DavWebPutBegin (REQUEST_STRUCT *rqptr) { int status; WEBDAV_TASK *tkptr; /*********/ /* begin */ /*********/ if (WATCHMOD (rqptr, WATCH_MOD_PUT)) WatchThis (WATCHITM(rqptr), WATCH_MOD_PUT, "DavWebPutBegin() !UL", rqptr->WebDavTaskPtr->TestLockState); tkptr = rqptr->WebDavTaskPtr; /***********/ /* locking */ /***********/ switch (tkptr->TestLockState) { case 0 : /* establish an exclusive VMS DLM lock on the resource */ tkptr->TestLockState++; DavWebDlmEnqueue (rqptr, &tkptr->DlmSource, rqptr->ParseOds.ExpFileName, NULL, true, false, DavWebPutBegin, rqptr); return; case 1 : if (VMSnok (status = tkptr->DlmSource.LockSb.lksb$w_status)) { /* ordinarily shouldn't return any errors */ DavWebResponse (rqptr, 500, status, NULL, FI_LI); PutEnd (rqptr); return; } /* check for meta-lock on existing source */ tkptr->TestLockState++; DavLockTest (rqptr, rqptr->ParseOds.ExpFileName, false, DavWebPutBegin, rqptr); return; case 2 : if (VMSnok (tkptr->TestLockStatus)) { DavWebResponse (rqptr, 423, 0, "target locked", FI_LI); PutEnd (rqptr); return; } /*********************/ /* webdav=put=locked */ /*********************/ if (rqptr->rqPathSet.WebDavPutLock) { /* test for no lock at all */ if (!DavLockFindToken (rqptr)) { DavWebResponse (rqptr, 412, 0, "target NOT locked (path set WEBDAV=PUT=LOCKED)", FI_LI); PutEnd (rqptr); return; } } } /************/ /* continue */ /************/ if (WATCHING (rqptr, WATCH_WEBDAV)) WatchThis (WATCHITM(rqptr), WATCH_WEBDAV, "PUT !AZ", rqptr->ParseOds.NamDevicePtr); rqptr->PutTaskPtr->ContentLengthFixed = true; BodyReadBegin (rqptr, &PutWriteFile, &BodyProcessByVirtualBlock); } /*****************************************************************************/ /* End processing of WebDAV-specific request. Any necessary clean-up. */ DavWebEnd (REQUEST_STRUCT *rqptr) { WEBDAV_TASK *tkptr; /*********/ /* begin */ /*********/ if (WATCHMOD (rqptr, WATCH_MOD_WEBDAV)) WatchThis (WATCHITM(rqptr), WATCH_MOD_WEBDAV, "DavWebEnd()"); tkptr = rqptr->WebDavTaskPtr; /* dequeue any VMS DLM locks that *may* be extant */ DavWebDequeue (&tkptr->DlmSource); DavWebDequeue (&tkptr->DlmDestin); DavWebDequeue (&tkptr->MetaData.DlmData); DavWebDequeue (&tkptr->MetaData.DlmDest); /* belt and braces */ OdsParseRelease (&tkptr->DestOds); OdsParseRelease (&tkptr->SearchOds); /* indicate the task has successfully concluded */ rqptr->WebDavTaskPtr = NULL; RequestEnd (rqptr); } /*****************************************************************************/ /* Provide a response (with a little traceability, particularly useful when troubleshooting via WATCH). Generally WebDAV clients are applications other than browsers and so response bodies with human-readable error explanations are unnecessary and so only headers are provided. If a response body is required then ResponseHeader() should be used instead. */ DavWebResponse ( REQUEST_STRUCT *rqptr, int HttpStatus, int VmsStatus, char *WatchExplanation, char *SourceModuleName, int SourceLineNumber ) { char ExplanBuf [256], StatusBuf [256]; /*********/ /* begin */ /*********/ if (WATCHMOD (rqptr, WATCH_MOD_WEBDAV)) WatchThis (WATCHITM(rqptr), WATCH_MOD_WEBDAV, "DavWebResponse()"); if (!HttpStatus) HttpStatus = VmsToHttpStatus (VmsStatus); if (WATCHING (rqptr, WATCH_WEBDAV)) { if (WatchExplanation) FaoToBuffer (ExplanBuf, sizeof(ExplanBuf), NULL, " !AZ", WatchExplanation); else ExplanBuf[0] = '\0'; if (VmsStatus) FaoToBuffer (StatusBuf, sizeof(StatusBuf), NULL, " !&S", VmsStatus); else StatusBuf[0] = '\0'; WatchThis (WATCHITM(rqptr), WATCH_WEBDAV, "STATUS !UL (!AZ)!AZ!AZ (!AZ:!UL)", HttpStatus, HttpStatusCodeText(HttpStatus), ExplanBuf, StatusBuf, SourceModuleName, SourceLineNumber); } ResponseHeader (rqptr, HttpStatus, NULL, 0, NULL, NULL); } /*****************************************************************************/ /* Generate a 201 (created) response complete with "Location:.." field. DaveWebHref() must be called prior to set up URL components. */ DavWebResponse201 (REQUEST_STRUCT *rqptr) { char LocationField [ODS_MAX_FILE_NAME_LENGTH+128+32]; /*********/ /* begin */ /*********/ if (WATCHMOD (rqptr, WATCH_MOD_WEBDAV)) WatchThis (WATCHITM(rqptr), WATCH_MOD_WEBDAV, "DavWebResponse201()"); FaoToBuffer (LocationField, sizeof(LocationField), NULL, "Location: !AZ!&%AZ\r\n", rqptr->WebDavTaskPtr->HrefHost, rqptr->WebDavTaskPtr->HrefPath); ResponseHeader (rqptr, 201, NULL, 0, NULL, LocationField); } /*****************************************************************************/ /* Provide a multistatus response header and leading XML of the response body. */ DavWebResponse207 (REQUEST_STRUCT *rqptr) { /*********/ /* begin */ /*********/ if (WATCHMOD (rqptr, WATCH_MOD_WEBDAV)) WatchThis (WATCHITM(rqptr), WATCH_MOD_WEBDAV, "DavWebResponse207()"); if (rqptr->rqResponse.HttpStatus) return; ResponseHeader (rqptr, 207, "text/xml; charset=\"utf-8\"", -1, NULL, NULL); FaoToNet (rqptr, "\n\ \n"); } /*****************************************************************************/ /* Write a WebDAV multistatus XML entry. */ DavWebMultiStatus ( REQUEST_STRUCT *rqptr, int VmsStatus, char *FileName ) { int HttpStatus; WEBDAV_TASK *tkptr; /*********/ /* begin */ /*********/ if (WATCHMOD (rqptr, WATCH_MOD_WEBDAV)) WatchThis (WATCHITM(rqptr), WATCH_MOD_WEBDAV, "DavWebMultiStatus() !&S !&Z !UL", VmsStatus, FileName, rqptr->WebDavTaskPtr->ToDepth); tkptr = rqptr->WebDavTaskPtr; if (!rqptr->rqResponse.HttpStatus) DavWebResponse207 (rqptr); HttpStatus = VmsToHttpStatus (VmsStatus); DavWebHref (rqptr, FileName, 0); FaoToNet (rqptr, " \n\ !AZ!&%AZ\n\ HTTP/1.1 !UL !AZ\n\ \n", tkptr->HrefHost, tkptr->HrefPath, HttpStatus, HttpStatusCodeText(HttpStatus)); } /*****************************************************************************/ /* Generate an URL from the specified file name. Place the path into the tkptr->HrefPath buffer (not URL-escaped). On the first (and perhaps only) time through also generate an appropriate scheme://host:port and place into tkptr->HrefHost. Use both in conjuction as required. */ DavWebHref ( REQUEST_STRUCT *rqptr, char *FileName, int ReallyADirStatus ) { int status; char *cptr, *sptr, *zptr; char SpecBuffer [ODS_MAX_FILE_NAME_LENGTH+1]; ODS_STRUCT SpecOds; WEBDAV_TASK *tkptr; /*********/ /* begin */ /*********/ if (WATCHMOD (rqptr, WATCH_MOD_WEBDAV)) WatchThis (WATCHITM(rqptr), WATCH_MOD_WEBDAV, "DavWebHref() !&Z !&S", FileName, ReallyADirStatus); tkptr = rqptr->WebDavTaskPtr; if (!tkptr->HrefHost[0]) { /* first (perhaps only) time through */ zptr = (sptr = tkptr->HrefHost) + sizeof(tkptr->HrefHost)-1; if (rqptr->ServicePtr->RequestScheme == SCHEME_HTTP) cptr = "http://"; else if (rqptr->ServicePtr->RequestScheme == SCHEME_HTTPS) cptr = "https://"; else cptr = NULL; if (cptr) { while (*cptr && sptr < zptr) *sptr++ = *cptr++; if (rqptr->rqHeader.HostPtr && rqptr->rqHeader.HostPtr[0]) cptr = rqptr->rqHeader.HostPtr; else cptr = rqptr->ServicePtr->ServerHostPort; while (*cptr && sptr < zptr) *sptr++ = *cptr++; } #if WATCH_MOD else ErrorNoticed (rqptr, SS$_BUGCHECK, NULL, FI_LI); #endif /* WATCH_MOD */ *sptr = '\0'; } zptr = (sptr = SpecBuffer) + sizeof(SpecBuffer)-1; for (cptr = FileName; *cptr && sptr < zptr; cptr++) { if (*cptr & 0x80) { if (!rqptr->rqPathSet.OdsName || rqptr->rqPathSet.OdsName == MAPURL_ODS_8BIT) { /* convert to UTF-8 */ *sptr++ = ((*cptr & 0xc0) >> 6) | 0xc0; if (sptr < zptr) *sptr++ = (*cptr & 0x3f) | 0x80; } else *sptr++ = *cptr; } else *sptr++ = *cptr; } if (sptr >= zptr) { ErrorNoticed (rqptr, SS$_RESULTOVF, NULL, FI_LI); return (""); } *sptr = '\0'; while (sptr > SpecBuffer && (*sptr != '.' || SAME2(sptr-1,'^.')) && (*sptr != ';' || SAME2(sptr-1,'^;')) && (*sptr != ']' || SAME2(sptr-1,'^]')) && (*sptr != '[' || SAME2(sptr-1,'^[' || SAME2(sptr-1,'][')))) sptr--; if (*sptr == ';') { /* eliminate the version component */ *sptr = '\0'; for (sptr--; sptr > SpecBuffer && (*sptr != '.' || SAME2(sptr-1,'^.')) && (*sptr != ']' || SAME2(sptr-1,'^]')) && (*sptr != '[' || SAME2(sptr-1,'^[') || SAME2(sptr-1,'][')); sptr--); } if (MATCH5 (sptr, ".DIR\0")) { if (!ReallyADirStatus) { OdsStructInit (&SpecOds, true); AuthAccessEnable (rqptr, SpecBuffer, AUTH_ACCESS_READ); status = OdsParse (&SpecOds, SpecBuffer, sptr-SpecBuffer+4, NULL, 0, 0, NULL, rqptr); AuthAccessEnable (rqptr, NULL, 0); if (VMSnok(status)) ErrorNoticed (rqptr, status, SpecBuffer, FI_LI); ReallyADirStatus = OdsReallyADir (rqptr, &SpecOds); OdsParseRelease (&SpecOds); } if (VMSok (ReallyADirStatus)) { /* munge into a directory specification */ SET2(sptr,']\0'); for (sptr--; sptr > SpecBuffer && (*sptr != ']' || SAME2(sptr-1,'^]')); sptr--); if (*sptr == ']') *sptr = '.'; } } while (*sptr) sptr++; /* map the file-system name into a URL-compliant format */ tkptr->HrefPath[0] = '\0'; sptr = MapUrl_Map (tkptr->HrefPath, sizeof(tkptr->HrefPath), SpecBuffer, sptr-SpecBuffer, NULL, 0, NULL, 0, NULL, 0, NULL, rqptr, NULL); if (!sptr[0] && sptr[1]) ErrorNoticed (rqptr, SS$_BUGCHECK, sptr+1, FI_LI); /* Nautilus (gvfs/1.2.2), at least, considers it an invalid response if the leading href= path portions do not case-match. */ cptr = rqptr->rqHeader.PathInfoPtr; sptr = tkptr->HrefPath; while (*cptr && *sptr) { if (*cptr != *sptr && TOLO(*cptr) == TOLO(*sptr)) *sptr = *cptr; cptr++; sptr++; } } /*****************************************************************************/ /* Copy the file specification into the buffer upper-casing it, removing any redundant MFD, and trimming any trailing version and empty file type. This produces a standardised file specification. Then generate the hash used to represent the resource. */ DavWebNameHash ( char *FileSpec, unsigned long *HashPtr ) { char *cptr, *sptr, *zptr; char Buffer [ODS_MAX_FILE_NAME_LENGTH+1]; /*********/ /* begin */ /*********/ if (WATCH_MODULE(WATCH_MOD_WEBDAV)) WatchThis (WATCHALL, WATCH_MOD_WEBDAV, "DavWebNameHash() !&Z", FileSpec); zptr = (sptr = Buffer) + sizeof(Buffer)-1; cptr = FileSpec; while (*cptr && sptr < zptr) { if (*cptr == '[' && MATCH8 (cptr, "[000000.")) { /* eliminate any redundant MFD component */ cptr += 8; *sptr++ = '['; } else { /* the hash must be case-insensitive */ *sptr++ = TOLO(*cptr++); } } *sptr = '\0'; /* eliminate any trailing version component delimiter */ for (cptr = sptr; cptr > Buffer && *cptr != ';' && *cptr != '.' && *cptr != ']' && !SAME2(cptr-1,'^;'); cptr--); if (*cptr == ';') { sptr = cptr; *sptr = '\0'; } /* eliminate any trailing empty type */ if (sptr > Buffer && *(sptr-1) == '.' && !SAME2(sptr-2,'^.')) sptr--; *sptr = '\0'; Md5Digest (Buffer, sptr - Buffer, HashPtr); if (WATCH_MODULE(WATCH_MOD_WEBDAV)) WatchThis (WATCHALL, WATCH_MOD_WEBDAV, "!&Z !8XL!8XL!8XL!8XL", Buffer, HashPtr[0], HashPtr[1], HashPtr[2], HashPtr[3]); } /*****************************************************************************/ /* Create a directory name from a directory filename, i.e. DEVICE:[DIRECTORY] from DEVICE:[000000]DIRECTORY.DIR and DEVICE:[DIR1.DIR2] from DEVICE:[DIR1]DIR2.DIR. Return the length of the directory name. */ int DavWebDirFromName ( char *FileSpec, char *Buffer, int SizeOfBuffer ) { char *cptr, *sptr, *zptr; /*********/ /* begin */ /*********/ if (WATCH_MODULE(WATCH_MOD_WEBDAV)) WatchThis (WATCHALL, WATCH_MOD_WEBDAV, "DavWebDirFromName() !&Z", FileSpec); zptr = (sptr = Buffer) + SizeOfBuffer-1; cptr = FileSpec; while (*cptr && sptr < zptr) { if (*cptr == '[' && MATCH8 (cptr, "[000000.")) { /* eliminate any redundant MFD component */ cptr += 8; *sptr++ = '['; } else if (*cptr == ']' && *(cptr+1) == '[') { /* eliminate concealed device component */ cptr += 2; } else if (*cptr == ']' && *(sptr-1) == ':') { /* top-level directory */ cptr++; *sptr++ = '['; } else if (*cptr == ']' && *(sptr-1) != ':') { /* end of directory spec */ cptr++; *sptr++ = '.'; } else *sptr++ = *cptr++; } *sptr = '\0'; /* eliminate the trailing type (and version) */ for (cptr = sptr; sptr > Buffer && (*sptr != '.' || SAME2(sptr-1,'^.')); sptr--); if (sptr < zptr) *sptr++ = ']'; *sptr = '\0'; return (sptr - Buffer); } /*****************************************************************************/ /* Initialise the specified DLM structure, including generating the resource name and hash from the supplied file name. Then enqueue a VMS lock on the resource delivering a completion AST as specified if appropriate, or if NOQUEUE then returning the enqueue status (which must be checked). When the AST is delivered the routine must check 'dlmptr->LockSb.lksb$w_status' for success or failure. For convenience (to the calling routine) if 'FileName' is NULL just initialise the DLM structure and directly call the AST function. */ int DavWebDlmEnqueue ( REQUEST_STRUCT *rqptr, WEBDAV_DLM *dlmptr, char *FileName, unsigned long *HashPtr, BOOL ExMode, BOOL NoQueue, REQUEST_AST AstFunction, unsigned long AstParam ) { int status; char *cptr, *sptr, *zptr; /*********/ /* begin */ /*********/ if (WATCHMOD (rqptr, WATCH_MOD_WEBDAV)) WatchThis (WATCHITM(rqptr), WATCH_MOD_WEBDAV, "DavWebDlmEnqueue() !8XL", dlmptr); memset (dlmptr, 0, sizeof(WEBDAV_DLM)); dlmptr->RequestPtr = rqptr; dlmptr->TaskPtr = rqptr->WebDavTaskPtr; if (HashPtr) memcpy (dlmptr->ResourceHash, HashPtr, sizeof(dlmptr->ResourceHash)); else DavWebNameHash (FileName, dlmptr->ResourceHash); zptr = (sptr = dlmptr->ResourceName) + sizeof(dlmptr->ResourceName)-1; for (cptr = FileName; *cptr && sptr < zptr; *sptr++ = TOLO(*cptr++)); *sptr = '\0'; status = DavWebEnqueue (dlmptr, ExMode, NoQueue, AstFunction, AstParam); return (status); } /*****************************************************************************/ /* Enqueue a VMS DLM lock on the name hash. Resource name is "WASDDAV_bbbbbbbbbbbbbbbb" where the 'b' represents a byte of the file name hash. When the AST is delivered the routine must check 'dlmptr->LockSb.lksb$w_status' for success or failure. */ int DavWebEnqueue ( WEBDAV_DLM *dlmptr, BOOL ExMode, BOOL NoQueue, REQUEST_AST AstFunction, unsigned long AstParam ) { static char EnqName [7+16] = "WASDAV_"; static $DESCRIPTOR (EnqNameDsc, EnqName); int status; unsigned long EnqFlags, EnqMode; REQUEST_STRUCT *rqptr; /*********/ /* begin */ /*********/ rqptr = dlmptr->RequestPtr; if (WATCHMOD (rqptr, WATCH_MOD_WEBDAV)) WatchThis (WATCHITM(rqptr), WATCH_MOD_WEBDAV, "DavWebEnqueue() !8XL exmode:!&B noqueue:!&B !8XL!8XL!8XL!8XL !AZ", dlmptr, ExMode, NoQueue, dlmptr->ResourceHash[0], dlmptr->ResourceHash[1], dlmptr->ResourceHash[2], dlmptr->ResourceHash[3], dlmptr->ResourceName); memcpy (EnqName+7, &dlmptr->ResourceHash, sizeof(dlmptr->ResourceHash)); if (NoQueue) EnqFlags = LCK$M_SYSTEM | LCK$M_NOQUEUE; else EnqFlags = LCK$M_SYSTEM; if (dlmptr->ExMode = ExMode) EnqMode = LCK$K_EXMODE; else EnqMode = LCK$K_CRMODE; sys$setprv (1, &SysLckMask, 0, 0); status = sys$enq (EfnNoWait, EnqMode, &dlmptr->LockSb, EnqFlags, &EnqNameDsc, 0, AstFunction, AstParam, 0, 2, 0); sys$setprv (0, &SysLckMask, 0, 0); if (VMSnok (status) && status != SS$_NOTQUEUED) ErrorExitVmsStatus (status, "sys$enq()", FI_LI); return (status); } /*****************************************************************************/ /* If a VMS DLM lock exists on the meta-data then dequeue it and return true indicating to the calling routine it must let the AST routine finish processing (which shows an SS$_ABORT status). If the AST has already been delivered return false. */ BOOL DavWebDequeue (WEBDAV_DLM *dlmptr) { int status; char *cptr; REQUEST_STRUCT *rqptr; struct lksb *lksbptr; /*********/ /* begin */ /*********/ if (!(rqptr = dlmptr->RequestPtr)) return (true); lksbptr = &dlmptr->LockSb; if (WATCHMOD (rqptr, WATCH_MOD_WEBDAV)) WatchThis (WATCHITM(rqptr), WATCH_MOD_WEBDAV, "DavWebDequeue() !8XL !&B !&S !8XL!8XL!8XL!8XL !&Z", dlmptr, lksbptr->lksb$l_lkid, lksbptr->lksb$w_status, dlmptr->ResourceHash[0], dlmptr->ResourceHash[1], dlmptr->ResourceHash[2], dlmptr->ResourceHash[3], dlmptr->ResourceName); if (!lksbptr->lksb$l_lkid) return (false); if (VMSnok (lksbptr->lksb$w_status)) ErrorNoticed (rqptr, lksbptr->lksb$w_status, NULL, FI_LI); sys$setprv (1, &SysLckMask, 0, 0); status = sys$deq (lksbptr->lksb$l_lkid, 0, 0, LCK$M_CANCEL); if (status == SS$_CANCELGRANT) status = sys$deq (lksbptr->lksb$l_lkid, 0, 0, 0); else if (VMSok (status)) if (WATCHMOD (rqptr, WATCH_MOD_WEBDAV)) WatchThis (WATCHITM(rqptr), WATCH_MOD_WEBDAV, "CANCELLED"); sys$setprv (0, &SysLckMask, 0, 0); lksbptr->lksb$l_lkid = 0; if (VMSnok (status)) ErrorNoticed (rqptr, status, NULL, FI_LI); return (true); } /*****************************************************************************/ /* Used via the CLI to DLM lock a resource for testing purposes. Used to DLM (b)lock access to a meta-data file or a resource file/directory. 'M' neta-data or 'R' resource, 'E' exclusive or 'C' for concurrent read, and 'Q' for queue or 'N' for no queue, and then the file name; as with: $ HTTPD /TEST "webdav=dlm=MEQdevice:[directory]file.ext" */ int DavWebDlmTest (char *CliParam) { #if WATCH_CAT int status; static WEBDAV_DLM DlmData; static REQUEST_STRUCT DummyRequest; /*********/ /* begin */ /*********/ if (!CliParam) { fprintf (stdout, "AST lksb$w_status: %%X%08.08X\n", DlmData.LockSb.lksb$w_status); sys$hiber(); return (DlmData.LockSb.lksb$w_status); } Watch.Module |= WATCH_MOD_WEBDAV; Watch.StdoutOnly = true; DummyRequest.WatchItem = 1; status = DavWebDlmEnqueue (&DummyRequest, &DlmData, CliParam+3, NULL, TOUP(CliParam[1]) == 'E', TOUP(CliParam[2]) == 'N', &DavWebDlmTest, NULL); fprintf (stdout, "DavWebDlmEnqueue: %%X%08.08X\n", status); if (VMSok(status)) sys$hiber(); return (status); #else return (SS$_ABORT); #endif /* WATCH_CAT */ } /*****************************************************************************/ /* Create a collection (directory)! */ DavWebMkCol (REQUEST_STRUCT *rqptr) { int status; /*********/ /* begin */ /*********/ if (WATCHMOD (rqptr, WATCH_MOD_WEBDAV)) WatchThis (WATCHITM(rqptr), WATCH_MOD_WEBDAV, "DavWebMkCol()"); if (rqptr->rqBody.DataPtr) { DavWebResponse (rqptr, 415, 0, "request body not defined for MKCOL", FI_LI); DavWebEnd (rqptr); return; } if (rqptr->rqHeader.ContentLength) { /* any body at all in fact! */ DavWebResponse (rqptr, 415, 0, "request entity not supported", FI_LI); DavWebEnd (rqptr); return; } status = DavWebSlashlessMunge (rqptr, false); if (VMSnok (status)) { DavWebResponse (rqptr, 500, status, NULL, FI_LI); DavWebEnd (rqptr); return; } DavWebMkCol2 (rqptr); } /*****************************************************************************/ /* Test if it is locked and continue with the directory creation if not. This function will be called multiple times as asynchronous testing of the DLM and meta locking occurs. */ DavWebMkCol2 (REQUEST_STRUCT *rqptr) { int len, status; WEBDAV_TASK *tkptr; /*********/ /* begin */ /*********/ tkptr = rqptr->WebDavTaskPtr; if (WATCHMOD (rqptr, WATCH_MOD_WEBDAV)) WatchThis (WATCHITM(rqptr), WATCH_MOD_WEBDAV, "DavWebMkCol2() !UL", tkptr->TestLockState); /***********/ /* locking */ /***********/ switch (tkptr->TestLockState) { case 0 : /* establish a request (overall) VMS DLM lock on source */ tkptr->TestLockState++; DavWebDlmEnqueue (rqptr, &tkptr->DlmSource, rqptr->ParseOds.ExpFileName, false, true, false, DavWebMkCol2, rqptr); return; case 1 : if (VMSnok (status = tkptr->DlmSource.LockSb.lksb$w_status)) { /* ordinarily shouldn't return any errors */ DavWebResponse (rqptr, 500, status, NULL, FI_LI); DavWebEnd (rqptr); return; } /* check for meta-lock on existing source */ tkptr->TestLockState++; DavLockTest (rqptr, rqptr->ParseOds.ExpFileName, false, DavWebMkCol2, rqptr); return; case 2 : if (VMSnok (tkptr->TestLockStatus)) { DavWebResponse (rqptr, 423, 0, "source locked", FI_LI); DavWebEnd (rqptr); return; } } /**************/ /* not locked */ /**************/ if (VMSnok (DavWebParentExists (NULL, rqptr->ParseOds.ExpFileName))) { DavWebResponse (rqptr, 412, 0, "parent does not exist", FI_LI); DavWebEnd (rqptr); return; } len = rqptr->ParseOds.NamNamePtr - rqptr->ParseOds.NamDevicePtr; if (WATCHING (rqptr, WATCH_RESPONSE)) WatchThis (WATCHITM(rqptr), WATCH_RESPONSE, "MKCOL !#AZ", len, rqptr->ParseOds.NamDevicePtr); status = DavWebCreateDir (rqptr, rqptr->ParseOds.NamDevicePtr, len); if (status == SS$_CREATED) { DavWebHref (rqptr, rqptr->ParseOds.ExpFileName, 0); DavWebResponse201 (rqptr); } else if (status == SS$_NORMAL) DavWebResponse (rqptr, 405, 0, "directory existed", FI_LI); else if (status == SS$_NOPRIV || status == SS$_NORAD50) DavWebResponse (rqptr, 403, status, NULL, FI_LI); else DavWebResponse (rqptr, 500, status, NULL, FI_LI); DavWebEnd (rqptr); } /*****************************************************************************/ /* Create a directory using lib$create_dir()! Used by the above and MOVE and COPY when reproducing trees.. */ int DavWebCreateDir ( REQUEST_STRUCT *rqptr, char *DirName, int DirNameLength ) { /* ensure SYSPRV can control the directory */ static unsigned short ProtEnable = 0x000f, ProtValue = 0x0000; /* S:RWED */ static $DESCRIPTOR (DirNameDsc, ""); int status; /*********/ /* begin */ /*********/ if (WATCHMOD (rqptr, WATCH_MOD_WEBDAV)) WatchThis (WATCHITM(rqptr), WATCH_MOD_WEBDAV, "DavWebCreateDir()"); if (DirNameLength <= 0) DirNameLength = strlen(DirName); DirNameDsc.dsc$a_pointer = DirName; DirNameDsc.dsc$w_length = DirNameLength; AuthAccessEnable (rqptr, DirName, AUTH_ACCESS_WRITE); status = lib$create_dir (&DirNameDsc, 0, &ProtEnable, &ProtValue, 0, 0); if (WATCHING (rqptr, WATCH_WEBDAV)) if (VMSok (status)) WatchThis (WATCHITM(rqptr), WATCH_WEBDAV, "CREATED !#AZ", DirNameLength, DirName); else WatchThis (WATCHITM(rqptr), WATCH_WEBDAV, "CREATE !#AZ !&S", DirNameLength, DirName, status); AuthAccessEnable (rqptr, 0, 0); return (status); } /*****************************************************************************/ /* Check if the parent directory exists. Return a VMS status; */ int DavWebParentExists ( REQUEST_STRUCT *rqptr, char *DirectoryName ) { int status; char *cptr, *sptr, *zptr; char FileSpec [ODS_MAX_FILE_NAME_LENGTH+1]; /*********/ /* begin */ /*********/ if (WATCHMOD (rqptr, WATCH_MOD_WEBDAV)) WatchThis (WATCHITM(rqptr), WATCH_MOD_WEBDAV, "DavWebParentExists() !AZ", DirectoryName); zptr = (sptr = FileSpec) + sizeof(FileSpec)-1; for (cptr = DirectoryName; *cptr && sptr < zptr; *sptr++ = *cptr++); *sptr-- = '\0'; if (sptr > FileSpec && *sptr == ']' && !SAME2(sptr-1,'^]')) { /* passed a directory specification */ while (sptr > FileSpec && (*sptr != '.' || SAME2(sptr-1,'^.')) && (*sptr != '[' || SAME2(sptr-1,'^['))) sptr--; if (*sptr == '.') SET2(sptr,']\0'); else { while (*sptr) sptr++; sptr--; } } while (sptr > FileSpec && (*sptr != ']' || SAME2(sptr-1,'^]')) && (*sptr != '.' || SAME2(sptr-1,'^.')) && (*sptr != '[' || SAME2(sptr-1,'^['))) sptr--; if (*sptr == ']') while (sptr > FileSpec && (*sptr != '.' || SAME2(sptr-1,'^.')) && (*sptr != '[' || SAME2(sptr-1,'^['))) sptr--; /* if at the MFD then implicitly a parent directory */ if (*sptr == '[') return (SS$_NORMAL); /* still a subdirectory */ *sptr++ = ']'; while (*sptr && (*sptr != ']' || SAME2(sptr-1,'^]'))) sptr++; for (cptr = ".DIR;"; *cptr && sptr < zptr; *sptr++ = *cptr++); *sptr = '\0'; sys$setprv (1, &SysPrvMask, 0, 0); status = OdsFileExists (NULL, FileSpec); sys$setprv (0, &SysPrvMask, 0, 0); if (WATCHMOD (rqptr, WATCH_MOD_WEBDAV)) WatchThis (WATCHITM(rqptr), WATCH_MOD_WEBDAV, "!&S !AZ", status, FileSpec); return (status); } /*****************************************************************************/ /* Simply return a string representing the path WebDAV SETing. */ char* DavWebPathAccess (REQUEST_STRUCT *rqptr) { /*********/ /* begin */ /*********/ /* same order as in AuthAccessEnable() */ if (rqptr->rqPathSet.WebDavWrite) return ("WRITE"); else if (rqptr->rqPathSet.WebDavProfile) return ("PROFILE"); else if (rqptr->rqPathSet.WebDavRead) return ("READ"); else if (rqptr->rqPathSet.WebDavServer) return ("SERVER"); else return ("BUGCHECK"); } /*****************************************************************************/ /* Maps and parses the URL specified in the request "Destination:" header into the destination ODS structure. Returns a VMS status to indicate the success or otherwise of the function. Return SS$_ABORT if no destination field. */ int DavWebDestination (REQUEST_STRUCT *rqptr) { BOOL IsURL; int status, DirNameLength; char *cptr, *sptr, *zptr, *PathPtr; char DestPath [ODS_MAX_FILE_NAME_LENGTH+1], DestSpec [ODS_MAX_FILE_NAME_LENGTH+1], DirName [ODS_MAX_FILE_NAME_LENGTH+1], HostPort [128+16]; REQUEST_PATHSET PathSet; WEBDAV_TASK *tkptr; /*********/ /* begin */ /*********/ if (WATCHMOD (rqptr, WATCH_MOD_WEBDAV)) WatchThis (WATCHITM(rqptr), WATCH_MOD_WEBDAV, "DavWebDestination() !&Z", rqptr->rqHeader.WebDavDestinationPtr); tkptr = rqptr->WebDavTaskPtr; if (!(cptr = rqptr->rqHeader.WebDavDestinationPtr)) return (SS$_ABORT); IsURL = true; if (MATCH7 (cptr, "http://")) cptr += 7; else if (MATCH8 (cptr, "https://")) cptr += 8; else { for (sptr = cptr; *sptr && *sptr != ':' && *sptr != '/'; sptr++); if (MATCH3 (sptr, "://")) return (SS$_BADPARAM); IsURL = false; } if (IsURL) { /* some WebDAV include a username in the host specification, eliminate */ for (sptr = cptr; *sptr && *sptr != '@' && *sptr != '/'; sptr++); if (*sptr == '@') { while (*cptr && *cptr != '@') cptr++; cptr++; } /* only local destinations supported */ zptr = (sptr = HostPort) + sizeof(HostPort)-1; while (*cptr && *cptr != ':' && *cptr != '/' && sptr < zptr) *sptr++ = *cptr++; if (*cptr == ':') { /* compare host components only if both contain them */ if (strchr (rqptr->rqHeader.HostPtr, ':')) while (*cptr && *cptr != '/' && sptr < zptr) *sptr++ = *cptr++; else while (*cptr && *cptr != '/') cptr++; } if (!*cptr) return (SS$_BADPARAM); if (sptr >= zptr) { ErrorNoticed (rqptr, SS$_RESULTOVF, NULL, FI_LI); return (SS$_RESULTOVF); } *sptr = '\0'; if (rqptr->rqHeader.HostPtr && !strsame (rqptr->rqHeader.HostPtr, HostPort, -1)) { if (WATCHING (rqptr, WATCH_WEBDAV)) WatchThis (WATCHITM(rqptr), WATCH_WEBDAV, "DESTINATION (!AZ) not this host (!AZ)", HostPort, rqptr->rqHeader.HostPtr); return (SS$_BADPARAM); } } PathPtr = cptr; zptr = (sptr = DestPath) + sizeof(DestPath)-1; while (*cptr && sptr < zptr) *sptr++ = *cptr++; if (sptr >= zptr) { ErrorNoticed (rqptr, SS$_RESULTOVF, NULL, FI_LI); return (SS$_RESULTOVF); } *sptr = '\0'; if (StringUrlDecode (DestPath) <= 0) { if (WATCHING (rqptr, WATCH_WEBDAV)) WatchThis (WATCHITM(rqptr), WATCH_WEBDAV, "DESTINATION decode !AZ", DestPath); return (SS$_BADPARAM); } if (!rqptr->rqPathSet.OdsName || rqptr->rqPathSet.OdsName == MAPURL_ODS_8BIT) { /* is there a potentially UTF-8 bit pattern here? */ for (cptr = DestPath; *cptr && ((*cptr & 0xe0) != 0xc0); cptr++); if (*cptr) { /* some UTF-8 encode (e.g. KDE), others (e.g. MS WebFolders) do not */ if (ConvertFromUtf8 (DestPath, -1, '$') > 0) { if (WATCHING (rqptr, WATCH_WEBDAV)) WatchThis (WATCHITM(rqptr), WATCH_WEBDAV, "UTF-8 to native !AZ", DestPath); } else { /* we know from above it fits and decodes */ sptr = DestPath; for (cptr = PathPtr; *cptr; *sptr++ = *cptr++); *sptr = '\0'; StringUrlDecode (DestPath); } } } else if (rqptr->rqPathSet.OdsName == MAPURL_ODS_UTF8) { if (ConvertUtf8ToEscape (DestPath, DestPath, sizeof(DestPath)) > 0) if (WATCHING (rqptr, WATCH_REQUEST)) WatchThis (WATCHITM(rqptr), WATCH_REQUEST, "UTF-8 to escaped 8-bit !AZ", DestPath); } memset (&PathSet, 0, sizeof(PathSet)); cptr = MapUrl_Map (DestPath, 0, DestSpec, sizeof(DestSpec), NULL, 0, NULL, 0, NULL, 0, NULL, rqptr, &PathSet); if (!cptr[0]) { if (WATCHING (rqptr, WATCH_WEBDAV)) WatchThis (WATCHITM(rqptr), WATCH_WEBDAV, "DESTINATION mapping !AZ", cptr+1); return (SS$_BADPARAM); } OdsParse (&tkptr->DestOds, DestSpec, strlen(DestSpec), NULL, 0, NAM$M_SYNCHK, NULL, rqptr); if (WATCHMOD (rqptr, WATCH_MOD_WEBDAV)) WatchThis (WATCHITM(rqptr), WATCH_MOD_WEBDAV, "!&S !AZ", tkptr->DestOds.Fab.fab$l_sts, tkptr->DestOds.ExpFileName); if (VMSnok (status = tkptr->DestOds.Fab.fab$l_sts)) { ErrorNoticed (rqptr, status, NULL, FI_LI); return (status); } if (VMSnok (status = OdsParseTerminate (&tkptr->DestOds))) { ErrorNoticed (rqptr, status, NULL, FI_LI); return (status); } if (!(PathSet.WebDavProfile || PathSet.WebDavRead || PathSet.WebDavServer || PathSet.WebDavWrite)) { if (WATCHING (rqptr, WATCH_WEBDAV)) WatchThis (WATCHITM(rqptr), WATCH_WEBDAV, "DESTINATION is not mapped for WebDAV access"); return (SS$_NOPRIV); } /* if the source is a file specification */ if (rqptr->ParseOds.NamNameLength || rqptr->ParseOds.NamTypeLength) if (DavMetaDir (rqptr, &rqptr->ParseOds)) return (RMS$_DNF); else return (SS$_NORMAL); /* if destination is already a directory specification */ if (!tkptr->DestOds.NamNameLength && !tkptr->DestOds.NamTypeLength) if (DavMetaDir (rqptr, &tkptr->DestOds)) return (RMS$_DNF); else return (SS$_NORMAL); /*************************/ /* make into a directory */ /*************************/ if (tkptr->DestOds.NamTypeLength) { /* file specification has a type */ zptr = (sptr = DirName) + sizeof(DirName)-1; for (cptr = tkptr->DestOds.ExpFileName; *cptr && sptr < zptr; *sptr++ = *cptr++); if (sptr < zptr) *sptr++ = '\0'; if (sptr < zptr) *sptr++ = ']'; if (sptr >= zptr) { ErrorNoticed (rqptr, SS$_RESULTOVF, NULL, FI_LI); return (SS$_RESULTOVF); } *sptr = '\0'; DirNameLength = sptr - DirName; sptr -= 2; for (cptr = sptr-1; cptr > DirName && *cptr != '.' && !SAME2(cptr-1,'^.'); *sptr-- = *cptr--); *cptr++ = '^'; *cptr-- = '.'; while (cptr > DirName && (*cptr != ']' || SAME2(cptr-1,'^]'))) cptr--; if (*cptr == ']') *cptr = '.'; } else { /* file specification has no type */ zptr = (sptr = DirName) + sizeof(DirName)-1; for (cptr = tkptr->DestOds.ExpFileName; *cptr && sptr < zptr; *sptr++ = *cptr++); if (sptr < zptr) *sptr++ = ']'; if (sptr >= zptr) { ErrorNoticed (rqptr, SS$_RESULTOVF, NULL, FI_LI); return (SS$_RESULTOVF); } *sptr = '\0'; DirNameLength = sptr - DirName; sptr -= 2; while (sptr > DirName && (*sptr != ']' || SAME2(sptr-1,'^]'))) sptr--; if (*sptr == ']') *sptr = '.'; } OdsParse (&tkptr->DestOds, DirName, DirNameLength, NULL, 0, NAM$M_SYNCHK, NULL, rqptr); if (WATCHMOD (rqptr, WATCH_MOD_WEBDAV)) WatchThis (WATCHITM(rqptr), WATCH_MOD_WEBDAV, "!&S !AZ", tkptr->DestOds.Fab.fab$l_sts, tkptr->DestOds.ExpFileName); if (VMSnok (status = tkptr->DestOds.Fab.fab$l_sts)) { ErrorNoticed (rqptr, status, NULL, FI_LI); return (status); } if (VMSnok (status = OdsParseTerminate (&tkptr->DestOds))) { ErrorNoticed (rqptr, status, NULL, FI_LI); return (status); } if (WATCHING (rqptr, WATCH_WEBDAV)) WatchThis (WATCHITM(rqptr), WATCH_WEBDAV, "MUNGED !AZ", tkptr->DestOds.ExpFileName); if (DavMetaDir (rqptr, &tkptr->DestOds)) return (RMS$_DNF); else return (SS$_NORMAL); } /*****************************************************************************/ /* Check the request user agent field for indications of a Microsoft WebDAV agent and return true if detected. Return false if not detected. */ BOOL DavWebMicrosoftDetect (REQUEST_STRUCT *rqptr) { char *cptr; WEBDAV_TASK *tkptr; /*********/ /* begin */ /*********/ if (WATCHMOD (rqptr, WATCH_MOD_WEBDAV)) WatchThis (WATCHITM(rqptr), WATCH_MOD_WEBDAV, "DavWebMicrosoftDetect()"); if (!(cptr = rqptr->rqHeader.UserAgentPtr)) return (false); tkptr = rqptr->WebDavTaskPtr; if (tkptr) tkptr->MicrosoftAgent = 0; if (strstr (cptr, "Microsoft")) { if (strstr (cptr, "WebDAV-MiniRedir/")) { if (WATCHING (rqptr, WATCH_WEBDAV)) WatchThis (WATCHITM(rqptr), WATCH_WEBDAV, "MICROSOFT WebDAV MiniRedir"); if (tkptr) tkptr->MicrosoftAgent = WEBDAV_MICROSOFT_MINIREDIR; return (true); } if (strstr (cptr, "Data Access Internet Publishing Provider")) { if (WATCHING (rqptr, WATCH_WEBDAV)) WatchThis (WATCHITM(rqptr), WATCH_WEBDAV, "MICROSOFT Internet Publishing Provider"); if (tkptr) tkptr->MicrosoftAgent = WEBDAV_MICROSOFT_MDAIPP; return (true); } if (WATCHING (rqptr, WATCH_WEBDAV)) WatchThis (WATCHITM(rqptr), WATCH_WEBDAV, "MICROSOFT unknown agent"); if (tkptr) tkptr->MicrosoftAgent = WEBDAV_MICROSOFT_UNKNOWN; return (true); } if (strstr (cptr, "WebDrive")) { if (WATCHING (rqptr, WATCH_WEBDAV)) WatchThis (WATCHITM(rqptr), WATCH_WEBDAV, "South River Technologies WebDrive (MICROSOFT)"); if (tkptr) tkptr->MicrosoftAgent = WEBDAV_MICROSOFT_WEBDRIVE; return (true); } if (strstr (cptr, "BitKinex")) { if (WATCHING (rqptr, WATCH_WEBDAV)) WatchThis (WATCHITM(rqptr), WATCH_WEBDAV, "BARAD-DUR LLC, BitKinex (MICROSOFT)"); if (tkptr) tkptr->MicrosoftAgent = WEBDAV_MICROSOFT_BITKINEX; return (true); } return (false); } /*****************************************************************************/ /* Some agents specify collections (directories) without a trailing slash which are interpreted by WASD as a file specification, with or without a file type. This function changes such a file specification into a directory equivalent. WEB:[This]is^_an^_example.; into WEB:[This.is^_an^_example] WEB:[This]is^_an.example; " WEB:[This.is^_an^.example] WEB:[This]is^.another.example; " WEB:[This.is^.another^.example] Depending on the 'IfExists' boolean, it will only do this if the resulting directory specification actually exists in the file-system, or unconditionally. The function returns SS$_NORMAL if no change has been made, SS$_SUPERCEDE if the resulting specification is reparsed into the request result, or it can return a VMS error status. */ int DavWebSlashlessMunge ( REQUEST_STRUCT *rqptr, BOOL IfExists ) { int status, DirNameLength; char *cptr, *sptr, *zptr; char DirName [ODS_MAX_FILE_NAME_LENGTH+1]; /*********/ /* begin */ /*********/ if (WATCHMOD (rqptr, WATCH_MOD_WEBDAV)) WatchThis (WATCHITM(rqptr), WATCH_MOD_WEBDAV, "DavWebSlashlessMunge() !&B !AZ", IfExists, rqptr->ParseOds.ExpFileName); if (rqptr->ParseOds.NamTypeLength) { /* file specification has a type */ zptr = (sptr = DirName) + sizeof(DirName)-1; for (cptr = rqptr->ParseOds.ExpFileName; *cptr && sptr < zptr; *sptr++ = *cptr++) { /* swallow any MFD component */ if (*cptr == '0' && MATCH7 (cptr, "000000]")) cptr += 7; } if (sptr < zptr) *sptr++ = '\0'; if (sptr < zptr) *sptr++ = ']'; if (sptr >= zptr) { ErrorNoticed (rqptr, SS$_RESULTOVF, NULL, FI_LI); return (SS$_RESULTOVF); } *sptr = '\0'; DirNameLength = sptr - DirName; sptr -= 2; for (cptr = sptr-1; cptr > DirName && *cptr != '.'; *sptr-- = *cptr--); *cptr++ = '^'; *cptr-- = '.'; while (cptr > DirName && (*cptr != ']' || SAME2(cptr-1,'^]'))) cptr--; if (*cptr == ']' && !SAME2(cptr-1,'^]')) *cptr = '.'; } else if (rqptr->ParseOds.NamNameLength) { /* file specification has no type */ zptr = (sptr = DirName) + sizeof(DirName)-1; for (cptr = rqptr->ParseOds.ExpFileName; *cptr && sptr < zptr; *sptr++ = *cptr++) { /* swallow any MFD component */ if (*cptr == '0' && MATCH7 (cptr, "000000]")) cptr += 7; } if (sptr < zptr) *sptr++ = ']'; if (sptr >= zptr) { ErrorNoticed (rqptr, SS$_RESULTOVF, NULL, FI_LI); return (SS$_RESULTOVF); } *sptr = '\0'; DirNameLength = sptr - DirName; sptr -= 2; while (sptr > DirName && (*sptr != ']' || SAME2(sptr-1,'^]'))) sptr--; if (*sptr == ']' && !SAME2(sptr-1,'^]')) *sptr = '.'; } else { /* no type and no name - must be a directory specification already! */ return (SS$_NOTMODIFIED); } if (IfExists) { /* test to see if the directory actually exists */ sys$setprv (1, &SysPrvMask, 0, 0); status = OdsFileExists (DirName, "*.*"); sys$setprv (0, &SysPrvMask, 0, 0); if (status == RMS$_FNF || status == RMS$_NMF) status = SS$_NORMAL; /* no it does not (or error) */ if (VMSnok (status)) return (SS$_NORMAL); } OdsParse (&rqptr->ParseOds, DirName, DirNameLength, NULL, 0, NAM$M_SYNCHK, NULL, rqptr); if (WATCHMOD (rqptr, WATCH_MOD_WEBDAV)) WatchThis (WATCHITM(rqptr), WATCH_MOD_WEBDAV, "!&S !AZ", rqptr->ParseOds.Fab.fab$l_sts, rqptr->ParseOds.ExpFileName); if (VMSnok (status = rqptr->ParseOds.Fab.fab$l_sts)) { ErrorNoticed (rqptr, status, NULL, FI_LI); return (status); } if (VMSnok (status = OdsParseTerminate (&rqptr->ParseOds))) { ErrorNoticed (rqptr, status, NULL, FI_LI); return (status); } if (WATCHING (rqptr, WATCH_WEBDAV)) WatchThis (WATCHITM(rqptr), WATCH_WEBDAV, "MUNGED !AZ", rqptr->ParseOds.ExpFileName); return (SS$_SUPERSEDE); } /*****************************************************************************/ /* Microsoft agent(s) has an observed behaviour where it seems to bungle the concatenation of resource names resulting in requests containing resources with empty directories such as '/web//test/test.txt'. These are flagged as illegal file-system syntax during initial request processing. If this happens and it's a WebDAV request this function is called to investigate and internally redirect with a 'corrected' URI if appropriate. */ DavWebMicrosoftMunge1 (REQUEST_STRUCT *rqptr) { char *cptr, *sptr, *zptr; char Location [1024]; /*********/ /* begin */ /*********/ if (WATCHMOD (rqptr, WATCH_MOD_WEBDAV)) WatchThis (WATCHITM(rqptr), WATCH_MOD_WEBDAV, "DavWebMicrosoftMunge1() !&Z", rqptr->rqHeader.RequestUriPtr); if (!rqptr->WebDavTaskPtr || !rqptr->WebDavTaskPtr->MicrosoftAgent) return; for (cptr = rqptr->rqHeader.RequestUriPtr; *cptr; cptr++) if (SAME2(cptr,'//')) break; if (!*cptr) return; zptr = (sptr = Location) + sizeof(Location)-1; for (cptr = rqptr->rqHeader.RequestUriPtr; *cptr && sptr < zptr; cptr++) if (!SAME2(cptr,'//')) *sptr++ = *cptr; if (sptr >= zptr) { ErrorNoticed (rqptr, SS$_RESULTOVF, NULL, FI_LI); return; } *sptr = '\0'; cptr = ResponseLocation (rqptr, Location, sptr - Location); if (WATCHING (rqptr, WATCH_WEBDAV)) WatchThis (WATCHITM(rqptr), WATCH_WEBDAV, "MICROSOFT munge !AZ", cptr); } /*****************************************************************************/ /* According to http://www.webdavsystem.com/server/documentation/troubleshooting Web Folders client on Windows Vista sometimes provides incorrect Lock-Token header. Instead of Lock-Token: header Lock-Token: opaquelocktoken:66db0722-1053-42c4-9940-3d6693457e32 header is sent. If it doesn't look quite correct then assume the worst and munge the header! */ DavWebMicrosoftMunge2 (REQUEST_STRUCT *rqptr) { char *cptr, *sptr, *zptr; char LockToken [256]; DICT_ENTRY_STRUCT *denptr; /*********/ /* begin */ /*********/ if (WATCHMOD (rqptr, WATCH_MOD_WEBDAV)) WatchThis (WATCHITM(rqptr), WATCH_MOD_WEBDAV, "DavWebMicrosoftMunge2() !&Z", rqptr->rqHeader.WebDavLockTokenPtr); if (*rqptr->rqHeader.WebDavLockTokenPtr == '<') return; zptr = (sptr = LockToken) + sizeof(LockToken)-2; cptr = rqptr->rqHeader.WebDavLockTokenPtr; *sptr++ = '<'; while (*cptr && sptr < zptr) *sptr++ = *cptr++; *sptr++ = '>'; *sptr = '\0'; denptr = DictInsert (rqptr->rqDictPtr, DICT_TYPE_REQUEST, "lock-token", 10, LockToken, sptr - LockToken); DICT_GET_VALUE (denptr); if (WATCHING (rqptr, WATCH_WEBDAV)) WatchThis (WATCHITM(rqptr), WATCH_WEBDAV, "MICROSOFT munge !AZ", rqptr->rqHeader.WebDavLockTokenPtr); } /*****************************************************************************/ /* Generate a null-terminated string containing the supplied binary time in the IETF RFC 3339 format 'TZ'. Assumes the supplied string storage is large enough to store 21 characters. */ int DavWebDateTimeTo3339 ( char *TimeString, unsigned long *Time64Ptr ) { static $DESCRIPTOR (Rfc3339FaoDsc, "!4ZW-!2ZW-!2ZWT!2ZW:!2ZW:!2ZWZ\0"); static $DESCRIPTOR (TimeStringDsc, ""); int status; unsigned long GmTime [2]; unsigned short NumTime [7]; /*********/ /* begin */ /*********/ if (WATCH_MODULE(WATCH_MOD_WEBDAV)) WatchThis (WATCHALL, WATCH_MOD_WEBDAV, "DavWebDateTimeTo3339() !%D", Time64Ptr); if (Time64Ptr) PUT_QUAD_QUAD (Time64Ptr, GmTime) else sys$gettim (&GmTime); TimeAdjustGMT (true, &GmTime); sys$numtim (&NumTime, &GmTime); TimeStringDsc.dsc$w_length = 21; TimeStringDsc.dsc$a_pointer = TimeString; sys$fao (&Rfc3339FaoDsc, 0, &TimeStringDsc, NumTime[0], NumTime[1], NumTime[2], NumTime[3], NumTime[4], NumTime[5]); return (SS$_NORMAL); } /*****************************************************************************/ /* From the supplied IETF RFC 3339 format 'TZ' generate a VMS binary time. String can have leading white-space and trailing anything. */ int DavWebDateTimeFrom3339 ( char *TimeString, unsigned long *Time64Ptr ) { int status; char *cptr; unsigned long GmTime [2]; unsigned short NumTime [7]; /*********/ /* begin */ /*********/ if (WATCH_MODULE(WATCH_MOD_WEBDAV)) WatchThis (WATCHALL, WATCH_MOD_WEBDAV, "DavWebDateTimeFrom3339() !&Z", TimeString); memset (&NumTime, 0, sizeof(NumTime)); PUT_ZERO_QUAD(Time64Ptr) for (cptr = TimeString; *cptr && isspace(*cptr); cptr++); NumTime[0] = atoi(cptr); while (*cptr && *cptr != '-') cptr++; if (!*cptr++) return (SS$_IVTIME); NumTime[1] = atoi(cptr); while (*cptr && *cptr != '-') cptr++; if (!*cptr++) return (SS$_IVTIME); NumTime[2] = atoi(cptr); while (*cptr && *cptr != 'T') cptr++; if (!*cptr++) return (SS$_IVTIME); NumTime[3] = atoi(cptr); while (*cptr && *cptr != ':') cptr++; if (!*cptr++) return (SS$_IVTIME); NumTime[4] = atoi(cptr); while (*cptr && *cptr != ':') cptr++; if (!*cptr++) return (SS$_IVTIME); NumTime[5] = atoi(cptr); while (*cptr && *cptr != 'Z') cptr++; if (!*cptr++) return (SS$_IVTIME); status = lib$cvt_vectim (&NumTime, Time64Ptr); if (VMSnok (status)) return (status); status = TimeAdjustGMT (false, Time64Ptr); if (VMSnok (status)) return (status); if (WATCH_MODULE(WATCH_MOD_WEBDAV)) WatchThis (WATCHALL, WATCH_MOD_WEBDAV, "!%D", Time64Ptr); return (SS$_NORMAL); } /*****************************************************************************/ /* Report on WebDAV activity. */ DavWebReport (REQUEST_STRUCT *rqptr) { static char ReportPageFao [] = "

\n\ \n\
\n\ \n\ \n\ \n\
\n\ \n\ \ \n\ \n\ \n\ \n\ \n\ \n\ \n\ \n\ \n\ \ \n\ \ \n\ \n\ \n\ \n\ \ \n\ \n\ \
Configuration
Expat Version:!AZ
WebDAV:!AZ
Locking:!AZ
Lock Timeout Default:!AZ
Lock Timeout Max:!AZ
Lock Collection Depth:!UL
Quota:!AZ
Meta File Directory:!AZ
 
Statistics
Module:!&L
Identified-as:!&L(!&L)
\ \ XML Parse:!&L
Meta  /Read:!&L(!&L)
/Write:!&L(!&L)
\n\ \
\n\ \n\ \ \n\ \n\ \n\ \n\ \n\ \n\ \n\ \n\ \n\ \n\ \n\ \n\ \n\ \ \n\ \ \n\ \ \n\ \ \n\ \ \n\ \
Method
COPY:!&L
**DELETE:!&L
**GET:!&L
LOCK:!&L
MKCOL:!&L
MOVE:!&L
**OPTIONS:!&L
PROPFIND:!&L
PROPPATCH:!&L
**PUT:!&L
UNLOCK:!&L
Total:!&L(!UL%*)
 
Traffic
Rx:!&,@SQ
Tx:!&,@SQ
Total:!&,@SQ  (!UL%*)
\n\
\ *of server total\n\
**standard HTTP method in conjunction with a \ request characteristic indicating WebDAV
\n\
\n\ \

\n\ \ \n\ \ \ \ \ \ \ \n"; static char ReportRequestFao [] = "\ \ \ \n\ \ \ \n\ \n\ \n"; static char NoneFao [] = "\n"; static char ReportPageEndFao [] = "\n\
\n\
VMS DLM Locks
Service / ClientRequest / Time / Meta-dataSource / Destin /\ Meta-src / Meta-dstMode
!3ZL!AZ//!AZ  !AZ !AZ!&@!AZ
!AZ!20%D  (!AZ)!&@!AZ
!&@!&@!AZ
!&@!&@!AZ
000none
\ *possible meta-data only
\n\ !AZ\ \n\ \n\ \n"; int status, AgentCount, DisplayCount = 0, MethodCount; unsigned long BinaryTime [2], BytesRxTx [2], BytesTotal [2], ResponseDuration [2], FaoVector [48]; unsigned long *vecptr; char *cptr; LIST_ENTRY *leptr; REQUEST_STRUCT *rqeptr; /*********/ /* begin */ /*********/ if (WATCHMOD (rqptr, WATCH_MOD_WEBDAV)) WatchThis (WATCHITM(rqptr), WATCH_MOD_WEBDAV, "DavWebReport()"); AdminPageTitle (rqptr, "WebDAV Report"); vecptr = FaoVector; InstanceMutexLock (INSTANCE_MUTEX_HTTPD); *vecptr++ = XML_ExpatVersion() + 6; *vecptr++ = WebDavEnabled ? "[enabled]" : "[disabled]"; *vecptr++ = WebDavLockingEnabled ? "[enabled]" : "[disabled]"; *vecptr++ = MetaConShowSeconds (rqptr, WebDavLockTimeoutDefaultSeconds); *vecptr++ = MetaConShowSeconds (rqptr, WebDavLockTimeoutMaxSeconds); *vecptr++ = WebDavLockCollectionDepth; *vecptr++ = WebDavQuotaEnabled ? "[enabled]" : "[disabled]"; *vecptr++ = Config.cfWebDav.MetaFileDirectory[0] ? Config.cfWebDav.MetaFileDirectory : "(none)"; *vecptr++ = AccountingPtr->DoWebDavCount; *vecptr++ = AccountingPtr->WebDavRequestCount; *vecptr++ = AccountingPtr->WebDavAromaCount; *vecptr++ = AccountingPtr->WebDavXmlParseCount; *vecptr++ = AccountingPtr->WebDavMetaReadCount; *vecptr++ = AccountingPtr->WebDavMetaReadAttemptCount; *vecptr++ = AccountingPtr->WebDavMetaWriteCount; *vecptr++ = AccountingPtr->WebDavMetaWriteAttemptCount; PLUS_QUAD_QUAD (AccountingPtr->WebDavBytesRawRx, AccountingPtr->WebDavBytesRawTx, BytesRxTx); PLUS_QUAD_QUAD (AccountingPtr->BytesRawRx, AccountingPtr->BytesRawTx, BytesTotal); *vecptr++ = AccountingPtr->MethodWebDavCopyCount, *vecptr++ = AccountingPtr->MethodWebDav_DeleteCount, *vecptr++ = AccountingPtr->MethodWebDav_GetCount, *vecptr++ = AccountingPtr->MethodWebDavLockCount, *vecptr++ = AccountingPtr->MethodWebDavMkColCount, *vecptr++ = AccountingPtr->MethodWebDavMoveCount, *vecptr++ = AccountingPtr->MethodWebDav_OptionsCount, *vecptr++ = AccountingPtr->MethodWebDavPropFindCount, *vecptr++ = AccountingPtr->MethodWebDavPropPatchCount, *vecptr++ = AccountingPtr->MethodWebDav_PutCount, *vecptr++ = AccountingPtr->MethodWebDavUnLockCount, MethodCount = AccountingPtr->MethodWebDavCopyCount + AccountingPtr->MethodWebDav_DeleteCount + AccountingPtr->MethodWebDav_GetCount + AccountingPtr->MethodWebDavLockCount + AccountingPtr->MethodWebDavMkColCount + AccountingPtr->MethodWebDavMoveCount + AccountingPtr->MethodWebDav_OptionsCount + AccountingPtr->MethodWebDavPropFindCount + AccountingPtr->MethodWebDavPropPatchCount + AccountingPtr->MethodWebDav_PutCount + AccountingPtr->MethodWebDavUnLockCount; *vecptr++ = MethodCount, *vecptr++ = PercentOf (MethodCount, AccountingPtr->ProcessingTotalCount[HTTP12]); *vecptr++ = &AccountingPtr->WebDavBytesRawRx; *vecptr++ = &AccountingPtr->WebDavBytesRawTx; *vecptr++ = &BytesRxTx; *vecptr++ = QuadPercentOf (&BytesRxTx, &BytesTotal); InstanceMutexUnLock (INSTANCE_MUTEX_HTTPD); rqptr->rqResponse.PreExpired = PRE_EXPIRE_ADMIN; ResponseHeader200 (rqptr, "text/html", NULL); FaoCheck (sizeof(FaoVector), &FaoVector, vecptr, FI_LI); status = FaolToNet (rqptr, ReportPageFao, &FaoVector); if (VMSnok (status)) ErrorNoticed (NULL, status, NULL, FI_LI); /*******************/ /* WebDAV requests */ /*******************/ DisplayCount = 0; sys$gettim (&BinaryTime); /* process the request list from least to most recent */ for (leptr = RequestList.HeadPtr; leptr; leptr = leptr->NextPtr) { rqeptr = (REQUEST_STRUCT*)leptr; if (!rqeptr->WebDavTaskPtr) continue; if (!rqeptr->WebDavTaskPtr->MetaData.DlmData.LockSb.lksb$l_lkid && !rqeptr->WebDavTaskPtr->DlmSource.LockSb.lksb$l_lkid && !rqeptr->WebDavTaskPtr->DlmDestin.LockSb.lksb$l_lkid) continue; vecptr = FaoVector; *vecptr++ = ++DisplayCount; *vecptr++ = rqeptr->ServicePtr->RequestSchemeNamePtr; *vecptr++ = rqeptr->ServicePtr->ServerHostPort; *vecptr++ = rqeptr->rqHeader.MethodName; *vecptr++ = rqeptr->rqHeader.RequestUriPtr; if (rqeptr->WebDavTaskPtr->DlmSource.LockSb.lksb$l_lkid) { *vecptr++ = "!AZ"; *vecptr++ = rqeptr->WebDavTaskPtr->DlmSource.ResourceName; *vecptr++ = rqeptr->WebDavTaskPtr->DlmSource.ExMode ? "EX" : "CR"; } else { *vecptr++ = "none"; *vecptr++ = ""; } *vecptr++ = UserAtClient (rqeptr); *vecptr++ = &rqeptr->rqTime.BeginTime64; status = lib$sub_times (&BinaryTime, &rqeptr->rqTime.BeginTime64, &ResponseDuration); if (VMSnok (status)) PUT_ZERO_QUAD (ResponseDuration); *vecptr++ = DurationString (rqptr, &ResponseDuration); if (rqeptr->WebDavTaskPtr->DlmDestin.LockSb.lksb$l_lkid) { *vecptr++ = "!AZ"; *vecptr++ = rqeptr->WebDavTaskPtr->DlmDestin.ResourceName; *vecptr++ = rqeptr->WebDavTaskPtr->DlmDestin.ExMode ? "EX" : "CR"; } else { *vecptr++ = "none"; *vecptr++ = ""; } if (rqeptr->WebDavTaskPtr->MetaData.DlmData.LockSb.lksb$l_lkid) { *vecptr++ = "!AZ  "; *vecptr++ = rqeptr->WebDavTaskPtr->MetaData.ReadMetaName; *vecptr++ = "!AZ"; *vecptr++ = rqeptr->WebDavTaskPtr->MetaData.DlmData.ResourceName; *vecptr++ = rqeptr->WebDavTaskPtr->MetaData.DlmData.ExMode ? "EX" : "CR"; } else { *vecptr++ = ""; *vecptr++ = "none"; *vecptr++ = ""; } if (rqeptr->WebDavTaskPtr->MetaData.DlmDest.LockSb.lksb$l_lkid) { *vecptr++ = "!AZ  "; *vecptr++ = rqeptr->WebDavTaskPtr->MetaData.WriteMetaName; *vecptr++ = "!AZ"; *vecptr++ = rqeptr->WebDavTaskPtr->MetaData.DlmDest.ResourceName; *vecptr++ = rqeptr->WebDavTaskPtr->MetaData.DlmDest.ExMode ? "EX" : "CR"; } else { *vecptr++ = ""; *vecptr++ = "none"; *vecptr++ = ""; } FaoCheck (sizeof(FaoVector), &FaoVector, vecptr, FI_LI); status = FaolToNet (rqptr, ReportRequestFao, &FaoVector); if (VMSnok (status)) ErrorNoticed (NULL, status, NULL, FI_LI); } if (!DisplayCount) FaolToNet (rqptr, NoneFao, NULL); vecptr = FaoVector; *vecptr++ = AdminRefresh(); status = FaolToNet (rqptr, ReportPageEndFao, FaoVector); if (VMSnok (status)) ErrorNoticed (NULL, status, NULL, FI_LI); AdminEnd (rqptr); } /*****************************************************************************/