/*****************************************************************************/ /* DAVprop.c Provide DAV properties of collections (directories) and resources (files). These include 'live' properties (those stored as part of the file-system, e.g. CDT, RDT) and 'dead' properties (those provided by clients and stored as XML in the meta-data). Apparently is deprecated by RFC 4981. VERSION HISTORY --------------- 25-SEP-2020 MGD bugfix; DavPropBegin() WATCH report no reverse mapping 12-SEP-2018 MGD bugfix; significant refactor of locking 06-SEP-2018 MGD bugfix; DavPropLive() metadata file also needs to be read for ->LockDiscovery as well as ->AllProp and ->FindProp 15-APR-2018 MGD SS$_ABORT on ->RequestRundown bugfix; DavPropDead() flush the network buffer periodically (for rediculously but legally sized file it might not quickly enough to prevent no-progress timeout) bugfix; DavPropSearchAst() resultant name already terminated 22-JUN-2016 MGD DavPropSearchAst() ignore ambiguous file names containing an escaped ("^.") period but no type 27-SEP-2014 MGD improve "no reverse mapping" detection and reporting bugfix; DavPropEnd() ensure unused meta-data file deleted 16-JUN-2014 MGD DavPropSearchAst() meta file subdirectory detect and suppress 06-JUN-2013 MGD bugfix; DavPropSearchAst() on non-ODS_EXTENDED platforms (i.e. VAX) reset .nam$b_rsl to changed resultant length or it can generate RMS$_RSL errors - check it out! 05-SEP-2009 MGD refinements 24-JUN-2009 MGD DavPropQuota() RFC 4331 quota properties bugfix; DavPropName() XML 19-APR-2007 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 /* standard C header files */ #include #include #include #include /* VMS related header files */ #include #include #include #include #include #include #include #include #include #include #include #include /* application related header file */ #include "wasd.h" #include "davweb.h" #define WASD_MODULE "DAVPROP" #define FCH$M_DIRECTORY 0x2000 #define TEST_PROP_QUOTA 0 #define _TEST_PROP_QUOTA_USERNAME "DANIEL" /********************/ /* external storage */ /********************/ extern BOOL WebDavQuotaEnabled; extern char ErrorSanityCheck[]; extern int SysPrvMask[], ToLowerCase[], ToUpperCase[]; extern int EfnWait, EfnNoWait, HttpdTickSecond; extern ACCOUNTING_STRUCT *AccountingPtr; extern CONFIG_STRUCT Config; extern HTTPD_PROCESS HttpdProcess; extern WATCH_STRUCT Watch; /*****************************************************************************/ /* This function should check for a mapping-generated error message to determine is there were any problems. */ DavPropBegin (REQUEST_STRUCT *rqptr) { int status, Length; char *sptr; char Scratch [ODS_MAX_FILE_NAME_LENGTH+1]; WEBDAV_TASK *tkptr; /*********/ /* begin */ /*********/ if (WATCHMOD (rqptr, WATCH_MOD_WEBDAV)) WatchThis (WATCHITM(rqptr), WATCH_MOD_WEBDAV, "DavPropBegin() depth:!UL !&Z", rqptr->WebDavTaskPtr->ToDepth, rqptr->ParseOds.ExpFileName); tkptr = rqptr->WebDavTaskPtr; if (WATCHING (rqptr, WATCH_RESPONSE)) WatchThis (WATCHITM(rqptr), WATCH_RESPONSE, "PROPFIND!AZ !AZ depth:!UL path:!AZ", rqptr->rqPathSet.WebDavNoProp ? " (path set NOprop)" : "", rqptr->ParseOds.ExpFileName, tkptr->ToDepth, DavWebPathAccess(rqptr)); /* always flush before no-progress timeout (at 75%) */ tkptr->PropFindFlushAfter = Config.cfTimeout.NoProgress * 75 / 100; tkptr->PropFindFlushSecond = HttpdTickSecond + tkptr->PropFindFlushAfter; /* ensure a reverse mapping exists for the file specification */ Scratch[0] = '\0'; sptr = MapUrl_Map (Scratch, sizeof(Scratch), rqptr->ParseOds.NamDevicePtr, 0, NULL, 0, NULL, 0, NULL, 0, NULL, rqptr, NULL); if (!sptr[0] && sptr[1] || (MATCH8(sptr,MAPURL_NO_REVERSE_PATH) && strsame(sptr,MAPURL_NO_REVERSE_PATH,-1))) { if (WATCHING (rqptr, WATCH_WEBDAV)) { if (!sptr[0] && sptr[1]) WatchThis (WATCHITM(rqptr), WATCH_WEBDAV, "REVERSE MAPPING !AZ error !AZ", rqptr->ParseOds.NamDevicePtr, sptr+1); else if (WATCHING (rqptr, WATCH_WEBDAV)) WatchThis (WATCHITM(rqptr), WATCH_WEBDAV, "NO REVERSE MAPPING from !AZ to URI", rqptr->ParseOds.NamDevicePtr); } rqptr->rqResponse.HttpStatus = 500; ErrorVmsStatus (rqptr, SS$_ABORT, FI_LI); DavPropEnd (rqptr); return; } /* default to reporting all properties */ if (!rqptr->rqHeader.ContentLength64) tkptr->PropData.AllProp = true; /* take out a concurrent read lock on the resource */ DavWebDlmEnqueue (rqptr, &tkptr->DlmSource, rqptr->ParseOds.ExpFileName, NULL, false, false, DavPropBegin2, rqptr); } /*****************************************************************************/ /* Entry point for AST from VMS DLM lock. */ DavPropBegin2 (REQUEST_STRUCT *rqptr) { int status; WEBDAV_DLM *dlmptr; WEBDAV_TASK *tkptr; /*********/ /* begin */ /*********/ tkptr = rqptr->WebDavTaskPtr; dlmptr = &tkptr->DlmSource; if (WATCHMOD (rqptr, WATCH_MOD_WEBDAV)) WatchThis (WATCHITM(rqptr), WATCH_MOD_WEBDAV, "DavPropBegin2() !&S", dlmptr->LockSb.lksb$w_status); if (VMSnok (dlmptr->LockSb.lksb$w_status)) { DavPropEnd (rqptr); return; } /* network writes are checked for success, fudge the first one! */ rqptr->NetIoPtr->WriteStatus = SS$_NORMAL; if (rqptr->ParseOds.NamNameLength > 0 || rqptr->ParseOds.NamTypeLength > 1) { /*******************/ /* single resource */ /*******************/ /* any other depth is meaningless */ tkptr->ToDepth = WEBDAV_DEPTH_ZERO; status = DavPropParse (rqptr); if (VMSnok(status)) { /* HTTP status will be set from VMS status */ ErrorVmsStatus (rqptr, status, FI_LI); DavPropEnd (rqptr); return; } if (tkptr->PropData.PropName) DavPropName (rqptr); else DavPropLiveAcp (rqptr); return; } /**************/ /* collection */ /**************/ if (tkptr->ToDepth == WEBDAV_DEPTH_ZERO) { /* collection itself */ DavPropParent (rqptr); return; } if (tkptr->ToDepth == WEBDAV_DEPTH_ONE) { /* collection and children, provide the parent */ DavPropParent (rqptr); return; } if (tkptr->ToDepth == WEBDAV_DEPTH_INFINITY) { /* potentially too expensive! */ DavWebResponse (rqptr, 501, 0, "Infinite depth too expensive", FI_LI); DavPropEnd (rqptr); return; } ErrorExitVmsStatus (SS$_BUGCHECK, ErrorSanityCheck, FI_LI); } /*****************************************************************************/ /* As necessary, close the multistatus XML. */ DavPropEnd (REQUEST_STRUCT *rqptr) { int status; WEBDAV_META *mtaptr; WEBDAV_TASK *tkptr; /*********/ /* begin */ /*********/ if (WATCHMOD (rqptr, WATCH_MOD_WEBDAV)) WatchThis (WATCHITM(rqptr), WATCH_MOD_WEBDAV, "DavPropEnd() !&F", &DavPropEnd); tkptr = rqptr->WebDavTaskPtr; mtaptr = &tkptr->MetaData; if (mtaptr->ReadOds.Fab.fab$l_sts == RMS$_CREATED && mtaptr->ReadOds.DeleteOnClose) { /* meta-data file created and never updated */ status = OdsClose (&mtaptr->ReadOds, NULL, NULL); if (VMSnok (status)) ErrorNoticed (rqptr, status, mtaptr->ReadMetaName, FI_LI); } /* if an 'empty' multistatus response was generated */ if (!rqptr->rqResponse.HttpStatus) { rqptr->rqResponse.NoGzip = true; DavWebResponse207 (rqptr); } if (rqptr->rqResponse.HttpStatus == 207) FaoToNet (rqptr, "\n"); DavWebEnd (rqptr); } /*****************************************************************************/ /* Get the file name of the directory containing this collection and begin to process to get it's WebDAV properties. */ DavPropParent (REQUEST_STRUCT *rqptr) { int status, DirectoryFileLength; char DirectoryFile [ODS_MAX_FILE_NAME_LENGTH+1]; WEBDAV_TASK *tkptr; /*********/ /* begin */ /*********/ if (WATCHMOD (rqptr, WATCH_MOD_WEBDAV)) WatchThis (WATCHITM(rqptr), WATCH_MOD_WEBDAV, "DavPropParent() !AZ", rqptr->ParseOds.NamDevicePtr); tkptr = rqptr->WebDavTaskPtr; /* get name of directory in parent */ OdsNameOfDirectoryFile (rqptr->ParseOds.NamDevicePtr, rqptr->ParseOds.NamNamePtr - rqptr->ParseOds.NamDevicePtr, DirectoryFile, &DirectoryFileLength); AuthAccessEnable (rqptr, rqptr->ParseOds.ExpFileName, AUTH_ACCESS_READ); if (VMSok (status = OdsFileExists (NULL, DirectoryFile))) OdsParse (&tkptr->SearchOds, DirectoryFile, DirectoryFileLength, NULL, 0, NULL, NULL, rqptr); AuthAccessEnable (rqptr, 0, 0); if (VMSnok (status)) { DavWebResponse (rqptr, 404, status, "collection does not exist", FI_LI); DavPropEnd (rqptr); return; } if (VMSok (status = tkptr->SearchOds.Fab.fab$l_sts)) status = OdsParseTerminate (&tkptr->SearchOds); if (VMSnok(status)) { /* HTTP status will be set from VMS status */ ErrorNoticed (rqptr, status, NULL, FI_LI); ErrorVmsStatus (rqptr, status, FI_LI); DavPropEnd (rqptr); return; } tkptr->PropParent = true; AuthAccessEnable (rqptr, 0, AUTH_ACCESS_SYSPRV); OdsSearch (&tkptr->SearchOds, &DavPropSearchAst, rqptr); AuthAccessEnable (rqptr, 0, 0); } /*****************************************************************************/ /* Parse the search structure. */ int DavPropParse (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, "DavPropParse()"); tkptr = rqptr->WebDavTaskPtr; cptr = rqptr->ParseOds.ExpFileName; zptr = (sptr = tkptr->SearchSpec) + sizeof(tkptr->SearchSpec); while (*cptr && sptr < zptr) *sptr++ = *cptr++; if (!(rqptr->ParseOds.NamNameLength > 0 || rqptr->ParseOds.NamTypeLength > 1)) { /* no name or type was supplied with the request, wildcard it */ for (cptr = "*.*;0"; *cptr && sptr < zptr; *sptr++ = *cptr++); } if (sptr >= zptr) { ErrorNoticed (rqptr, SS$_RESULTOVF, NULL, FI_LI); return (SS$_RESULTOVF); } *sptr = '\0'; tkptr->SearchSpecLength = sptr - tkptr->SearchSpec; AuthAccessEnable (rqptr, rqptr->ParseOds.ExpFileName, AUTH_ACCESS_READ); OdsParse (&tkptr->SearchOds, tkptr->SearchSpec, tkptr->SearchSpecLength, NULL, 0, NULL, NULL, rqptr); AuthAccessEnable (rqptr, 0, 0); if (VMSok (status = tkptr->SearchOds.Fab.fab$l_sts)) status = OdsParseTerminate (&rqptr->ParseOds); return (status); } /*****************************************************************************/ /* AST function to invoke another $SEARCH call. */ DavPropSearch (REQUEST_STRUCT *rqptr) { int status; WEBDAV_TASK *tkptr; /*********/ /* begin */ /*********/ if (WATCHMOD (rqptr, WATCH_MOD_WEBDAV)) WatchThis (WATCHITM(rqptr), WATCH_MOD_WEBDAV, "DavPropSearch() !&F !&S", &DavPropSearch, rqptr->NetIoPtr->WriteStatus); if (VMSnok (rqptr->NetIoPtr->WriteStatus)) { /* network write has failed (as AST), bail out now */ DavPropEnd (rqptr); return; } tkptr = rqptr->WebDavTaskPtr; if (tkptr->PropParent) { /*******************/ /* finished parent */ /*******************/ tkptr->PropParent = false; OdsParseRelease (&tkptr->SearchOds); if (tkptr->ToDepth == WEBDAV_DEPTH_ZERO) { DavPropEnd (rqptr); return; } /******************/ /* begin children */ /******************/ status = DavPropParse (rqptr); if (VMSnok(status)) { /* HTTP status will be set from VMS status */ ErrorVmsStatus (rqptr, status, FI_LI); DavPropEnd (rqptr); return; } } AuthAccessEnable (rqptr, 0, AUTH_ACCESS_SYSPRV); OdsSearch (&tkptr->SearchOds, &DavPropSearchAst, rqptr); AuthAccessEnable (rqptr, 0, 0); } /*****************************************************************************/ /* AST completion routine called each time sys$search() completes. It will either point to another file name found or have "no more files found" status (or an error!). */ DavPropSearchAst (REQUEST_STRUCT *rqptr) { int status; char *cptr, *sptr; WEBDAV_TASK *tkptr; /*********/ /* begin */ /*********/ #if WATCH_MOD HttpdCheckPriv (FI_LI); #endif /* WATCH_MOD */ if (WATCHMOD (rqptr, WATCH_MOD_WEBDAV)) WatchThis (WATCHITM(rqptr), WATCH_MOD_WEBDAV, "DavPropSearchAst() !&F sts:!&S stv:!&S", &DavPropSearchAst, rqptr->WebDavTaskPtr->SearchOds.Fab.fab$l_sts, rqptr->WebDavTaskPtr->SearchOds.Fab.fab$l_stv); if (rqptr->RequestState >= REQUEST_STATE_ABORT) { DavPropEnd (rqptr); return; } tkptr = rqptr->WebDavTaskPtr; if (VMSnok (status = tkptr->SearchOds.Fab.fab$l_sts)) { /* if its a search list treat directory not found as if file not found */ if ((tkptr->SearchOds.Nam_fnb & NAM$M_SEARCH_LIST) && status == RMS$_DNF) status = RMS$_FNF; if (status == RMS$_FNF || status == RMS$_NMF) { /***************************/ /* end of directory search */ /***************************/ tkptr->SearchOds.ParseInUse = false; DavPropEnd (rqptr); return; } /*************************/ /* protection violation? */ /*************************/ if (status == SS$_NOPRIV || status == RMS$_PRV) { /* continue the search */ DavPropSearch (rqptr); return; } /**********************/ /* sys$search() error */ /**********************/ /* assume this can only happen on the first call */ ErrorVmsStatus (rqptr, status, FI_LI); DavPropEnd (rqptr); return; } /*****************/ /* search result */ /*****************/ #ifndef ODS_EXTENDED /* 06-JUN-2013 MGD VAX VMS V7.3 seems to require this adjusted or it generates RMS$_RSL errors (!?) */ if (tkptr->SearchOds.NamTypeLength > 1) tkptr->SearchOds.Nam.nam$b_rsl = tkptr->SearchOds.NamVersionPtr - tkptr->SearchOds.NamDevicePtr; else tkptr->SearchOds.Nam.nam$b_rsl = tkptr->SearchOds.NamTypePtr - tkptr->SearchOds.NamDevicePtr; #endif if (WATCHMOD (rqptr, WATCH_MOD_WEBDAV)) WatchThis (WATCHITM(rqptr), WATCH_MOD_WEBDAV, "!&Z", tkptr->SearchOds.NamDevicePtr); if (MATCH7(tkptr->SearchOds.NamNamePtr,"000000.") && strsame (tkptr->SearchOds.NamNamePtr, "000000.DIR;", 11)) { status = OdsReallyADir (rqptr, &tkptr->SearchOds); if (VMSok (status)) { /* no need to report the MFD */ DavPropSearch (rqptr); return; } } /* if a meta file directory it is not accessible */ if (DavMetaDir (rqptr, &tkptr->SearchOds)) { DavPropSearch (rqptr); return; } if (DavMetaFile (&tkptr->SearchOds)) { DavPropSearch (rqptr); return; } /* in true Unix-style "hidden" files do not appear (those beginning ".") */ if (SAME1(tkptr->SearchOds.NamNamePtr,'.') || SAME2(tkptr->SearchOds.NamNamePtr,'^.')) { /* except in demo mode or to VMS authenticated and profiled requests */ if (rqptr->rqPathSet.WebDavNoHidden) { if (WATCHMOD (rqptr, WATCH_MOD_WEBDAV)) WatchThis (WATCHITM(rqptr), WATCH_MOD_WEBDAV, ".FILE (hidden)"); DavPropSearch (rqptr); return; } } if (tkptr->SearchOds.NamTypeLength <= 1) { /* no explicit type - look to see if name contains escaped period */ for (cptr = tkptr->SearchOds.NamNamePtr; cptr < tkptr->SearchOds.NamTypePtr; cptr++) if (*(USHORTPTR)cptr == '^.') break; if (cptr < tkptr->SearchOds.NamTypePtr) { /* escaped period - do not list these ambiguous file names */ if (WATCHING (rqptr, WATCH_WEBDAV)) WatchThis (WATCHITM(rqptr), WATCH_WEBDAV, "^.NAME.; !AZ", tkptr->SearchOds.NamNamePtr); DavPropSearch (rqptr); return; } } /**************/ /* properties */ /**************/ if (tkptr->PropData.PropName) DavPropName (rqptr); else DavPropLiveAcp (rqptr); } /*****************************************************************************/ /* Provide the property (all property names without values). */ DavPropName (REQUEST_STRUCT *rqptr) { WEBDAV_TASK *tkptr; /*********/ /* begin */ /*********/ if (WATCHMOD (rqptr, WATCH_MOD_WEBDAV)) WatchThis (WATCHITM(rqptr), WATCH_MOD_WEBDAV, "DavPropName()"); tkptr = rqptr->WebDavTaskPtr; DavWebHref (rqptr, tkptr->SearchOds.NamDevicePtr, 0); if (!rqptr->rqResponse.HttpStatus) DavWebResponse207 (rqptr); FaoToNet (rqptr, " \n\ !AZ!&%AZ\n\ \n\ HTTP/1.1 200 OK\n\ \n\ \n\ \n\ \n\ \n\ \n\ \n\ \n\ \n\ \n", tkptr->HrefHost, tkptr->PropParent ? rqptr->rqHeader.PathInfoPtr : tkptr->HrefPath); if (tkptr->LockingEnabled) FaoToNet (rqptr, " \n\ \n"); if (tkptr->QuotaEnabled) FaoToNet (rqptr, " \n\ \n"); FaoToNet (rqptr, " \n\ \n\ \n"); DavPropEnd (rqptr); } /*****************************************************************************/ /* Using the ACP-QIO interface obtain the live file properties (size, created date/time, last modified, etc.) */ DavPropLiveAcp (REQUEST_STRUCT *rqptr) { int status; char *cptr; WEBDAV_TASK *tkptr; /*********/ /* begin */ /*********/ if (WATCHMOD (rqptr, WATCH_MOD_WEBDAV)) WatchThis (WATCHITM(rqptr), WATCH_MOD_WEBDAV, "DavPropLiveAcp() !AZ", rqptr->WebDavTaskPtr->SearchOds.NamDevicePtr); tkptr = rqptr->WebDavTaskPtr; AuthAccessEnable (rqptr, tkptr->SearchOds.NamDevicePtr, AUTH_ACCESS_READ); OdsFileAcpInfo (&tkptr->SearchOds, &DavPropLive, rqptr); AuthAccessEnable (rqptr, 0, 0); } /****************************************************************************/ /* AST called from OdsFileAcpInfo() (invoked by DavPropLiveAcp()) when ACP QIO completes. This function supplies the 'live' properties associated with a file (or directory file). */ DavPropLive (REQUEST_STRUCT *rqptr) { int status, Bytes, NameLength; unsigned long AllocatedVbn, EndOfFileVbn; char *cptr, *sptr, *zptr; char DateTime [32], EntityTag [32], FileSpec [ODS_MAX_FILE_NAME_LENGTH+1]; WEBDAV_PROP *proptr; WEBDAV_TASK *tkptr; /*********/ /* begin */ /*********/ #if WATCH_MOD HttpdCheckPriv (FI_LI); #endif /* WATCH_MOD */ if (WATCHMOD (rqptr, WATCH_MOD_WEBDAV)) WatchThis (WATCHITM(rqptr), WATCH_MOD_WEBDAV, "DavPropLive() !&F !&S", &DavPropLive, rqptr->WebDavTaskPtr->SearchOds.FileQio.IOsb.Status); tkptr = rqptr->WebDavTaskPtr; proptr = &tkptr->PropData; /* first deassign the channel allocated by OdsFileAcpInfo() */ sys$dassgn (tkptr->SearchOds.FileQio.AcpChannel); if ((status = tkptr->SearchOds.FileQio.IOsb.Status) == SS$_NOSUCHFILE) status = RMS$_FNF; if (VMSnok (status)) { /****************/ /* error status */ /****************/ if (tkptr->ToDepth == WEBDAV_DEPTH_ZERO) { /* just the single resource */ ErrorVmsStatus (rqptr, status, FI_LI); DavPropEnd (rqptr); return; } /*************************/ /* protection violation? */ /*************************/ if (status == SS$_NOPRIV || status == RMS$_PRV) { DavPropSearch (rqptr); return; } /*******************/ /* something else! */ /*******************/ DavWebMultiStatus (rqptr, status, tkptr->SearchOds.ExpFileName); DavPropEnd (rqptr); return; } if (!rqptr->rqResponse.HttpStatus) DavWebResponse207 (rqptr); /**********/ /* report */ /**********/ /* same status as OdsReallyADir() */ if (tkptr->SearchOds.FileQio.AtrUchar & FCH$M_DIRECTORY) { tkptr->SearchIsDirectory = true; status = SS$_NORMAL; } else { tkptr->SearchIsDirectory = false; status = SS$_ABORT; } DavWebHref (rqptr, tkptr->SearchOds.NamDevicePtr, status); FaoToNet (rqptr, " \n\ !AZ!&%AZ\n\ \n\ HTTP/1.1 200 OK\n\ \n", tkptr->HrefHost, tkptr->PropParent ? rqptr->rqHeader.PathInfoPtr : tkptr->HrefPath); if (proptr->AllProp || proptr->DisplayName) { /********/ /* name */ /********/ if (tkptr->PropParent) zptr = cptr = ""; else { /* resolve the name from the href path */ for (cptr = sptr = tkptr->HrefPath; *cptr; cptr++); zptr = cptr; while (cptr > sptr && *cptr != '/') cptr--; if (tkptr->SearchIsDirectory) { if (cptr > sptr && *cptr == '/') { cptr--; zptr--; } while (cptr > sptr && *cptr != '/') cptr--; } if (*cptr == '/') cptr++; } FaoToNet (rqptr, " !#&;AZ\n", zptr-cptr, cptr); } if (proptr->AllProp || proptr->ResourceType) { /******************/ /* file/directory */ /******************/ if (tkptr->SearchIsDirectory) FaoToNet (rqptr, " \n"); else FaoToNet (rqptr, " \n"); } if (proptr->AllProp || proptr->CreationDate) { /***********/ /* created */ /***********/ /* this property format is "1995-0825T17:32:40Z" */ DavWebDateTimeTo3339 (DateTime, &tkptr->SearchOds.FileQio.CdtTime64); FaoToNet (rqptr, " !AZ\n", DateTime); } if (proptr->AllProp || proptr->GetContentLanguage) /* WASD dosn't handle this datum */ FaoToNet (rqptr, " \n"); if (proptr->AllProp || proptr->GetContentLength) { /******************/ /* content length */ /******************/ if (!tkptr->SearchIsDirectory) { AllocatedVbn = ((tkptr->SearchOds.FileQio.RecAttr.fat$l_hiblk & 0xffff) << 16) | ((tkptr->SearchOds.FileQio.RecAttr.fat$l_hiblk & 0xffff0000) >> 16); EndOfFileVbn = ((tkptr->SearchOds.FileQio.RecAttr.fat$l_efblk & 0xffff) << 16) | ((tkptr->SearchOds.FileQio.RecAttr.fat$l_efblk & 0xffff0000) >> 16); if (EndOfFileVbn <= 1) Bytes = tkptr->SearchOds.FileQio.RecAttr.fat$w_ffbyte; else Bytes = ((EndOfFileVbn-1) << 9) + tkptr->SearchOds.FileQio.RecAttr.fat$w_ffbyte; if (WATCHMOD (rqptr, WATCH_MOD_WEBDAV)) WatchThis (WATCHITM(rqptr), WATCH_MOD_WEBDAV, "AllocatedVbn:!UL EndOfFileVbn:!UL FirstFreeByte:!UL Bytes;!UL", AllocatedVbn, EndOfFileVbn, tkptr->SearchOds.FileQio.RecAttr.fat$w_ffbyte, Bytes); FaoToNet (rqptr, " !UL\n", Bytes); } } if (proptr->AllProp || proptr->GetContentType) { /****************/ /* content type */ /****************/ if (tkptr->SearchIsDirectory) cptr = "httpd/unix-directory"; else { if (rqptr->PathOds == MAPURL_PATH_ODS_ADS || rqptr->PathOds == MAPURL_PATH_ODS_SMB) cptr = MapOdsAdsFileType (tkptr->SearchOds.NamTypePtr); else if (rqptr->PathOds == MAPURL_PATH_ODS_SRI) cptr = MapOdsSriFileType (tkptr->SearchOds.NamTypePtr); else cptr = tkptr->SearchOds.NamTypePtr; ConfigContentType (&tkptr->ContentInfo, cptr); cptr = tkptr->ContentInfo.ContentTypePtr; /* transmogrify WASD internal types */ if (SAME2(cptr,'x-') || SAME2(cptr,'X-')) cptr = "application/octet-stream"; } FaoToNet (rqptr, " !AZ\n", cptr); } if (proptr->AllProp || proptr->GetEtag) { /********/ /* etag */ /********/ FileGenerateEntityTag (EntityTag, &tkptr->SearchOds.FileQio); FaoToNet (rqptr, " \"!AZ\"\n", EntityTag); } if (proptr->AllProp || proptr->GetLastModified) { /*****************/ /* last modified */ /*****************/ /* this property format is "Fri, 25 Aug 1995 17:32:40 GMT" */ HttpGmTimeString (DateTime, &tkptr->SearchOds.FileQio.RdtTime64); FaoToNet (rqptr, " !AZ\n", DateTime); } #if TEST_PROP_QUOTA WebDavQuotaEnabled = proptr->QuotaUsedBytes = proptr->QuotaAvailableBytes = true; #endif if (WebDavQuotaEnabled) if (proptr->QuotaUsedBytes || proptr->QuotaAvailableBytes) DavPropQuota (rqptr); if (tkptr->LockingEnabled && (proptr->AllProp || proptr->LockDiscovery)) { /*********************/ /* locking available */ /*********************/ FaoToNet (rqptr, " \n\ \n\ \n\ \n\ \n\ \n\ \n\ \n\ \n\ \n"); } if (tkptr->MicrosoftAgent && rqptr->rqPathSet.WebDavNoWinProp && (proptr->AllProp || proptr->FindProp)) { /******************************/ /* fudge Microsoft properties */ /******************************/ /* these property formats are "Fri, 25 Aug 1995 17:32:40 GMT" */ HttpGmTimeString (DateTime, &tkptr->SearchOds.FileQio.CdtTime64); FaoToNet (rqptr, " \ !AZ\n", DateTime); HttpGmTimeString (DateTime, &tkptr->SearchOds.FileQio.RdtTime64); FaoToNet (rqptr, " \ !AZ\n", DateTime); /* NOT the last access time but what the hey it's a fudge! */ FaoToNet (rqptr, " \ !AZ\n", DateTime); /* apparently it is good practice to always set the archive bit */ FaoToNet (rqptr, " \ 00000020\n"); } tkptr->MetaData.VmsStatus = 0; if (rqptr->rqPathSet.WebDavNoProp) DavPropDead (rqptr); else if (proptr->AllProp || proptr->FindProp || proptr->LockDiscovery) { /*************/ /* meta-data */ /*************/ if (tkptr->ToDepth == WEBDAV_DEPTH_ZERO) { /* The collection itself. Do NOT use DavPropParent() non-concealed specification to access the meta-data. Use the original request specification. */ DavMetaRead (rqptr, &tkptr->MetaData, rqptr->ParseOds.NamDevicePtr, &DavPropDead, rqptr); return; } zptr = (sptr = FileSpec) + sizeof(FileSpec)-1; cptr = tkptr->SearchOds.NamDevicePtr; if (tkptr->SearchOds.FileQio.AtrUchar & FCH$M_DIRECTORY) { /* munge the ]NAME.DIR into a directory specification .NAME] */ while (*cptr && cptr < tkptr->SearchOds.NamNamePtr-1 && sptr < zptr) *sptr++ = *cptr++; if (sptr < zptr) *sptr++ = '.'; if (*cptr) cptr++; while (*cptr && cptr < tkptr->SearchOds.NamTypePtr && sptr < zptr) *sptr++ = *cptr++; if (sptr < zptr) *sptr++ = ']'; } else { /* just a POF */ while (*cptr && cptr < tkptr->SearchOds.NamVersionPtr && sptr < zptr) *sptr++ = *cptr++; } *sptr = '\0'; DavMetaRead (rqptr, &tkptr->MetaData, FileSpec, &DavPropDead, rqptr); } else DavPropDead (rqptr); } /*****************************************************************************/ /* AST called from DavMetaRead() (invoked by DavPropLive()), or called directly from DavPropLive(). This function supplies the 'dead' properties assocated with a file (or directory). These properties are stored in the meta-data and accessed as required. */ DavPropDead (REQUEST_STRUCT *rqptr) { int status; STR_DSC *fdptr, *sdptr; WEBDAV_META *mtaptr; WEBDAV_PROP *proptr; WEBDAV_TASK *tkptr; /*********/ /* begin */ /*********/ if (WATCHMOD (rqptr, WATCH_MOD_WEBDAV)) WatchThis (WATCHITM(rqptr), WATCH_MOD_WEBDAV, "DavPropDead() !&S", rqptr->WebDavTaskPtr->MetaData.VmsStatus); tkptr = rqptr->WebDavTaskPtr; mtaptr = &tkptr->MetaData; proptr = &tkptr->PropData; if (VMSok (tkptr->MetaData.VmsStatus)) { if (tkptr->LockingEnabled && (proptr->AllProp || proptr->LockDiscovery)) { /* what locking is available */ DavLockDiscovery (rqptr); } if (proptr->AllProp) { /* list all of the 'dead' properties */ STR_DSC_ITERATE (sdptr, &tkptr->MetaData.PropDsc) if (STR_DSC_LEN(sdptr)) FaoToNet (rqptr, " !AZ\n", STR_DSC_PTR(sdptr)); } else if (proptr->FindProp) { /* search for requested properties and report any found */ STR_DSC_ITERATE (sdptr, &tkptr->XmlData.PropFindDsc) if (fdptr = DavMetaSearch (rqptr, &tkptr->MetaData.PropDsc, sdptr)) if (STR_DSC_LEN(fdptr)) FaoToNet (rqptr, " !AZ\n", STR_DSC_PTR(fdptr)); } } FaoToNet (rqptr, " \n\ \n\ \n"); if (tkptr->ToDepth == WEBDAV_DEPTH_ZERO) DavPropEnd (rqptr); else if (tkptr->PropFindFlushSecond <= HttpdTickSecond) { /* no output for this period so let's force a flush */ tkptr->PropFindFlushSecond = HttpdTickSecond + tkptr->PropFindFlushAfter; NetWriteBuffered (rqptr, DavPropSearch, NULL, 0); } else { /* reset flush timer if the buffer was at the last attempt */ if (STR_DSC_LEN(&rqptr->NetWriteBufferDsc) < tkptr->PropFindFlushLength) tkptr->PropFindFlushSecond = HttpdTickSecond + tkptr->PropFindFlushAfter; /* set the current length of the buffer */ tkptr->PropFindFlushLength = STR_DSC_LEN(&rqptr->NetWriteBufferDsc); /* flush if buffer full(ish) */ NetWriteBuffered (rqptr, DavPropSearch, NULL, -1); } } /*****************************************************************************/ /* Implement the PROPPATCH method. Modfiy the meta-data properties. */ DavPropPatchBegin (REQUEST_STRUCT *rqptr) { STR_DSC *sdptr; WEBDAV_TASK *tkptr; /*********/ /* begin */ /*********/ if (WATCHMOD (rqptr, WATCH_MOD_WEBDAV)) WatchThis (WATCHITM(rqptr), WATCH_MOD_WEBDAV, "DavPropPatchBegin() !&Z", rqptr->ParseOds.ExpFileName); if (!rqptr->RemoteUser[0]) { DavWebResponse (rqptr, 403, SS$_BUGCHECK, "RemoteUser?", FI_LI); DavWebEnd (rqptr); return; } tkptr = rqptr->WebDavTaskPtr; if (rqptr->rqPathSet.WebDavNoProp) { if (WATCHING (rqptr, WATCH_WEBDAV)) WatchThis (WATCHITM(rqptr), WATCH_WEBDAV, "PROPPATCH (path set NOprop) !AZ", rqptr->ParseOds.ExpFileName); DavPropPatchEnd (rqptr); return; } if (tkptr->MicrosoftAgent && rqptr->rqPathSet.WebDavNoWinProp) { /* step through each property in the request body */ STR_DSC_ITERATE (sdptr, &tkptr->XmlData.PropSetDsc) { if (!STR_DSC_LEN(sdptr)) continue; if (strstr(STR_DSC_PTR(sdptr),"urn:schemas-microsoft-com:")) continue; /* break on the first non-MS property */ break; } if (!sdptr) { /* no properties (unlikely) or all MS properties (almost certainly) */ if (WATCHING (rqptr, WATCH_WEBDAV)) WatchThis (WATCHITM(rqptr), WATCH_WEBDAV, "PROPPATCH (path set NOWINPROP) !AZ", rqptr->ParseOds.ExpFileName); tkptr->MetaData.VmsStatus = SS$_NORMAL; DavPropPatchEnd (rqptr); return; } /* otherwise drop thru to continue */ } tkptr->TestLockState = 0; DavPropPatchBegin2 (rqptr); } /*****************************************************************************/ /* Asynchronously test for locking then if not begin the patching. */ DavPropPatchBegin2 (REQUEST_STRUCT *rqptr) { int status; char *cptr; WEBDAV_META *mtaptr; WEBDAV_TASK *tkptr; /*********/ /* begin */ /*********/ tkptr = rqptr->WebDavTaskPtr; mtaptr = &tkptr->MetaData; if (WATCHMOD (rqptr, WATCH_MOD_WEBDAV)) WatchThis (WATCHITM(rqptr), WATCH_MOD_WEBDAV, "DavPropPatchBegin2() !&F !UL", DavPropPatchBegin2, tkptr->TestLockState); if (tkptr->SearchOds.NamDeviceLength) cptr = tkptr->SearchOds.ExpFileName; else if (rqptr->ParseOds.NamDeviceLength) cptr = rqptr->ParseOds.ExpFileName; else ErrorExitVmsStatus (SS$_BUGCHECK, ErrorSanityCheck, FI_LI); /***********/ /* locking */ /***********/ switch (tkptr->TestLockState) { case 0 : /* establish a request (overall) VMS DLM lock on source */ tkptr->TestLockState++; DavWebDlmEnqueue (rqptr, &tkptr->DlmSource, rqptr->ParseOds.ExpFileName, NULL, true, false, DavPropPatchBegin2, 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); DavPropPatchEnd (rqptr); return; } /* check for meta-lock on existing source */ tkptr->TestLockState++; DavLockTest (rqptr, rqptr->ParseOds.ExpFileName, false, DavPropPatchBegin2, rqptr); return; case 2 : if (VMSnok (tkptr->TestLockStatus)) { mtaptr->VmsStatus = tkptr->TestLockStatus; DavWebResponse (rqptr, 423, 0, "source locked", FI_LI); DavPropPatchEnd (rqptr); return; } } /**************/ /* not locked */ /**************/ if (WATCHING (rqptr, WATCH_WEBDAV)) WatchThis (WATCHITM(rqptr), WATCH_WEBDAV, "PROPPATCH !AZ", cptr); DavMetaLock (rqptr, &tkptr->MetaData, rqptr->ParseOds.ExpFileName, &DavPropPatchUpdate, &tkptr->MetaData); } /*****************************************************************************/ /* */ DavPropPatchUpdate (WEBDAV_META *mtaptr) { int status; REQUEST_STRUCT *rqptr; /*********/ /* begin */ /*********/ rqptr = mtaptr->RequestPtr; if (WATCHMOD (rqptr, WATCH_MOD_WEBDAV)) WatchThis (WATCHITM(rqptr), WATCH_MOD_WEBDAV, "DavPropPatchUpdate() !&F !&S", DavPropPatchUpdate, mtaptr->VmsStatus); if (VMSok (mtaptr->VmsStatus)) DavMetaUpdate (mtaptr, DavPropPatchEnd, rqptr); else DavPropPatchEnd (rqptr); } /*****************************************************************************/ /* Complete a PROPPATCH request. */ DavPropPatchEnd (REQUEST_STRUCT *rqptr) { int status, xmlsts; char *cptr; WEBDAV_META *mtaptr; WEBDAV_TASK *tkptr; /*********/ /* begin */ /*********/ if (WATCHMOD (rqptr, WATCH_MOD_WEBDAV)) WatchThis (WATCHITM(rqptr), WATCH_MOD_WEBDAV, "DavPropPatchEnd()"); tkptr = rqptr->WebDavTaskPtr; mtaptr = &tkptr->MetaData; if (VMSok (mtaptr->VmsStatus)) { rqptr->rqResponse.NoGzip = true; DavWebResponse207 (rqptr); DavWebHref (rqptr, rqptr->ParseOds.ExpFileName, 0); FaoToNet (rqptr, " \n\ !AZ!&%AZ\n\ \n\ \n", tkptr->HrefHost, tkptr->PropParent ? rqptr->rqHeader.PathInfoPtr : tkptr->HrefPath); } else { if (mtaptr->VmsStatus == SS$_ACCONFLICT) { /* resource is locked */ DavWebResponse (rqptr, 423, 0, NULL, FI_LI); } else { /* other reason for failure */ DavWebResponse (rqptr, 500, mtaptr->VmsStatus, NULL, FI_LI); } } DavWebEnd (rqptr); } /*****************************************************************************/ /* Provide RFC 4331 quota properties (all synchronous). If the path access is SET webdav=server attempt to return any disk quota for the HTTPd server account or if SET webdav=profile the SYSUAF authenticated request's VMS username. Both of these require a $GETUAI to obtain the UIC. If neither of these (or if the $GETUAI fails for some reason) then return the request path's device used and available space. */ DavPropQuota (REQUEST_STRUCT *rqptr) { static unsigned long AddendZero = 0, FiveTwelve = 512, UaiContext = -1; static unsigned long DviFreeBlocks, DviMaxBlock, UaiUic; static char UserName [12+1]; static int UserNameLength; static $DESCRIPTOR (UserNameDsc, UserName); static VMS_ITEM_LIST2 DeviceFibDsc, InQuotaDsc, OutQuotaDsc; static VMS_ITEM_LIST3 UaiItems [] = { { sizeof(UaiUic), UAI$_UIC, &UaiUic, 0 }, { 0,0,0,0 } }, BlocksItemList [] = { { sizeof(DviMaxBlock), DVI$_MAXBLOCK, &DviMaxBlock, 0 }, { sizeof(DviFreeBlocks), DVI$_FREEBLOCKS, &DviFreeBlocks, 0 }, { 0, 0, 0, 0 } }; int status; unsigned short DeviceChannel, OutLength; unsigned long BytesAvailable, BytesUsed; uint64 bytes64; $DESCRIPTOR (DeviceDsc, ""); struct fibdef DeviceFib; WEBDAV_PROP *proptr; WEBDAV_TASK *tkptr; struct { unsigned long flags; unsigned long uic; unsigned long used; unsigned long perm; unsigned long over; unsigned long unused[3]; } InQuota, OutQuota; struct { unsigned short iosb$w_status; unsigned short iosb$w_bcnt; unsigned long iosb$l_reserved; } IOsb; /*********/ /* begin */ /*********/ if (WATCHMOD (rqptr, WATCH_MOD_WEBDAV)) WatchThis (WATCHITM(rqptr), WATCH_MOD_WEBDAV, "DavPropQuota()"); tkptr = rqptr->WebDavTaskPtr; proptr = &tkptr->PropData; DeviceDsc.dsc$a_pointer = rqptr->ParseOds.NamDevicePtr; DeviceDsc.dsc$w_length = rqptr->ParseOds.NamDeviceLength; if (WATCHMOD (rqptr, WATCH_MOD_WEBDAV)) WatchThis (WATCHITM(rqptr), WATCH_MOD_WEBDAV, "!#AZ", DeviceDsc.dsc$w_length, DeviceDsc.dsc$a_pointer); status = sys$assign (&DeviceDsc, &DeviceChannel, 0, 0); if (VMSnok (status)) { if (WATCHMOD (rqptr, WATCH_MOD_WEBDAV)) WatchThis (WATCHITM(rqptr), WATCH_MOD_WEBDAV, "sys$assign() !&S", status); return (status); } UserNameDsc.dsc$a_pointer = NULL; if (rqptr->rqPathSet.WebDavServer) { /* try to check HTTPd server account quota */ UserNameDsc.dsc$a_pointer = HttpdProcess.UserName; UserNameDsc.dsc$w_length = HttpdProcess.UserNameLength; } else if (rqptr->rqPathSet.WebDavProfile) { /* try to check SYSUAF authenticated user account quota */ if (rqptr->rqAuth.SysUafAuthenticated && rqptr->rqAuth.VmsUserProfileLength) { UserNameDsc.dsc$a_pointer = rqptr->RemoteUser; UserNameDsc.dsc$w_length = rqptr-> RemoteUserLength; } } #if TEST_PROP_QUOTA #ifdef TEST_PROP_QUOTA_USERNAME UserNameDsc.dsc$a_pointer = TEST_PROP_QUOTA_USERNAME; UserNameDsc.dsc$w_length = strlen(TEST_PROP_QUOTA_USERNAME); #endif #endif status = 0; if (UserNameDsc.dsc$a_pointer) { /****************/ /* username UIC */ /****************/ if (WATCHMOD (rqptr, WATCH_MOD_WEBDAV)) WatchThis (WATCHITM(rqptr), WATCH_MOD_WEBDAV, "{!UL}!#AZ !&Z", UserNameDsc.dsc$w_length, UserNameDsc.dsc$w_length, UserNameDsc.dsc$a_pointer, UserName); if (UserNameLength != UserNameDsc.dsc$w_length || strncmp (UserName, UserNameDsc.dsc$a_pointer, UserNameLength)) { /* buffer the username (and consequently the UIC) */ char *cptr, *sptr, *zptr; zptr = (sptr = UserName) + sizeof(UserName)-1; for (cptr = UserNameDsc.dsc$a_pointer; cptr < UserNameDsc.dsc$a_pointer + UserNameDsc.dsc$w_length; *sptr++ = *cptr++); *sptr = '\0'; UserNameLength = sptr - UserName; UserNameDsc.dsc$a_pointer = UserName; UserNameDsc.dsc$w_length = UserNameLength; sys$setprv (1, &SysPrvMask, 0, 0); status = sys$getuai (0, &UaiContext, &UserNameDsc, &UaiItems, 0, 0, 0); sys$setprv (0, &SysPrvMask, 0, 0); if (WATCHMOD (rqptr, WATCH_MOD_WEBDAV)) WatchThis (WATCHITM(rqptr), WATCH_MOD_WEBDAV, "sys$getuai() !&S", status); } else status = SS$_NORMAL; } /* if UIC was successfully retrieved */ if (VMSok (status)) { /********************/ /* check disk quota */ /********************/ memset (&DeviceFib, 0, sizeof(DeviceFib)); DeviceFib.fib$w_exctl = FIB$C_EXA_QUOTA; DeviceFibDsc.buf_len = sizeof(DeviceFib); DeviceFibDsc.buf_addr = &DeviceFib; memset (&InQuota, 0, sizeof(InQuota)); InQuotaDsc.buf_len = sizeof(InQuota); InQuotaDsc.buf_addr = &InQuota; InQuota.uic = UaiUic; memset (&OutQuota, 0, sizeof(OutQuota)); OutQuotaDsc.buf_len = sizeof(OutQuota); OutQuotaDsc.buf_addr = &OutQuota; status = sys$qiow (0, DeviceChannel, IO$_ACPCONTROL, &IOsb, 0, 0, &DeviceFibDsc, &InQuotaDsc, &OutLength, &OutQuotaDsc, 0, 0); if (VMSok (status)) status = IOsb.iosb$w_status; if (VMSok (status)) { BytesAvailable = OutQuota.perm - OutQuota.used; BytesUsed = OutQuota.used; if (WATCHMOD (rqptr, WATCH_MOD_WEBDAV)) WatchThis (WATCHITM(rqptr), WATCH_MOD_WEBDAV, "QUOTA perm:!UL used:!UL", OutQuota.perm, OutQuota.used); } else if (WATCHMOD (rqptr, WATCH_MOD_WEBDAV)) WatchThis (WATCHITM(rqptr), WATCH_MOD_WEBDAV, "sys$qiow() !&S", status); } /* if the quota ACP failed or if quota not checked */ if (VMSnok (status)) { /********************/ /* get device usage */ /********************/ status = sys$getdviw (EfnWait, DeviceChannel, 0, &BlocksItemList, &IOsb, 0, 0, 0); if (VMSok (status)) status = IOsb.iosb$w_status; if (VMSok (status)) { BytesAvailable = DviFreeBlocks; BytesUsed = DviMaxBlock - DviFreeBlocks; if (WATCHMOD (rqptr, WATCH_MOD_WEBDAV)) WatchThis (WATCHITM(rqptr), WATCH_MOD_WEBDAV, "BLOCKS max:!UL free:!UL", DviMaxBlock, DviFreeBlocks); } else if (WATCHMOD (rqptr, WATCH_MOD_WEBDAV)) WatchThis (WATCHITM(rqptr), WATCH_MOD_WEBDAV, "sys$qiow() !&S", status); } if (VMSok (status)) { /***************/ /* list quotas */ /***************/ if (proptr->QuotaUsedBytes) { status = lib$emul (&FiveTwelve, &BytesUsed, &AddendZero, &bytes64); if (VMSok (status)) FaoToNet (rqptr, " !@SQ\n", &bytes64); else ErrorNoticed (rqptr, status, NULL, FI_LI); } if (proptr->QuotaAvailableBytes) { status = lib$emul (&FiveTwelve, &BytesAvailable, &AddendZero, &bytes64); if (VMSok (status)) FaoToNet (rqptr, " !@SQ\n", &bytes64); else ErrorNoticed (rqptr, status, NULL, FI_LI); } } sys$dassgn (DeviceChannel); return (status); } /*****************************************************************************/