home *** CD-ROM | disk | FTP | other *** search
/ OS/2 Shareware BBS: 35 Internet / 35-Internet.zip / wrpdis20.zip / NEWNEWS.CMD < prev    next >
OS/2 REXX Batch file  |  1996-04-21  |  40KB  |  1,167 lines

  1. /****************************************************************************/
  2. /*  NEWNEWS.CMD - an ka9q compatible OS/2 nntp client                       */
  3. /*  Copyright (C) 1995,1996 Alex Chapman <alex@budgetweb.com>               */
  4. /*                                                                          */
  5. /*  This program is free software; you can redistribute it and/or modify    */
  6. /*  it under the terms of the GNU General Public License as published by    */
  7. /*  the Free Software Foundation; either version 2 of the License, or       */
  8. /*  (at your option) any later version.                                     */
  9. /*                                                                          */
  10. /*  This program is distributed in the hope that it will be useful,         */
  11. /*  but WITHOUT ANY WARRANTY; without even the implied warranty of          */
  12. /*  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the           */
  13. /*  GNU General Public License for more details.                            */
  14. /*                                                                          */
  15. /*  You should have received a copy of the GNU General Public License       */
  16. /*  along with this program; if not, write to the Free Software             */
  17. /*  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.               */
  18. /*                                                                          */
  19. /*  Requires rxsock.zip from IBM Employee Written Software                  */
  20. /*  <ftp://src.doc.ic.ac.uk/packages/os2/ibm/ews/rxsock.zip>                */
  21. /*                                                                          */
  22. /*  Last Modified: 21st April, 1996                                         */
  23.     Version = 1.64
  24. /****************************************************************************/
  25.  
  26. /************************************************************/
  27. /* Change History                                           */
  28. /************************************************************/
  29. /* 0.1  950115  First version                               */
  30. /* 0.11 950115  fixed nntp.dat problem                      */
  31. /* 0.12 950116  put in workaround for history problem       */
  32. /* 0.13 950116  last newsgroup empty problem                */
  33. /* 0.14 950117  change logging of #! rnews 1234 lines       */
  34. /* 0.15 950122  request multiple newsgroups in a newnews    */
  35. /* 0.16 950124  only accept 200/201 as reply to connect     */
  36. /* 0.17 950127  set nntp clock back a few minutes           */
  37. /* 0.18 950128  improved lock/unlock routines               */
  38. /* 0.19 950129  added GNU public license                    */
  39. /* 0.20 950131  removed GNU license for purposes of testing */
  40. /* 0.21 950131  improved code a little and added logging    */
  41. /* 0.22 950201  implemented getfile                         */
  42. /* 0.23 950203  fixed newtime setting in nntp.dat in morning*/
  43. /* 0.24 950203  os/2 rexx thinks that ' .' == '.'           */
  44. /* 0.25 950203  'NEWNEWS F' deletes all .lck files and runs */
  45. /* 0.26 950203  provide measure of throughput               */
  46. /* 0.27 950203  implemented stacked article requests        */
  47. /* 0.28 950204  send control messages through queue         */
  48. /* 0.29 950205  workaround for nntp update                  */
  49. /* 0.50 950205  Final Beta Release                          */
  50. /* 0.51 950207  Divide by zero error                        */
  51. /* 0.52 950211  Around midnight problem                     */
  52. /* 1.00 950211  First Release                               */
  53. /* 1.01 950219  improved stacking <paul@barnett.demon.co.uk>*/
  54. /* 1.02 950219  Implemented dot transparency                */
  55. /* 1.03 950220  Wasn't releasing sockets on bad replies     */
  56. /* 1.04 950304  wind nntp back 5 minutes if not before 00:05*/
  57. /* 1.05 950305  allow for retries if nntp server too busy   */
  58. /* 1.06 950305  slight change to queue sequence             */
  59. /* 1.07 950318  moved queueing into SendMsg routine         */
  60. /* 1.08 950410  read ka9q root directory from KA9Q env var. */
  61. /* 1.09 950410  unlock files if user presses CTRL+BREAK     */
  62. /* 1.10 950410  fixed ReadNNTP to ignore blank lines        */
  63. /* 1.11 950414  added ControlQ to expose for procedures     */
  64. /* 1.12 950414  added maximum articles to download variable */
  65. /* 1.20 950508  read settings from newnews.ini              */
  66. /* 1.21 950515  patch for rnews article length count        */
  67. /* 1.22 950521  moved call to readinifile                   */
  68. /* 1.23 950527  implemented NEWGROUPS request option        */
  69. /* 1.24 950529  fixed writing to NEWGROUP file              */
  70. /* 1.25 950530  added checking of ini file settings         */
  71. /* 1.26 950601  negative max_articles disables feature      */
  72. /* 1.27 950606  display messages when fetching new groups   */
  73. /* 1.28 950607  rearrange collecting of articles            */
  74. /* 1.29 950614  added first part of kill file support       */
  75. /* 1.30 950618  unstacked kill file implementation          */
  76. /* 1.31 950619  fixed one or two problems with kill files   */
  77. /* 1.32 950620  beta release of newnews - unstacked kill    */
  78. /* 1.33 950621  use WARPDIS as the rexx queue               */
  79. /* 1.34 950705  Fixed max_articles disabling feature        */
  80. /* 1.35 950710  Implementing stacking in kill file fetching */
  81. /* 1.36 950710  Fixed x//stack and nextmessage problems     */
  82. /* 1.37 950711  "stack" needs to be at least 2*stack large  */
  83. /* 1.38 950716  misscalculated loop size for stacking       */
  84. /* 1.39 950716  get rid of // and sx clever thing           */
  85. /* 1.40 950716  beta release of newnews - stacked kill      */
  86. /* 1.41 950718  move queue settings into ini file           */
  87. /* 1.42 950718  display newsgroups to which article posted  */
  88. /* 1.43 950717  add option to run unbatcher after collection*/
  89. /* 1.44 950721  fixed problem in non-kill file reporting    */
  90. /* 1.45 950722  add I param for ini file selection          */
  91. /* 1.46 950723  max_articles = -1 should work now...honest  */
  92. /* 1.47 950727  get file not overriding the kill file       */
  93. /* 1.48 950727  force unlock problem fixed                  */
  94. /* 1.49 950813  use GMT on NEWNEWS and NEWGROUPS commands   */
  95. /* 1.50 950813  temporary fix for suspected missing news    */
  96. /* 1.51 950814  implement use of server DATE command        */
  97. /* 1.52 950814  don't read history file every retry         */
  98. /* 1.53 950814  move determining of hostname outside restart*/
  99. /* 1.54 950815  accept more responses to date command       */
  100. /* 1.55 950907  kill_headers option to kill header & article*/
  101. /* 1.56 950907  430 message abbreviated                     */
  102. /* 1.57 950909  improved messages during news collection    */
  103. /* 1.58 950909  rnews_patch works for kill files now        */
  104. /* 1.59 950909  kill_afterthefact fetches then kills        */
  105. /* 1.60 950913  GetWholeArticles needed to expose some vars */
  106. /* 1.61 950913  GET file must override any kill action      */
  107. /* 1.62 950916  Bad Artithmetic Conversion (headerend)      */
  108. /* 1.63 950918  Was killing when shouldn't have been        */
  109. /* 1.64 960421  Cleanup lck files upon errors               */
  110. /************************************************************/
  111.  
  112. arg gnu .
  113.  
  114. port     = 119                                      /* NNTP port       */
  115. crlf     = d2c(13)||d2c(10)                         /* CR + LF         */
  116. ControlQ = ''                                       /* Control Queue   */
  117. CurrentQ = ''                                       /* Current Queue   */
  118. buffer   = ''                                       /* Empty buffer    */
  119. attempts = 0                                        /* Attempts so far */
  120. inifile  = 'NEWNEWS.INI'                            /* INI file        */
  121. force_unlock = 'NO'                                 /* delete *.lck    */
  122.  
  123. Say 'NEWNEWS.CMD - OS/2 nntp client (version' version')'
  124. Say 'Copyright (C) 1995 Alex Chapman'
  125. Say "NEWNEWS comes with ABSOLUTELY NO WARRANTY; for details type 'NEWNEWS w'."
  126. Say 'This is free software, and you are welcome to redistribute it under certain'
  127. Say "conditions; type `NEWNEWS c' for details."
  128. Say
  129.  
  130. call RxFuncAdd 'SysLoadFuncs', 'RexxUtil', 'SysLoadFuncs'
  131. call SysLoadFuncs
  132.  
  133. Call RxFuncAdd 'RXMATCHLOADFUNCS', 'rxmatch', 'RXMATCHLOADFUNCS'
  134. Call RXMATCHLOADFUNCS
  135.  
  136. Select
  137.   When gnu = 'C' Then Do
  138.     Call ShowConditions
  139.     Exit 0
  140.   End
  141.   When gnu = 'W' Then Do
  142.     Call ShowWarranty
  143.     Exit 0
  144.   End
  145.   When gnu = 'H' | gnu = '?' Then Do
  146.     Exit 0
  147.   End
  148.   When gnu = 'F' Then Do
  149.     force_unlock = 'YES'
  150.   End
  151.   When gnu = 'Q' Then Do
  152.     Say 'The Q parameter is now obsolete, and has been superceded by the use of'
  153.     Say 'the ini settings queue_messages and queue_name'
  154.     Exit 0
  155.   End
  156.   When Left(gnu, 1) = 'I' Then Do
  157.     inifile = Substr(gnu, 2)
  158.   End
  159.   When gnu<>'' Then Do
  160.     Say 'Invalid parameter.  Process terminated.'
  161.     Exit 0
  162.   End
  163.   Otherwise
  164. End
  165.  
  166. Call ReadINIFile inifile, 'NEWNEWS'
  167. Call CheckParameters
  168.  
  169. If force_unlock = 'YES' Then Do
  170.   Call UnlockFiles
  171. End
  172.  
  173. If queue_messages = 'YES' Then Do
  174.   ControlQ = queue_name
  175.   CurrentQ = RXQUEUE('Create', ControlQ)
  176.   If CurrentQ<>ControlQ Then Do
  177.     Call RXQUEUE 'Delete', CurrentQ
  178.   End
  179.   CurrentQ = RXQUEUE('Set', ControlQ)
  180.   Call SendMsg '<NEWNEWS> START'
  181. End
  182.  
  183. Call RxFuncAdd 'SockLoadFuncs', 'RxSock', 'SockLoadFuncs'
  184. Call SockLoadFuncs('QUIET')
  185.  
  186. /* Read NNTP.DAT file */
  187. Call ReadNNTP nntp_dat
  188.  
  189. /* Read History file */
  190. Call ReadHistory history
  191.  
  192. /* Read KILL file (if it exists) */
  193. killline. = 0
  194. keepline. = 0
  195. If kill_articles = 'YES' | kill_afterthefact = 'YES' Then Do
  196.   Call ReadKillFile kill_file
  197. End
  198.  
  199. Say 'NNTPSERVER' server
  200. retcode = SockGetHostByName(server, 'host.!')
  201. If retcode = 0 Then Do
  202.   Say 'SockGetHostByName()' errno
  203.   Call Log 'SockGetHostByName()' errno
  204.   Call UnLockFiles
  205.   Call SendMsg '<NEWNEWS> FAIL SOCK' errno
  206.   Exit errno
  207. End
  208.  
  209. server = host.!addr;
  210.  
  211. Say 'NNTPSERVER' server
  212.  
  213. Restart:      /* Restart from here in event of retry */
  214. If attempts > retries Then Do
  215.   Say 'NEWNEWS quits after' attempts 'retries'
  216.   Call Log 'NEWNEWS quits after' attempts 'retries'
  217.   Call SendMsg '<NEWNEWS> FAIL NNTP 400'
  218.   Call halt
  219. End
  220. Else Do
  221.   attempts = attempts + 1
  222.   Say 'NEWNEWS attempt' attempts
  223.   Call Log 'NEWNEWS attempt' attempts
  224. End
  225.  
  226. /* Lock all files */
  227. Call LockFiles
  228.  
  229. Call time 'R'  /* Reset elapsed timer */
  230. stage = 1      /* 1 = MsgIDS 2 = Articles */
  231. time. = 0      /* time spent in stage     */
  232. BytesSent. = 0 /* outgoing bytes in stage */
  233. BytesRecv. = 0 /* incoming bytes in stage */
  234.  
  235. /* Open Socket */
  236. socket  = SockSocket('AF_INET', 'SOCK_STREAM', 0)
  237. If socket < 0 Then Do
  238.   Say 'SockSocket()' errno
  239.   Call UnLockFiles
  240.   Call SendMsg '<NEWNEWS> FAIL SOCK' errno
  241.   Exit errno
  242. End
  243.  
  244. signal on halt
  245.  
  246. Call Log '-------------------------------------------------------------'
  247. Call Log 'NEWNEWS version' version 'started' date() time()
  248.  
  249. /* Connect Socket */
  250. server.!family = 'AF_INET'
  251. server.!port   = port
  252. server.!addr   = server
  253.  
  254. retcode = SockConnect(socket,'server.!')
  255. If retcode < 0 Then Do
  256.   Say 'SockConnect()' errno
  257.   Call UnLockFiles
  258.   Call SendMsg '<NEWNEWS> FAIL SOCK' errno
  259.   Exit errno
  260. End
  261.  
  262. /* Get response from connect */
  263. reply = GetResponse(socket)
  264.  
  265. If reply <> 200 & reply <> 201 Then Do
  266.   Say 'Failed.  Reply was' allreply
  267.   Call UnLockFiles
  268.   If reply = 400 Then Do          /* Retry for busy */
  269.     retcode = SockSoClose(socket)
  270.     If retcode < 0 Then Do
  271.       Say 'SockSoClose()' errno
  272.       Exit errno
  273.     End
  274.     If attempts <= retries & retry_delay > 0 Then Do
  275.       Say 'NEWNEWS about to retry... sleeping for' retry_delay
  276.       Call SysSleep retry_delay
  277.     End
  278.     Signal Restart
  279.   End
  280.   Call SendMsg '<NEWNEWS> FAIL NNTP' reply
  281.   Call halt
  282. End
  283.  
  284. Say 'Connected.  Reply was' allreply
  285.  
  286. /* Get DATE and TIME that the server thinks it is */
  287. Call GetServerDate socket
  288.  
  289. /* Handle the GET file before everything else */
  290. msgid. = ''
  291. msgid.0 = 0
  292. count = ReadGetFile()
  293. If count > 0 Then Do
  294.   totalmsg = count
  295.   Say 'GET IDS    (' count ')'
  296.   Call GetArticles socket 'GET'
  297.   Call SysFileDelete getfile
  298. End
  299.  
  300. msgid. = ''
  301. msgid.0 = 0
  302. newsgroups = ''
  303. commandlength = 512 - Length('NEWNEWS  000000 000000 GMT') - 2 /* CR LF */
  304. Do i = 1 to group.0
  305.   If Length(newsgroups) + Length(group.i) > commandlength Then Do
  306.     newsgroups = Left(newsgroups, Length(newsgroups) - 1)
  307.     Say newsgroups
  308.     count = GetMsgIds(socket, LastDate, LastTime, newsgroups)
  309.     newsgroups = ''
  310.     Say 'Headers    (' count ')'
  311.   End
  312.   newsgroups = newsgroups||group.i','
  313. End
  314. newsgroups = Left(newsgroups, Length(newsgroups) - 1)
  315. Say newsgroups
  316. count = GetMsgIds(socket, LastDate, LastTime, newsgroups)
  317. newsgroups = ''
  318. Say 'Headers    (' count ')'
  319. totalmsg = 0
  320. duplicate = 0
  321. crosspost = 0
  322. Do i = 1 to msgid.0
  323.   MessageID = msgid.i
  324.   If hit.MessageID = 0 & ((max_articles < 1) | (totalmsg < max_articles)) Then Do
  325.     totalmsg = totalmsg + 1
  326.     hit.MessageID = 2
  327.   End
  328.   Else Do
  329.     msgid.i = ''
  330.     If hit.MessageID = 1 Then duplicate = duplicate + 1
  331.     If hit.MessageID = 2 Then crosspost = crosspost + 1
  332.   End
  333. End
  334. Say 'Duplicate  (' duplicate ')'
  335. Say 'Crossposts (' crosspost ')'
  336. Say 'Download   (' totalmsg ')'
  337. If max_articles = totalmsg Then Do
  338.   Say '*maximum article limit reached for this session'
  339.   Call Log '*maximum article limit reached for this session'
  340. End
  341. Call Log 'Duplicate  (' duplicate ')'
  342. Call Log 'Crossposts (' crosspost ')'
  343. Call Log 'Download   (' totalmsg ')'
  344.  
  345. time.stage = time('R')  /* Elapsed time for message ids */
  346. stage = stage + 1
  347.  
  348. If totalmsg > 0 Then Do
  349.   Call GetArticles socket 'KILL'
  350. End
  351.  
  352. If fetch_newgroups = 'YES' Then Do
  353.   retcode = GetNewGroups(socket, LastDate, LastTime)
  354. End
  355.  
  356. time.stage = time('R')  /* Elapsed time for articles */
  357.  
  358. /* Report and log times */
  359. Call ReportTimes
  360.  
  361. /* Update NNTP.DAT */
  362. If totalmsg > 0 & (totalmsg < max_articles | max_articles = -1) Then Do
  363.    Call UpdateNNTP(nntp_dat)
  364. End
  365.  
  366. /* UnLock all files */
  367. Call UnLockFiles
  368.  
  369. /* Start Unbatcher if configured */
  370. If unbatch_news = 'YES' Then Do
  371.   /* If there is a BATCH.TXT file */
  372.   If Stream(batch_txt, 'c', 'query exists') <> '' Then Do
  373.     Call Log 'Unbatching <'unbatch_command'>'
  374.     Say 'Unbatching news...'
  375.     '@START /C' unbatch_command '2>NUL'
  376.     If RC <> 0 Then Do
  377.       Say 'Failed to start unbatcher:' unbatch_command
  378.       Say 'Check settings in NEWNEWS.INI'
  379.       Call Log 'Unbatching failed to start RC='RC
  380.     End
  381.   End
  382. End
  383.  
  384. Call Log 'NEWNEWS version' version 'completed' date() time()
  385. Call Log '-------------------------------------------------------------'
  386.  
  387. Call SendMsg '<NEWNEWS> STOP NEWNEWS' totalmsg
  388. Call halt
  389.  
  390. /* Report and log times */
  391. ReportTimes: procedure expose crlf logfile time. BytesSent. BytesRecv. ControlQ CurrentQ,
  392.                               batch_txt history
  393.  
  394.  stage.1 = 'Getting msg-ids'
  395.  stage.2 = 'Getting article'
  396.  totalstage = 3
  397.  stage.totalstage     = 'Total throughput'
  398.  time.totalstage      = 0
  399.  BytesSent.totalstage = 0
  400.  BytesRecv.totalstage = 0
  401.  Do i = 1 to totalstage
  402.    If time.i > 0 Then Do /* Can't divide by zero */
  403.      bytes = BytesSent.i + BytesRecv.i
  404.      throughput = bytes / time.i
  405.      report = stage.i throughput 'bytes/sec (' bytes 'bytes'
  406.      report = report time.i 'seconds )'
  407.      Say report
  408.      Call Log report
  409.      If i < totalstage Then Do
  410.        BytesSent.totalstage = BytesSent.totalstage + BytesSent.i
  411.        BytesRecv.totalstage = BytesRecv.totalstage + BytesRecv.i
  412.        time.totalstage = time.totalstage + time.i
  413.      End
  414.    End
  415.  End
  416.  Return
  417.  
  418. /* Lock all files */
  419. LockFiles: procedure expose batch_txt history crlf logfile ControlQ CurrentQ
  420.  
  421.   Parse var batch_txt batch_lck '.' .
  422.   batch_lck = batch_lck||'.LCK'
  423.   Parse var history history_lck '.' .
  424.   history_lck=history_lck||'.LCK'
  425.  
  426.   If Stream(batch_lck, 'c', 'query exists') <> '' Then Do
  427.     Say 'Batch file locked' batch_lck
  428.     Call SendMsg '<NEWNEWS> FAIL NEWNEWS batch_lck'
  429.     Exit 1
  430.   End
  431.  
  432.   If Stream(history_lck, 'c', 'query exists') <> '' Then Do
  433.     Say 'History file locked' history_lck
  434.     Call SendMsg '<NEWNEWS> FAIL NEWNEWS history_lck'
  435.     Exit 1
  436.   End
  437.  
  438.   If Stream(batch_lck, 'c', 'open write') <> 'READY:' Then Do
  439.     Say 'Batch file lock failed' batch_lck
  440.     Call SendMsg '<NEWNEWS> FAIL NEWNEWS batch_lck'
  441.     Exit 1
  442.   End
  443.  
  444.   retcode = Stream(batch_lck, 'c', 'close')
  445.  
  446.   If Stream(history_lck, 'c', 'open write') <> 'READY:' Then Do
  447.     Say 'History file lock failed' history_lck
  448.     Call SendMsg '<NEWNEWS> FAIL NEWNEWS history_lck'
  449.     Exit 1
  450.   End
  451.  
  452.   retcode = stream(history_lck, 'c', 'close')
  453.  
  454.   Return
  455.  
  456. /* Unlock all files */
  457. UnLockFiles: procedure expose batch_txt history crlf logfile ControlQ CurrentQ
  458.  
  459.   Parse var batch_txt batch_lck '.' .
  460.   batch_lck = batch_lck||'.LCK'
  461.   Parse var history history_lck '.' .
  462.   history_lck=history_lck||'.LCK'
  463.  
  464.   Call SysFileDelete batch_lck
  465.   Call SysFileDelete history_lck
  466.  
  467.   Return
  468.  
  469.  
  470. /* Fetch new groups and write into newgroup_file */
  471. GetNewGroups: Procedure expose crlf logfile ControlQ CurrentQ newgroup_file,
  472.                                buffer BytesSent. BytesRecv. stage,
  473.                                batch_txt history
  474.   Parse arg socket,LastDate,LastTime
  475.   command = 'newgroups' LastDate LastTime 'GMT'
  476.   Call Log '>>'command
  477.   Say 'Fetching new groups created since' LastDate LastTime '...'
  478.   command = command||crlf
  479.   Call MySockSend socket, command
  480.   reply = GetResponse(socket)
  481.   If reply <> 231 Then Do
  482.     Call Log '<<' reply
  483.     Say 'Expected a 231 to indicate a list of groups to follow'
  484.     Say 'Instead received following reply:' reply
  485.   End
  486.   Else Do
  487.     retcode = Stream(newgroup_file, 'c', 'open write')
  488.     If retcode <> 'READY:' Then Do
  489.       Call Log 'Error opening ('newgroup_file')' retcode
  490.       Say 'Error opening ('newgroup_file')' retcode
  491.       Call SendMsg '<NEWNEWS> FAIL NEWNEWS' retcode
  492.       Call UnlockFiles
  493.       Exit 1
  494.     End
  495.     Say 'New Newsgroups:' line.0
  496.     Do i = 1 to line.0
  497.       Call LINEOUT newgroup_file, line.i
  498.     End
  499.     retcode = Stream(newgroup_file, 'c', 'close')
  500.   End
  501.   Return 0
  502.  
  503. /* Read message ids into msgid. */
  504. GetMsgIds: Procedure expose msgid. buffer crlf logfile BytesSent. BytesRecv.,
  505.                             stage ControlQ CurrentQ,
  506.                             batch_txt history
  507.   Parse arg socket,LastDate,LastTime,newsgroups
  508.   command = 'newnews' newsgroups LastDate LastTime 'GMT'
  509.   Call Log '>>'command
  510.   command = command||crlf
  511.   Call MySockSend socket, command
  512.   reply = GetResponse(socket)
  513.   If reply <> 230 Then Do
  514.     Say 'Expected a 230 to indicate a list of message ids to follow'
  515.     Say 'Instead received following reply:' reply
  516.     Call SendMsg '<NEWNEWS> FAIL NNTP' reply
  517.     Call UnlockFiles
  518.     Exit reply
  519.   End
  520.   x = msgid.0
  521.   Do i = 1 to line.0
  522.     x = i + msgid.0
  523.     msgid.x = line.i
  524.   End
  525.   msgid.0 = x
  526.   Return line.0
  527.  
  528. /* Read message ids from get file and add to msgid. */
  529. ReadGetFile: Procedure expose msgid. buffer crlf logfile getfile ControlQ CurrentQ,
  530.                               batch_txt history
  531.   x = msgid.0
  532.   start = x
  533.   retcode = Stream(getfile, 'c', 'open read')
  534.   If retcode = 'READY:' Then Do
  535.     Do While Lines(getfile)<>0
  536.       x = x + 1
  537.       msgid.x = LINEIN(getfile)
  538.     End
  539.     msgid.0 = x
  540.     retcode = Stream(getfile, 'c', 'close')
  541.   End
  542.   Return (x - start)
  543.  
  544. /* Test if article should be killed on basis of header */
  545. KillArticle: Procedure expose killline. line. keepline. logfile,
  546.                               batch_txt history
  547.   keep = 0
  548.   kill = 0
  549.   Do i = 1 to keepline.0 While keep = 0
  550.     Do j = 1 to line.0 While keep = 0
  551.       If RXMATCHIT(line.j, keepline.i) = 0 Then Do
  552.         Call Log 'KEEPLINE' keepline.i
  553.         Call Log 'MATCHES ' line.j
  554.         keep = 1
  555.       End
  556.     End
  557.   End
  558.   If keep = 0 Then Do
  559.     Do i = 1 to killline.0 While kill = 0
  560.       Do j = 1 to line.0 While kill = 0
  561.         If RXMATCHIT(line.j, killline.i) = 0 Then Do
  562.           Call Log 'KILLLINE' killline.i
  563.           Call Log 'MATCHES ' line.j
  564.           kill = 1
  565.         End
  566.       End
  567.     End
  568.   End
  569.   Return kill
  570.  
  571. /* Get Articles and write to batch_txt */
  572.  
  573. GetArticles:  Procedure expose batch_txt msgid. buffer history totalmsg,
  574.                                crlf logfile BytesSent. BytesRecv. stage,
  575.                                stack ControlQ CurrentQ rnews_patch killline.,
  576.                                keepline. kill_headers kill_afterthefact,
  577.                                kill_articles
  578.   Parse arg socket command
  579.   Call Log 'GetArticles: command =<'command'>'
  580.   If killline.0 = 0 | command = 'GET' | kill_articles <> 'YES' Then Do
  581.     Call GetWholeArticles socket command
  582.   End
  583.   Else Do
  584.     Call GetHeadAndBody socket
  585.   End
  586.   Return
  587.  
  588.  
  589. GetHeadAndBody:  Procedure expose batch_txt msgid. buffer history totalmsg,
  590.                                   crlf logfile BytesSent. BytesRecv. stage,
  591.                                   stack ControlQ CurrentQ rnews_patch killline.,
  592.                                   keepline. kill_headers
  593.   Parse arg socket
  594.   Say '[n.b. kill file use reduces performance by approx. 50%]'
  595.   Say '[     set kill_articles = NO in newnews.ini to disable]'
  596.   If kill_headers = 'YES' Then Do
  597.     Say '[ n.b. headers of killed articles will not appear in batch.txt ]'
  598.     Say '[      set kill_headers = NO in newnews.ini to keep them in it ]'
  599.   End
  600.   retcode = Stream(batch_txt, 'c', 'open write')
  601.   If retcode <> 'READY:' Then Do
  602.     Say 'Error opening ('batch_txt')' retcode
  603.     Call SendMsg '<NEWNEWS> FAIL NEWNEWS' retcode
  604.     Call UnlockFiles
  605.     Exit 1
  606.   End
  607.   retcode = Stream(history, 'c', 'open')
  608.   If retcode <> 'READY:' Then Do
  609.     Say 'Error opening ('history')' retcode
  610.     Call SendMsg '<NEWNEWS> FAIL NEWNEWS' retcode
  611.     Call UnlockFiles
  612.     Exit 1
  613.   End
  614.   retcode = Stream(history, 'c', 'seek <1')   /* Look at last char */
  615.   junk = charin(history)
  616.   if c2d(junk)=26 Then Do                     /* If it's an EOF    */
  617.     retcode = Stream(history, 'c', 'seek -1') /* overwrite it      */
  618.   End
  619.   nextmessage = 0
  620.   ss. = '' ; in = 0 ; out = 0
  621.   target. = '????'
  622.   Do x = 1 to ((2 * msgid.0) + stack)
  623.     If x <= msgid.0 & msgid.x = '' Then iterate
  624.     If x <= msgid.0 Then Do
  625.       command = 'HEAD' msgid.x
  626.       Call Log '>>'command
  627.       command = command||crlf
  628.       Call MySockSend socket, command
  629.       in = in + 1
  630.       ss.in = 'H' in msgid.x
  631.     End
  632.     If x >= stack Then Do
  633.       out = out + 1
  634.       Parse var ss.out type n msgid
  635.       ss.out = ''
  636.       If type = 'H' Then Do
  637.         reply = GetResponse(socket)
  638.         If line.0 = 0 Then Do
  639.           nextmessage = nextmessage + 1
  640.           Say reply '('nextmessage'/'totalmsg')' msgid
  641.         End
  642.         Else Do
  643.           size.n = line.0 /* 1 character count for a crlf */
  644.           Do j = 1 to line.0
  645.             size.n = size.n + Length(line.j)
  646.             article.n.j = line.j
  647.             If Left(line.j, 11) = 'Newsgroups:' Then Do
  648.               Parse var line.j . target.n
  649.             End
  650.           End
  651.           If rnews_patch = '1' Then Do      /* rnews crlf = 2 */
  652.             size.n = size.n + line.0 /* +1 (=2) character count for a crlf */
  653.           End
  654.           If rnews_patch = '2' Then Do      /* cheeky fix */
  655.             lastline = line.0
  656.             line.lastline = line.lastline || Left(' ', line.0, ' ')
  657.           End
  658.           article.n.0 = line.0
  659.           If KillArticle() = 0 Then Do
  660.             command = 'BODY' msgid
  661.             Call Log '>>'command
  662.             command = command||crlf
  663.             Call MySockSend socket, command
  664.             in = in + 1
  665.             ss.in = 'B' n msgid
  666.           End
  667.           Else Do
  668.             nextmessage = nextmessage + 1
  669.             If kill_headers = 'YES' Then Do
  670.               Say '*evaporate* ('nextmessage'/'totalmsg')' msgid target.n
  671.               Call Log 'article and header killed' msgid
  672.               Call LINEOUT history, msgid
  673.             End
  674.             Else Do
  675.               Say '*kill* ('nextmessage'/'totalmsg')' msgid target.n
  676.               Call Log 'article killed' msgid
  677.               rnews = '#! rnews' size.n
  678.               Call LINEOUT batch_txt, rnews
  679.               Do j = 1 to article.n.0
  680.                 Call LINEOUT batch_txt, article.n.j
  681.               End
  682.               Call LINEOUT history, msgid
  683.             End
  684.           End
  685.         End
  686.       End
  687.       If type = 'B' Then Do
  688.         reply = GetResponse(socket)
  689.         nextmessage = nextmessage + 1
  690.         If line.0 = 0 Then Do
  691.           Say reply '('nextmessage'/'totalmsg')' msgid
  692.         End
  693.         Else Do
  694.           Say '('nextmessage'/'totalmsg')' msgid target.n
  695.           size.n = size.n + line.0 /* 1 character count for a crlf */
  696.           Do j = 1 to line.0
  697.             size.n = size.n + Length(line.j)
  698.           End
  699.           size.n = size.n + 1 /* for line between HEAD and BODY */
  700.           If rnews_patch = '1' Then Do      /* rnews crlf = 2 */
  701.             size.n = size.n + line.0 /* +1 (=2) character count for a crlf */
  702.             size.n = size.n + 1 /* +1 (=2) for line between head and body */
  703.           End
  704.           If rnews_patch = '2' Then Do      /* cheeky fix */
  705.             lastline = line.0
  706.             line.lastline = line.lastline || Left(' ', line.0, ' ')
  707.           End
  708.           rnews = '#! rnews' size.n
  709.           Call LINEOUT batch_txt, rnews
  710.           Do j = 1 to article.n.0
  711.             Call LINEOUT batch_txt, article.n.j
  712.           End
  713.           Call LINEOUT batch_txt, ''
  714.           Do j = 1 to line.0
  715.             Call LINEOUT batch_txt, line.j
  716.           End
  717.           Call LINEOUT history, msgid
  718.         End
  719.       End
  720.     End
  721.   End
  722.   retcode = Stream(history, 'c', 'close')
  723.   retcode = Stream(batch_txt, 'c', 'close')
  724.   Return
  725.  
  726.  
  727. GetWholeArticles:  Procedure expose batch_txt msgid. buffer history totalmsg,
  728.                                     crlf logfile BytesSent. BytesRecv. stage,
  729.                                     stack ControlQ CurrentQ rnews_patch,
  730.                                     kill_afterthefact keepline. killline.
  731.   Parse arg socket command
  732.   If kill_afterthefact = 'YES' & command <> 'GET' Then Do
  733.     Say '[ n.b. all articles will be fetched before processing kill file  ]'
  734.     Say '[      set kill_afterthefact = NO in newnews.ini to prevent this ]'
  735.   End
  736.   retcode = Stream(batch_txt, 'c', 'open write')
  737.   If retcode <> 'READY:' Then Do
  738.     Say 'Error opening ('batch_txt')' retcode
  739.     Call SendMsg '<NEWNEWS> FAIL NEWNEWS' retcode
  740.     Call UnlockFiles
  741.     Exit 1
  742.   End
  743.   retcode = Stream(history, 'c', 'open')
  744.   If retcode <> 'READY:' Then Do
  745.     Say 'Error opening ('history')' retcode
  746.     Call SendMsg '<NEWNEWS> FAIL NEWNEWS' retcode
  747.     Call UnlockFiles
  748.     Exit 1
  749.   End
  750.   retcode = Stream(history, 'c', 'seek <1')   /* Look at last char */
  751.   junk = charin(history)
  752.   if c2d(junk)=26 Then Do                     /* If it's an EOF    */
  753.     retcode = Stream(history, 'c', 'seek -1') /* overwrite it      */
  754.   End
  755.   nextmessage = 0
  756.   output = 0                         /* ARTICLE <msgid.output> being sent */
  757.   target. = '????'
  758.   Do input = 1 to msgid.0                       /* msgid.input being read */
  759.     Do While output < ,
  760.              min(msgid.0,input+stack) /* send stack ARTICLE commands */
  761.       output = output + 1
  762.  
  763.       If msgid.output='' Then Iterate
  764.  
  765.       command = 'ARTICLE' msgid.output
  766.       Call Log '>>'command
  767.       command = command||crlf
  768.       Call MySockSend socket, command
  769.     End
  770.  
  771.     If msgid.input='' Then Iterate
  772.  
  773.     reply = GetResponse(socket)
  774.     size = line.0 /* 1 character count for a crlf */
  775.     nextmessage = nextmessage + 1
  776.     If line.0 = 0 Then Do
  777.       Say reply '('nextmessage'/'totalmsg')' msgid.input
  778.     End
  779.     Else Do
  780.       If rnews_patch = '1' Then Do      /* rnews crlf = 2 */
  781.         size = size + line.0 /* +1 (=2) character count for a crlf */
  782.       End
  783.       If rnews_patch = '2' Then Do      /* cheeky fix */
  784.         lastline = line.0
  785.         line.lastline = line.lastline || Left(' ', line.0, ' ')
  786.       End
  787.       header_end = 0
  788.       real_length = line.0
  789.       Do j = 1 to line.0
  790.         size = size + Length(line.j)
  791.         If header_end = 0 & line.j = '' Then header_end = j - 1
  792.       End
  793.       line.0 = header_end
  794.       If kill_afterthefact<>'YES' | command='GET' | KillArticle()=0 Then Do
  795.         line.0 = real_length
  796.         Do j = 1 to header_end
  797.           If Left(line.j, 11) = 'Newsgroups:' Then Do
  798.             Parse var line.j . target.input
  799.           End
  800.         End
  801.         rnews = '#! rnews' size
  802.         Call LINEOUT batch_txt, rnews
  803.         Do j = 1 to line.0
  804.           Call LINEOUT batch_txt, line.j
  805.         End
  806.         Call LINEOUT history, msgid.input
  807.         Say '('nextmessage'/'totalmsg')' msgid.input target.input
  808.       End
  809.       Else Do
  810.         Say '*DISCARDED* ('nextmessage'/'totalmsg')' msgid.input
  811.       End
  812.     End
  813.   End
  814.   retcode = Stream(history, 'c', 'close')
  815.   retcode = Stream(batch_txt, 'c', 'close')
  816.   Return
  817.  
  818.  
  819. /* read KILL. to determine the articles which should be killed */
  820.  
  821. ReadKillFile: Procedure expose killline. crlf logfile ControlQ CurrentQ keepline.,
  822.                                batch_txt history
  823.  
  824.   Parse arg kill_file
  825.   killline. = ''
  826.   killline.0 = 0
  827.   retcode = Stream(kill_file, 'c', 'open read')
  828.   If retcode <> 'READY:' Then Do
  829.     Say 'No kill file available'
  830.     Call Log 'No kill file available ('kill_file')'
  831.     Return
  832.   End
  833.   Say 'Reading' kill_file
  834.   Call Log 'Reading' kill_file
  835.   kill = 0
  836.   keep = 0
  837.   Do While Lines(kill_file) <> 0
  838.     next = LINEIN(kill_file)
  839.     If Left(next, 1) = '!' Then Do
  840.       keep = keep + 1
  841.       keepline.keep = Substr(next, 2)
  842.       Call Log 'KEEP' keepline.keep
  843.     End
  844.     Else Do
  845.       kill = kill + 1
  846.       killline.kill = next
  847.       Call Log 'KILL' killline.kill
  848.     End
  849.   End
  850.   killline.0 = kill
  851.   keepline.0 = keep
  852.   Return
  853.  
  854.  
  855. /* Determine server date and time from DATE command */
  856.  
  857. GetServerDate: Procedure expose NewDate NewTime crlf logfile ControlQ CurrentQ,
  858.                                 buffer BytesSent. BytesRecv. stage,
  859.                                 batch_txt history
  860.  
  861.   Parse arg socket
  862.   Say 'Attempting to fetch server date/time:'
  863.   command = 'date'
  864.   Call Log '>>'command
  865.   command = command||crlf
  866.   Call MySockSend socket, command
  867.   reply = GetResponse(socket)
  868.   If reply > 299 Then Do
  869.     Call Log '<<'allreply
  870.     Say 'Server does not understand DATE command'
  871.   End
  872.   Else Do
  873.     Parse var allreply . serverdate .
  874.     NewDate = Substr(serverdate, 3, 6)
  875.     NewTime = Substr(serverdate, 9, 6)
  876.     Call Log 'server date:'NewDate 'time:'NewTime
  877.     Say 'Server date:'NewDate 'time:'NewTime
  878.   End
  879.   Return 0
  880.  
  881. /* read NNTP.DAT to determine newsserver, date and time last complete */
  882. /* news read, and all the groups to read                              */
  883.  
  884. ReadNNTP: Procedure expose server LastDate LastTime group. NewDate NewTime,
  885.                            crlf logfile ControlQ CurrentQ,
  886.                            batch_txt history
  887.   Parse arg nntp_dat
  888.   Say 'Reading' nntp_dat
  889.   retcode = Stream(nntp_dat, 'c', 'open read')
  890.   If retcode <> 'READY:' Then Do
  891.     Say 'Error opening ('nntp_dat')' retcode
  892.     Call SendMsg '<NEWNEWS> FAIL NEWNEWS' retcode
  893.     Call UnlockFiles
  894.     Exit 1
  895.   End
  896.   Parse value linein(nntp_dat) with server LastDate LastTime
  897.   Say server LastDate LastTime
  898.   NumGroups = 0
  899.   Do While Lines(nntp_dat) <> 0
  900.     NumGroups = NumGroups + 1
  901.     group.NumGroups = LINEIN(nntp_dat)
  902.     group.NumGroups = Strip(group.NumGroups)
  903.     If group.NumGroups = '' Then NumGroups = NumGroups - 1
  904.   End
  905.   group.0 = NumGroups
  906.   retcode = Stream(nntp_dat, 'c', 'close')
  907.   NewDate = Right(date('s'), 6)
  908.   NewTime = WindTimeBack5Minutes(time('n'))
  909.   Return
  910.  
  911. /* Update date and time in NNTP.DAT */
  912.  
  913. UpdateNNTP: Procedure expose NewDate NewTime crlf logfile ControlQ CurrentQ,
  914.                              batch_txt history
  915.   Parse arg nntp_dat
  916.   Say 'Updating' nntp_dat
  917.   retcode = Stream(nntp_dat, 'c', 'open')
  918.   If retcode <> 'READY:' Then Do
  919.     Say 'Error opening ('nntp_dat')' retcode
  920.     Call SendMsg '<NEWNEWS> FAIL NEWNEWS' retcode
  921.     Call UnlockFiles
  922.     Exit 1
  923.   End
  924.   Parse value linein(nntp_dat) with server LastDate LastTime
  925.   retcode = Stream(nntp_dat, 'c', 'seek =1')
  926.   Call LINEOUT nntp_dat, server NewDate NewTime
  927.   retcode = Stream(nntp_dat, 'c', 'close')
  928.   Return
  929.  
  930. /* read history file to mark all message ids listed in it as already read */
  931. ReadHistory: Procedure expose hit. crlf logfile ControlQ CurrentQ,
  932.                               batch_txt history
  933.   Parse arg history
  934.   hit. = 0
  935.   Say 'Reading' history
  936.   retcode = Stream(history, 'c', 'open read')
  937.   If retcode <> 'READY:' Then Do
  938.     Say 'Error opening ('history')' retcode
  939.     Call SendMsg '<NEWNEWS> FAIL NEWNEWS' retcode
  940.     Call UnlockFiles
  941.     Exit 1
  942.   End
  943.   Do While Lines(history) <> 0
  944.     MessageId = LINEIN(history)
  945.     hit.MessageId = 1
  946.   End
  947.   retcode = Stream(history, 'c', 'close')
  948.   Return
  949.  
  950. /* Close socket */
  951. halt:
  952.   If CurrentQ <> '' Then Do
  953.     Call RXQUEUE 'Set', CurrentQ
  954.   End
  955.   Say 'Closing socket...'
  956.   retcode = SockSoClose(socket)
  957.   If retcode < 0 Then Do
  958.     Say 'SockSoClose()' errno
  959.     Exit errno
  960.   End
  961.   Call UnLockFiles
  962.   Exit 0
  963.  
  964. /* recv() multiple lines and store in line. */
  965.  
  966. GetResponse:     procedure expose line. buffer crlf logfile ControlQ CurrentQ,
  967.                                   BytesSent. BytesRecv. stage allreply,
  968.                                   batch_txt history
  969.  
  970.   Parse arg socket .
  971.   replies = '100 215 220 221 222 223 230 231'
  972.   line. = ''
  973.   line.0 = 0
  974.   response = GetResponseLine(socket)
  975.   allreply = response
  976.   Parse var response reply junk
  977.   Call Log '<<'response
  978.   If WordPos(reply, replies) = 0 Then Do
  979.     Return reply
  980.   End
  981.   Call Log '++additional lines'
  982.   numline = 0
  983.   inheader = 1
  984.   Do Until line = '.' & Length(line) = 1
  985.     line = GetResponseLine(socket)
  986.     if line <> '.' | Length(line) <> 1 Then Do
  987.       numline = numline + 1
  988.       If line = '' Then inheader = 0
  989.       If Left(line, 1) = '.' Then Do   /* Transparency, as per rfc821 */
  990.         line = Substr(line, 2)
  991.       End
  992.       If Left(line, 5) = 'From ' & inheader = 0 Then Do
  993.         line = '>' || line
  994.       End
  995.       line.numline = line
  996.       line = ''           /* Not interested in line if we get in here */
  997.     End
  998.     Else Do
  999.       numline = numline + 1
  1000.       line.numline = ''     /* blank line to separate messages */
  1001.     End
  1002.   End
  1003.   line.0 = numline - 1
  1004.   Call Log '--total lines received (including .):'numline
  1005.   Return reply
  1006.  
  1007. /* recv() a single line */
  1008.  
  1009. GetResponseLine: procedure expose buffer crlf logfile BytesRecv. stage,
  1010.                                   ControlQ CurrentQ,
  1011.                                   batch_txt history
  1012.  
  1013.   Parse arg socket .
  1014.   Do While Pos(crlf, buffer) = 0
  1015.     retcode = SockRecv(socket, 'data', 10000)
  1016.     If retcode < 0 Then Do
  1017.       Say 'SockRecv()' errno
  1018.       Call SendMsg '<NEWNEWS> FAIL SOCK' errno
  1019.       Call UnlockFiles
  1020.       Exit errno
  1021.     End
  1022.     buffer = buffer || data
  1023.   End
  1024.   data = Left(buffer, Pos(crlf, buffer) - 1)
  1025.   buffer = Substr(buffer, Pos(crlf, buffer) + 2)
  1026.   BytesRecv.stage = BytesRecv.stage + Length(data) + 2 /* for crlf */
  1027.   Return data
  1028.  
  1029. MySockSend: Procedure expose crlf logfile BytesSent. stage ControlQ CurrentQ,
  1030.                              batch_txt history
  1031.  
  1032.   Parse arg socket, data
  1033.   retcode = 0
  1034.   BytesSent.stage = BytesSent.stage + Length(data) + 2 /* for crlf */
  1035.   Do While retcode < Length(data)
  1036.     retcode = SockSend(socket, data)
  1037.     If retcode < 0 Then Do
  1038.       Say 'SockSend()' errno
  1039.       Call SendMsg '<NEWNEWS> FAIL SOCK' errno
  1040.       Call UnlockFiles
  1041.       Exit errno
  1042.     End
  1043.     If retcode < Length(data) Then Do
  1044.       data = Substr(data, retcode + 1)
  1045.       retcode = 0
  1046.     End
  1047.   End
  1048.   Return
  1049.  
  1050. Log: Procedure expose logfile ControlQ CurrentQ,
  1051.                       batch_txt history
  1052.  
  1053.   Parse arg line
  1054.   retcode = Stream(logfile, 'c', 'open write')
  1055.   retcode = LINEOUT(logfile, line)
  1056.   retcode = Stream(logfile, 'c', 'close')
  1057.   Return
  1058.  
  1059. WindTimeBack5Minutes: Procedure expose logfile ControlQ CurrentQ,
  1060.                                 batch_txt history
  1061.  
  1062.   Parse arg hh':'mm':'ss
  1063.   If mm >= 5 Then  Do      /* minutes 5 or more */
  1064.     mm = mm - 5
  1065.   End
  1066.   Else If hh > 0 Then Do   /* minutes less than 5 but hour 1 or more */
  1067.     mm = 60 + mm - 5
  1068.     hh = hh - 1
  1069.   End
  1070.   Else Do                  /* Less than 5 minutes after midnight */
  1071.     ss = 1                 /* Just wind back to midnight to avoid having */
  1072.     mm = 0                 /* to worry about months, leap years etc      */
  1073.   End
  1074.   If hh > 0 Then Do
  1075.     hh = hh - 1
  1076.   End
  1077.   Return Right(hh, 2, '0')||Right(mm, 2, '0')||Right(ss, 2, '0')
  1078.  
  1079. SendMsg: Procedure expose ControlQ CurrentQ
  1080.  
  1081.   Parse arg message
  1082.   If ControlQ <> '' & ControlQ <> 'CONTROLQ' Then Do
  1083.     Queue message
  1084.   End
  1085.   Return
  1086.  
  1087. CheckParameters:
  1088.   If DataType(max_articles) <> 'NUM' Then Do
  1089.     Say 'MAX_ARTICLES has an invalid setting ('max_articles')'
  1090.     Say 'Please correct NEWNEWS.INI and try again'
  1091.     Call Log 'NEWNEWS.INI: MAX_ARTICLES = 'max_articles
  1092.     Exit 1
  1093.   End
  1094.   Return
  1095.  
  1096. ReadINIFile:
  1097.  
  1098.   arg inifile, application
  1099.   file = Stream(inifile, 'c', 'query exists')
  1100.   If file = '' Then Do
  1101.     file = SysSearchPath('PATH',inifile)
  1102.   End
  1103.   If file = '' Then Do
  1104.     Say 'Unable to find' inifile
  1105.     Exit 1
  1106.   End
  1107.   Say 'inifile' file
  1108.   app = ''
  1109.   ini. = 0
  1110.   retcode = Stream(file, 'c', 'open read')
  1111.   If retcode <> 'READY:' Then Do
  1112.     Say 'Unable to open' file
  1113.     Exit 2
  1114.   End
  1115.   Do While Lines(file) <> 0
  1116.     line = LINEIN(file)
  1117.     If Left(line, 1) = '[' Then Do
  1118.       Parse Upper var line '[' app ']' .
  1119.     End
  1120.     Else Do
  1121.       If line <> '' & Left(line, 1) <> '#' Then Do
  1122.         If app = '' Then Do
  1123.           Say 'Invalid line in' file 'expected [application_name]'
  1124.           Exit 1
  1125.         End
  1126.         If app = application | app = 'DEFAULT' Then Do
  1127.           Parse var line varname '=' varvalue
  1128.           Parse Upper var varname varname
  1129.           varname = Strip(varname)
  1130.           varvalue = Strip(varvalue)
  1131.           If ini.varname = 0 | app = application Then Do
  1132.             retcode = Value(varname, varvalue)
  1133.             ini.varname = 1
  1134.           End
  1135.         End
  1136.       End
  1137.     End
  1138.   End
  1139.   retcode = Stream(file, 'c', 'close')
  1140.   Return
  1141.  
  1142. ShowWarranty:
  1143.   Say 'Because the program is licensed free of charge, there is no warranty'
  1144.   Say 'for the program, to the extent permitted by applicable law.  Except when'
  1145.   Say 'otherwise stated in writing the copyright holders and/or other parties'
  1146.   Say 'provide the program "as is" without warranty of any kind, either expressed'
  1147.   Say 'or implied, including, but not limited to, the implied warranties of'
  1148.   Say 'merchantability and fitness for a particular purpose.  The entire risk as'
  1149.   Say 'to the quality and performance of the program is with you.  Should the'
  1150.   Say 'program prove defective, you assume the cost of all necessary servicing,'
  1151.   Say 'repair or correction.'
  1152.   Say
  1153.   Say 'Read the GNU PUBLIC LICENSE for full details'
  1154.   Return
  1155.  
  1156. ShowConditions:
  1157.   Say 'You may copy and distribute verbatim copies of the Program''s'
  1158.   Say 'source code as you receive it, in any medium, provided that you'
  1159.   Say 'conspicuously and appropriately publish on each copy an appropriate'
  1160.   Say 'copyright notice and disclaimer of warranty; keep intact all the'
  1161.   Say 'notices that refer to this License and to the absence of any warranty;'
  1162.   Say 'and give any other recipients of the Program a copy of this License'
  1163.   Say 'along with the Program.'
  1164.   Say
  1165.   Say 'Read the GNU PUBLIC LICENSE for full details'
  1166.   Return
  1167.