home *** CD-ROM | disk | FTP | other *** search
/ OS/2 Shareware BBS: 35 Internet / 35-Internet.zip / ftl08.zip / FTL.CMD next >
OS/2 REXX Batch file  |  1994-09-20  |  23KB  |  658 lines

  1. /* FTPD trace/log decoder
  2. // Version 0.8
  3. // Copyright (C) 1994 Justin H. Dolske, All Rights Reserved
  4. // Questions? Contact jdolske@mail.bgsu.edu
  5. // Version History:
  6. // 0.5b - Initial public release
  7. // 0.51b - fixed stupid bug that caused logging to die after login
  8. // 0.6b - more commands, SITE/HELP correctly handled, ABOR handled
  9. // 0.7 - CWD logging, handles user timeout, ABOR really works now, note
  10. //       sync errors in log
  11. // 0.8 - log failure to set login directory, CDUP logged as "CWD ..", log
  12. //       deleted files
  13. // projected - new Sort, USER command, document, cache IPs.
  14. */
  15.  
  16. /* TO DO*/
  17. /* Create an example ftpd.trc showing what it can do*/
  18. /* do a REAL sort*/
  19. /* RNTO existingfile*/
  20. /* convert parse upper to parse + translate() where needed*/
  21. /* more robust IP logging */
  22. /* Support APPE for STOR and ABOR*/
  23.  
  24. /* DEFAULTS */
  25. ver = '0.8'
  26. TraceFile = 'FTPD.TRC'  /* Default tracefile */
  27. ReportFile = ''         /* '' defaults to screen only*/
  28. ReportFileOnly = ''     /* '' defaults to always write log to screen */
  29. ReportingLevel = 1      /* Reporting Level if not specified on command line*/
  30. LookupHostname = 'NO'   /* Translate IP to Hostname via nslookup*/
  31. XferLevel = 1           /* Determines Reporting Level for uploads & downloads*/
  32. CWDLevel = 3            /* Reporting level for CWD logging, 4 to disable*/
  33. MaxArgs = 8
  34.  
  35. /* IMPORTANT VARIABLES */
  36. Connects = 0; Logins = 0; FailedLogins = 0
  37. FilesTotal = 0; FilesUp = 0; FilesDown = 0
  38. FilesUpAbor = 0; FilesDownAbor = 0; BytesDown = 0
  39. Sync_Errors = 0; DeletedFiles = 0
  40.  
  41.  
  42. parse upper arg args.1 args.2 args.3 args.4 args.5 args.6 args.8
  43.  
  44.  
  45. i = 1
  46. do while ((Args.i \= '') & (i <= MaxArgs))
  47.   select
  48.     when Args.i = '-?' then call HelpScreen
  49.     when Args.i = '-3' then ReportingLevel = 3
  50.     when Args.i = '-2' then ReportingLevel = 2
  51.     when Args.i = '-1' then ReportingLevel = 1
  52.     when Args.i = '-0' then ReportingLevel = 0
  53.     when Args.i = '-Q' then ReportFileOnly = 'YES'
  54.     when Args.i = '-H' then LookupHostname = 'YES'
  55.     when Args.i = '-T' then do
  56.                               i = i + 1          /*checks for vaild filename*/
  57.                               TraceFile = Args.i  /*  after LoadTrace call   */
  58.                               end
  59.     when Args.i = '-L' then do
  60.                               i = i + 1
  61.                               ReportFile = Args.i /*checks on-the-fly*/
  62.                               end
  63.     otherwise do
  64.         say 'Invalid argument:' Args.i
  65.         MaxArgs = 0
  66.         end
  67.   end /*select*/
  68.   i = i + 1
  69. end /*while*/
  70. if MaxArgs = 0 then exit /*if invalid argument*/
  71.  
  72. call Report 3, 'Beginning Execution'
  73. call Report 1, 'FTL version' ver
  74. call Report 0, 'OS/2 FTPD Tracefile report generated' date('W')',' date() 'at' time()
  75. call Report 0, ''
  76.  
  77. call LoadTrace     /*Read in the FTPD tracing file*/
  78. if MaxLine = 0 then do
  79.   call Report 0, 'ERROR: Tracefile' TraceFile 'was not found or was empty'
  80.   exit
  81.   end
  82. call SortLog          /*Sort by port user used*/
  83. call GetIPsForSockets /*associate an IP to each socket*/
  84.  
  85. LastSocket = LogLine.1.Socket
  86. LogStart = 1;
  87. index = 1;
  88.  
  89. do until index > MaxLine
  90.   if LogLine.index.Socket \= LastSocket then do
  91.     LogStop = index - 1
  92.     call ParseLog LogStart, LogStop
  93.     LogStart = index
  94.     LastSocket = LogLine.index.Socket
  95.     end
  96.   else index = index + 1
  97. end /*do until*/
  98. LogStop = MaxLine
  99. call ParseLog LogStart, LogStop
  100.  
  101. call GenStats
  102. call Report 3, 'Logging finished'
  103. exit
  104.  
  105.  
  106.  
  107.  
  108.  
  109. /* BEGIN PROCEDURES */
  110.  
  111.  
  112.  
  113.  
  114.  
  115. /* Procedure: HelpScreen
  116. // Purpose  : Display command line help
  117. // Args     : none
  118. */
  119.  
  120. HelpScreen:
  121.  
  122. say 'FTL - FTPD Tracefile Log utility, version' ver
  123. say '(C) 1994 by Justin H. Dolske. All Rights Reserved.'
  124. say ''
  125. say 'Usage:'
  126. say 'ftl [-0|1|2|3] [-h] [-t tracefile] [-l logfile] [-q]'
  127. say ''
  128. say '-0 -- Output statistics only'
  129. say '-1 -- Output statistics and some logging (DEFAULT)'
  130. say '-2 -- Output statistics and full logging'
  131. say '-3 -- Output statistics and full logging and debugging info'
  132. say '-t tracefile -- Specify a tracefile other than the default (FTPD.TRC)'
  133. say '-l logfile   -- Output logging info to logfile'
  134. say '-q -- Do not write log to screen, useful only with -l.'
  135. say '-h -- Perform a nslookup on IPs found'
  136. say ''
  137. say 'This is NOT a security scanner, it is only a tool for summarizing data!'
  138. exit
  139.  
  140. return
  141.  
  142.  
  143. /* Procedure: LoadTrace
  144. // Purpose  : Read in the FTPD Trace file
  145. // Args     : none
  146. */
  147.  
  148. LoadTrace:
  149.  
  150. index = 0                                   /*index for an array*/
  151. do while lines(TraceFile) <> 0              /*check for EOF*/
  152.   index = index + 1                         /*step up in array*/
  153.   LogLine.index = linein(TraceFile,,1)      /*Read in one line*/
  154.   if LogLine.index == '' then               /*if empty line, discard it*/
  155.     index = index - 1
  156.   else                                   /*what socket is this line from?*/
  157.     parse var LogLine.index 'Socket: ' LogLine.index.Socket ',' .
  158. end
  159. MaxLine = index
  160. CloseResult = stream(TraceFile, 'C', 'CLOSE') /*be nice and close the file*/
  161. if CloseResult \= 'READY:' then do
  162.   say 'ERROR: Error ('CloseResult') closing file' TraceFile
  163.   exit
  164.   end
  165. call Report 3, 'TraceFile successfully read' MaxLine 'lines.'
  166. return
  167.  
  168. /* Procedure: SortLog
  169. // Purpose  : Sort array LogLine.index by socket number (LogLine.index.Socket)
  170. // Args     : none
  171. */
  172.  
  173. SortLog:
  174.  
  175. call Report 3, 'TraceFile sorting started'
  176. Sorted = 'NO'
  177. do until Sorted = 'YES'
  178.   Sorted = 'YES'
  179.   index = 1
  180.   do while index <= (MaxLine - 1)
  181.     index2 = index + 1
  182.     if LogLine.index.Socket > LogLine.index2.Socket then
  183.       do 
  184.         Temp = LogLine.index; Temp.Socket = LogLine.index.Socket
  185.         LogLine.index = LogLine.index2
  186.         LogLine.index.Socket = LogLine.index2.Socket
  187.         LogLine.index2 = Temp
  188.         LogLine.index2.Socket = Temp.Socket
  189.         Sorted = 'NO'
  190.       end /*if*/
  191.     index = index + 1          
  192.   end /*do while*/
  193. end /*do until*/
  194. call Report 3, 'TraceLog sucessfully sorted'
  195. return
  196.  
  197. /* Procedure: Report
  198. // Purpose  : Given a message and it's "level", output to the right place
  199. // Args     : #1 - Level of message according to:
  200. //                 0 = Just statistics
  201. //                 1 = General Logging
  202. //                 2 = Detailed Logging
  203. //                 3 = Debugging
  204. //            #2 - Message
  205. */
  206.  
  207. Report:
  208.  
  209. RepLevel = arg(1)
  210. RepMessage = arg(2)
  211. if RepLevel <= ReportingLevel then do
  212.   if ReportFile \= '' then do
  213.       WriteStatus = lineout(ReportFile, RepMessage)
  214.       if WriteStatus \= 0 then do
  215.         say 'ERROR: Error ('WriteStatus') occured when attempting to write to' ReportFile
  216.         exit
  217.         end /*if WriteStatus*/
  218.       end
  219.   if ReportFileOnly \= 'YES' then
  220.       say RepMessage 
  221.   end
  222. return
  223.  
  224. /* Procedure: GenStats
  225. // Purpose  : Display general usage statistics
  226. // Args     : none
  227. */
  228.  
  229. GenStats:
  230.  
  231. call Report 0, '*******************************************************************************'
  232. call Report 0, 'Usage Report:'
  233. call Report 0, 'Total Connects:' Connects
  234. call Report 0, 'Successful Logins:' Logins '    Failed Login Attempts:' FailedLogins
  235. call Report 0, ''
  236. call Report 0, 'Files Transfered:' FilesTotal '  ('FilesUp 'uploaded,' FilesDown 'downloaded)'
  237. call Report 0, 'Bytes Downloaded:' BytesDown
  238. call Report 0, ''
  239. if Sync_Errors \= 0 then
  240. call Report 0, 'Synching Errors while parsing tracefile:' Sync_Errors
  241. if DeletedFiles \= 0 then
  242. call Report 0, 'Files Deleted:' DeletedFiles
  243. call Report 0, 'Aborted  uploads :' FilesUpAbor
  244. call Report 0, 'Aborted downloads:' FilesDownAbor
  245. call Report 0, '*******************************************************************************'
  246. call Report 0, ''
  247. return
  248.  
  249.  
  250. /* Procedure: GetIPsForSockets
  251. // Purpose  : Scan through the tracefile and try to match an IP to each socket
  252. // Args     : none
  253. */
  254.  
  255. GetIPsForSockets:
  256.  
  257. LastSocket = LogLine.1.Socket; i = 1; LastIP = ''
  258. do while i <= MaxLine                          /*don't overrun*/
  259.     IPlookup.LastSocket = ''
  260.     do while LogLine.i.Socket = LastSocket     /*isolate each socket*/
  261.         parse var LogLine.i . . 'Command:' Command Parm .
  262.         Sock = LogLine.i.Socket
  263.  
  264.         if Command = 'PORT' then do
  265.             parse var Parm aa','bb','cc','dd',' .
  266.             IP = aa||'.'||bb'.'||cc'.'||dd
  267.             if ((LastIP \= '') & (IP \= LastIP)) then
  268.               call Report 1, 'WARNING: Multiple IPs recieved for Socket' Sock ', may be using proxy mode!'
  269.             if LastIP = '' then do
  270.                 IPlookup.Sock = IP   /*only assign first IP found*/
  271.                 if LookupHostname = 'YES' then
  272.                   HOSTlookup.Sock = NslookupHack(IP)
  273.                 else HOSTlookup.Sock = IP
  274.                 call Report 3, 'IP:' IP '('HOSTlookup.Sock')'
  275.                 end
  276.             LastIP = IP
  277.             end /*if*/
  278.  
  279.         LastSocket = LogLine.i.Socket
  280.         i = i + 1
  281.         end /*while*/
  282.  
  283.     /*done parsing a socket*/
  284.     if IPlookup.Sock = '' then do  /*No PORT commands found*/
  285.         IPlookup.Sock = 'Unknown'
  286.         HOSTlookup.Sock = 'Unknown'
  287.         end
  288.     LastIP = ''
  289.     LastSocket = LogLine.i.Socket
  290.     end /*while, do next socket*/
  291.  
  292. return
  293.  
  294.  
  295. /* Procedure: NslookupHack
  296. // Purpose  : get hostname for an ip
  297. // Args     : IP - Numeric IP to get hostname for
  298. */
  299.  
  300. NslookupHack:
  301.  
  302. HostIP = arg(1); Hostname = ''
  303.  
  304. call Report 3, 'Attempting to resolve' HostIP
  305. '@nslookup' HostIP '| RxQueue 2> NUL:'
  306. do while Hostname = ''
  307.   parse pull Reply Parm       /*we want "Name: host.domain.xxx" */
  308.   if Reply = 'Name:' then 
  309.         Hostname = strip(Parm)      /*remove leading spaces*/
  310.   end /*while*/
  311.  
  312. if Hostname = '' then do
  313.     Hostname = HostIP
  314.     call Report 3, 'NOTICE: Unable to resolve' HostIP
  315.     end
  316.  
  317. return Hostname
  318.  
  319. /* Procedure: ParseLog
  320. // Purpose  : Figure out what was done on one socket
  321. // Args     : i     : first line of log related to this socket
  322. //            i_max : last line of log related to this socket
  323. */
  324.  
  325. ParseLog:
  326.  
  327. i = arg(1); i_max = arg(2)
  328. Sock = LogLine.i.Socket
  329.  
  330. call Report 1, ''
  331. call Report 3, 'Beginning ParseLog for Socket' Sock
  332. call Report 1, 'Connect from' IPlookup.Sock '('HOSTlookup.Sock')'
  333. Connects = Connects + 1
  334. i = i + 1  /*Skip the FTPD intro, no useful info*/
  335.  
  336. call ParseLogin
  337. call Report 3, 'Returned from ParseLogin, i=' i
  338. if i_max = 0 then do /*user failed to login*/
  339.   call Report 3, 'Done with ParseLog for Socket' Sock '(did not login)'
  340.   return
  341.   end
  342. call ParsePostLogin
  343. call Report 3, 'Done with ParseLog for Socket' Sock
  344. if i_max \= 0 then
  345.   call Report 0, 'ERROR: Unexpected end of tracing for Socket' Sock
  346.  
  347.  
  348. return
  349.  
  350. /* Procedure: ParseLogin
  351. // Purpose  : Parse tracefile until a user has loged in with a vaile
  352. //            USERname and PASSword, or disconnects
  353. // Args     : none
  354. */
  355.  
  356. ParseLogin:
  357.  
  358. /* at this point the user must login with USER&PASS or QUIT*/
  359.  
  360. call Report 3, 'Beginning ParseLogin for Socket' Sock
  361. do while i <= i_max 
  362.   parse upper var LogLine.i . . . Command Username Parm2 .
  363.   i = i + 1
  364.  
  365.   if Command = 'QUIT' then do
  366.         call Report 2, 'Socket' Sock 'user QUIT before loging in.' 
  367.         i_max = 0
  368.         return
  369.         end /*QUIT*/
  370.  
  371.   else if Command = 'USER' then do
  372.         parse var LogLine.i . . 'Reply:'  NumericCode .
  373.         i = i + 1
  374.         if NumericCode = 230 then do  /*logged in w/o a password*/
  375.             Logins = Logins + 1
  376.             call Report 1, 'Socket' Sock 'logged in as' Username '(no password req)'
  377.             return
  378.             end
  379.         else if NumericCode = 550 then do
  380.             call Report 0, 'ERROR: Could not set default directory for user' Username
  381.             FailedLogins = FailedLogins + 1
  382.             i_max = 0
  383.             return
  384.             end
  385.         /*At this point NumericCode should thus be 331 - Password required*/
  386.  
  387.         parse var LogLine.i . .'Command:' Command .
  388.         i = i + 1
  389.         if Command \= 'PASS' then do  /*I don't think any clients would allow this*/
  390.             call Report 0, 'ERROR: User on Socket' Socket 'didnt follow USER with PASS. Skipping Socket.'
  391.             FailedLogins = FailedLogins + 1
  392.             i_max = 0
  393.             return
  394.             end
  395.  
  396.         parse var LogLine.i . .'Reply:' NumericCode .
  397.         i = i + 1
  398.         if NumericCode = 230 then do  /*230 - User username logged in*/
  399.             call Report 1, 'Socket' Sock 'user logged in as ' Username
  400.             Logins = Logins + 1
  401.             return
  402.             end
  403.         else if ((NumericCode = 530) | (NumericCode = 550)) then do
  404.             /*530 - Login incorrect, 550 - Invalid Syntax for PASS*/
  405.             call Report 2, 'Socket' Sock 'user failed login as' Username
  406.             FailedLogins = FailedLogins + 1
  407.             end
  408.          else if NumericCode = 550 then do
  409.             /*couldn't set a directory for the user*/
  410.             call Report 0, 'ERROR: Could not set default directory for user' Username
  411.             FailedLogins = FailedLogins + 1
  412.             i_max = 0
  413.             return
  414.             end
  415.  
  416.         else do
  417.             call Report 0, 'ERROR: Unknown PASS reply on Socket' Sock 'during login, skipping Socket'
  418.             FailedLogins = FailedLogins + 1
  419.             i_max = 0
  420.             return
  421.             end
  422.         end /*USER*/
  423.  
  424.   else do  /*user did not do USER or QUIT*/
  425.         i = i + 1     /*skip reply, jump to users next command*/
  426.         if Command = 'HELP' then do
  427.             if Username = '' then i = i + 1  /*Username used as a Parm*/
  428.             else if ((Username = 'SITE') & (Parm2 = '')) then i = i + 1
  429.             else nop
  430.             end
  431.         if ((Command = 'SITE') & (Username = 'HELP')) then i = i + 1
  432.         call Report 3, 'Socket' Sock 'login - entered illegal command ('Command')'
  433.         end  
  434.   parse var LogLine.i . . . NumericCode Word1 .
  435.   if ((NumericCode = 221) & (Word1 \= 'Goodbye.')) then do
  436.         call Report 2, 'Socket' Sock 'unexpectedly logged out before logging in (no QUIT).'
  437.         i_max = 0
  438.         return
  439.         end
  440.  
  441.   end /*while*/  
  442.  
  443. /* check here for overflow into next socket area!*/
  444.  
  445. /*user has a valid login at this point*/
  446.  
  447. return
  448.  
  449. /* Procedure: ParsePostLogin
  450. // Purpose  : after user logs in, parse what he has done
  451. // Args     : none
  452. */
  453.  
  454. ParsePostLogin:
  455.  
  456. call Report 3, 'Beginning ParsePostLogin for Socket' Sock
  457. do while i <= i_max
  458.   parse var LogLine.i . . 'Command:' Command Parm Parm2 .
  459.   Command = translate(Command) /*make sure its uppercase*/
  460.   /*say 'I:' i ' ' LogLine.i*/
  461.   /*say 'Line:' i 'Command:' Command 'Parm:' parm*/
  462.   i = i + 1
  463.   select
  464.    when Command = 'RETR' then do /*user is downloading*/
  465.         Filename = Parm
  466.         parse var LogLine.i . . 'Reply:' NumericCode Word1 . . . . . . PreSize .
  467.         i = i + 1
  468.         if NumericCode = 150 then do /*successful*/
  469.             parse var LogLine.i . . . NumericCode .
  470.             if NumericCode = 226 then /*skip resultline only if successful*/
  471.                 i = i + 1
  472.             parse var Presize '(' Filesize .
  473.             call Report XferLevel, 'Socket' Sock' retrieved' Filename '('Filesize 'bytes)'
  474.             BytesDown = BytesDown + Filesize
  475.             FilesTotal = FilesTotal + 1
  476.             FilesDown = FilesDown + 1
  477.             end /*150*/
  478.         else if NumericCode = 550 then do  /*bad attempt*/
  479.             if Word1 = 'You' then
  480.               call Report 1, 'Socket' Sock 'tried to illegaly retrieve' Filename
  481.               end
  482.         else do
  483.             call Report 0, 'ERROR: Unknown result code ('NumericCode') from RETR on socket' Sock
  484.             i_max = 0
  485.             return
  486.             end
  487.         end /*RETR*/
  488.  
  489.    when Command = 'STOR' then do /*user is uploading*/
  490.         Filename = Parm
  491.         parse var LogLine.i . . 'Reply:' NumericCode Word1 .
  492.         i = i + 1
  493.         if NumericCode = 150 then do /*successful*/
  494.             parse var LogLine.i . . . NumericCode .
  495.             if NumericCode = 226 then
  496.                 i = i + 1 /*skip resultline only if successful*/
  497.             call Report XferLevel, 'Socket' Sock' uploaded' Filename
  498.             FilesTotal = FilesTotal + 1
  499.             FilesUp = FilesUp + 1
  500.             end /*150*/
  501.         else if NumericCode = 550 then do  /*bad attempt*/
  502.             if Word1= 'You' then 
  503.               call Report 1, 'Socket' Sock 'tried to illegaly upload' Filename
  504.             end
  505.         else do
  506.             call Report 0, 'ERROR: Unknown result code ('NumericCode') from SEND on socket' Sock
  507.             i_max = 0
  508.             return
  509.             end
  510.         end /*STOR*/
  511.  
  512.    when Command = 'ABOR' then do
  513.         select
  514.             when LastCommand = 'STOR' then do
  515.                     FilesUpAbor = FilesUpAbor + 1
  516.                     FilesUp = FilesUp - 1
  517.                     call Report XferLevel, 'Socket' Sock 'aborted upload of' Filename
  518.                     i = i + 1
  519.                     end /*ABOR STOR*/
  520.             when LastCommand = 'RETR' then do
  521.                     FilesDownAbor = FilesDownAbor + 1
  522.                     FilesDown = FilesDown - 1
  523.                     BytesDown = BytesDown - Filesize
  524.                     call Report XferLevel, 'Socket' Sock 'aborted download of' Filename
  525.                     i = i + 1
  526.                     end /*ABOR STOR*/
  527.             otherwise i = i + 1
  528.             end /*select*/
  529.  
  530.         /*handle ABOR closing a data connection*/
  531.         parse var LogLine.i . . . NumericCode .
  532.         if NumericCode = 226 then i = i + 1
  533.         end /*ABOR*/
  534.    
  535.    when Command = 'HELP' then do
  536.         if Parm = '' then i = i + 2 /*Display avail commands*/
  537.         else if ((Parm = 'SITE') & (Parm2 = '')) then i = i + 2
  538.         else i = i + 1 /*HELP for 1 command*/ 
  539.         end /*HELP*/
  540.  
  541.    when Command = 'SITE' then do
  542.         if Parm = 'IDLE' then do
  543.              call Report 2, 'Socket' Sock 'reset idle time to' Parm2 'seconds.'
  544.              i = i + 1
  545.              end
  546.         else if ((Parm = 'HELP') & (Parm2 = '')) then i = i + 2
  547.         else i = i + 1  /*no other SITE commands are implemented*/
  548.         end
  549.  
  550.    when Command = 'RNFR' then do /*see also RNTO*/
  551.         Filename = Parm
  552.         parse var LogLine.i . . 'Reply:' NumericCode Word1 .
  553.         i = i + 1
  554.         if ((NumericCode = 550) & (Word1 = 'You')) then
  555.           call Report 1, 'Socket' Sock 'tried to illegally rename' Filename
  556.         end /*RNFR*/
  557.  
  558.    when Command = 'DELE' then do
  559.         Filename = Parm
  560.         parse var LogLine.i . . 'Reply:' NumericCode Word1 .
  561.         i = i + 1
  562.         if NumericCode = 250 then do
  563.             DeletedFiles = DeletedFiles + 1
  564.             call Report 1, 'Socket' Sock 'deleted' Filename
  565.             end
  566.         else if Word1 = 'You' then call Report 1, 'Socket' Sock 'attemted to illegally delete' Filename     
  567.         end /*DELE*/
  568.  
  569.    when Command = 'LIST' then do
  570.         parse var LogLine.i . . 'Reply:' NumericCode .
  571.         i = i + 1
  572.         if NumericCode = 150 then i = i + 1 /*gives 2 Replys: for ok LIST*/
  573.         end /*LIST*/
  574.  
  575.    when Command = 'NLST' then do
  576.         parse var LogLine.i . . 'Reply:' NumericCode .
  577.         i = i + 1
  578.         if NumericCode = 150 then i = i + 1 /*gives 2 Replys: for ok LIST*/
  579.         end /*NLST*/
  580.  
  581.    when Command = 'QUIT' then do
  582.         call Report 2, 'Socket' Sock 'user logged out normally.'
  583.         i_max = 0
  584.         return
  585.         end /*QUIT*/
  586.  
  587.    when ((Command = 'CWD') | (Command = 'XCWD'))  then do
  588.         parse var LogLine.i . . 'Reply:' NumericCode .
  589.         i = i + 1
  590.         if NumericCode = 250 then
  591.             call Report CWDLevel, 'Socket' Sock 'did a CWD to' Parm
  592.         else
  593.             call Report CWDLevel, 'Socket' Sock 'failed a CWD to' Parm
  594.         end
  595.         
  596.    when ((Command = 'CDUP') | (Command = 'XCUP')) then do
  597.         parse var LogLine.i . . 'Reply:' NumericCode .
  598.         i = i + 1
  599.         /*log CDUP as a "CWD .."*/
  600.         if NumericCode = 250 then
  601.             call Report CWDLevel, 'Socket' Sock 'did a CWD to ..'
  602.         else
  603.             call Report CWDLevel, 'Socket' Sock 'failed a CWD to ..'
  604.         end
  605.         
  606.    when Command = 'PORT' then
  607.         i = i + 1 /*Can these be anything but successful?!*/
  608.  
  609.    when Command = 'RNTO' then i = i + 1
  610.    when Command = 'ACCT' then i = i + 1 /*Not Implemented by ftpd*/
  611.    when Command = 'SMNT' then i = i + 1 /*Not Implemented "   "  */
  612.    when Command = 'REIN' then i = i + 1 /*Not Implemented*/
  613.    when Command = 'MLFL' then i = i + 1 /*Not Implemented*/
  614.    when Command = 'MAIL' then i = i + 1 /*Not Implemented*/
  615.    when Command = 'MSND' then i = i + 1 /*Not Implemented*/
  616.    when Command = 'MSOM' then i = i + 1 /*Not Implemented*/
  617.    when Command = 'MSAM' then i = i + 1 /*Not Implemented*/
  618.    when Command = 'MRSQ' then i = i + 1 /*Not Implemented*/
  619.    when Command = 'MRCP' then i = i + 1 /*Not Implemented*/
  620.    when Command = 'REST' then i = i + 1 /*Not Implemented*/
  621.    when Command = 'ALLO' then i = i + 1 /*ALLO is ignored by FTPD*/
  622.    when Command = 'SYST' then i = i + 1 /*gives system type*/
  623.    when Command = 'STAT' then i = i + 2 /*gives server status*/
  624.    when Command = 'NOOP' then i = i + 1
  625.    when Command = 'MODE' then i = i + 1
  626.    when Command = 'SIZE' then i = i + 1
  627.    when Command = 'MDTM' then i = i + 1 /*gives time/date info on file*/
  628.    when Command = 'TYPE' then i = i + 1
  629.    when ((Command = 'MKD') | (Command = 'XMKD'))  then i = i + 1
  630.    when ((Command = 'RMD') | (Command = 'XRMD'))  then i = i + 1 /*can only RMD empty dirs anyway*/
  631.    when ((Command = 'PWD') | (Command = 'XPWD'))  then i = i + 1
  632.    otherwise do
  633.         call Report 2, 'Socket' Sock 'entered a command ('Command') not currently parsed.'
  634.         if Command = '' then do
  635.             call Report 0, 'ERROR: Parsing out of sync on line' i', skipping rest of Socket' Sock
  636.             Sync_Errors = Sync_Errors + 1
  637.             return
  638.             end
  639.         i = i + 1
  640.         end
  641.   end /*select*/
  642.   LastCommand = Command
  643.   parse var LogLine.i . . . NumericCode Word1 Junk .
  644.   if ((NumericCode = 221) & (Word1 \= 'Goodbye.')) then do
  645.         call Report 2, 'Socket' Sock 'unexpectedly logged out (no QUIT).'
  646.         i_max = 0
  647.         return
  648.         end
  649.   if ((NumericCode = 421) & (Word1 = 'Timeout')) then do
  650.         parse var Junk '('Time 'seconds' .
  651.         call Report 2, 'Socket' Sock 'timed out after' Time 'seconds.'
  652.         i_max = 0
  653.         return
  654.         end
  655. end /*while*/
  656.  
  657. return
  658.