/*****************************************************************************/ /* ProxyFTP.c ************* ** CAUTION ** ************* THIS MODULE IS TASK-ORIENTED, NOT REQUEST-ORIENTED. That is, most of the functions take a pointer to proxy task rather than a pointer to request as do other modules. Many of these functions deal primarily and immediately with task rather than request data. Proxied FTP access (well surprise, surprise! - in Gomer Pyle intonation) This is a moderately long and complex module providing a basic FTP proxy service capable of stand-alone use (i.e. standard browser) as well with non-browser applications such as "Windows Commander". CONTENT TYPE ------------ The content-type of a remote file is determined by the proxy FTP server using the loaded configuration content types. Why? Well, why not? FTP contains so information as to the file type or how it should be treated. The proxy server interpreting it at the local end from the file type (extension) is probably as good as anything. To provide some further control on how a file is transfered the {AddType] and [MimeTypes] directives support syntax to indicated how FTP should transfer the file. If the proxy server interpretation is incorrect then the client can explicitly specify how the file should be handled. FILE PATHS ---------- By default all file paths are relative to the login directory. That is path "/dir2/dir3/file.txt" in anonymous login to "/pub/" refers to the file "/pub/dir2/dir3/file.txt". The server would do a "CWD ./pub/dir2/dir3/" before beginning any other activity. If the request file path begins with consecutive forward-slashes then it will be considered an absolute path and for path "//pub/dir2/dir3/file.txt" it would do a "CWD /pub/dir2/dir3/" before any other activity. QUERY STRING MODIFIERS ---------------------- The following query string keywords may be used to control the behaviour of the FTP proxy. One or more may be used in appropriate combinations. Unknown strings are ignored without warning. Many of these are provided to assist in working around proxy server misinterpretation of remote resources. The client may explicitly specify how the resource will be accessed or interpreted. dos process listing as if DOS format unix process listing as if Unix format vms process listing as if VMS format text file(s) returned as "text/plain" octet file(s) returned as "application/octet-stream" ascii retrieve file as ASCII (i.e. text) image retrieve file as a bag-o'-bytes (i.e. binary) content:string content-type of file(s) (e.g. "?content:text/plain") content=string content-type of file(s) (e.g. "?content=text/plain") alt provide alternate access via listing icon email:string for anonymous FTP servers insisting on a valid email-password email=string for anonymous FTP servers insisting on a valid email-password list display in plain-text the LIST command transfer login server to get username/password via HTTP authentication raw do not munge file name/size/date upload supply a form that allows a file to be uploaded Keywords parsing is designed so that they can be supplied as "?keyword", "?keyword1+keyword2", "keyword=anything", or "keyword1=anything&keyword2=anything", or where the keyword occurs as the value of a form field, for example "?type=ascii". This allows for simply adding the keyword to the URL or use in an HTML form. USING THE "LOGIN" QUERY STRING ------------------------------ The usual mechanism for supplying the username and password for access to a non-anonymous proxied FTP server area is to place it as part of the request line (i.e. "ftp://username:password@the.host.name/path/"). This has the obvious disadvantage that it's there for all and sundry to see, in the browser URL field, in the server logs, in any other caching agent along the network. Of course all plain-text authentication suffers this fate to a greater or lesser extent. The "login" query string is provided to work around the more obvious of these issues, having the authentication credentials as part of the request URL. When this string is placed in the request query string the FTP proxy requests the browser to prompt for authentication (i.e. returns a 401 status). When request header authentication data is present it decodes the username and password and uses this as the remote FTP server username and password. Hence the remote username and password never need to appear in plain-text on screen or in server logs. NON-BROWSER PROXY BEHAVIOUR --------------------------- The ability to interact with non-bowser clients has been refined and tested against "Windows Commander v4.54". Other clients YMMV. VERSION HISTORY --------------- 27-APR-2021 MGD BSD 4.4 sockaddr.. IO$M_EXTEND to $QIO 15-AUG-2015 MGD ProxyFtpListOutput() update in line with directory listing 23-JAN-2010 MGD ProxyFtpListProcessUnix() file names containing white-space 04-MAY-2007 MGD ProxyFtpLifeCycle() process HEAD as for GET 10-JUL-2004 MGD bugfix; ProxyFtpPasvData() if PASV response address is 0.0.0.0 then use connect address 10-APR-2004 MGD modifications to support IPv6 12-JAN-2004 MGD bugfix; ProxyFtpListProcessUnix() maximum fields handling (thanks Jean-Pierre Petit, jpp@esme.fr) 08-MAR-2003 MGD set html= FTP directory listing header and footer, add column headings to FTP Index of, bugfix; ResponseHeader() content-length of zero 29-MAY-2002 MGD allow for a server disconnecting immediately upon QUIT 23-MAR-2002 MGD FTP agent specifics, starting with "MadGoat" DELE (arghh!) 18-MAR-2002 MGD rework directory list processing 26-JAN-2002 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 #include /* VMS related header files */ #include #include #include #include #include #include /* application-related header files */ #include "wasd.h" #define WASD_MODULE "PROXYFTP" /******************/ /* global storage */ /******************/ /********************/ /* external storage */ /********************/ extern int EfnWait, EfnNoWait, HttpdTickSecond, NetReadBufferSize, ProxyReadBufferSize; extern int ToLowerCase[], ToUpperCase[]; extern char ConfigContentTypeBlank[], ConfigContentTypeDir[], ConfigDefaultFileContentType[], ErrorSanityCheck[], ServerHostPort[], SoftwareID[]; extern struct dsc$descriptor TcpIpDeviceDsc; extern ACCOUNTING_STRUCT *AccountingPtr; extern CONFIG_STRUCT Config; extern MSG_STRUCT Msgs; extern PROXY_ACCOUNTING_STRUCT *ProxyAccountingPtr; extern TCP_SOCKET_ITEM TcpIpSocket4, TcpIpSocket6; extern VMS_ITEM_LIST2 TcpIpFullDuplexCloseOption; extern WATCH_STRUCT Watch; /****************************************************************************/ /* Controls the state and state transitions of a proxied FTP request. Generally it's from top to bottom but of course different paths may be taken. */ ProxyFtpLifeCycle (PROXY_TASK *tkptr) { char *cptr, *sptr, *zptr; char String [256]; REQUEST_STRUCT *rqptr; /*********/ /* begin */ /*********/ if (WATCHMOD (tkptr, WATCH_MOD_PROXY)) WatchThis (WATCHITM(tkptr), WATCH_MOD_PROXY, "ProxyFtpLifeCycle() !&F !UL !UL", &ProxyFtpLifeCycle, tkptr->FtpState, tkptr->FtpResponseCode); rqptr = tkptr->RequestPtr; switch (tkptr->FtpState) { /*********/ /* login */ /*********/ case PROXY_FTP_STATE_NONE : /* actually zero! */ InstanceGblSecIncrLong (&ProxyAccountingPtr->FtpCount); if (!ProxyFtpFilePath (tkptr)) goto QuitNow; /* determine if a directory listing or file transfer */ cptr = tkptr->FtpFilePathPtr + tkptr->FtpFilePathLength; while (cptr > tkptr->FtpFilePathPtr && *cptr != '/' && *cptr != '*') cptr--; if (*cptr == '*') { tkptr->FtpDirList = true; while (cptr > tkptr->FtpFilePathPtr && *cptr != '/') cptr--; if (*cptr == '/') cptr++; tkptr->FtpWildPtr = cptr; } else if (SAME2(cptr,'/\0')) { tkptr->FtpDirList = true; tkptr->FtpWildPtr = ""; } else tkptr->FtpDirList = false; /* read the FTP server's announcement */ tkptr->FtpState = PROXY_FTP_STATE_USER; ProxyFtpResponse (tkptr); break; case PROXY_FTP_STATE_USER : if (tkptr->FtpResponseClass > 3) goto QuitNow; tkptr->FtpState = PROXY_FTP_STATE_USER_DONE; if (rqptr->RemoteUser[0]) ProxyFtpCommand (tkptr, true, "USER !AZ", rqptr->RemoteUser); else if (tkptr->UrlUserName[0]) ProxyFtpCommand (tkptr, true, "USER !AZ", tkptr->UrlUserName); else ProxyFtpCommand (tkptr, true, "USER anonymous"); break; case PROXY_FTP_STATE_USER_DONE : if (tkptr->FtpResponseClass > 3) { InstanceGblSecIncrLong (&ProxyAccountingPtr->FtpLoginFailCount); goto QuitNow; } if (tkptr->FtpResponseCode == 331) { tkptr->FtpState = PROXY_FTP_STATE_PASS_DONE; if (rqptr->RemoteUser[0]) ProxyFtpCommand (tkptr, true, "PASS !AZ", rqptr->RemoteUserPassword); else if (tkptr->UrlPassword[0]) ProxyFtpCommand (tkptr, true, "PASS !AZ", tkptr->UrlPassword); else if (tkptr->UrlUserName[0]) ProxyFtpCommand (tkptr, true, "PASS !AZ@!AZ", tkptr->UrlUserName, rqptr->ServicePtr->ServerIpAddressString); else { cptr = rqptr->rqHeader.QueryStringPtr; if (cptr[0] && (sptr = ProxyFtpInQueryString (cptr, "email:"))) { /* keyword style */ cptr = sptr + 6; zptr = (sptr = String) + sizeof(String)-1; while (*cptr && *cptr != '+' && sptr < zptr) *sptr++ = *cptr++; *sptr = '\0'; ProxyFtpCommand (tkptr, true, "PASS !AZ", String); } else if (cptr[0] && (sptr = ProxyFtpInQueryString (cptr, "email="))) { /* form field style */ cptr = sptr + 6; zptr = (sptr = String) + sizeof(String)-1; while (*cptr && *cptr != '&' && sptr < zptr) *sptr++ = *cptr++; *sptr = '\0'; ProxyFtpCommand (tkptr, true, "PASS !AZ", String); } else ProxyFtpCommand (tkptr, true, "PASS proxy@!AZ", rqptr->ServicePtr->ServerHostName); } break; } /* otherwise just drop thru, password not required! */ case PROXY_FTP_STATE_PASS_DONE : if (tkptr->FtpResponseClass > 3) { InstanceGblSecIncrLong (&ProxyAccountingPtr->FtpLoginFailCount); goto QuitNow; } if (tkptr->FtpResponseCode == 230) ProxyFtpResponse230 (tkptr); tkptr->FtpState = PROXY_FTP_STATE_SYST_DONE; ProxyFtpCommand (tkptr, true, "SYST"); break; /*****************/ /* now logged in */ /*****************/ case PROXY_FTP_STATE_SYST_DONE : if (tkptr->FtpResponseClass > 3) goto QuitNow; zptr = (sptr = tkptr->FtpSYST) + sizeof(tkptr->FtpSYST)-1; for (cptr = tkptr->FtpResponsePtr+4; *cptr && ISLWS(*cptr); cptr++); while (*cptr && NOTEOL(*cptr) && sptr < zptr) { if (*cptr == '\"') *sptr++ = '\''; else *sptr++ = *cptr; cptr++; } *sptr = '\0'; if (strstr (tkptr->FtpSYST, "MadGoat")) tkptr->FtpSpecific = PROXY_FTP_SPECIFIC_MADGOAT; tkptr->FtpState = PROXY_FTP_STATE_PWD_DONE; ProxyFtpCommand (tkptr, true, "PWD"); break; case PROXY_FTP_STATE_PWD_DONE : if (tkptr->FtpResponseClass > 3) goto QuitNow; ProxyFtpRemoteFileSystem (tkptr); if (tkptr->HttpMethod == HTTP_METHOD_DELETE) tkptr->FtpState = PROXY_FTP_STATE_DELE; else tkptr->FtpState = PROXY_FTP_STATE_CWD_DONE; ProxyFtpCwd (tkptr); break; case PROXY_FTP_STATE_CWD_DONE : if (tkptr->FtpResponseClass > 3) goto QuitNow; zptr = (sptr = tkptr->FtpCWD) + sizeof(tkptr->FtpCWD)-1; for (cptr = tkptr->FtpResponsePtr+4; *cptr && ISLWS(*cptr); cptr++); while (*cptr && NOTEOL(*cptr) && sptr < zptr) { if (*cptr == '\"') *sptr++ = '\''; else *sptr++ = *cptr; cptr++; } *sptr = '\0'; tkptr->FtpState = PROXY_FTP_STATE_PASV_DONE; ProxyFtpCommand (tkptr, true, "PASV"); break; case PROXY_FTP_STATE_PASV_DONE : if (tkptr->FtpResponseClass > 3) goto QuitNow; /* process the IP address and port supplied by the PASV response */ if (!ProxyFtpPasvData (tkptr)) { ProxyFtpResponseInvalid (tkptr); goto QuitNow; } if (tkptr->HttpMethod == HTTP_METHOD_POST || tkptr->HttpMethod == HTTP_METHOD_PUT) tkptr->FtpState = PROXY_FTP_STATE_STOR; else if (tkptr->HttpMethod == HTTP_METHOD_GET || tkptr->HttpMethod == HTTP_METHOD_HEAD) { /* determine if a directory listing or file transfer */ cptr = tkptr->FtpFilePathPtr + tkptr->FtpFilePathLength; while (cptr > tkptr->FtpFilePathPtr && *cptr != '/' && *cptr != '*') cptr--; if (*cptr == '*' || SAME2(cptr,'/\0')) tkptr->FtpState = PROXY_FTP_STATE_LIST; else tkptr->FtpState = PROXY_FTP_STATE_RETR_MODE; } else ErrorExitVmsStatus (SS$_BUGCHECK, ErrorSanityCheck, FI_LI); /* connect to the address and port specified by the PASV response */ ProxyFtpDataConnect (tkptr); break; /*********************/ /* directory listing */ /*********************/ case PROXY_FTP_STATE_LIST : if (tkptr->FtpResponseClass > 3) goto QuitNow; /* send the file system specific command for a directory listing */ tkptr->FtpState = PROXY_FTP_STATE_LIST_RECEIVE; ProxyFtpList (tkptr); break; case PROXY_FTP_STATE_LIST_RECEIVE : if (tkptr->FtpResponseClass > 3) goto QuitNow; tkptr->FtpState = PROXY_FTP_STATE_LIST_CHECK; /* transfer the listing */ ProxyFtpListReceive (tkptr); break; case PROXY_FTP_STATE_LIST_CHECK : if (tkptr->FtpResponseClass > 3) goto QuitNow; /* get so we can check the transfer complete response */ tkptr->FtpState = PROXY_FTP_STATE_LIST_PROCESS; ProxyFtpResponse (tkptr); break; case PROXY_FTP_STATE_LIST_PROCESS : if (tkptr->FtpResponseClass > 3) goto QuitNow; /* format and output the directory listing */ tkptr->FtpState = PROXY_FTP_STATE_QUIT; ProxyFtpListProcess (tkptr); break; /*********************/ /* retrieving a file */ /*********************/ case PROXY_FTP_STATE_RETR_MODE : if (tkptr->FtpResponseClass > 3) goto QuitNow; /* send the command for binary or ASCII transfer */ tkptr->FtpState = PROXY_FTP_STATE_RETR; ProxyFtpRetrieveMode (tkptr); break; case PROXY_FTP_STATE_RETR : if (tkptr->FtpResponseClass > 3) goto QuitNow; /* send the command to retrieve the file */ tkptr->FtpState = PROXY_FTP_STATE_RETR_FILE; ProxyFtpRetrieve (tkptr); break; case PROXY_FTP_STATE_RETR_FILE : if (tkptr->FtpResponseClass > 3) goto QuitNow; tkptr->FtpState = PROXY_FTP_STATE_RETR_DONE; /* transfer the file */ ProxyFtpRetrieveAst (tkptr); break; case PROXY_FTP_STATE_RETR_DONE : /* get the response to the file transfer */ tkptr->FtpState = PROXY_FTP_STATE_QUIT; ProxyFtpResponse (tkptr); break; /******************/ /* storing a file */ /******************/ case PROXY_FTP_STATE_STOR : if (tkptr->FtpResponseClass > 3) goto QuitNow; /* begin request body (file), will initiate the STOR command */ tkptr->FtpState = PROXY_FTP_STATE_STOR_TYPE; ProxyFtpStoreBodyReadBegin (tkptr); break; case PROXY_FTP_STATE_STOR_TYPE : if (tkptr->FtpResponseClass > 3) goto QuitNow; /* TYPE sent, go back to transfering the request body (file) */ tkptr->FtpState = PROXY_FTP_STATE_STOR_FILE; ProxyFtpStoreBodyReadAst (tkptr->RequestPtr); break; case PROXY_FTP_STATE_STOR_FILE : if (tkptr->FtpResponseClass > 3) goto QuitNow; /* STOR sent, go back to transfering the request body (file) */ tkptr->FtpState = PROXY_FTP_STATE_STOR_CHECK; ProxyFtpStoreBodyReadAst (tkptr->RequestPtr); break; case PROXY_FTP_STATE_STOR_CHECK : /* get the response to the file transfer */ tkptr->FtpState = PROXY_FTP_STATE_STOR_DONE; ProxyFtpResponse (tkptr); break; case PROXY_FTP_STATE_STOR_DONE : /* get the response to the file transfer */ if (tkptr->FtpResponseClass > 3) goto QuitNow; for (cptr = tkptr->FtpResponsePtr; NOTEOL(*cptr); cptr++); *cptr = '\0'; for (cptr = tkptr->FtpResponsePtr+4; ISLWS(*cptr); cptr++); ReportSuccess (rqptr, "!AZ  !&;AZ", MsgFor(rqptr,MSG_PROXY_FTP_SERVER_REPLIED), cptr); tkptr->FtpState = PROXY_FTP_STATE_QUIT; SysDclAst (&ProxyFtpLifeCycle, tkptr); break; /*******************/ /* deleting a file */ /*******************/ case PROXY_FTP_STATE_DELE : if (tkptr->FtpResponseClass > 3) goto QuitNow; /* just delete the file */ tkptr->FtpState = PROXY_FTP_STATE_DELE_DONE; ProxyFtpDelete (tkptr); break; case PROXY_FTP_STATE_DELE_DONE : /* get the response to the file transfer */ if (tkptr->FtpResponseClass > 3) goto QuitNow; for (cptr = tkptr->FtpResponsePtr; NOTEOL(*cptr); cptr++); *cptr = '\0'; for (cptr = tkptr->FtpResponsePtr+4; ISLWS(*cptr); cptr++); ReportSuccess (rqptr, "!AZ  !&;AZ", MsgFor(rqptr,MSG_PROXY_FTP_SERVER_REPLIED), cptr); tkptr->FtpState = PROXY_FTP_STATE_QUIT; SysDclAst (&ProxyFtpLifeCycle, tkptr); break; /********/ /* quit */ /********/ case PROXY_FTP_STATE_QUIT : tkptr->FtpState = PROXY_FTP_STATE_QUIT_DONE; ProxyFtpCommand (tkptr, true, "QUIT"); break; case PROXY_FTP_STATE_ABORT : case PROXY_FTP_STATE_QUIT_DONE : ProxyFtpDataCloseSocket (tkptr); ProxyEnd (tkptr); break; default : ErrorExitVmsStatus (SS$_BUGCHECK, ErrorSanityCheck, FI_LI); } return; /* where's Dijkstra when you really need him? */ QuitNow : for (cptr = tkptr->FtpResponsePtr; NOTEOL(*cptr); cptr++); *cptr = '\0'; for (cptr = tkptr->FtpResponsePtr+4; ISLWS(*cptr); cptr++); /* bogus status code 599 is used to represent WASD generated errors */ if (tkptr->FtpResponseCode < 599) { ProxyFtpHttpStatus (tkptr); FaoToBuffer (String, sizeof(String), NULL, "!AZ  !&;AZ", MsgFor(rqptr,MSG_PROXY_FTP_SERVER_REPLIED), cptr); ErrorGeneral (rqptr, String, FI_LI); } else { rqptr->rqResponse.HttpStatus = 502; ErrorGeneral (rqptr, tkptr->FtpResponsePtr+4, FI_LI); } tkptr->FtpState = PROXY_FTP_STATE_QUIT; SysDclAst (&ProxyFtpLifeCycle, tkptr); } /****************************************************************************/ /* Format a command string and write it to the FTP server. Optionally get a response back from the server. If 'GetResponse' is true this function will AST to ProxyFtpResponse() to initiate the read. In this case the AST function should check the read IO status block for success. If no response is required the AST function should check the write IO status block. If 'FormatString' is NULL then consider the command buffer to already contain a formatted command. */ ProxyFtpCommand ( PROXY_TASK *tkptr, BOOL GetResponse, char *FormatString, ... ) { int argcnt, status; unsigned long *vecptr; unsigned long FaoVector [32]; PROXY_AST AstFunction; REQUEST_STRUCT *rqptr; va_list argptr; /*********/ /* begin */ /*********/ va_count (argcnt); if (WATCHMOD (tkptr, WATCH_MOD_PROXY)) WatchThis (WATCHITM(tkptr), WATCH_MOD_PROXY, "ProxyFtpCommand() !&B !UL !&Z", GetResponse, argcnt, FormatString); rqptr = tkptr->RequestPtr; if (!tkptr->FtpCommandPtr) { /* first command issued, allocate buffer space */ tkptr->FtpCommandSize = PROXY_FTP_COMMAND_SIZE; tkptr->FtpCommandPtr = VmGetHeap (rqptr, tkptr->FtpCommandSize); } if (FormatString) { if (argcnt > 32+3) ErrorExitVmsStatus (SS$_BUGCHECK, ErrorSanityCheck, FI_LI); vecptr = FaoVector; va_start (argptr, FormatString); for (argcnt -= 3; argcnt; argcnt--) *vecptr++ = va_arg (argptr, unsigned long); va_end (argptr); status = FaolSAK (NULL, tkptr->FtpCommandPtr, tkptr->FtpCommandSize-2, &tkptr->FtpCommandCount, FormatString, &FaoVector); if (VMSnok (status)) { tkptr->NetIoPtr->WriteStatus = status; tkptr->NetIoPtr->WriteCount = 0; SysDclAst (&ProxyFtpLifeCycle, tkptr); return; } /* append required carriage-control */ tkptr->FtpCommandPtr[tkptr->FtpCommandCount++] = '\r'; tkptr->FtpCommandPtr[tkptr->FtpCommandCount++] = '\n'; } if (WATCHING (tkptr, WATCH_PROXY)) { char *cptr, *sptr; cptr = sptr = tkptr->FtpCommandPtr; while (*cptr && *cptr != '\r' && *cptr != '\n') cptr++; if (MATCH4 (sptr, "PASS")) WatchThis (WATCHITM(tkptr), WATCH_PROXY, ">>PASS !#**", cptr-sptr-5); else WatchThis (WATCHITM(tkptr), WATCH_PROXY, ">>!#AZ", cptr-sptr, sptr); } if (GetResponse) AstFunction = &ProxyFtpCommandResponseAst; else AstFunction = &ProxyFtpCommandAst; ProxyNetWrite (tkptr, AstFunction, tkptr->FtpCommandPtr, tkptr->FtpCommandCount); } /****************************************************************************/ /* Check the write status and if successful AST back to the function originally supplied to ProxyFtpCommand(). If command write unsuccessful terminate the request. */ ProxyFtpCommandAst (PROXY_TASK *tkptr) { REQUEST_STRUCT *rqptr; /*********/ /* begin */ /*********/ if (WATCHMOD (tkptr, WATCH_MOD_PROXY)) WatchThis (WATCHITM(tkptr), WATCH_MOD_PROXY, "ProxyFtpCommandAst() !&F !&S !UL", &ProxyFtpCommandAst, tkptr->NetIoPtr->WriteStatus, tkptr->NetIoPtr->WriteCount); if (VMSok (tkptr->NetIoPtr->WriteStatus)) { SysDclAst (&ProxyFtpLifeCycle, tkptr); return; } /* error writing to the remote FTP server */ rqptr = tkptr->RequestPtr; rqptr->rqResponse.HttpStatus = 502; rqptr->rqResponse.ErrorTextPtr = tkptr->RequestHostPort; ErrorVmsStatus (rqptr, tkptr->NetIoPtr->WriteStatus, FI_LI); tkptr->FtpState = PROXY_FTP_STATE_ABORT; SysDclAst (&ProxyFtpLifeCycle, tkptr); } /****************************************************************************/ /* This is AST is used only if 'GetResponse' was true. Check the write status and if successful read the FTP server's response. If command write unsuccessful terminate the request. */ ProxyFtpCommandResponseAst (PROXY_TASK *tkptr) { REQUEST_STRUCT *rqptr; /*********/ /* begin */ /*********/ if (WATCHMOD (tkptr, WATCH_MOD_PROXY)) WatchThis (WATCHITM(tkptr), WATCH_MOD_PROXY, "ProxyFtpCommandResponseAst() !&F !&S !UL", &ProxyFtpCommandAst, tkptr->NetIoPtr->WriteStatus, tkptr->NetIoPtr->WriteCount); if (VMSok (tkptr->NetIoPtr->WriteStatus)) { ProxyFtpResponse (tkptr); return; } /* error writing to the remote FTP server */ rqptr = tkptr->RequestPtr; rqptr->rqResponse.HttpStatus = 502; rqptr->rqResponse.ErrorTextPtr = tkptr->RequestHostPort; ErrorVmsStatus (rqptr, tkptr->NetIoPtr->WriteStatus, FI_LI); tkptr->FtpState = PROXY_FTP_STATE_ABORT; SysDclAst (&ProxyFtpLifeCycle, tkptr); } /****************************************************************************/ /* Read the response from the remote FTP server. */ ProxyFtpResponse (PROXY_TASK *tkptr) { char *cptr; REQUEST_STRUCT *rqptr; /*********/ /* begin */ /*********/ if (WATCHMOD (tkptr, WATCH_MOD_PROXY)) WatchThis (WATCHITM(tkptr), WATCH_MOD_PROXY, "ProxyFtpResponse() !&F !&S !UL !&X", &ProxyFtpResponse, tkptr->NetIoPtr->WriteStatus, tkptr->NetIoPtr->WriteCount, tkptr->FtpNextResponsePtr); rqptr = tkptr->RequestPtr; if (!tkptr->FtpResponsePtr) { /* first response, allocate receive buffer */ tkptr->FtpResponseSize = ProxyReadBufferSize; tkptr->FtpResponsePtr = VmGetHeap (rqptr, tkptr->FtpResponseSize); tkptr->FtpResponseCurrentPtr = tkptr->FtpResponsePtr; } if (tkptr->FtpResponseCurrentPtr == tkptr->FtpResponsePtr) { /* start of new response, reset buffer and data */ tkptr->FtpResponseRemaining = tkptr->FtpResponseSize; tkptr->FtpResponseCode = tkptr->FtpResponseClass = tkptr->FtpResponseCount = tkptr->FtpResponseLineCount = 0; } if (tkptr->FtpNextResponsePtr) { /* previous "response" contained at least two responses! */ for (cptr = tkptr->FtpNextResponsePtr; *cptr; cptr++); memcpy (tkptr->FtpResponsePtr, tkptr->FtpNextResponsePtr, cptr - tkptr->FtpNextResponsePtr+1); /* fudge this an an actual network read! */ tkptr->NetIoPtr->ReadStatus = SS$_NORMAL; tkptr->NetIoPtr->ReadCount = cptr - tkptr->FtpNextResponsePtr; SysDclAst (&ProxyFtpResponseAst, tkptr); return; } if (tkptr->FtpResponseRemaining < PROXY_FTP_RESPONSE_LOW_BUFFER) { /* buffer space is getting a bit low, reallocate */ tkptr->FtpResponseSize += ProxyReadBufferSize; tkptr->FtpResponseRemaining += ProxyReadBufferSize; tkptr->FtpResponsePtr = VmReallocHeap (rqptr, tkptr->FtpResponsePtr, tkptr->FtpResponseSize, FI_LI); tkptr->FtpResponseCurrentPtr = tkptr->FtpResponsePtr + tkptr->FtpResponseSize - tkptr->FtpResponseRemaining; } ProxyNetRead (tkptr, &ProxyFtpResponseAst, tkptr->FtpResponseCurrentPtr, tkptr->FtpResponseRemaining); } /****************************************************************************/ /* A response has been read from the FTP server. If successful AST to the function originally supplied to ProxyFtpRequest(), if unsuccessful just terminate the request. */ ProxyFtpResponseAst (PROXY_TASK *tkptr) { BOOL ResponseContinued; int ResponseCount; char *cptr, *dptr, *sptr; REQUEST_STRUCT *rqptr; /*********/ /* begin */ /*********/ if (WATCHMOD (tkptr, WATCH_MOD_PROXY)) WatchThis (WATCHITM(tkptr), WATCH_MOD_PROXY, "ProxyFtpResponseAst() !&F !&S !UL", &ProxyFtpResponseAst, tkptr->NetIoPtr->ReadStatus, tkptr->NetIoPtr->ReadCount); ResponseCount = 0; tkptr->FtpNextResponsePtr = NULL; if (VMSok (tkptr->NetIoPtr->ReadStatus)) { cptr = tkptr->FtpResponseCurrentPtr; tkptr->FtpResponseCount += tkptr->NetIoPtr->ReadCount; tkptr->FtpResponseCurrentPtr += tkptr->NetIoPtr->ReadCount; tkptr->FtpResponseRemaining -= tkptr->NetIoPtr->ReadCount; *tkptr->FtpResponseCurrentPtr = '\0'; for (sptr = cptr; *sptr; sptr++) { if (*sptr != '\r' && *sptr != '\n') continue; /* some Microsoft servers seem to "\r\r\n" (?) e.g. Compaq */ if (MATCH3 (sptr, "\r\r\n")) { /* just turn the leading '\r' into a (hopefully) harmless space */ *sptr = ' '; continue; } while (*sptr == '\r' || *sptr == '\n') sptr++; if (!*sptr) { sptr = NULL; break; } } if (sptr) { /* response not yet terminated by carriage control, get more */ ProxyFtpResponse (tkptr); return; } /* non-continued response terminated by carriage-control */ ResponseContinued = false; cptr = tkptr->FtpResponsePtr; for (;;) { if (cptr > tkptr->FtpResponsePtr && *cptr == ' ') { /* a 'logical' line with embedded carriage-control */ cptr++; } else if ((cptr[0] == '1' || cptr[0] == '2' || cptr[0] == '3' || cptr[0] == '4' || cptr[0] == '5') && isdigit(cptr[1]) && isdigit(cptr[2])) { /* establish the success or otherwise of the response */ if (!tkptr->FtpResponseCode) { /* use the status of the first response line */ tkptr->FtpResponseClass = cptr[0] - '0'; tkptr->FtpResponseCode = atoi(cptr); } if (cptr[3] == '-') ResponseContinued = true; else { ResponseContinued = false; if (ResponseCount++) tkptr->FtpNextResponsePtr = cptr; } } else { if (WATCHING (tkptr, WATCH_PROXY)) WatchDataDump (tkptr->FtpResponsePtr, tkptr->FtpResponseCount); ProxyFtpResponseInvalid (tkptr); return; } /* find the start of the next 'physical' line */ while (*cptr && *cptr != '\r' && *cptr != '\n') cptr++; while (*cptr == '\r' || *cptr == '\n') cptr++; tkptr->FtpResponseLineCount++; if (!*cptr) break; } /* end of buffer */ if (ResponseContinued) { /* this response is to be continued with at least another line */ ProxyFtpResponse (tkptr); } else { /* indicate that the next response read is a totally fresh one */ tkptr->FtpResponseCurrentPtr = tkptr->FtpResponsePtr; if (WATCHING (tkptr, WATCH_PROXY)) { sptr = tkptr->FtpResponsePtr; cptr--; while (cptr > sptr && *cptr == '\r' || *cptr == '\n') cptr--; if (cptr > sptr) cptr++; WatchThis (WATCHITM(tkptr), WATCH_PROXY, "<FtpState == PROXY_FTP_STATE_QUIT_DONE && tkptr->FtpDataReadIOsb.Status == SS$_LINKDISCON) { /* can live with the FTP server disconnecting immediately upon QUIT */ tkptr->FtpDataReadIOsb.Status = SS$_NORMAL; tkptr->FtpResponseClass = 2; tkptr->FtpResponseCode = 221; tkptr->FtpResponsePtr = "221 Session was disconnected by remote server!"; tkptr->FtpResponseCurrentPtr = tkptr->FtpResponsePtr; SysDclAst (&ProxyFtpLifeCycle, tkptr); return; } /* error reading from the remote FTP server */ rqptr = tkptr->RequestPtr; rqptr->rqResponse.HttpStatus = 502; rqptr->rqResponse.ErrorTextPtr = tkptr->RequestHostPort; ErrorVmsStatus (rqptr, tkptr->NetIoPtr->ReadStatus, FI_LI); tkptr->FtpState = PROXY_FTP_STATE_ABORT; SysDclAst (&ProxyFtpLifeCycle, tkptr); } /****************************************************************************/ /* Couldn't understand the FTP server's response. Create a bogus FTP-like status line and appropriate status values to indicate this. Set the state to QUIT and declare the state processor so that the calling routine should return imediately after calling this function. */ ProxyFtpResponseInvalid (PROXY_TASK *tkptr) { /*********/ /* begin */ /*********/ if (WATCHMOD (tkptr, WATCH_MOD_PROXY)) WatchThis (WATCHITM(tkptr), WATCH_MOD_PROXY, "ProxyFtpResponseInvalid()"); tkptr->FtpResponseClass = 5; tkptr->FtpResponseCode = 599; strcpy (tkptr->FtpResponsePtr, "599 Invalid response from FTP server."); tkptr->FtpResponseCount = strlen(tkptr->FtpResponsePtr); tkptr->FtpState = PROXY_FTP_STATE_QUIT; SysDclAst (&ProxyFtpLifeCycle, tkptr); } /****************************************************************************/ /* The server has returned a 230 response status. This is where site announcements are often delivered. Get the contents of this response for use in the directory listing page. */ ProxyFtpResponse230 (PROXY_TASK *tkptr) { char *cptr, *sptr; REQUEST_STRUCT *rqptr; /*********/ /* begin */ /*********/ if (WATCHMOD (tkptr, WATCH_MOD_PROXY)) WatchThis (WATCHITM(tkptr), WATCH_MOD_PROXY, "ProxyFtpResponse230() !UL", tkptr->FtpResponseLineCount); if (tkptr->FtpResponseLineCount <= 2) return; rqptr = tkptr->RequestPtr; /* won't be using -more- than this memory */ sptr = tkptr->Ftp230Ptr = VmGetHeap (rqptr, tkptr->FtpResponseCount); cptr = tkptr->FtpResponsePtr; while (*cptr) { if (cptr[3] == '-') { cptr += 4; while (*cptr) { while (*cptr && *cptr != '\r' && *cptr != '\n') *sptr++ = *cptr++; while (*cptr == '\r') cptr++; if (*cptr == '\n') *sptr++ = *cptr++; if (*cptr != ' ') break; } } else { while (*cptr) { while (*cptr && *cptr != '\r' && *cptr != '\n') cptr++; while (*cptr == '\n' || *cptr == '\r') cptr++; if (*cptr != ' ') break; } } } /* looks better on the page if trailing newlines are suppressed */ while (sptr > tkptr->Ftp230Ptr && *(sptr-1) == '\n') sptr--; *sptr = '\0'; tkptr->Ftp230Length = sptr - tkptr->Ftp230Ptr; } /****************************************************************************/ /* Generate a URL-decoded file path from the request URI. */ BOOL ProxyFtpFilePath (PROXY_TASK *tkptr) { int Length; char *cptr, *sptr; REQUEST_STRUCT *rqptr; /*********/ /* begin */ /*********/ if (WATCHMOD (tkptr, WATCH_MOD_PROXY)) WatchThis (WATCHITM(tkptr), WATCH_MOD_PROXY, "ProxyFtpFilePath() !&Z", tkptr->RequestUriPtr); rqptr = tkptr->RequestPtr; for (sptr = tkptr->RequestUriPtr; *sptr && *sptr != '?'; sptr++); Length = sptr - tkptr->RequestUriPtr; tkptr->FtpFilePathPtr = VmGetHeap (rqptr, Length+1); memcpy (tkptr->FtpFilePathPtr, tkptr->RequestUriPtr, Length); tkptr->FtpFilePathPtr[Length] = '\0'; /* we know the decode will be OK because the path has already been done! */ Length = StringUrlDecode (tkptr->FtpFilePathPtr); if (Length == -1) return (false); tkptr->FtpFilePathLength = Length; return (true); } /****************************************************************************/ /* Try and determine the remote file system type from the PWD response. */ ProxyFtpRemoteFileSystem (PROXY_TASK *tkptr) { char *cptr, *sptr; /*********/ /* begin */ /*********/ if (WATCHMOD (tkptr, WATCH_MOD_PROXY)) WatchThis (WATCHITM(tkptr), WATCH_MOD_PROXY, "ProxyFtpRemoteFileSystem()"); tkptr->FtpFileSystem = PROXY_FTP_FILE_SYSTEM_UNKNOWN; tkptr->FtpFileSystemPtr = "unknown"; cptr = tkptr->FtpResponsePtr + 4; while (*cptr) { if (*cptr == '/') { tkptr->FtpFileSystem = PROXY_FTP_FILE_SYSTEM_UNIX; tkptr->FtpFileSystemPtr = "Unix"; InstanceGblSecIncrLong (&ProxyAccountingPtr->FtpUnixCount); break; } if (*cptr == '\\') { tkptr->FtpFileSystem = PROXY_FTP_FILE_SYSTEM_DOS; tkptr->FtpFileSystemPtr = "DOS"; InstanceGblSecIncrLong (&ProxyAccountingPtr->FtpDosCount); break; } if (SAME2(cptr,':[')) { tkptr->FtpFileSystem = PROXY_FTP_FILE_SYSTEM_VMS; tkptr->FtpFileSystemPtr = "VMS"; InstanceGblSecIncrLong (&ProxyAccountingPtr->FtpVmsCount); break; } cptr++; } if (!*cptr) InstanceGblSecIncrLong (&ProxyAccountingPtr->FtpUnknownCount); if (WATCHING (tkptr, WATCH_PROXY)) WatchThis (WATCHITM(tkptr), WATCH_PROXY, "FILE-SYSTEM !AZ", tkptr->FtpFileSystemPtr); } /****************************************************************************/ /* Issue a Change Working Directory (CWD) command. The URI path specification needs to be massaged according to file system specifics. */ ProxyFtpCwd (PROXY_TASK *tkptr) { int DirCount; char *cptr, *dptr, *sptr, *zptr; REQUEST_STRUCT *rqptr; /*********/ /* begin */ /*********/ if (WATCHMOD (tkptr, WATCH_MOD_PROXY)) WatchThis (WATCHITM(tkptr), WATCH_MOD_PROXY, "ProxyFtpCwd()"); rqptr = tkptr->RequestPtr; DirCount = 0; for (cptr = tkptr->FtpFilePathPtr + 1; *cptr; *cptr++) if (*cptr == '/') DirCount++; if (!DirCount) { /* root of FTP directory structure, no CWD required */ SysDclAst (&ProxyFtpLifeCycle, tkptr); return; } zptr = (sptr = tkptr->FtpCommandPtr) + tkptr->FtpCommandSize - 1; SET4(sptr,'CWD '); sptr += 4; cptr = tkptr->FtpFilePathPtr; if (tkptr->FtpFileSystem == PROXY_FTP_FILE_SYSTEM_VMS) { if (SAME2(cptr,'//')) { /* absolute path */ cptr += 2; while (*cptr && *cptr != '/' && sptr < zptr) *sptr++ = *cptr++; if (*cptr) cptr++; if (sptr < zptr) *sptr++ = ':'; if (sptr < zptr) *sptr++ = '['; DirCount = 0; dptr = sptr; while (*cptr && sptr < zptr) { while (*cptr && *cptr != '/' && sptr < zptr) *sptr++ = *cptr++; if (*cptr) { /* just another directory */ DirCount++; cptr++; dptr = sptr; if (sptr < zptr) *sptr++ = '.'; } } sptr = dptr; if (!DirCount) { if (sptr < zptr) *sptr++ = '0'; if (sptr < zptr) *sptr++ = '0'; if (sptr < zptr) *sptr++ = '0'; if (sptr < zptr) *sptr++ = '0'; if (sptr < zptr) *sptr++ = '0'; if (sptr < zptr) *sptr++ = '0'; } *sptr++ = ']'; } else { if (*cptr) cptr++; *sptr++ = '['; *sptr++ = '.'; dptr = sptr; while (*cptr && sptr < zptr) { while (*cptr && *cptr != '/' && sptr < zptr) *sptr++ = *cptr++; if (*cptr) { /* just another directory */ cptr++; dptr = sptr; if (sptr < zptr) *sptr++ = '.'; } } sptr = dptr; *sptr++ = ']'; } } else { if (!SAME2(cptr,'//')) { /* if not an absolute path */ *sptr++ = '.'; *sptr++ = '/'; } if (*cptr) cptr++; dptr = sptr; while (*cptr && sptr < zptr) { if (*cptr == '/') dptr = sptr; *sptr++ = *cptr++; } sptr = dptr; if (tkptr->FtpFileSystem == PROXY_FTP_FILE_SYSTEM_DOS) { /* for DOS, turn each forward slash into a back-slash */ *sptr = '\0'; for (cptr = tkptr->FtpCommandPtr + 4; *cptr; cptr++) if (*cptr == '/') *cptr = '\\'; } } if (sptr < zptr) *sptr++ = '\r'; if (sptr < zptr) *sptr++ = '\n'; *sptr = '\0'; if (sptr >= zptr) ErrorNoticed (rqptr, SS$_BUFFEROVF, NULL, FI_LI); /* length must be set if we're going to play with the buffer directly */ tkptr->FtpCommandCount = sptr - tkptr->FtpCommandPtr; ProxyFtpCommand (tkptr, true, NULL); } /****************************************************************************/ /* Parse the IP address and port from the PASV response into the FTP data IP address and port storage. If there's a problem return false otherwise true. */ BOOL ProxyFtpPasvData (PROXY_TASK *tkptr) { int cnt; int PasvOctets [6]; char *cptr, *sptr; unsigned char *optr; /*********/ /* begin */ /*********/ if (WATCHMOD (tkptr, WATCH_MOD_PROXY)) WatchThis (WATCHITM(tkptr), WATCH_MOD_PROXY, "ProxyFtpPasvData() !&Z", tkptr->FtpResponsePtr); cptr = tkptr->FtpResponsePtr + 4; while (*cptr != '(') cptr++; cnt = sscanf (cptr, "(%d,%d,%d,%d,%d,%d)", &PasvOctets[0], &PasvOctets[1], &PasvOctets[2], &PasvOctets[3], &PasvOctets[4], &PasvOctets[5]); if (cnt != 6) return (false); if (IPADDRESS_IS_V4 (&tkptr->ConnectIpAddress)) { IPADDRESS_ZERO4 (&tkptr->FtpDataIpAddress) optr = IPADDRESS_ADR4 (&tkptr->FtpDataIpAddress); for (cnt = 0; cnt < 4; cnt++) { if (PasvOctets[cnt] < 0 || PasvOctets[cnt] > 255) return (false); *optr++ = PasvOctets[cnt]; } /* if PASV response address is 0.0.0.0 then use connect address */ if (!IPADDRESS_IS_SET (&tkptr->FtpDataIpAddress)) IPADDRESS_COPY (&tkptr->FtpDataIpAddress, &tkptr->ConnectIpAddress) } else if (IPADDRESS_IS_V6 (&tkptr->ConnectIpAddress)) { /* the convention is for IPv6 clients to ignore the supplied address */ IPADDRESS_COPY (&tkptr->FtpDataIpAddress, &tkptr->ConnectIpAddress) } else return (false); tkptr->FtpDataIpPort = 0; optr = &tkptr->FtpDataIpPort; for (cnt = 4; cnt < 6; cnt++) { if (PasvOctets[cnt] < 0 || PasvOctets[cnt] > 255) return (false); *optr++ = PasvOctets[cnt]; } if (WATCHMOD (tkptr, WATCH_MOD_PROXY)) WatchThis (WATCHITM(tkptr), WATCH_MOD_PROXY, "!&I,!UL", &tkptr->FtpDataIpAddress, tkptr->FtpDataIpPort); if (IPADDRESS_IS_SET (&tkptr->FtpDataIpAddress) && tkptr->FtpDataIpPort) return (true); else return (false); } /****************************************************************************/ /* Generate a directory listing command appropriate to the environment. */ ProxyFtpList (PROXY_TASK *tkptr) { int status; char *cptr; REQUEST_STRUCT *rqptr; /*********/ /* begin */ /*********/ if (WATCHMOD (tkptr, WATCH_MOD_PROXY)) WatchThis (WATCHITM(tkptr), WATCH_MOD_PROXY, "ProxyFtpList()"); InstanceGblSecIncrLong (&ProxyAccountingPtr->FtpListCount); switch (tkptr->FtpFileSystem) { case PROXY_FTP_FILE_SYSTEM_DOS : ProxyFtpCommand (tkptr, true, "NLIST!AZ!AZ", tkptr->FtpWildPtr[0] ? " " : "", tkptr->FtpWildPtr); break; case PROXY_FTP_FILE_SYSTEM_UNIX : ProxyFtpCommand (tkptr, true, "LIST!AZ!AZ", tkptr->FtpWildPtr[0] ? " " : "", tkptr->FtpWildPtr); break; case PROXY_FTP_FILE_SYSTEM_VMS : if (tkptr->FtpWildPtr[0]) { /* need to constrain or allow version numbers */ cptr = tkptr->FtpFilePathPtr + tkptr->FtpFilePathLength; while (cptr > tkptr->FtpFilePathPtr && *cptr != '/' && *cptr != ';') cptr--; if (*cptr == ';') ProxyFtpCommand (tkptr, true, "LIST !AZ", tkptr->FtpWildPtr); else ProxyFtpCommand (tkptr, true, "LIST !AZ;0", tkptr->FtpWildPtr); } else ProxyFtpCommand (tkptr, true, "LIST *.*;0"); break; default : ProxyFtpCommand (tkptr, true, "NLIST!AZ!AZ", tkptr->FtpWildPtr[0] ? " " : "", tkptr->FtpWildPtr); } /* reset the IO status block for the first read */ tkptr->FtpDataReadIOsb.Status = tkptr->FtpDataReadIOsb.Count = 0; } /****************************************************************************/ /* Received a chunk of a directory lsiting from the remote server. After checking the netwrok read status and it being OK add this to the dynamically allocated listing buffer and then read some more. */ ProxyFtpListReceive (PROXY_TASK *tkptr) { int status; REQUEST_STRUCT *rqptr; /*********/ /* begin */ /*********/ if (WATCHMOD (tkptr, WATCH_MOD_PROXY)) WatchThis (WATCHITM(tkptr), WATCH_MOD_PROXY, "ProxyFtpListReceive() !&S !UL", tkptr->FtpDataReadIOsb.Status, tkptr->FtpDataReadIOsb.Count); rqptr = tkptr->RequestPtr; if (tkptr->FtpDataReadIOsb.Status == SS$_LINKDISCON) { /* transfer is complete (or aborted!) */ ProxyFtpDataCloseSocket (tkptr); SysDclAst (&ProxyFtpLifeCycle, tkptr); return; } if (!tkptr->FtpDataReadIOsb.Status) { /* first time the function has been called */ if (!tkptr->ResponseBufferPtr) { tkptr->ResponseBufferSize = ProxyReadBufferSize; tkptr->ResponseBufferPtr = VmGetHeap (rqptr, ProxyReadBufferSize); } tkptr->ResponseBufferCount = 0; tkptr->ResponseBufferRemaining = tkptr->ResponseBufferSize - 1; tkptr->ResponseBufferCurrentPtr = tkptr->ResponseBufferPtr; /* plus, fudge the first IO status block appropriately */ tkptr->FtpDataReadIOsb.Status = SS$_NORMAL; } if (VMSnok (tkptr->FtpDataReadIOsb.Status)) { /* check the previous read status */ rqptr->rqResponse.ErrorTextPtr = tkptr->RequestHostPort; rqptr->rqResponse.HttpStatus = 502; ErrorVmsStatus (rqptr, tkptr->FtpDataReadIOsb.Status, FI_LI); tkptr->FtpState = PROXY_FTP_STATE_ABORT; SysDclAst (&ProxyFtpLifeCycle, tkptr); return; } tkptr->ResponseBufferCount += tkptr->FtpDataReadIOsb.Count; tkptr->ResponseBufferCurrentPtr += tkptr->FtpDataReadIOsb.Count; tkptr->ResponseBufferRemaining -= tkptr->FtpDataReadIOsb.Count; if (tkptr->ResponseBufferRemaining < PROXY_FTP_LIST_LOW_BUFFER) { tkptr->ResponseBufferSize += ProxyReadBufferSize; tkptr->ResponseBufferRemaining += ProxyReadBufferSize; tkptr->ResponseBufferPtr = VmReallocHeap (rqptr, tkptr->ResponseBufferPtr, tkptr->ResponseBufferSize, FI_LI); tkptr->ResponseBufferCurrentPtr = tkptr->ResponseBufferPtr + tkptr->ResponseBufferCount; } *tkptr->ResponseBufferCurrentPtr = '\0'; ProxyFtpDataReadRaw (tkptr, &ProxyFtpListReceive, tkptr->ResponseBufferCurrentPtr, tkptr->ResponseBufferRemaining); } /****************************************************************************/ /* The directory listing has been received and is currently stored in memory pointed to by ->ResponseBufferPtr, is ->ResponseBufferCount in size, and is null-terminated. Format this according to the type of file system. Write this to the proxy client. */ ProxyFtpListProcess (PROXY_TASK *tkptr) { int status; char *cptr; REQUEST_STRUCT *rqptr; /*********/ /* begin */ /*********/ if (WATCHMOD (tkptr, WATCH_MOD_PROXY)) WatchThis (WATCHITM(tkptr), WATCH_MOD_PROXY, "ProxyFtpListProcess()"); rqptr = tkptr->RequestPtr; if (tkptr->FtpFileSystem == PROXY_FTP_FILE_SYSTEM_UNIX) { /* first let's double-check it's not DOS masquerading as Unix */ cptr = tkptr->ResponseBufferPtr; if (isdigit(cptr[0]) && isdigit(cptr[1]) && (cptr[2] == '-' || cptr[2] == '/')) { /* hmmm, looks suspiciously like a ... */ while (!ISLWS(*cptr) && NOTEOL(*cptr)) cptr++; while (ISLWS(*cptr)) cptr++; if (isdigit(cptr[0]) && isdigit(cptr[1]) && cptr[2] == ':' && isdigit(cptr[3]) && isdigit(cptr[4])) { /* ... DOS date and time (dd/mm/yy hh:mm) */ tkptr->FtpFileSystem = PROXY_FTP_FILE_SYSTEM_DOS; tkptr->FtpFileSystemPtr = "DOS"; /* (and nothing like Unix file permissions!) */ InstanceMutexLock (INSTANCE_MUTEX_HTTPD); ProxyAccountingPtr->FtpUnixCount--; ProxyAccountingPtr->FtpDosCount++; InstanceMutexUnLock (INSTANCE_MUTEX_HTTPD); } } } if (*(cptr = rqptr->rqHeader.QueryStringPtr)) { if (ProxyFtpInQueryString (cptr, "dos")) tkptr->FtpFileSystem = PROXY_FTP_FILE_SYSTEM_DOS; else if (ProxyFtpInQueryString (cptr, "unix")) tkptr->FtpFileSystem = PROXY_FTP_FILE_SYSTEM_UNIX; else if (ProxyFtpInQueryString (cptr, "vms")) tkptr->FtpFileSystem = PROXY_FTP_FILE_SYSTEM_VMS; else if (ProxyFtpInQueryString (cptr, "list")) { rqptr->rqResponse.PreExpired = PRE_EXPIRE_FTP_PROXY; ResponseHeader (rqptr, 200, "text/plain", 0, NULL, NULL); NetWrite (rqptr, &ProxyFtpListWriteAst, tkptr->ResponseBufferPtr, tkptr->ResponseBufferCount); return; } if (ProxyFtpInQueryString (cptr, "alt")) tkptr->FtpListAlt = true; if (ProxyFtpInQueryString (cptr, "hide")) tkptr->FtpListHide = true; if (ProxyFtpInQueryString (cptr, "raw")) tkptr->FtpListRaw = true; } switch (tkptr->FtpFileSystem) { case PROXY_FTP_FILE_SYSTEM_DOS : ProxyFtpListProcessDOS (tkptr); break; case PROXY_FTP_FILE_SYSTEM_UNIX : ProxyFtpListProcessUnix (tkptr); break; case PROXY_FTP_FILE_SYSTEM_VMS : ProxyFtpListProcessVMS (tkptr); break; default : /* unknown format */ rqptr->rqResponse.PreExpired = PRE_EXPIRE_FTP_PROXY; ResponseHeader (rqptr, 200, "text/plain", 0, NULL, NULL); NetWrite (rqptr, &ProxyFtpListWriteAst, tkptr->ResponseBufferPtr, tkptr->ResponseBufferCount); return; } ProxyFtpListOutput (tkptr); SysDclAst (&ProxyFtpLifeCycle, tkptr); } /****************************************************************************/ /* ************ *** NOTE *** This function takes a pointer to a request!!! ************ */ ProxyFtpListWriteAst (REQUEST_STRUCT *rqptr) { PROXY_TASK *tkptr; /*********/ /* begin */ /*********/ if (WATCHMOD (rqptr, WATCH_MOD_PROXY)) WatchThis (WATCHITM(rqptr), WATCH_MOD_PROXY, "ProxyFtpListWriteAst()"); tkptr = rqptr->ProxyTaskPtr; SysDclAst (&ProxyFtpLifeCycle, tkptr); } /****************************************************************************/ /* Process the native DOS directory listing into the standard format used by ProxyFtpListOutput(). */ ProxyFtpListProcessDOS (PROXY_TASK *tkptr) { #define FORMAT_DOS_DATE 1 static char *MonthName [] = { NULL, "Jan","Feb","Mar","Apr","May","Jun", "Jul","Aug","Sep","Oct","Nov","Dec" }; BOOL IsDirectory, MonthDayOrdering; int DateDay, DateHour, DateMinute, DateMonth, DateYear; char *cptr, *sptr, *tptr, *zptr; char FileDate [64], FileName [256], FileSize [64]; REQUEST_STRUCT *rqptr; /*********/ /* begin */ /*********/ if (WATCHMOD (tkptr, WATCH_MOD_PROXY)) WatchThis (WATCHITM(tkptr), WATCH_MOD_PROXY, "ProxyFtpListProcessDOS()"); rqptr = tkptr->RequestPtr; #if FORMAT_DOS_DATE if (!tkptr->FtpListRaw) { MonthDayOrdering = false; cptr = tkptr->ResponseBufferPtr; while (*cptr) { if (WATCHMOD (tkptr, WATCH_MOD_PROXY)) WatchDataFormatted ("-!&E\n", cptr); /* skip leading white-space, blank lines, etc. */ while (ISLWS(*cptr)) cptr++; if (!*cptr) break; if (ISEOL(*cptr)) { while (*cptr && ISEOL(*cptr)) cptr++; continue; } if (isdigit(cptr[0]) && isdigit(cptr[1]) && (cptr[2] == '-' || cptr[2] == '/')) { DateDay = atoi(cptr); cptr += 3; DateMonth = atoi(cptr); if (DateDay > 0 && DateDay <= 31 && DateMonth > 0 && DateMonth <= 31 && DateMonth > 12 && DateMonth <= 31) { MonthDayOrdering = true; break; } } /* skip to start of next line */ while (NOTEOL(*cptr)) cptr++; while (*cptr && ISEOL(*cptr)) cptr++; } } #endif /* FORMAT_DOS_DATE */ tkptr->ResponseBufferCount = 0; cptr = tkptr->ResponseBufferPtr; while (*cptr) { if (WATCHMOD (tkptr, WATCH_MOD_PROXY)) WatchDataFormatted ("-!&E\n", cptr); /* skip leading white-space, blank lines, etc. */ while (ISLWS(*cptr)) cptr++; if (!*cptr) break; if (ISEOL(*cptr)) { while (*cptr && ISEOL(*cptr)) cptr++; continue; } /* file date and time */ zptr = (sptr = FileDate) + sizeof(FileDate)-1; while (!ISLWS(*cptr) && NOTEOL(*cptr) && sptr < zptr) *sptr++ = *cptr++; while (ISLWS(*cptr) && sptr < zptr) *sptr++ = *cptr++; while (!ISLWS(*cptr) && NOTEOL(*cptr) && sptr < zptr) *sptr++ = *cptr++; *sptr = '\0'; /* absorb intervening white-space */ while (ISLWS(*cptr)) cptr++; if (*cptr == '<' && MATCH5 (cptr, "")) { IsDirectory = true; FileSize[0] = '\0'; while (!ISLWS(*cptr) && NOTEOL(*cptr)) cptr++; while (ISLWS(*cptr)) cptr++; } else { IsDirectory = false; zptr = (sptr = FileSize) + sizeof(FileSize)-1; while (!ISLWS(*cptr) && NOTEOL(*cptr) && sptr < zptr) *sptr++ = *cptr++; *sptr = '\0'; } /* absorb intervening white-space */ while (ISLWS(*cptr)) cptr++; zptr = (sptr = FileName) + sizeof(FileName)-1; /* allow for the likes of "Copy of file.name" */ while (NOTEOL(*cptr) && sptr < zptr) *sptr++ = *cptr++; *sptr = '\0'; /* skip to start of next line */ while (NOTEOL(*cptr)) cptr++; while (*cptr && ISEOL(*cptr)) cptr++; /* if all components not present then just ignore */ if (!FileName[0] || !FileDate[0] || (!FileSize[0] && !IsDirectory)) continue; /* try to eliminate other lines ... file size really look like one? */ for (sptr = FileSize; isdigit(*sptr) || *sptr == ','; sptr++); if (*sptr) continue; /* try to eliminate other lines ... file date really look like one? */ sptr = FileDate; if (!(isdigit(sptr[0]) && isdigit(sptr[1]) && (sptr[2] == '/' || sptr[2] == '-'))) continue; #if FORMAT_DOS_DATE if (!tkptr->FtpListRaw) { sptr = FileDate; if (MonthDayOrdering) { DateMonth = atoi(sptr); sptr += 3; DateDay = atoi(sptr); } else { DateDay = atoi(sptr); sptr += 3; DateMonth = atoi(sptr); } sptr += 3; DateYear = atoi(sptr); if (DateYear <= 99) { DateYear += 1900; if (DateYear < 1970) DateYear += 100; } while (*sptr && isdigit(*sptr)) sptr++; while (*sptr && ISLWS(*sptr)) sptr++; DateHour = atoi(sptr); sptr += 3; DateMinute = atoi(sptr); sptr += 2; if (TOLO(*sptr) == 'p') { /* allow for "12:00PM" i.e. midday */ if (DateHour <= 11) DateHour += 12; } else /* if it's not PM or AM then force an error */ if (TOLO(*sptr) != 'a') DateHour = -1; if (DateMinute >= 0 && DateMinute < 59 && DateHour >= 0 && DateHour <= 23 && DateDay > 0 && DateDay <= 31 && DateMonth > 0 && DateMonth <= 12 && DateYear > 1970 && DateYear <= 2099) { /* seems to make sense, reformat */ if (DateYear == rqptr->rqTime.BeginTime7[0]) FaoToBuffer (FileDate, sizeof(FileDate), NULL, "!AZ !AZ!UL !2ZL:!2ZL", MonthName[DateMonth], DateDay < 10 ? " " : "", DateDay, DateHour, DateMinute); else FaoToBuffer (FileDate, sizeof(FileDate), NULL, "!AZ !AZ!UL !UL", MonthName[DateMonth], DateDay < 10 ? " " : "", DateDay, DateYear); } } #endif /* FORMAT_DOS_DATE */ sptr = tkptr->ResponseBufferPtr + tkptr->ResponseBufferCount; zptr = tkptr->ResponseBufferPtr + tkptr->ResponseBufferSize - 1; tptr = FileName; while (*tptr && sptr < zptr) *sptr++ = *tptr++; if (sptr < zptr) *sptr++ = '\t'; tptr = FileSize; while (*tptr && sptr < zptr) *sptr++ = *tptr++; if (sptr < zptr) *sptr++ = '\t'; tptr = FileDate; while (*tptr && sptr < zptr) *sptr++ = *tptr++; if (sptr < zptr) *sptr++ = '\n'; if (sptr >= cptr) { tkptr->ResponseBufferPtr[tkptr->ResponseBufferCount = 0] = '\0'; rqptr->rqResponse.HttpStatus = 500; ErrorGeneral (rqptr, ErrorSanityCheck, FI_LI); return; } *sptr = '\0'; tkptr->ResponseBufferCount = sptr - tkptr->ResponseBufferPtr; } tkptr->ResponseBufferPtr[tkptr->ResponseBufferCount] = '\0'; if (WATCHMOD (tkptr, WATCH_MOD_PROXY)) WatchDataFormatted ("!&Z\n", tkptr->ResponseBufferPtr); } /****************************************************************************/ /* Process the native Unix directory listing into the standard format used by ProxyFtpListOutput(). */ ProxyFtpListProcessUnix (PROXY_TASK *tkptr) { #define MAX_FIELDS 16 #define SIZE_FIELDS 128 #define SIZE_INTER_FIELDS 32 static char *MonthName [] = { "Jan","Feb","Mar","Apr","May","Jun", "Jul","Aug","Sep","Oct","Nov","Dec" }; BOOL IsDirectory; int idx, status, FieldCount, FileNameLength, MonthField; char *cptr, *sptr, *tptr, *zptr; char Fields [MAX_FIELDS][SIZE_FIELDS], InterFields [MAX_FIELDS][SIZE_INTER_FIELDS]; REQUEST_STRUCT *rqptr; /*********/ /* begin */ /*********/ if (WATCHMOD (tkptr, WATCH_MOD_PROXY)) WatchThis (WATCHITM(tkptr), WATCH_MOD_PROXY, "ProxyFtpListProcessUnix()"); rqptr = tkptr->RequestPtr; tkptr->ResponseBufferCount = 0; cptr = tkptr->ResponseBufferPtr; while (*cptr) { if (WATCHMOD (tkptr, WATCH_MOD_PROXY)) WatchDataFormatted ("-!&E\n", cptr); /* skip leading white-space, blank lines, etc. */ while (ISLWS(*cptr)) cptr++; if (!*cptr) break; if (ISEOL(*cptr)) { while (*cptr && ISEOL(*cptr)) cptr++; continue; } FieldCount = 0; while (*cptr && NOTEOL(*cptr)) { if (FieldCount < MAX_FIELDS) { zptr = (sptr = Fields[FieldCount]) + SIZE_FIELDS-1; while (*cptr && !ISLWS(*cptr) && NOTEOL(*cptr) && sptr < zptr) *sptr++ = *cptr++; *sptr = '\0'; } while (*cptr && !ISLWS(*cptr) && NOTEOL(*cptr)) cptr++; if (FieldCount < MAX_FIELDS) { zptr = (sptr = InterFields[FieldCount]) + SIZE_INTER_FIELDS-1; while (*cptr == ' ' && sptr < zptr) *sptr++ = *cptr++; *sptr = '\0'; } while (ISLWS(*cptr)) cptr++; FieldCount++; } if (WATCHMOD (tkptr, WATCH_MOD_PROXY)) for (idx = 1; idx < FieldCount; idx++) WatchDataFormatted ("!UL !&Z !&Z\n", idx, Fields[idx], InterFields[idx]); /* skip to start of next line */ while (NOTEOL(*cptr)) cptr++; while (*cptr && ISEOL(*cptr)) cptr++; /* won't make any sense at all unless there are at least six fields */ if (FieldCount < 6) continue; /* find field containing month name */ for (MonthField = 1; MonthField < FieldCount; MonthField++) { for (idx = 0; idx < 12; idx++) if (strsame (Fields[MonthField], MonthName[idx], 3)) break; if (idx < 12) break; } /* doesn't make sense if it doesn't contain a month name somewhere */ if (MonthField >= FieldCount) continue; /* dot and double-dot directories are ignored */ tptr = Fields[MonthField+3]; if (SAME2(tptr,'.\0')) continue; if (SAME2(tptr,'./')) continue; if (SAME2(tptr,'..')) continue; if (Fields[0][0] == '-') IsDirectory = false; else if (Fields[0][0] == 'd') IsDirectory = true; else if (Fields[0][0] == 'l') { /* a symbolic link, check if it looks like a file (i.e. has type) */ for (tptr = Fields[MonthField+5]; *tptr && *tptr != '.'; tptr++); if (!*tptr) IsDirectory = true; } else /* doesn't make sense, ignore */ continue; sptr = tkptr->ResponseBufferPtr + tkptr->ResponseBufferCount; zptr = tkptr->ResponseBufferPtr + tkptr->ResponseBufferSize - 1; /* file name - all remaining fields (for names containing white-space) */ for (idx = MonthField+3; idx < FieldCount; idx++) { for (tptr = Fields[idx]; *tptr && sptr < zptr; *sptr++ = *tptr++); for (tptr = InterFields[idx]; *tptr && sptr < zptr; *sptr++ = *tptr++); } if (sptr < zptr) *sptr++ = '\t'; /* file size */ if (!IsDirectory) { tptr = Fields[MonthField-1]; while (*tptr && sptr < zptr) *sptr++ = *tptr++; } if (sptr < zptr) *sptr++ = '\t'; /* file date/time */ tptr = Fields[MonthField]; while (*tptr && sptr < zptr) *sptr++ = *tptr++; if (sptr < zptr) *sptr++ = ' '; if (!Fields[MonthField+1][1] && sptr < zptr) *sptr++ = ' '; else if (Fields[MonthField+1][0] == '0') Fields[MonthField+1][0] = ' '; tptr = Fields[MonthField+1]; while (*tptr && sptr < zptr) *sptr++ = *tptr++; if (sptr < zptr) *sptr++ = ' '; if (strlen(Fields[MonthField+2]) == 4 && sptr < zptr) *sptr++ = ' '; tptr = Fields[MonthField+2]; while (*tptr && sptr < zptr) *sptr++ = *tptr++; if (sptr < zptr) *sptr++ = '\n'; if (sptr >= cptr) { tkptr->ResponseBufferPtr[tkptr->ResponseBufferCount = 0] = '\0'; rqptr->rqResponse.HttpStatus = 500; ErrorGeneral (rqptr, ErrorSanityCheck, FI_LI); return; } *sptr = '\0'; tkptr->ResponseBufferCount = sptr - tkptr->ResponseBufferPtr; } tkptr->ResponseBufferPtr[tkptr->ResponseBufferCount] = '\0'; if (WATCHMOD (tkptr, WATCH_MOD_PROXY)) WatchDataFormatted ("!&Z\n", tkptr->ResponseBufferPtr); #undef MAX_FIELDS } /****************************************************************************/ /* Process the native VMS directory listing into the standard format used by ProxyFtpListOutput(). Needless-to-say this is the more complicated of the reformatting functions. To ease integration with non-browser applications it attempts to make the standard VMS listing look more generic (particularly as far as date/time goes). If there is a version component in the path it then reverts back displaying version infomation, size and date/time in a more VMS-like fashion. */ ProxyFtpListProcessVMS (PROXY_TASK *tkptr) { BOOL IsDirectory, LooksOk; int year, FileSizeBytes; unsigned short Length; char *cptr, *sptr, *tptr, *zptr; char DateDay [4], DateMonth [4], DateTime [16], DateYear [8], FileName [128], FileSize [32]; REQUEST_STRUCT *rqptr; /*********/ /* begin */ /*********/ if (WATCHMOD (tkptr, WATCH_MOD_PROXY)) WatchThis (WATCHITM(tkptr), WATCH_MOD_PROXY, "ProxyFtpListProcessVMS()"); rqptr = tkptr->RequestPtr; cptr = tkptr->FtpFilePathPtr + tkptr->FtpFilePathLength; while (cptr > tkptr->FtpFilePathPtr && *cptr != '/' && *cptr != ';') cptr--; if (*cptr == ';') tkptr->FtpHasVersion = true; else tkptr->FtpHasVersion = false; if (WATCHMOD (tkptr, WATCH_MOD_PROXY)) WatchDataFormatted ("!&Z\n", tkptr->ResponseBufferPtr); tkptr->ResponseBufferCount = 0; cptr = tkptr->ResponseBufferPtr; while (*cptr) { if (WATCHMOD (tkptr, WATCH_MOD_PROXY)) WatchDataFormatted ("-!&E\n", cptr); /* skip leading white-space, blank lines, etc. */ while (ISLWS(*cptr)) cptr++; if (!*cptr) break; if (ISEOL(*cptr)) { while (*cptr && ISEOL(*cptr)) cptr++; continue; } LooksOk = true; zptr = (sptr = FileName) + sizeof(FileName)-1; if (tkptr->FtpHasVersion || tkptr->FtpListRaw) { while (!ISLWS(*cptr) && NOTEOL(*cptr) && sptr < zptr) *sptr++ = *cptr++; } else { while (!ISLWS(*cptr) && *cptr != ';' && NOTEOL(*cptr) && sptr < zptr) *sptr++ = *cptr++; if (*cptr == ';') { /* skip version number in file specification */ while (!ISLWS(*cptr) && NOTEOL(*cptr)) cptr++; } } *sptr = '\0'; if (!FileName[0]) LooksOk = false; while (sptr > FileName && *sptr != '.') sptr--; if (strsame (sptr, ".DIR", -1) || strsame (sptr, ".DIR;", 5)) { /* terminate to eliminate the ".DIR" */ *sptr = '\0'; IsDirectory = true; } else IsDirectory = false; /* absorb white-space after filename and before size */ while (ISLWS(*cptr)) cptr++; if (ISEOL(*cptr)) { /* file name too long for one line, try size/date on next line */ while (NOTEOL(*cptr)) cptr++; while (*cptr && ISEOL(*cptr)) cptr++; if (WATCHMOD (tkptr, WATCH_MOD_PROXY)) WatchDataFormatted ("+!&E\n", cptr); /* absorb leading white-space */ while (ISLWS(*cptr)) cptr++; } zptr = (sptr = FileSize) + sizeof(FileSize)-1; while (!ISLWS(*cptr) && NOTEOL(*cptr) && sptr < zptr) { if (!isdigit(*cptr) && *cptr != '/') LooksOk = false; *sptr++ = *cptr++; } /* a null file size indicates it's a directory */ if (IsDirectory) sptr = FileSize; else if (!(tkptr->FtpHasVersion || tkptr->FtpListRaw)) { /* not a VMS specification, convert from blocks to bytes */ *sptr = '\0'; FileSizeBytes = atoi(FileSize) * 512; FaoToBuffer (FileSize, sizeof(FileSize), &Length, "!UL", FileSizeBytes); sptr = FileSize + Length; } *sptr = '\0'; if (!FileSize[0] && !IsDirectory) LooksOk = false; /* absorb white-space between size and date */ while (ISLWS(*cptr)) cptr++; /* note that we are retrieving the date into working storage here */ sptr = DateDay; if (cptr[1] == '-') *sptr++ = ' '; if (*cptr && NOTEOL(*cptr)) *sptr++ = *cptr++; if (*cptr && *cptr != '-') *sptr++ = *cptr++; *sptr = '\0'; if (*cptr == '-') cptr++; else LooksOk = false; sptr = DateMonth; if (*cptr && NOTEOL(*cptr)) *sptr++ = *cptr++; if (*cptr && NOTEOL(*cptr)) *sptr++ = *cptr++; if (*cptr && NOTEOL(*cptr)) *sptr++ = *cptr++; *sptr = '\0'; if (*cptr == '-') cptr++; else LooksOk = false; sptr = DateYear; if (*cptr && NOTEOL(*cptr)) *sptr++ = *cptr++; if (*cptr && NOTEOL(*cptr)) *sptr++ = *cptr++; if (*cptr && NOTEOL(*cptr)) *sptr++ = *cptr++; if (*cptr && NOTEOL(*cptr)) *sptr++ = *cptr++; *sptr = '\0'; if (*cptr == ' ') cptr++; else LooksOk = false; tptr = (sptr = DateTime) + sizeof(DateTime)-1; while (!ISLWS(*cptr) && NOTEOL(*cptr) && sptr < tptr) { if (!isdigit(*cptr) && *cptr != ':' && *cptr != '.') LooksOk = false; *sptr++ = *cptr++; } *sptr = '\0'; /* if all components do not appear present */ if (!(DateDay[0] && DateMonth[0] && DateYear[0] && DateTime[0])) LooksOk = false; /* skip to start of next line */ while (NOTEOL(*cptr)) cptr++; while (*cptr && ISEOL(*cptr)) cptr++; /* if it doesn't look right then just ignore */ if (!LooksOk) continue; sptr = tkptr->ResponseBufferPtr + tkptr->ResponseBufferCount; zptr = tkptr->ResponseBufferPtr + tkptr->ResponseBufferSize - 1; for (tptr = FileName; *tptr && sptr < zptr; *sptr++ = *tptr++); if (sptr < zptr) *sptr++ = '\t'; for (tptr = FileSize; *tptr && sptr < zptr; *sptr++ = *tptr++); if (sptr < zptr) *sptr++ = '\t'; if (tkptr->FtpHasVersion || tkptr->FtpListRaw) { tptr = DateDay; if (*tptr && sptr < zptr) *sptr++ = *tptr++; if (*tptr && sptr < zptr) *sptr++ = *tptr++; if (sptr < zptr) *sptr++ = '-'; tptr = DateMonth; if (*tptr && sptr < zptr) *sptr++ = *tptr++; if (*tptr && sptr < zptr) *sptr++ = *tptr++; if (*tptr && sptr < zptr) *sptr++ = *tptr++; if (sptr < zptr) *sptr++ = '-'; tptr = DateYear; if (*tptr && sptr < zptr) *sptr++ = *tptr++; if (*tptr && sptr < zptr) *sptr++ = *tptr++; if (*tptr && sptr < zptr) *sptr++ = *tptr++; if (*tptr && sptr < zptr) *sptr++ = *tptr++; if (sptr < zptr) *sptr++ = ' '; tptr = DateTime; if (*tptr && sptr < zptr) *sptr++ = *tptr++; if (*tptr && sptr < zptr) *sptr++ = *tptr++; if (*tptr && sptr < zptr) *sptr++ = *tptr++; if (*tptr && sptr < zptr) *sptr++ = *tptr++; if (*tptr && sptr < zptr) *sptr++ = *tptr++; if (*tptr && sptr < zptr) *sptr++ = *tptr++; if (*tptr && sptr < zptr) *sptr++ = *tptr++; if (*tptr && sptr < zptr) *sptr++ = *tptr++; } else { tptr = DateMonth; if (*tptr && sptr < zptr) *sptr++ = *tptr++; if (*tptr && sptr < zptr) *sptr++ = TOLO(*tptr++); if (*tptr && sptr < zptr) *sptr++ = TOLO(*tptr++); if (sptr < zptr) *sptr++ = ' '; if (!DateDay[1] && sptr < zptr) *sptr++ = ' '; for (tptr = DateDay; *tptr && sptr < zptr; *sptr++ = *tptr++); if (sptr < zptr) *sptr++ = ' '; year = atoi(DateYear); if (year == rqptr->rqTime.BeginTime7[0]) { tptr = DateTime; if (sptr < zptr) *sptr++ = *tptr++; } else { tptr = DateYear; if (sptr < zptr) *sptr++ = ' '; } if (sptr < zptr) *sptr++ = *tptr++; if (sptr < zptr) *sptr++ = *tptr++; if (sptr < zptr) *sptr++ = *tptr++; if (sptr < zptr) *sptr++ = *tptr++; } if (sptr < zptr) *sptr++ = '\n'; if (sptr >= cptr) { tkptr->ResponseBufferPtr[tkptr->ResponseBufferCount = 0] = '\0'; rqptr->rqResponse.HttpStatus = 500; ErrorGeneral (rqptr, ErrorSanityCheck, FI_LI); return; } *sptr = '\0'; tkptr->ResponseBufferCount = sptr - tkptr->ResponseBufferPtr; } tkptr->ResponseBufferPtr[tkptr->ResponseBufferCount] = '\0'; if (WATCHMOD (tkptr, WATCH_MOD_PROXY)) WatchDataFormatted ("!&Z\n", tkptr->ResponseBufferPtr); } /****************************************************************************/ /* Output the reformatted directory listing, directories first, then files. The reformatted version is "" as in the following file example "EXAMPLE.TXT\t1560\tOct 28 2001\n" and directory example "EXAMPLE\t\tOct 28 2001\n". As can be seen from the example a directory is indicated by an empty size field. */ ProxyFtpListOutput (PROXY_TASK *tkptr) { #define NAME_WIDTH 30 #define SIZE_WIDTH 15 static char DirFao [] = "!AZ\ !#&;AZ/\ \ !#AZ\n"; static char FileFao [] = "!&@\ !#&;AZ\ !#AZ\ !#AZ\n"; static char BeginPage1Fao [] = "!AZ\ \n\ \n\ \n\ \n\ \n\ \n\ !&@\ !AZ//!AZ!AZ\n\ \n\ !&@\ !&@", BeginPage2Fao [] = "!&@\ !AZ\

\n\ \n\ \ \ \ \n\ \n\ \n\ \n"; static char EndPageFao [] = "\n\ \n\ !&@\ \n\ \n"; int status, DirCount, FileCount; unsigned short Length, FileDateLength, FileNameLength, FileSizeLength; unsigned long *vecptr; unsigned long FaoVector [32]; char *cptr, *sptr, *AltTextPtr, *FileDatePtr, *FileNamePtr, *FileSizePtr, *IconUriPtr, *IndexOfPtr; char CommaSize [32], IconImg [256], PrevContentType [128]; REQUEST_STRUCT *rqptr; /*********/ /* begin */ /*********/ if (WATCHMOD (tkptr, WATCH_MOD_PROXY)) WatchThis (WATCHITM(tkptr), WATCH_MOD_PROXY, "ProxyFtpListOutput()"); rqptr = tkptr->RequestPtr; IconImg[0] = PrevContentType[0] = '\0'; rqptr->rqResponse.PreExpired = PRE_EXPIRE_FTP_PROXY; ResponseHeader (rqptr, 200, "text/html", -1, NULL, NULL); IndexOfPtr = MsgFor(rqptr,MSG_PROXY_FTP_INDEX_OF); sptr = MsgFor(rqptr,MSG_DIR_COLUMN_HEADINGS); cptr = VmGetHeap (rqptr, strlen(sptr)+1); strcpy (cptr, sptr); while (*cptr && *cptr != '|') cptr++; if (*cptr = '|') cptr++; while (*cptr && *cptr != '|') cptr++; if (*cptr = '|') cptr++; FileNamePtr = cptr; while (*cptr && *cptr != '|') cptr++; if (*cptr = '|') *cptr++ = '\0'; while (*cptr && *cptr != '|') cptr++; if (*cptr = '|') cptr++; while (*cptr && *cptr != '|') cptr++; if (*cptr = '|') cptr++; FileDatePtr = cptr; while (*cptr && *cptr != '|') cptr++; if (*cptr = '|') *cptr++ = '\0'; FileSizePtr = cptr; vecptr = FaoVector; *vecptr++ = WASD_DOCTYPE; *vecptr++ = tkptr->FtpSYST; *vecptr++ = tkptr->FtpCWD; *vecptr++ = tkptr->FtpFileSystemPtr; if (rqptr->rqPathSet.DirFont) *vecptr++ = rqptr->rqPathSet.DirFont; else *vecptr++ = "monospace"; if (rqptr->rqPathSet.StyleSheetPtr) { *vecptr++ = "\n"; *vecptr++ = rqptr->rqPathSet.StyleSheetPtr; } else *vecptr++ = ""; *vecptr++ = IndexOfPtr; if (rqptr->rqHeader.HostPtr && rqptr->rqHeader.HostPtr[0]) *vecptr++ = rqptr->rqHeader.HostPtr; else *vecptr++ = rqptr->ServicePtr->ServerHostPort; *vecptr++ = tkptr->FtpFilePathPtr; if (rqptr->rqPathSet.HtmlBodyTagPtr) { /* */ if (rqptr->rqPathSet.HtmlBodyTagPtr[0] == '<') *vecptr++ = "!AZ\n"; else *vecptr++ = "\n"; *vecptr++ = rqptr->rqPathSet.HtmlBodyTagPtr; } else { *vecptr++ = "!AZ\n"; *vecptr++ = Config.cfDir.BodyTag; } if (rqptr->rqPathSet.HtmlHeaderPtr || rqptr->rqPathSet.HtmlHeaderTagPtr) { if (rqptr->rqPathSet.HtmlHeaderTagPtr && rqptr->rqPathSet.HtmlHeaderTagPtr[0] == '<') *vecptr++ = "!AZ\n!&/AZ"; else *vecptr++ = "
!AZ!AZ!AZ


\ \n!&/AZ"; *vecptr++ = rqptr->rqPathSet.HtmlHeaderTagPtr; *vecptr++ = rqptr->rqPathSet.HtmlHeaderPtr; } else *vecptr++ = ""; status = FaolToNet (rqptr, BeginPage1Fao, &FaoVector); if (VMSnok (status)) ErrorNoticed (rqptr, status, NULL, FI_LI); ProxyFtpIndexOf (tkptr, IndexOfPtr); vecptr = FaoVector; if (tkptr->Ftp230Ptr) { *vecptr++ = "!&;AZ\n"; if (rqptr->rqPathSet.DirFont) *vecptr++ = rqptr->rqPathSet.DirFont; else *vecptr++ = "monospace"; *vecptr++ = tkptr->Ftp230Ptr; } else *vecptr++ = ""; if (rqptr->rqPathSet.HtmlHeaderPtr || rqptr->rqPathSet.HtmlHeaderTagPtr) *vecptr++ = "
\n"; else *vecptr++ = ""; *vecptr++ = FileNamePtr; *vecptr++ = FileSizePtr; *vecptr++ = FileDatePtr; status = FaolToNet (rqptr, BeginPage2Fao, &FaoVector); if (VMSnok (status)) ErrorNoticed (rqptr, status, NULL, FI_LI); /***************/ /* directories */ /***************/ DirCount = 0; cptr = tkptr->ResponseBufferPtr; while (*cptr) { FileNamePtr = cptr; while (*cptr && *cptr != '\t') cptr++; FileNameLength = cptr - FileNamePtr; if (*cptr) cptr++; FileSizePtr = cptr; while (*cptr && *cptr != '\t') cptr++; FileSizeLength = cptr - FileSizePtr; if (*cptr) cptr++; FileDatePtr = cptr; while (*cptr && *cptr != '\n') cptr++; FileDateLength = cptr - FileDatePtr; if (*cptr) cptr++; /* empty file size indicates a directory */ if (*FileSizePtr != '\t') continue; if (tkptr->FtpListHide && *FileNamePtr == '.') continue; if (!IconImg[0]) { ConfigIconFor (ConfigContentTypeDir, &IconUriPtr, &AltTextPtr); FaoToBuffer (IconImg, sizeof(IconImg), NULL, "\"!AZ\"", rqptr->ServicePtr->RequestSchemeNamePtr, rqptr->ServicePtr->ServerHostPort, IconUriPtr, AltTextPtr); } vecptr = FaoVector; *vecptr++ = IconImg; *vecptr++ = FileNameLength; *vecptr++ = FileNamePtr; *vecptr++ = tkptr->FtpWildPtr; *vecptr++ = tkptr->RequestUriQueryStringPtr; *vecptr++ = FileNameLength; *vecptr++ = FileNamePtr; *vecptr++ = FileDateLength; *vecptr++ = FileDatePtr; status = FaolToNet (rqptr, DirFao, &FaoVector); if (VMSnok (status)) ErrorNoticed (rqptr, status, NULL, FI_LI); DirCount++; } /*********/ /* files */ /*********/ FileCount = 0; cptr = tkptr->ResponseBufferPtr; while (*cptr) { FileNamePtr = cptr; while (*cptr && *cptr != '\t') cptr++; FileNameLength = cptr - FileNamePtr; if (*cptr) cptr++; FileSizePtr = cptr; while (*cptr && *cptr != '\t') cptr++; FileSizeLength = cptr - FileSizePtr; if (*cptr) cptr++; FileDatePtr = cptr; while (*cptr && *cptr != '\n') cptr++; FileDateLength = cptr - FileDatePtr; if (*cptr) cptr++; /* empty file size indicates a directory */ if (*FileSizePtr == '\t') continue; if (tkptr->FtpListHide && *FileNamePtr == '.') continue; FileNamePtr[FileNameLength] = '\0'; for (sptr = FileNamePtr + FileNameLength; sptr > FileNamePtr && *sptr != '.'; sptr--); if (*sptr == '.') sptr = ConfigContentType (NULL, sptr); else sptr = ConfigDefaultFileContentType; FileNamePtr[FileNameLength] = '\t'; if (!PrevContentType[0] || !strsame (sptr, PrevContentType, -1)) { strzcpy (PrevContentType, sptr, sizeof(PrevContentType)); ConfigIconFor (sptr, &IconUriPtr, &AltTextPtr); FaoToBuffer (IconImg, sizeof(IconImg), NULL, "\"!AZ\"", rqptr->ServicePtr->RequestSchemeNamePtr, rqptr->ServicePtr->ServerHostPort, IconUriPtr, AltTextPtr); } if (DirCount && !FileCount) FaoToNet (rqptr, " \n"); vecptr = FaoVector; if (tkptr->FtpListAlt) { *vecptr++ = "!AZ"; *vecptr++ = FileNameLength; *vecptr++ = FileNamePtr; *vecptr++ = tkptr->RequestUriQueryStringPtr; *vecptr++ = tkptr->RequestUriQueryStringPtr[0] ? "" : "?"; *vecptr++ = strsame(sptr, "text/", 5) ? "+octet+image" : "+text+ascii"; *vecptr++ = IconImg; } else *vecptr++ = IconImg; *vecptr++ = FileNameLength; *vecptr++ = FileNamePtr; *vecptr++ = tkptr->RequestUriQueryStringPtr; *vecptr++ = FileNameLength; *vecptr++ = FileNamePtr; for (sptr = FileSizePtr; *sptr && *sptr != '\t' && *sptr != ','; sptr++); if (!(tkptr->FtpHasVersion || tkptr->FtpListRaw) && *sptr != ',') { FileSizePtr[FileSizeLength] = '\0'; FaoToBuffer (CommaSize, sizeof(CommaSize), &Length, "!&,AZ", FileSizePtr); FileSizePtr[FileSizeLength] = '\t'; *vecptr++ = Length; *vecptr++ = CommaSize; } else { *vecptr++ = FileSizeLength; *vecptr++ = FileSizePtr; } *vecptr++ = FileDateLength; *vecptr++ = FileDatePtr; status = FaolToNet (rqptr, FileFao, &FaoVector); if (VMSnok (status)) ErrorNoticed (rqptr, status, NULL, FI_LI); FileCount++; } vecptr = &FaoVector; if (rqptr->rqPathSet.HtmlFooterPtr || rqptr->rqPathSet.HtmlFooterTagPtr) { if (rqptr->rqPathSet.HtmlFooterTagPtr && rqptr->rqPathSet.HtmlFooterTagPtr[0] == '<') *vecptr++ = "!AZ\n!&@"; else *vecptr++ = "\n!&@"; *vecptr++ = rqptr->rqPathSet.HtmlFooterTagPtr; *vecptr++ = "!&/AZ
\n"; *vecptr++ = rqptr->rqPathSet.HtmlFooterPtr; } else *vecptr++ = ""; status = FaolToNet (rqptr, EndPageFao, &FaoVector); if (VMSnok (status)) ErrorNoticed (rqptr, status, NULL, FI_LI); #undef NAME_WIDTH #undef SIZE_WIDTH } /*****************************************************************************/ /* Provide the "Index of" page heading in any of the post-v8.2, traditional WASD, or the "ABI" styles. */ int ProxyFtpIndexOf ( PROXY_TASK *tkptr, char *IndexOfPtr ) { /* allows as directory nesting of up to 64 (should be enough ;^) */ static char DotDotSlash64 [] = "FtpFilePathPtr); rqptr = tkptr->RequestPtr; if (rqptr->rqPathSet.DirStyle == MAPURL_DIR_STYLE_ORIGINAL) { /**************************/ /* traditional WASD style */ /**************************/ vecptr = &FaoVector; *vecptr++ = IndexOfPtr; *vecptr++ = tkptr->FtpFilePathPtr; status = FaolToNet (rqptr, "

!AZ  !AZ

\n", &FaoVector); return (status); } /****************/ /* other styles */ /****************/ /* calculate the number of directory elements */ SlashCount = 0; FinalSlashPtr = ""; for (cptr = tkptr->FtpFilePathPtr; *cptr; cptr++) { if (*cptr == '/') { SlashCount++; FinalSlashPtr = cptr; } } if (SlashCount > 64) return (SS$_BUGCHECK); if (*FinalSlashPtr) FinalSlashPtr++; vecptr = &FaoVector; if (rqptr->rqPathSet.DirStyle == MAPURL_DIR_STYLE_HTDIR) { /* ABI's style begins with the server name as a 'root' anchor */ if (rqptr->rqHeader.HostPtr && rqptr->rqHeader.HostPtr[0]) *vecptr++ = rqptr->rqHeader.HostPtr; else *vecptr++ = rqptr->ServicePtr->ServerHostPort; *vecptr++ = tkptr->RequestUriQueryStringPtr; status = FaolToNet (rqptr, "

//!AZ!AZ/", &FaoVector); } else { /* WASD style provides an "Index of" heading */ *vecptr++ = IndexOfPtr; *vecptr++ = tkptr->RequestUriQueryStringPtr; if (rqptr->rqHeader.HostPtr && rqptr->rqHeader.HostPtr[0]) *vecptr++ = rqptr->rqHeader.HostPtr; else *vecptr++ = rqptr->ServicePtr->ServerHostPort; status = FaolToNet (rqptr, "

!AZ  //!AZ/", &FaoVector); } if (VMSnok (status)) return (status); cptr = tkptr->FtpFilePathPtr; /* provide a self-relative (../) anchor for each directory element */ if (SlashCount) SlashCount--; while (SlashCount-- > 0) { vecptr = &FaoVector; *vecptr++ = 11 + (SlashCount * 3); *vecptr++ = DotDotSlash64; if (SlashCount) *vecptr++ = FinalSlashPtr; else *vecptr++ = ""; *vecptr++ = tkptr->RequestUriQueryStringPtr; if (*cptr == '/') cptr++; for (sptr = cptr; *sptr && *sptr != '/'; sptr++); if (rqptr->rqPathSet.DirStyle == MAPURL_DIR_STYLE_HTDIR) { /* ABI's style, trailing slashes are part of the link */ if (*sptr) sptr++; *vecptr++ = sptr - cptr; *vecptr++ = cptr; *vecptr++ = 0; *vecptr++ = ""; } else { /* WASD style, trailing slashes are not part of the link */ *vecptr++ = sptr - cptr; *vecptr++ = cptr; *vecptr++ = 1; *vecptr++ = sptr; } if (VMSnok (status)) return (status); status = FaolToNet (rqptr, "!#AZ!AZ!&;AZ\">!#AZ!#AZ", &FaoVector); cptr = sptr; } if (rqptr->rqPathSet.DirStyle == MAPURL_DIR_STYLE_HTDIR) { /* ABI's style, 'file' name and type element as an anchor */ if (*cptr == '/') cptr++; vecptr = &FaoVector; *vecptr++ = cptr; status = FaolToNet (rqptr, "!-!AZ

\n", &FaoVector); } else { /* WASD style, 'file' name and type just displayed */ if (*cptr == '/') cptr++; vecptr = &FaoVector; *vecptr++ = cptr; status = FaolToNet (rqptr, "!AZ

\n", &FaoVector); } return (status); } /****************************************************************************/ /* Before retrieving a file set the FTP server transfer TYPE to ASCII or IMAGE depending on whether carriage-control is require to be translated (at least in some cases) or whether the file should be considered a bag-o'-bytes. */ ProxyFtpRetrieveMode (PROXY_TASK *tkptr) { char *cptr; CONTENT_TYPE ContentType; REQUEST_STRUCT *rqptr; /*********/ /* begin */ /*********/ if (WATCHMOD (tkptr, WATCH_MOD_PROXY)) WatchThis (WATCHITM(tkptr), WATCH_MOD_PROXY, "ProxyFtpRetrieveMode()"); rqptr = tkptr->RequestPtr; /* find the start of the file type (if any) */ cptr = tkptr->FtpFilePathPtr + tkptr->FtpFilePathLength; while (cptr > tkptr->FtpFilePathPtr && *cptr != '.' && *cptr != '/') cptr--; /* content type will subsequently be used by ProxyFtpRetrieve() */ if (*cptr == '.') tkptr->FtpContentTypePtr = ConfigContentType (&ContentType, cptr); else tkptr->FtpContentTypePtr = ConfigDefaultFileContentType; /* is the client explicitly setting the transfer TYPE? */ cptr = rqptr->rqHeader.QueryStringPtr; if (ContentType.FtpMode == 'A' || ProxyFtpInQueryString (cptr, "ascii")) ProxyFtpCommand (tkptr, true, "TYPE A"); else if (ContentType.FtpMode == 'I' || ContentType.FtpMode == 'B' || ProxyFtpInQueryString (cptr, "image")) ProxyFtpCommand (tkptr, true, "TYPE I"); else if (strsame (tkptr->FtpContentTypePtr, "text/", 5)) ProxyFtpCommand (tkptr, true, "TYPE A"); else ProxyFtpCommand (tkptr, true, "TYPE I"); } /****************************************************************************/ /* Retrieve a file from the remote FTP server. This function initializes the required data and issues a RETR command to the FTP server. If successful ProxyFtpLifeCycle() will then call ProxyFtpRetrieveAst() to start retrieving the file and writing it to the client. */ ProxyFtpRetrieve (PROXY_TASK *tkptr) { int status; char *cptr, *sptr, *zptr; char ContentType [64]; REQUEST_STRUCT *rqptr; /*********/ /* begin */ /*********/ if (WATCHMOD (tkptr, WATCH_MOD_PROXY)) WatchThis (WATCHITM(tkptr), WATCH_MOD_PROXY, "ProxyFtpRetrieve()"); rqptr = tkptr->RequestPtr; InstanceGblSecIncrLong (&ProxyAccountingPtr->FtpRetrCount); cptr = rqptr->rqHeader.QueryStringPtr; if (cptr[0] && ProxyFtpInQueryString (cptr, "text")) ResponseHeader (rqptr, 200, "text/plain", -1, NULL, NULL); else if (cptr[0] && ProxyFtpInQueryString (cptr, "octet")) ResponseHeader (rqptr, 200, "application/octet-stream", -1, NULL, NULL); else if (cptr[0] && (sptr = ProxyFtpInQueryString (cptr, "content:"))) { /* keyword style */ cptr = sptr + 9; zptr = (sptr = ContentType) + sizeof(ContentType)-1; while (*cptr && *cptr != '+' && sptr < zptr) *sptr++ = *cptr++; *sptr = '\0'; ResponseHeader (rqptr, 200, ContentType, -1, NULL, NULL); } else if (cptr[0] && (sptr = ProxyFtpInQueryString (cptr, "content="))) { /* form field style */ cptr = sptr + 9; zptr = (sptr = ContentType) + sizeof(ContentType)-1; while (*cptr && *cptr != '&' && sptr < zptr) *sptr++ = *cptr++; *sptr = '\0'; ResponseHeader (rqptr, 200, ContentType, -1, NULL, NULL); } else /* content type has been set up by ProxyFtpRetrieveMode() */ ResponseHeader (rqptr, 200, tkptr->FtpContentTypePtr, -1, NULL, NULL); cptr = tkptr->FtpFilePathPtr + tkptr->FtpFilePathLength; /* find the start of the file name */ while (cptr > tkptr->FtpFilePathPtr && *cptr != '/') cptr--; cptr++; if (!tkptr->ResponseBufferPtr) { tkptr->ResponseBufferSize = ProxyReadBufferSize; tkptr->ResponseBufferPtr = VmGetHeap (rqptr, ProxyReadBufferSize); } /* reset the IO status block for the first read */ tkptr->FtpDataReadIOsb.Status = tkptr->FtpDataReadIOsb.Count = 0; ProxyFtpCommand (tkptr, true, "RETR !AZ", cptr); } /****************************************************************************/ /* When a read from the remote FTP server completes this function is called as an AST. Check the read status. If OK then write it to the client, with an AST to ProxyFtpRetrieveWriteAst(). If not OK check if the status is reset. If so it's the first call and so kick off the loop with an initial read. If a real error then abort the transfer. */ ProxyFtpRetrieveAst (PROXY_TASK *tkptr) { REQUEST_STRUCT *rqptr; /*********/ /* begin */ /*********/ if (WATCHMOD (tkptr, WATCH_MOD_PROXY)) WatchThis (WATCHITM(tkptr), WATCH_MOD_PROXY, "ProxyFtpRetrieveAst() !&F !&S !UL", &ProxyFtpRetrieveAst, tkptr->FtpDataReadIOsb.Status, tkptr->FtpDataReadIOsb.Count); rqptr = tkptr->RequestPtr; if (VMSok (tkptr->FtpDataReadIOsb.Status)) { /* write it to the client */ NetWrite (rqptr, &ProxyFtpRetrieveWriteAst, tkptr->ResponseBufferPtr, tkptr->FtpDataReadIOsb.Count); return; } if (!tkptr->FtpDataReadIOsb.Status) { /* first call for the retrieve, kick off the first read */ ProxyFtpDataReadRaw (tkptr, &ProxyFtpRetrieveAst, tkptr->ResponseBufferPtr, tkptr->ResponseBufferSize); return; } if (tkptr->FtpDataReadIOsb.Status == SS$_LINKDISCON) { /* transfer is complete (or aborted!) */ ProxyFtpDataCloseSocket (tkptr); SysDclAst (&ProxyFtpLifeCycle, tkptr); return; } rqptr->rqResponse.HttpStatus = 502; rqptr->rqResponse.ErrorTextPtr = tkptr->RequestHostPort; ErrorVmsStatus (rqptr, tkptr->FtpDataReadIOsb.Status, FI_LI); tkptr->FtpState = PROXY_FTP_STATE_ABORT; SysDclAst (&ProxyFtpLifeCycle, tkptr); } /****************************************************************************/ /* ************ *** NOTE *** This function takes a pointer to a request!!! ************ Called as an AST when a write to the client, queued by ProxyFtpRetrieveAst(), completes. Check the status of the write and if OK queue another read from the FTP server. If an error abort the transfer. */ ProxyFtpRetrieveWriteAst (REQUEST_STRUCT *rqptr) { PROXY_TASK *tkptr; /*********/ /* begin */ /*********/ if (WATCHMOD (rqptr, WATCH_MOD_PROXY)) WatchThis (WATCHITM(rqptr), WATCH_MOD_PROXY, "ProxyFtpRetrieveWriteAst() !&S !UL", rqptr->NetIoPtr->WriteStatus, rqptr->NetIoPtr->WriteCount); tkptr = rqptr->ProxyTaskPtr; if (VMSok (rqptr->NetIoPtr->WriteStatus)) { ProxyFtpDataReadRaw (tkptr, &ProxyFtpRetrieveAst, tkptr->ResponseBufferPtr, tkptr->ResponseBufferSize); return; } /* write to client failed, just abandon the request */ tkptr->FtpState = PROXY_FTP_STATE_ABORT; SysDclAst (ProxyFtpLifeCycle, tkptr); } /****************************************************************************/ /* Begin to read the request body. */ ProxyFtpStoreBodyReadBegin (PROXY_TASK *tkptr) { int status; char *cptr, *sptr, *zptr; char ContentType [64]; REQUEST_STRUCT *rqptr; /*********/ /* begin */ /*********/ if (WATCHMOD (tkptr, WATCH_MOD_PROXY)) WatchThis (WATCHITM(tkptr), WATCH_MOD_PROXY, "ProxyFtpStoreBodyReadBegin()"); rqptr = tkptr->RequestPtr; InstanceGblSecIncrLong (&ProxyAccountingPtr->FtpStorCount); /* initialize the data transfer data */ if (ConfigSameContentType (rqptr->rqHeader.ContentTypePtr, "application/x-www-form-urlencoded", -1)) BodyReadBegin (rqptr, &ProxyFtpStoreBodyReadAst, &BodyProcessUrlEncoded); else if (ConfigSameContentType (rqptr->rqHeader.ContentTypePtr, "multipart/", 10)) BodyReadBegin (rqptr, &ProxyFtpStoreBodyReadAst, &BodyProcessMultipartFormData); else BodyReadBegin (rqptr, &ProxyFtpStoreBodyReadAst, NULL); } /****************************************************************************/ /* A chunk of the request body has been read from the client. This function will be called at least twice. After the first read, and the file name has been resolved from the request body, it needs to issue the STOR command. It does this and after the response ProxyFtpLifeCycle() calls this same function (which untouched body data) again. Write the body data to the remote FTP server. */ ProxyFtpStoreBodyReadAst (REQUEST_STRUCT *rqptr) { char *cptr, *sptr; BODY_PROCESS *prptr; PROXY_TASK *tkptr; /*********/ /* begin */ /*********/ if (WATCHMOD (rqptr, WATCH_MOD_PROXY)) WatchThis (WATCHITM(rqptr), WATCH_MOD_PROXY, "ProxyFtpStoreBodyReadAst() !&F !&X !&S !&X !UL", &ProxyFtpStoreBodyReadAst, rqptr->rqBody.ProcessPtr, rqptr->rqBody.DataStatus, rqptr->rqBody.DataPtr, rqptr->rqBody.DataCount); tkptr = rqptr->ProxyTaskPtr; prptr = rqptr->rqBody.ProcessPtr; if (!(tkptr->FtpTypeDone && tkptr->FtpTypeDone)) { cptr = NULL; if (prptr) { if (prptr->MultipartFileName[0]) cptr = prptr->MultipartFileName; if (prptr->MultipartUploadFileName[0]) cptr = prptr->MultipartUploadFileName; if (sptr = cptr) { /* all we're interested is the name component */ while (*cptr) cptr++; while (cptr > sptr && *cptr != '/' && *cptr != '\\' && *cptr != ']') cptr--; if (*cptr == '/' || *cptr == '\\' || *cptr == ']') cptr++; } } if (!cptr) { cptr = tkptr->FtpFilePathPtr + tkptr->FtpFilePathLength; /* find the start of the file name */ while (cptr > tkptr->FtpFilePathPtr && *cptr != '/') cptr--; cptr++; } if (!cptr || !*cptr) { rqptr->rqResponse.HttpStatus = 400; ErrorGeneral (rqptr, MsgFor(rqptr,MSG_PROXY_FTP_NO_FILENAME), FI_LI); tkptr->FtpState = PROXY_FTP_STATE_QUIT; SysDclAst (&ProxyFtpLifeCycle, tkptr); } } if (!tkptr->FtpTypeDone) { /*********************/ /* send TYPE command */ /*********************/ tkptr->FtpTypeDone = true; if (WATCHMOD (rqptr, WATCH_MOD_PROXY)) WatchThis (WATCHITM(rqptr), WATCH_MOD_PROXY, "!&Z", prptr ? prptr->FtpFileType : ""); /* is the client explicitly setting the transfer TYPE? */ cptr = rqptr->rqHeader.QueryStringPtr; if (ProxyFtpInQueryString (cptr, "ascii") || (prptr && TOLO(prptr->FtpFileType[0]) == 'a')) ProxyFtpCommand (tkptr, true, "TYPE A"); else if (ProxyFtpInQueryString (cptr, "image") || (prptr && TOLO(prptr->FtpFileType[0]) == 'i')) ProxyFtpCommand (tkptr, true, "TYPE I"); else if (prptr && prptr->MultipartContentTypePtr) { if (ConfigSameContentType (prptr->MultipartContentTypePtr, "text/", 5)) ProxyFtpCommand (tkptr, true, "TYPE A"); else ProxyFtpCommand (tkptr, true, "TYPE I"); } else if (rqptr->rqHeader.ContentTypePtr) { if (ConfigSameContentType (rqptr->rqHeader.ContentTypePtr, "text/", 5)) ProxyFtpCommand (tkptr, true, "TYPE A"); else ProxyFtpCommand (tkptr, true, "TYPE I"); } else ProxyFtpCommand (tkptr, true, "TYPE I"); /* will return to this function with the STOR command done! */ return; } if (!tkptr->FtpStorDone) { /*********************/ /* send STOR command */ /*********************/ tkptr->FtpStorDone = true; cptr = NULL; if (prptr) { if (prptr->MultipartFileName[0]) cptr = prptr->MultipartFileName; if (prptr->MultipartUploadFileName[0]) cptr = prptr->MultipartUploadFileName; if (sptr = cptr) { /* all we're interested is the name component */ while (*cptr) cptr++; while (cptr > sptr && *cptr != '/' && *cptr != '\\' && *cptr != ']') cptr--; if (*cptr == '/' || *cptr == '\\' || *cptr == ']') cptr++; } } if (!cptr) { cptr = tkptr->FtpFilePathPtr + tkptr->FtpFilePathLength; /* find the start of the file name */ while (cptr > tkptr->FtpFilePathPtr && *cptr != '/') cptr--; cptr++; } if (!cptr || !*cptr) { rqptr->rqResponse.HttpStatus = 400; ErrorGeneral (rqptr, MsgFor(rqptr,MSG_PROXY_FTP_NO_FILENAME), FI_LI); tkptr->FtpState = PROXY_FTP_STATE_QUIT; SysDclAst (&ProxyFtpLifeCycle, tkptr); } ProxyFtpCommand (tkptr, true, "STOR !AZ", cptr); /* will return to this function with the STOR command done! */ return; } /*****************/ /* transfer data */ /*****************/ if (VMSok (rqptr->rqBody.DataStatus)) { /* write this buffered chunk of the request body to the FTP server */ ProxyFtpDataWriteRaw (tkptr, &ProxyFtpStoreAst, rqptr->rqBody.DataPtr, rqptr->rqBody.DataCount); return; } if (rqptr->rqBody.DataStatus == SS$_ENDOFFILE) { /* end of body */ ProxyFtpDataCloseSocket (tkptr); SysDclAst (&ProxyFtpLifeCycle, tkptr); return; } rqptr->rqResponse.HttpStatus = 502; rqptr->rqResponse.ErrorTextPtr = tkptr->RequestHostPort; ErrorVmsStatus (rqptr, rqptr->rqBody.DataStatus, FI_LI); tkptr->FtpState = PROXY_FTP_STATE_ABORT; SysDclAst (&ProxyFtpLifeCycle, tkptr); } /****************************************************************************/ /* A chunk of the request body has been written to the remote FTP server. */ ProxyFtpStoreAst (PROXY_TASK *tkptr) { int DataCount; char *DataPtr; REQUEST_STRUCT *rqptr; /*********/ /* begin */ /*********/ if (WATCHMOD (tkptr, WATCH_MOD_PROXY)) WatchThis (WATCHITM(tkptr), WATCH_MOD_PROXY, "ProxyFtpStoreAst() !&F !&S !UL", &ProxyFtpStoreAst, tkptr->FtpDataWriteIOsb.Status, tkptr->FtpDataWriteIOsb.Count); rqptr = tkptr->RequestPtr; if (VMSok (tkptr->FtpDataWriteIOsb.Status)) { BodyRead (rqptr); return; } rqptr->rqResponse.HttpStatus = 502; rqptr->rqResponse.ErrorTextPtr = tkptr->RequestHostPort; ErrorVmsStatus (rqptr, tkptr->FtpDataWriteIOsb.Status, FI_LI); tkptr->FtpState = PROXY_FTP_STATE_ABORT; SysDclAst (&ProxyFtpLifeCycle, tkptr); } /****************************************************************************/ /* Delete a file from the remote FTP server. This function initializes the required data and issues a DELE command to the FTP server. */ ProxyFtpDelete (PROXY_TASK *tkptr) { int status; char *cptr, *sptr; char ContentType [64]; REQUEST_STRUCT *rqptr; /*********/ /* begin */ /*********/ if (WATCHMOD (tkptr, WATCH_MOD_PROXY)) WatchThis (WATCHITM(tkptr), WATCH_MOD_PROXY, "ProxyFtpRetrieve()"); rqptr = tkptr->RequestPtr; InstanceGblSecIncrLong (&ProxyAccountingPtr->FtpDeleCount); if (tkptr->FtpFileSystem == PROXY_FTP_FILE_SYSTEM_VMS) { /* of course, VMS requires a version number! */ cptr = tkptr->FtpFilePathPtr + tkptr->FtpFilePathLength; while (cptr > tkptr->FtpFilePathPtr && *cptr != '/' && *cptr != ';') cptr--; if (cptr[0] == ';') { if (cptr[1]) sptr = ""; else sptr = "*"; } else if (tkptr->FtpSpecific == PROXY_FTP_SPECIFIC_MADGOAT) sptr = ""; else sptr = ";*"; } else sptr = ""; /* find the start of the file name */ cptr = tkptr->FtpFilePathPtr + tkptr->FtpFilePathLength; while (cptr > tkptr->FtpFilePathPtr && *cptr != '/') cptr--; cptr++; if (!tkptr->ResponseBufferPtr) { tkptr->ResponseBufferSize = ProxyReadBufferSize; tkptr->ResponseBufferPtr = VmGetHeap (rqptr, ProxyReadBufferSize); } /* reset the IO status block for the first read */ tkptr->FtpDataReadIOsb.Status = tkptr->FtpDataReadIOsb.Count = 0; ProxyFtpCommand (tkptr, true, "DELE !AZ!AZ", cptr, sptr); } /****************************************************************************/ /* Searches the query string for the keyword. Keywords are designed to be detected whether supplied as "?keyword", "?keyword1+keyword2", "keyword=anything", "keyword1=anything&keyword2=anything", or where the keyword occurs as the value of a form field, for example "?type=ascii". This allows for simply adding the keyword to the URL or use in an HTML form. Needless to say, the keywords must be unique. Returns a pointer to the start of the keyword if found or NULL if not. */ char* ProxyFtpInQueryString ( char *QueryStringPtr, char *KeywordPtr ) { int KeywordLength; char *cptr, *sptr; /*********/ /* begin */ /*********/ if (WATCH_MODULE(WATCH_MOD_PROXY)) WatchThis (WATCHALL, WATCH_MOD_PROXY, "ProxyFtpInQueryString() !&Z !&Z", QueryStringPtr, KeywordPtr); KeywordLength = strlen(KeywordPtr); cptr = QueryStringPtr; while (*cptr) { /* "content=" is a special case for field value processing */ if (strsame (cptr, "content=", 8) && strsame (KeywordPtr, "content", 7)) return (cptr); /* all other the keywords can be anywhere in the string */ if (strsame (cptr, KeywordPtr, KeywordLength)) return (cptr); while (*cptr && *cptr != '+' && !cptr != '&' && *cptr != '=') cptr++; if (*cptr) cptr++; } return (NULL); } /****************************************************************************/ /* ************ *** NOTE *** This function takes a pointer to a request!!! ************ This function is called from ProxyRequestBegin() and so has nothing to do with ProxyFtpLifeCycle(). */ ProxyFtpStoreForm (REQUEST_STRUCT *rqptr) { static char FormFao [] = "!AZ\ \n\ \n\ FTP Upload\n\ \n\ \n\
\n\ default\n\ ASCII\n\ image\n\
\n\
\n\
\n\ \n\ \n"; char *cptr; /*********/ /* begin */ /*********/ if (WATCHMOD (rqptr, WATCH_MOD_PROXY)) WatchThis (WATCHITM(rqptr), WATCH_MOD_PROXY, "ProxyFtpStoreForm()"); /* make sure this form is only called the once! */ for (cptr = rqptr->rqHeader.RequestUriPtr; *cptr && *cptr != '?'; cptr++); if (*cptr) cptr++; cptr = ProxyFtpInQueryString (cptr, "upload"); if (cptr) *cptr = 'v'; rqptr->rqResponse.PreExpired = PRE_EXPIRE_FTP_PROXY; ResponseHeader (rqptr, 200, "text/html", -1, NULL, NULL); FaoToNet (rqptr, FormFao, WASD_DOCTYPE, rqptr->rqHeader.RequestUriPtr, "Upload"); } /****************************************************************************/ /* Attempt to open a socket connected to the IP address and port specified by the PASV response. This will become the data transfer connection. ASTs to ProxyFtpDataConnect(). */ ProxyFtpDataConnect (PROXY_TASK *tkptr) { static BOOL UseFullDuplexClose = true; int qiofun, status; void *BindSocketNamePtr; SOCKADDRIN *sin4ptr; SOCKADDRIN6 *sin6ptr; TCP_SOCKET_ITEM *TcpSocketPtr; VMS_ITEM_LIST2 *il2ptr; /*********/ /* begin */ /*********/ if (WATCHMOD (tkptr, WATCH_MOD_PROXY)) WatchThis (WATCHITM(tkptr), WATCH_MOD_PROXY, "ProxyFtpDataConnect()"); if (WATCHING (tkptr, WATCH_PROXY)) WatchThis (WATCHITM(tkptr), WATCH_PROXY, "FTP-DATA connect !&I,!UL", &tkptr->FtpDataIpAddress, tkptr->FtpDataIpPort); if (IPADDRESS_IS_SET (&tkptr->BindIpAddress)) { /* bind the FTP data socket to a specific IP address */ if (IPADDRESS_IS_V4 (&tkptr->BindIpAddress)) { SOCKADDRESS_ZERO4 (&tkptr->FtpDataBindSocketName) sin4ptr = &tkptr->FtpDataBindSocketName.sa.v4; sin4ptr->SIN$B_FAMILY = TCPIP$C_AF_INET; sin4ptr->SIN$W_PORT = 0; IPADDRESS_SET4 (&sin4ptr->SIN$L_ADDR, &tkptr->BindIpAddress) il2ptr = &tkptr->FtpDataBindSocketNameItem; il2ptr->buf_len = sizeof(SOCKADDRIN); il2ptr->item = 0; il2ptr->buf_addr = &tkptr->FtpDataBindSocketName.sa.v4; } else if (IPADDRESS_IS_V6 (&tkptr->BindIpAddress)) { SOCKADDRESS_ZERO6 (&tkptr->FtpDataBindSocketName) sin6ptr = &tkptr->FtpDataBindSocketName.sa.v6; sin6ptr->SIN6$B_FAMILY = TCPIP$C_AF_INET; sin6ptr->SIN6$W_PORT = 0; IPADDRESS_SET6 (sin6ptr->SIN6$R_ADDR_OVERLAY.SIN6$T_ADDR, &tkptr->BindIpAddress) il2ptr = &tkptr->FtpDataBindSocketNameItem; il2ptr->buf_len = sizeof(SOCKADDRIN6); il2ptr->item = 0; il2ptr->buf_addr = &tkptr->FtpDataBindSocketName.sa.v6; } else ErrorExitVmsStatus (SS$_BUGCHECK, ErrorSanityCheck, FI_LI); BindSocketNamePtr = (void*)il2ptr; } else BindSocketNamePtr = 0; if (IPADDRESS_IS_V4 (&tkptr->FtpDataIpAddress)) { TcpSocketPtr = &TcpIpSocket4; qiofun = IO$_SETMODE; } else if (IPADDRESS_IS_V6 (&tkptr->FtpDataIpAddress)) { TcpSocketPtr = &TcpIpSocket6; qiofun = IO$_SETMODE | IO$M_EXTEND; } else ErrorExitVmsStatus (SS$_BUGCHECK, ErrorSanityCheck, FI_LI); for (;;) { /* assign a channel to the internet template device */ status = sys$assign (&TcpIpDeviceDsc, &tkptr->FtpDataChannel, 0, 0); if (VMSnok (status)) { /* leave it to the AST function to report! */ tkptr->FtpDataConnectIOsb.Status = status; SysDclAst (ProxyFtpDataConnectAst, tkptr); return; } /* make the channel a TCP, connection-oriented socket */ status = sys$qiow (EfnWait, tkptr->FtpDataChannel, qiofun, &tkptr->FtpDataConnectIOsb, 0, 0, TcpSocketPtr, 0, BindSocketNamePtr, 0, UseFullDuplexClose ? &TcpIpFullDuplexCloseOption : 0, 0); if (WATCHMOD (tkptr, WATCH_MOD_PROXY)) WatchThis (WATCHITM(tkptr), WATCH_MOD_PROXY, "sys$qiow() !&X !&X", status, tkptr->FtpDataConnectIOsb.Status); if (VMSok (status) && VMSok (tkptr->FtpDataConnectIOsb.Status)) break; if (!UseFullDuplexClose) break; UseFullDuplexClose = false; /* Multinet 3.2 UCX driver barfs on FULL_DUPLEX_CLOSE, try without */ sys$dassgn (tkptr->FtpDataChannel); } /* it's a $QIOW so the IO status block is valid */ if (VMSok (status) && VMSnok (tkptr->FtpDataConnectIOsb.Status)) status = tkptr->FtpDataConnectIOsb.Status; if (VMSnok (status)) { /* leave it to the AST function to report! */ tkptr->FtpDataConnectIOsb.Status = status; SysDclAst (ProxyFtpDataConnectAst, tkptr); return; } if (IPADDRESS_IS_V4 (&tkptr->FtpDataIpAddress)) { SOCKADDRESS_ZERO4 (&tkptr->FtpDataSocketName) sin4ptr = &tkptr->FtpDataSocketName.sa.v4; sin4ptr->SIN$B_FAMILY = TCPIP$C_AF_INET; sin4ptr->SIN$W_PORT = tkptr->FtpDataIpPort; IPADDRESS_SET4 (&sin4ptr->SIN$L_ADDR, &tkptr->FtpDataIpAddress) il2ptr = &tkptr->FtpDataSocketNameItem; il2ptr->buf_len = sizeof(SOCKADDRIN); il2ptr->item = TCPIP$C_SOCK_NAME; il2ptr->buf_addr = sin4ptr; qiofun = IO$_ACCESS; } else if (IPADDRESS_IS_V6 (&tkptr->FtpDataIpAddress)) { SOCKADDRESS_ZERO6 (&tkptr->FtpDataSocketName) sin6ptr = &tkptr->FtpDataSocketName.sa.v6; sin6ptr->SIN6$B_FAMILY = TCPIP$C_AF_INET6; sin6ptr->SIN6$W_PORT = tkptr->FtpDataIpPort; IPADDRESS_SET6 (sin6ptr->SIN6$R_ADDR_OVERLAY.SIN6$T_ADDR, &tkptr->FtpDataIpAddress) il2ptr = &tkptr->FtpDataSocketNameItem; il2ptr->buf_len = sizeof(SOCKADDRIN6); il2ptr->item = TCPIP$C_SOCK_NAME; il2ptr->buf_addr = sin6ptr; qiofun = IO$_ACCESS | IO$M_EXTEND; } else ErrorExitVmsStatus (SS$_BUGCHECK, ErrorSanityCheck, FI_LI); status = sys$qio (EfnNoWait, tkptr->FtpDataChannel, qiofun, &tkptr->FtpDataConnectIOsb, &ProxyFtpDataConnectAst, tkptr, 0, 0, &tkptr->FtpDataSocketNameItem, 0, 0, 0); if (WATCHMOD (tkptr, WATCH_MOD_PROXY)) WatchThis (WATCHITM(tkptr), WATCH_MOD_PROXY, "sys$qiow() !&X", status); if (VMSnok (status)) { /* leave it to the AST function to report! */ tkptr->FtpDataConnectIOsb.Status = status; SysDclAst (ProxyFtpDataConnectAst, tkptr); return; } } /****************************************************************************/ /* Called as an AST by ProxyFtpDataConnect() once the PASV data connection is established or fails. */ ProxyFtpDataConnectAst (PROXY_TASK *tkptr) { int status; char *cptr; REQUEST_STRUCT *rqptr; /*********/ /* begin */ /*********/ if (WATCHMOD (tkptr, WATCH_MOD_PROXY)) WatchThis (WATCHITM(tkptr), WATCH_MOD_PROXY, "ProxyFtpDataConnectAst() !&F !&S", &ProxyFtpDataConnectAst, tkptr->FtpDataConnectIOsb.Status); rqptr = tkptr->RequestPtr; if (VMSnok (tkptr->FtpDataConnectIOsb.Status)) { /*****************/ /* connect error */ /*****************/ if (WATCHING (tkptr, WATCH_PROXY)) WatchThis (WATCHITM(rqptr), WATCH_PROXY, "FTP-DATA connect !&S", tkptr->FtpDataConnectIOsb.Status); ProxyFtpDataCloseSocket (tkptr); tkptr->ResponseStatusCode = 502; rqptr->rqResponse.HttpStatus = 502; switch (tkptr->FtpDataConnectIOsb.Status) { case PROXY_ERROR_CONNECT_REFUSED : cptr = MsgFor(rqptr,MSG_PROXY_CONNECT_REFUSED); ErrorGeneral (rqptr, cptr, FI_LI); break; case PROXY_ERROR_HOST_UNREACHABLE : cptr = MsgFor(rqptr,MSG_PROXY_HOST_UNREACHABLE); ErrorGeneral (rqptr, cptr, FI_LI); break; default : rqptr->rqResponse.ErrorTextPtr = tkptr->RequestHostPort; ErrorVmsStatus (rqptr, tkptr->FtpDataConnectIOsb.Status, FI_LI); } } SysDclAst (&ProxyFtpLifeCycle, tkptr); } /*****************************************************************************/ /* Write to the data connection of the FTP server. Explicitly declares any AST routine if an error occurs. The calling function must not do any error recovery if an AST routine has been supplied but the associated AST routine must! If an AST was not supplied then the return status can be checked. AST calls ProxyFtpWriteRawAST() which will then call the supplied AST function. */ int ProxyFtpDataWriteRaw ( PROXY_TASK *tkptr, PROXY_AST AstFunction, char *DataPtr, int DataCount ) { int status; REQUEST_STRUCT *rqptr; /*********/ /* begin */ /*********/ if (WATCHMOD (tkptr, WATCH_MOD_PROXY)) WatchThis (WATCHITM(tkptr), WATCH_MOD_PROXY, "ProxyFtpDataWriteRaw() !&F !&A !&X !UL", &ProxyFtpDataWriteRaw, AstFunction, DataPtr, DataCount); /* FTP data should never have blocking IO queued against it */ if (!AstFunction) ErrorExitVmsStatus (SS$_BUGCHECK, ErrorSanityCheck, FI_LI); rqptr = tkptr->RequestPtr; if (tkptr->FtpDataWriteRawAstFunction || !DataCount) ErrorExitVmsStatus (SS$_BUGCHECK, ErrorSanityCheck, FI_LI); tkptr->FtpDataWriteRawAstFunction = AstFunction; tkptr->FtpDataWriteRawDataPtr = DataPtr; tkptr->FtpDataWriteRawDataCount = DataCount; if (WATCHING (rqptr, WATCH_NETWORK_OCTETS)) { WatchThis (WATCHITM(rqptr), WATCH_NETWORK, "WRITE !UL bytes", DataCount); WatchDataDump (DataPtr, DataCount); } status = sys$qio (EfnNoWait, tkptr->FtpDataChannel, IO$_WRITEVBLK, &tkptr->FtpDataWriteIOsb, &ProxyFtpDataWriteRawAst, tkptr, DataPtr, DataCount, 0, 0, 0, 0); if (VMSok (status)) return (status); /* if resource wait enabled the only quota not waited for is ASTLM */ if (status == SS$_EXQUOTA) ErrorExitVmsStatus (status, NULL, FI_LI); /* write failed, call AST explicitly, status in the IOsb */ tkptr->FtpDataWriteIOsb.Status = status; tkptr->FtpDataWriteIOsb.Count = 0; SysDclAst (ProxyFtpDataWriteRawAst, tkptr); return (status); } /*****************************************************************************/ /* AST from ProxyFtpDataWriteRaw(). Call the AST function. */ ProxyFtpDataWriteRawAst (PROXY_TASK *tkptr) { int status; REQUEST_STRUCT *rqptr; /*********/ /* begin */ /*********/ if (WATCHMOD (tkptr, WATCH_MOD_PROXY)) WatchThis (WATCHITM(tkptr), WATCH_MOD_PROXY, "ProxyFtpDataWriteRawAst() !&F !&S !UL", &ProxyFtpDataWriteRawAst, tkptr->FtpDataWriteIOsb.Status, tkptr->FtpDataWriteIOsb.Count); rqptr = tkptr->RequestPtr; if (WATCHING (tkptr, WATCH_NETWORK)) { if (VMSnok(tkptr->FtpDataWriteIOsb.Status)) WatchThis (WATCHITM(tkptr), WATCH_NETWORK, "WRITE !&S", tkptr->FtpDataWriteIOsb.Status); else WatchThis (WATCHITM(tkptr), WATCH_NETWORK, "WRITE !&S !UL bytes", tkptr->FtpDataWriteIOsb.Status, tkptr->FtpDataWriteIOsb.Count); } if (VMSok (tkptr->FtpDataWriteIOsb.Status)) { tkptr->NetIoPtr->BlocksRawTx64++; tkptr->NetIoPtr->BlocksTallyTx64++; tkptr->NetIoPtr->BytesRawTx64 += tkptr->FtpDataWriteIOsb.Count; tkptr->NetIoPtr->BytesTallyTx64 += tkptr->FtpDataWriteIOsb.Count; } if (tkptr->FtpDataWriteRawAstFunction) SysDclAst (tkptr->FtpDataWriteRawAstFunction, tkptr); tkptr->FtpDataWriteRawDataCount = 0; tkptr->FtpDataWriteRawAstFunction = tkptr->FtpDataWriteRawDataPtr = NULL; } /*****************************************************************************/ /* Queue up a read from the data port of the FTP server. If 'AstFunction' is zero then no I/O completion AST routine is called. If it is non-zero then the function pointed to by the parameter is called when the network write completes. Explicitly declares any AST routine if an error occurs. The calling function must not do any error recovery if an AST routine has been supplied but the associated AST routine must! If an AST was not supplied then the return status can be checked. */ int ProxyFtpDataReadRaw ( PROXY_TASK *tkptr, PROXY_AST AstFunction, char *DataPtr, int DataSize ) { int status; REQUEST_STRUCT *rqptr; /*********/ /* begin */ /*********/ if (WATCHMOD (tkptr, WATCH_MOD_PROXY)) WatchThis (WATCHITM(tkptr), WATCH_MOD_PROXY, "ProxyFtpDataReadRaw() !&F !&A !&X !UL", &ProxyFtpDataReadRaw, AstFunction, DataPtr, DataSize); /* FTP data should never have blocking IO queued against it */ if (!AstFunction) ErrorExitVmsStatus (SS$_BUGCHECK, ErrorSanityCheck, FI_LI); rqptr = tkptr->RequestPtr; if (tkptr->FtpDataReadRawAstFunction) ErrorExitVmsStatus (SS$_BUGCHECK, ErrorSanityCheck, FI_LI); tkptr->FtpDataReadRawAstFunction = AstFunction; tkptr->FtpDataReadRawDataPtr = DataPtr; tkptr->FtpDataReadRawDataSize = DataSize; if (WATCHING (rqptr, WATCH_NETWORK_OCTETS)) WatchThis (WATCHITM(rqptr), WATCH_NETWORK, "READ !UL bytes max", DataSize); status = sys$qio (EfnNoWait, tkptr->FtpDataChannel, IO$_READVBLK, &tkptr->FtpDataReadIOsb, &ProxyFtpDataReadRawAst, tkptr, DataPtr, DataSize, 0, 0, 0, 0); if (VMSok (status)) return (status); /* with resource wait enabled the only quota not waited for is ASTLM */ if (status == SS$_EXQUOTA) ErrorExitVmsStatus (status, NULL, FI_LI); /* queuing of read failed, call AST explicitly, status in the IOsb */ tkptr->FtpDataReadIOsb.Status = status; tkptr->FtpDataReadIOsb.Count = 0; SysDclAst (ProxyFtpDataReadRawAst, tkptr); return (status); } /*****************************************************************************/ /* AST from ProxyFtpDataReadRaw(). Call the AST function. */ ProxyFtpDataReadRawAst (PROXY_TASK *tkptr) { int status; REQUEST_STRUCT *rqptr; /*********/ /* begin */ /*********/ if (WATCHMOD (tkptr, WATCH_MOD_PROXY)) WatchThis (WATCHITM(tkptr), WATCH_MOD_PROXY, "ProxyFtpDataReadRawAst() !&F !&S !UL", &ProxyFtpDataReadRawAst, tkptr->FtpDataReadIOsb.Status, tkptr->FtpDataReadIOsb.Count); rqptr = tkptr->RequestPtr; if (WATCHPNT(tkptr)) { if (WATCH_CATEGORY(WATCH_NETWORK) || WATCH_CATEGORY(WATCH_NETWORK_OCTETS)) { if (VMSok(tkptr->FtpDataReadIOsb.Status)) { WatchThis (WATCHITM(tkptr), WATCH_NETWORK, "READ !&S !UL bytes", tkptr->FtpDataReadIOsb.Status, tkptr->FtpDataReadIOsb.Count); if WATCH_CATEGORY(WATCH_NETWORK_OCTETS) WatchDataDump (tkptr->FtpDataReadRawDataPtr, tkptr->FtpDataReadIOsb.Count); } else WatchThis (WATCHITM(tkptr), WATCH_NETWORK, "READ !&S", tkptr->FtpDataReadIOsb.Status); } } if (VMSok (tkptr->FtpDataReadIOsb.Status)) { tkptr->NetIoPtr->BlocksRawRx64++; tkptr->NetIoPtr->BlocksTallyRx64++; tkptr->NetIoPtr->BytesRawRx64 += tkptr->FtpDataReadIOsb.Count; tkptr->NetIoPtr->BytesTallyRx64 += tkptr->FtpDataReadIOsb.Count; /* zero bytes with a normal status is a definite no-no (TGV-Multinet) */ if (!tkptr->FtpDataReadIOsb.Count) tkptr->FtpDataReadIOsb.Status = SS$_ABORT; } if (tkptr->FtpDataReadRawAstFunction) SysDclAst (tkptr->FtpDataReadRawAstFunction, tkptr); tkptr->FtpDataReadRawDataSize = 0; tkptr->FtpDataReadRawAstFunction = tkptr->FtpDataReadRawDataPtr = NULL; } /****************************************************************************/ /* Just shut the FTP data socket down, bang! */ int ProxyFtpDataCloseSocket (PROXY_TASK *tkptr) { int status; /*********/ /* begin */ /*********/ if (WATCHMOD (tkptr, WATCH_MOD_PROXY)) WatchThis (WATCHITM(tkptr), WATCH_MOD_PROXY, "ProxyFtpDataCloseSocket() !&F", &ProxyFtpDataCloseSocket); if (!tkptr->FtpDataChannel) return (SS$_NORMAL); status = sys$dassgn (tkptr->FtpDataChannel); tkptr->FtpDataChannel = 0; if (WATCHING (tkptr, WATCH_PROXY)) { if (VMSok(status)) WatchThis (WATCHITM(tkptr), WATCH_PROXY, "FTP-DATA close !AZ,!UL", tkptr->RequestHostName, tkptr->RequestPort); else WatchThis (WATCHITM(tkptr), WATCH_PROXY, "FTP-DATA close !AZ,!UL !&S", tkptr->RequestHostName, tkptr->RequestPort, status); } return (status); } /****************************************************************************/ /* Map FTP response status codes over into HTTP response status codes. */ int ProxyFtpHttpStatus (PROXY_TASK *tkptr) { /*********/ /* begin */ /*********/ if (WATCHMOD (tkptr, WATCH_MOD_PROXY)) WatchThis (WATCHITM(tkptr), WATCH_MOD_PROXY, "ProxyFtpHttpStatus() !UL", tkptr->FtpResponseCode); switch (tkptr->FtpResponseCode) { case 202 : return (tkptr->RequestPtr->rqResponse.HttpStatus = 501); case 257 : return (tkptr->RequestPtr->rqResponse.HttpStatus = 201); case 421 : return (tkptr->RequestPtr->rqResponse.HttpStatus = 503); case 425 : return (tkptr->RequestPtr->rqResponse.HttpStatus = 502); case 426 : return (tkptr->RequestPtr->rqResponse.HttpStatus = 502); case 450 : return (tkptr->RequestPtr->rqResponse.HttpStatus = 404); case 451 : return (tkptr->RequestPtr->rqResponse.HttpStatus = 502); case 452 : return (tkptr->RequestPtr->rqResponse.HttpStatus = 502); case 500 : return (tkptr->RequestPtr->rqResponse.HttpStatus = 502); case 501 : return (tkptr->RequestPtr->rqResponse.HttpStatus = 502); case 502 : return (tkptr->RequestPtr->rqResponse.HttpStatus = 501); case 503 : return (tkptr->RequestPtr->rqResponse.HttpStatus = 502); case 504 : return (tkptr->RequestPtr->rqResponse.HttpStatus = 501); case 530 : return (tkptr->RequestPtr->rqResponse.HttpStatus = 401); case 532 : return (tkptr->RequestPtr->rqResponse.HttpStatus = 403); case 550 : return (tkptr->RequestPtr->rqResponse.HttpStatus = 404); case 551 : return (tkptr->RequestPtr->rqResponse.HttpStatus = 502); case 552 : return (tkptr->RequestPtr->rqResponse.HttpStatus = 403); } return (tkptr->RequestPtr->rqResponse.HttpStatus = 500); } /****************************************************************************/