home *** CD-ROM | disk | FTP | other *** search
/ OS/2 Shareware BBS: 35 Internet / 35-Internet.zip / ucei04.zip / UCEi.cmd < prev    next >
OS/2 REXX Batch file  |  1997-12-10  |  73KB  |  1,767 lines

  1. /***************************************************************************/
  2. /* This is a non-interactive script to filter mail based on a database of  */
  3. /* valid and invalid domains.  The default is to reject likely invalid     */
  4. /* messages; the database can be used to add domains and addresses with    */
  5. /* specific handling (accept/reject).                                      */
  6. /*                                                                         */
  7. /* Files:                                                                  */
  8. /* UCEi.pdb     database of addresses and domains                          */
  9. /* UCEi.hdb     database of headers and patterns                           */
  10. /*                                                                         */
  11. /* Version dependencies:                                                   */
  12. /* OS/2 3.0 and later with REXX and REXXUTIL.DLL                           */
  13. /* LIBDB.DLL (BSD db library, included)                                    */
  14. /*                                                                         */
  15. /* Command line:                                                           */
  16. /* UCEi [/TEST[=n] message                                                 */
  17. /*      Test the specified message against the header pattern and address/ */
  18. /*      domain databases; if /TEST, logging is to standard output and the  */
  19. /*      result is printed instead of semaphored for MR/2 ICE; if =n is     */
  20. /*      specified and n is greater than 1, all matches are printed along   */
  21. /*      with the final per-header result                                   */
  22. /* UCEi /HLIST                                                             */
  23. /*      List entries in header pattern database                            */
  24. /* UCEi /PLIST                                                             */
  25. /*      List entries in address/domain database                            */
  26. /* UCEi /PADD address Y|N                                                  */
  27. /*      Add an entry to the address/domain database                        */
  28. /* UCEi /HADD Y|N priority header pattern                                  */
  29. /*      Add an entry to the header pattern database                        */
  30. /* UCEi /PDELETE address                                                   */
  31. /*      Remove an entry from the address/domain database                   */
  32. /* UCEi /HDELETE header [pattern]                                          */
  33. /*      Remove the specified or all entries for the specified header from  */
  34. /*      the header database                                                */
  35. /* UCEi /HDUMP                                                             */
  36. /*      Dump the header pattern database as a REXX script to reload it     */
  37. /* UCEi /PDUMP                                                             */
  38. /*      Dump the address/domain database as a REXX script to reload it     */
  39. /* UCEi /DUMP                                                              */
  40. /*      Dump all databases as a REXX script to reload them                 */
  41. /*                                                                         */
  42. /* Brandon S. Allbery                                                      */
  43. /* bsa@kf8nh.apk.net                                                       */
  44. /***************************************************************************/
  45. /* NOTICE:                                                                 */
  46. /* Any attempt to abuse the First Amendment of the U.S. Constitution by    */
  47. /* a known UCE producer in order to suppress this program or its databases */
  48. /* will be treated as an attempt to deny me my First Amendment rights, and */
  49. /* by extension the First Amendment rights of all Internet users.          */
  50. /***************************************************************************/
  51.  
  52. call RxFuncAdd 'SysLoadFuncs', 'REXXUTIL', 'SysLoadFuncs'
  53. call SysLoadFuncs
  54. call RxFuncAdd 'libDbLoadFuncs', 'LIBDB', 'libDbLoadFuncs'
  55. call libDbLoadFuncs
  56.  
  57. VERSION = 1.4
  58.  
  59. /* Set this nonempty for a log of the script's actions */
  60. _log = 'UCEi.log'
  61. /*_log = ''*/
  62. testing = 0
  63.  
  64. /* Threshold number of addresses in list for oversize trigger */
  65. _oversize = 10            /* mail */
  66. _ngoversize = 8            /* newsgroups */
  67.  
  68. /* Predeclared globals */
  69. globals = '_log _myname _what _rxvsn _oversize _ngoversize _self. testing'
  70. parse version . _rxvsn . . .
  71.  
  72. /* Allow cleanup on abort */
  73. signal on halt name cleanup
  74.  
  75. /* main routine */
  76. arg msg rest
  77. parse source . _what _myname
  78. i = lastpos('\', _myname)
  79. if i \= 0 then _myname = substr(_myname, i + 1)
  80. i = lastpos('.', _myname)
  81. if i \= 0 then _myname = left(_myname, i - 1)
  82.  
  83. prdb = _myname'.PDB'
  84. hldb = _myname'.HDB'
  85.  
  86. /* handle options */
  87. if '/PDUMP' == msg then do
  88.     rc = dumplist(prdb)
  89.     if _what \= 'COMMAND' then return rc
  90.     exit \rc
  91. end
  92. if '/HDUMP' == msg then do
  93.     rc = dumphlist(hldb)
  94.     if _what \= 'COMMAND' then return rc
  95.     exit \rc
  96. end
  97. if '/DUMP' == msg then do
  98.     rc = dumplist(prdb)
  99.     if rc then do
  100.     say ''
  101.     rc = dumphlist(hldb)
  102.     end
  103.     if _what \= 'COMMAND' then return rc
  104.     exit \rc
  105. end
  106. if abbrev('/PLIST', msg, 3) then do
  107.     rc = showlist(prdb)
  108.     if _what \= 'COMMAND' then return rc
  109.     exit \rc
  110. end
  111. if abbrev('/HLIST', msg, 3) then do
  112.     rc = showhlist(hldb)
  113.     if _what \= 'COMMAND' then return rc
  114.     exit \rc
  115. end
  116. if abbrev('/PADD', msg, 3) & rest \= '' then do
  117.     rc = addprv(prdb, strip(rest, 'B'))
  118.     if _what \= 'COMMAND' then return rc
  119.     exit \rc
  120. end
  121. if abbrev('/PDELETE', msg, 3) & rest \= '' then do
  122.     rc = delprv(prdb, strip(rest, 'B'))
  123.     if _what \= 'COMMAND' then return rc
  124.     exit \rc
  125. end
  126. if abbrev('/HADD', msg, 3) & rest \= '' then do
  127.     rc = addhdr(hldb, strip(rest, 'B'))
  128.     if _what \= 'COMMAND' then return rc
  129.     exit \rc
  130. end
  131. if abbrev('/HDELETE', msg, 3) & rest \= '' then do
  132.     rc = delhdr(hldb, strip(rest, 'B'))
  133.     if _what \= 'COMMAND' then return rc
  134.     exit \rc
  135. end
  136. if abbrev('/VERSION', msg, 2) then do
  137.     if _what \= 'COMMAND' then
  138.     return VERSION
  139.     else do
  140.     say 'This is' _myname 'version' VERSION
  141.     exit 0
  142.     end
  143. end
  144. if left(msg, 5) = '/TEST' & (length(msg) = 5 | substr(msg, 6, 1) = '=') then do
  145.     testing = substr(msg, 6)
  146.     if testing = '' | verify(testing, '0123456789') \= 0 then testing = 1
  147.     msg = rest
  148.     rest = ''
  149.     _log = 'STDERR'
  150. end
  151.  
  152. /* unrecognized options/arguments */
  153. if msg = '' | left(msg, 1) = '/' | rest \= '' then do
  154.     if _what = 'COMMAND' then do
  155.     sep1 = ' '
  156.     sep2 = ''
  157.     end; else do
  158.     sep1 = '('
  159.     sep2 = ')'
  160.     end
  161.     call lineout 'STDERR', 'usage:' _myname || sep1 || 'message' || sep2
  162.     call lineout 'STDERR', 'usage:' _myname || sep1 || '/TEST message' || sep2
  163.     call lineout 'STDERR', '      ' _myname || sep1 || '/PLIST' || sep2
  164.     call lineout 'STDERR', '      ' _myname || sep1 || '/PADD addr Y|N' || sep2
  165.     call lineout 'STDERR', '      ' _myname || sep1 || '/PDELETE addr' || sep2
  166.     call lineout 'STDERR', '      ' _myname || sep1 || '/HLIST' || sep2
  167.     call lineout 'STDERR', '      ' _myname || sep1 || '/HADD Y|N priority ' ||,
  168.     'header pattern' || sep2
  169.     call lineout 'STDERR', '      ' _myname || sep1 || '/HDELETE header ' ||,
  170.     '[pattern]' || sep2
  171.     if _what \= 'COMMAND' then return 0
  172.     exit 1
  173. end
  174.  
  175. /* obtain all local account names (implicitly valid) */
  176. /* @@@ assumes we're run from the ICE account directory @@@ */
  177. call SysFileTree '*.CFG', 'cf.', 'FO'
  178. k = 0
  179. do i = 1 to cf.0
  180.     ini = SysIni(cf.i, 'Mail', '.')
  181.     call getaddrs substr(ini, 117, pos('00'x, ini, 117) - 117), 'adx.'
  182.     /* should only be one, but who am I to enforce it? */
  183.     do j = 1 to adx.0
  184.     k = k + 1
  185.     _self.k = translate(adx.j)
  186.     end
  187. end
  188. _self.0 = k
  189. drop cf. adx. i j k ini
  190.  
  191. msg = strip(msg, 'B')
  192. rc = scan_msg(msg, prdb, hldb)
  193.  
  194. /* called as nice REXX function? be nice on return */
  195. if _what \= 'COMMAND' then return rc
  196. /* allow simple testing without confusing the blarg out of ICE... */
  197. if testing > 0 then do
  198.     if rc then
  199.     say _myname':' msg 'is valid.'
  200.     else
  201.     say _myname':' msg 'is not valid.'
  202. end; else do
  203.     /* current hacky MR/2 ICE return mechanism... */
  204.     if \rc then call lineout 'MR2_REXX.$$$', _what
  205. end
  206. exit rc
  207.  
  208. /***************************************************************************/
  209. /* scan_msg(MSG, PLIST, HLIST)                                             */
  210. /*                                                                         */
  211. /* Check MSG for headers referencing any of the users and/or domains in    */
  212. /* the databases named by PLIST and HLIST.                                 */
  213. /*                                                                         */
  214. /* Arguments:                                                              */
  215. /* MSG                                                                     */
  216. /*      A file containing an RFC-compliant mail message whose headers are  */
  217. /*      to be scanned.                                                     */
  218. /* PLIST                                                                   */
  219. /*      A database file containing addresses and states; assumed to be a   */
  220. /*      BSD db-1.85 hash database.                                         */
  221. /* HLIST                                                                   */
  222. /*      A database file containing header patterns; assumed to be a BSD    */
  223. /*      db-1.85 btree database.                                            */
  224. /*                                                                         */
  225. /* Returns:                                                                */
  226. /* 1                                                                       */
  227. /*      The message is valid: none of the listed users/domains were found. */
  228. /* 0                                                                       */
  229. /*      An addressing header referenced one of the users/domains listed.   */
  230. /*      (WARNING:  this includes TO:, CC:, and BCC:, thus will match any   */
  231. /*      outgoing messages as well as incoming messages.)                   */
  232. /*                                                                         */
  233. /* Globals:                                                                */
  234. /* (none)                                                                  */
  235. /*                                                                         */
  236. /* Notes:                                   */
  237. /* scan_hdr() does the real work; its return values are prioritized, and   */
  238. /* the last value seen at the highest priority wins.                       */
  239. /*                                                                         */
  240. /* A missing "key header" is regarded as an invalid address at the lowest  */
  241. /* priority.                                                               */
  242. /***************************************************************************/
  243.  
  244. scan_msg: procedure expose (globals); parse arg msg, plist, hlist
  245. if stream(msg, 'C', 'QUERY EXISTS') = '' then do
  246.     call log msg': no message???'
  247.     return 1
  248. end
  249. /* scan even with no database, as we have some non-db-dependent checks */
  250. stem.!openflags = 'O_RDONLY'
  251. dbp = open_db(plist, 'H', 'stem.!')
  252. if dbp = 0 then call log msg': error' libdb_errno 'opening' plist
  253. stem.!flags = 'R_DUP'
  254. hdbp = open_db(hlist, 'B', 'stem.!')
  255. if hdbp = 0 then call log msg': error' libdb_errno 'opening' hlist
  256. rc = stream(msg, 'C', 'OPEN')
  257. if rc \= 'READY:' then do
  258.     call log msg': error opening message:' substr(rc, 6)
  259.     if dbp \= 0 then do
  260.     call libDbClose dbp
  261.     dbp = 0
  262.     end
  263.     if hdbp \= 0 then do
  264.     call libDbClose hdbp
  265.     hdbp = 0
  266.     end
  267.     return 1
  268. end
  269. /* special cases */
  270. h_to.0 = 0
  271. h_from.0 = 0
  272. h_mask = 0
  273. o_globals = globals
  274. globals = 'h_to. h_from. h_mask dbp hdbp' globals
  275. new = 1
  276. line = ''
  277. found = 0
  278. pri = 0
  279. do forever
  280.     l = linein(msg)
  281.     if stream(msg) \= 'READY' then leave
  282.     if l = '' then leave
  283.     hc = isheadercont(l)
  284.     nhs = \isheaderstart(l)
  285.     if nhs & new & (left(l, 5) == 'From ' | left(l, 4) == '+OK ') then do
  286.     new = 0
  287.     iterate
  288.     end
  289.     new = 0
  290.     if nhs & \hc then leave
  291.     if \new & \hc then do
  292.     fnd = scanhdr(line)
  293.     parse var fnd yn','pf
  294.     yn = (yn = 'N')
  295.     if pf > 0 | yn then call log msg':' HCHK'('fnd')' line
  296.     if pf >= pri then do
  297.         found = yn
  298.         pri = pf
  299.     end
  300.     end
  301.     if hc then do
  302.     if length(line) > 16000 then do
  303.         call log msg': enough already! header > 16000 characters'
  304.         if pri <= 1 then do
  305.         found = 1
  306.         pri = 1
  307.         end
  308.         leave
  309.     end
  310.         line = line l
  311.     end; else
  312.         line = l
  313. end
  314. fnd = scanhdr(line)
  315. parse var fnd yn','pf
  316. yn = (yn = 'N')
  317. if pf > 0 | yn then call log msg':' HCHK'('fnd')' line
  318. if pf >= pri then do
  319.     found = yn
  320.     pri = pf
  321. end
  322. call stream msg, 'C', 'CLOSE'
  323. if dbp \= 0 then do
  324.     call libDbClose dbp
  325.     dbp = 0
  326. end
  327. if hdbp \= 0 then do
  328.     call libDbClose hdbp
  329.     hdbp = 0
  330. end
  331. globals = o_globals
  332. /* did we see all required headers?  if not, fail */
  333. if pri <= 1 & h_mask \= 7 then do
  334.     if bitand(h_mask, 1) = 0 then call log msg': missing header FROM'
  335.     if bitand(h_mask, 2) = 0 then call log msg': missing header TO'
  336.     if bitand(h_mask, 4) = 0 then call log msg': missing header MESSAGE-ID'
  337.     found = 1
  338.     pri = 1
  339. end
  340. /* check h_to against h_from */
  341. /* (this would be faster if I could guarantee ORexx on all systems...) */
  342. if pri <= 1 then do
  343.     fnd = 0
  344.     do i = 1 to h_from.0
  345.     do j = 1 to h_to.0
  346.         if h_from.i = h_to.j then do
  347.         call log msg': header from=to' i':'h_from.i j':'h_to.j
  348.         fnd = fnd + 1
  349.         leave
  350.         end
  351.     end
  352.     end
  353.     /* allow one nonexclusive F=T:  users may CC themselves */
  354.     /* @@@@ this WILL need sanity tuning... */
  355.     if fnd = 1 & h_to.0 > 1 then do
  356.     call log msg': assuming self-CC'
  357.     found = 0
  358.     end; else if fnd > 0 then do
  359.     found = 1
  360.     pri = 1
  361.     end
  362.     drop h_to. h_from.
  363. end
  364. call log msg': found =' found', priority =' pri'; valid =' (\found)
  365. return \found
  366.  
  367. /***************************************************************************/
  368. /* scanhdr(LINE)                                                           */
  369. /*                                                                         */
  370. /* If LINE is a transport address header, return Y if it contains a user   */
  371. /* or domain listed in the database open on DBP.  A priority level is also */
  372. /* returned; the caller should continue to scan all headers and retain the */
  373. /* result with the highest priority level.                                 */
  374. /*                                                                         */
  375. /* Arguments:                                                              */
  376. /* LINE                                                                    */
  377. /*      A line containing an RFC822 header.                                */
  378. /*                                                                         */
  379. /* Returns:                                                                */
  380. /* Y,n                                                                     */
  381. /*      A valid address was found with priority 'n'.                       */
  382. /* N,n                                                                     */
  383. /*      An invalid address was found, with priority 'n'.                   */
  384. /*                                                                         */
  385. /* Globals:                                                                */
  386. /* h_from. (exported to children)                                          */
  387. /*      A list of "from"-like addresses.                                   */
  388. /* h_to. (exported to children)                                            */
  389. /*      A list of "to"-like addresses.                                     */
  390. /* h_mask (exported to children)                                           */
  391. /*      A bitmask of required headers (1=From 2=To 4=Message-ID).          */
  392. /* dbp (exported to children)                                              */
  393. /*      The address database handle.                                       */
  394. /* hdbp (exported to children)                                             */
  395. /*      The general deader database handle.                                */
  396. /*                                                                         */
  397. /* Notes:                                                                  */
  398. /* Priorities are numeric, with 0 as a minimum.  'Y,0' is the default      */
  399. /* return value.  Three priority levels (including 0) are currently used.  */
  400. /*                                                                         */
  401. /* "From"-like and "To"-like addresses are collected into lists.  After    */
  402. /* all header processing, the caller may want to examine these lists and   */
  403. /* act accordingly (e.g. same address in both is often a bogon; too long   */
  404. /* a list is also usually a warning sign).                                 */
  405. /*                                                                         */
  406. /* We look for To:, From:, and Message-ID: (required headers) and set the  */
  407. /* appropriate bits of h_mask.  The caller should regard missing entries   */
  408. /* after the entire header has been processed as an error.                 */
  409. /***************************************************************************/
  410.  
  411. scanhdr: procedure expose (globals); parse upper arg tag':'val
  412. tag = strip(tag, 'B')
  413. say "["tag"|"val"]"
  414. /*
  415.  * "doit" codes:
  416.  *
  417.  * -1: NEWSGROUPS (only count "addresses")
  418.  *  0: not an address header
  419.  *  1: FROM address
  420.  *  2: TO address
  421.  *  3: TO address; don't check against TO list
  422.  *  4: Message-ID (validate domain as FROM, don't check "user ID")
  423.  *  5: TO address, but not guaranteed to have a domain
  424.  */
  425. select
  426.     when tag = 'FROM' then do
  427.         doit = 1
  428.         h_mask = bitor(h_mask, 1)
  429.     end
  430.     when tag = 'TO' then do
  431.         doit = 2
  432.         h_mask = bitor(h_mask, 2)
  433.     end
  434.     when tag = 'X-TO' then do
  435.     doit = 2
  436.     h_mask = bitor(h_mask, 2)
  437.     end
  438.     when tag = 'MESSAGE-ID' then do
  439.         doit = 4
  440.         h_mask = bitor(h_mask, 4)
  441.     end
  442.     /* Newsreaders usually do Cc: without To:; cheat */
  443.     when tag = 'NEWSGROUPS' then do
  444.     h_mask = bitor(h_mask, 2)
  445.     doit = -1
  446.     end
  447.     when tag = 'APPARENTLY-FROM' then doit = 1
  448.     when tag = 'APPARENTLY-TO' then do
  449.     doit = 2
  450.     h_mask = bitor(h_mask, 2)
  451.     end
  452.     when tag = 'RETURN-PATH' then doit = 1
  453.     /* see REPLY-TO --- this one is OS2-L's fault */
  454.     when tag = 'SENDER' then doit = 3
  455.     when tag = 'X-SENDER' then doit = 5
  456.     /* REPLY-TO should be a FROM code, but most mailing lists set it and TO */
  457.     /* the same --- and most spewers either omit it or make it different.   */
  458.     /* They don't want to be easily traced or hit by angry responses, since */
  459.     /* responsibility for their actions is *always* to be avoided.          */
  460.     when tag = 'REPLY-TO' then doit = 3
  461.     when tag = 'CC' then doit = 3
  462.     when tag = 'RESENT-FROM' then doit = 1
  463.     when tag = 'RESENT-TO' then doit = 2
  464.     when tag = 'RESENT-CC' then doit = 3
  465.     otherwise doit = 0
  466. end
  467. val = foldspaces(val)
  468. found = 'Y'
  469. pri = 0
  470. /* check badly-formed message IDs */
  471. xval = val
  472. do forever
  473.     i = pos('(', xval)
  474.     if i = 0 then leave
  475.     k = i
  476.     depth = 1
  477.     do forever
  478.     j = verify(xval, '()\', 'M', k + 1)
  479.     if j = 0 then leave
  480.     if substr(xval, j, 1) = '\' then
  481.         k = j + 1
  482.     else if substr(xval, j, 1) = '(' then do
  483.         depth = depth + 1
  484.         k = j
  485.     end; else do
  486.         depth = depth - 1
  487.         if depth > 0 then
  488.         k = j
  489.         else do
  490.         if i = 0 then
  491.             xval = strip(substr(xval, j + 1), 'L')
  492.         else
  493.             xval = strip(left(xval, i - 1)) strip(substr(xval, j + 1), 'L')
  494.         leave
  495.         end
  496.     end
  497.     end
  498.     if depth \= 0 then do
  499.     call log 'unmatched parentheses!' val
  500.     leave
  501.     end
  502. end
  503. xval = strip(xval, 'B')
  504. i = pos('@', xval)
  505. if doit = 4 & (left(xval, 1) \= '<' |,
  506.            right(xval, 1) \= '>' |,
  507.            i = 0) then do
  508.     call log2 1, 'badly formed message ID' val
  509.     found = 'N'
  510.     pri = 1
  511. end
  512. if (doit = 4 | doit = 5) & i \= 0 then do
  513.     if verify(xval, '0123456789.>', 'N', i + 1) = 0 then do
  514.     call log2 1, 'numeric host' val
  515.     found = 'N'
  516.     pri = 1
  517.     end
  518. end
  519. drop xval
  520. /*
  521.  * We now check all headers; non-address headers get scanned for address-like
  522.  * entities and passed through the address filter, *all* get passed through
  523.  * the generic filter.
  524.  */
  525. parse value check_gen(doit, tag, val) with nfnd','npri
  526. if npri >= pri then do
  527.     found = nfnd
  528.     pri = npri
  529. end
  530. if doit = -1 then do
  531.     /* Newsgroups: --- split at commas, count */
  532.     /* (this header permits no silliness) */
  533.     cnt = 0
  534.     do forever
  535.     cnt = cnt + 1
  536.     i = pos(',', val)
  537.     if i = 0 then leave
  538.     val = substr(val, i + 1)
  539.     end
  540.     if cnt > _ngoversize & pri <= 1 then do
  541.     call log2 1, 'too many newsgroups'
  542.     found = 'N'
  543.     pri = 1
  544.     end
  545. end; else if doit > 0 then do
  546.     call getaddrs val, 'adx.'
  547.     /* pull RFC-compliant chunks out of val and check against database */
  548.     /* shortcircuit:  if it's empty, it's almost always a bogon */
  549.     if adx.0 = 0 & pri <= 1 then do
  550.     call log2 1, 'empty header' tag
  551.     found = 'N'
  552.     pri = 1
  553.     end; else do
  554.     addrs.0 = 0
  555.     cnt = 0
  556.     do idx = 1 to adx.0
  557.         cnt = cnt + 1
  558.         l = 0
  559.         do j = 1 to addrs.0
  560.         if addrs.j = adx.idx then do
  561.             if pri <= 1 then do
  562.             call log2 1, 'duplicated address' adx.idx
  563.             found = 'N'
  564.             pri = 1
  565.             end
  566.             l = 1
  567.         end
  568.         end
  569.         if \l then do
  570.         addrs.0 = addrs.0 + 1
  571.         j = addrs.0 + 1
  572.         addrs.j = adx.idx
  573.         if pri <= 1 & doit = 1 then do
  574.             do j = 1 to _self.0
  575.             if _self.j = adx.idx then do
  576.                 call log2 1, 'assumed ok from my addr' adx.idx
  577.                 found = 'Y'
  578.                 pri = 5
  579.                 leave
  580.             end
  581.             end
  582.         end
  583.         parse value check_addr(doit, tag, adx.idx) with nfnd','npri
  584.         if npri >= pri then do
  585.             found = nfnd
  586.             pri = npri
  587.         end
  588.         end
  589.     end
  590.     if pri <= 1 & cnt > _oversize then do
  591.         call log2 1, 'oversize address list' tag
  592.         pri = 1
  593.         found = 'N'
  594.     end
  595.     end
  596. end
  597. return found','pri
  598.  
  599. /***************************************************************************/
  600. /* check_gen(TYPE, TAG, LINE)                                              */
  601. /*                                                                         */
  602. /* Check the non-address-specific header in LINE with header tag TAG for   */
  603. /* address-like "words" and check those "addresses" against the address    */
  604. /* database; check the entire line against the general header database.    */
  605. /*                                                                         */
  606. /* Arguments:                                                              */
  607. /* TYPE                                                                    */
  608. /*      The header type:  0/standard, 1-4/an address list.                 */
  609. /* TAG                                                                     */
  610. /*      The header tag ('FROM', 'TO', 'MESSAGE-ID', etc.)                  */
  611. /* LINE                                                                    */
  612. /*      The header line without the tag.                                   */
  613. /*                                                                         */
  614. /* Returns:                                                                */
  615. /* Y,n                                                                     */
  616. /*      The header is accepted with priority 'n'.                          */
  617. /* N,n                                                                     */
  618. /*      The header is rejected with priority 'n'.                          */
  619. /*                                                                         */
  620. /* Globals:                                                                */
  621. /* (none)                                                                  */
  622. /*                                                                         */
  623. /* Notes:                                                                  */
  624. /* Priorities are numeric, with 0 as a minimum.  'Y,0' is the default      */
  625. /* return value.  Three priority levels (including 0) are currently used.  */
  626. /***************************************************************************/
  627.  
  628. check_gen: procedure expose (globals); parse arg doit, tag, val
  629. found = 'Y'
  630. pri = 0
  631. if hdbp \= 0 then do
  632.     parse value check_mast(doit, tag, tag, val) with nfnd','npri
  633.     if npri >= pri then do
  634.     found = nfnd
  635.     pri = npri
  636.     end
  637.     parse value check_mast(doit, '*', tag, val) with nfnd','npri
  638.     if npri >= pri then do
  639.     found = nfnd
  640.     pri = npri
  641.     end
  642. end
  643. /* strip comments --- JUST comments, unlike the address line scanner */
  644. l = ''
  645. do forever
  646.     i = pos('(', val)
  647.     if i = 0 then leave
  648.     l = l || left(val, i - 1)
  649.     val = substr(val, i + 1)
  650.     depth = 1
  651.     do forever
  652.     j = verify(val, '()\', 'M')
  653.     if j = 0 then leave
  654.     if substr(val, j, 1) = ')' then do
  655.         depth = depth - 1
  656.         val = substr(val, j + 1)
  657.         if depth = 0 then leave
  658.     end; else if substr(val, j, 1) = '(' then do
  659.         depth = depth + 1
  660.         val = substr(val, j + 1)
  661.     end; else
  662.         val = substr(val, j + 2)
  663.     end
  664.     if j = 0 then leave
  665.     l = l || ' '
  666. end
  667. val = strip(l || val, 'T')
  668. /*
  669.  * Pull out words:  if they're address-ish, call check_addr.
  670.  *
  671.  * This one parses a bit more accurately; I should rework the address parser
  672.  * to match.  Basically, quotes can be used anywhere and tokens are broken at
  673.  * spaces outside of quotes.  Angle brackets *don't* quote spaces.
  674.  */
  675. chunk = ''
  676. do forever
  677.     val = strip(val, 'L')
  678.     if val = '' then leave
  679.     i = verify(' ",;', val, 'M')
  680.     if i = 0 then leave
  681.     chunk = chunk || left(val, i - 1)
  682.     c = substr(val, i, 1)
  683.     val = substr(val, i + 1)
  684.     i = verify('"', val, 'M')
  685.     if c = '"' & i \= 0 then do
  686.     chunk = chunk || left(val, i - 1)
  687.     val = substr(val, i + 1)
  688.     end; else do
  689.     i = pos('@', val)
  690.     if i \= 0 | (left(chunk, 1) = '<' & right(chunk, 1) = '>') then do
  691.         parse value check_addr(doit, tag, chunk) with nfnd','npri
  692.         if npri >= pri then do
  693.         found = nfnd
  694.         pri = npri
  695.         end
  696.     end
  697.     chunk = ''
  698.     end
  699. end
  700. return found','pri
  701.  
  702. /***************************************************************************/
  703. /* check_mast(TYPE, KEY, TAG, LINE)                                        */
  704. /*                                                                         */
  705. /* Check the specified header line against the general header database.    */
  706. /*                                                                         */
  707. /* Arguments:                                                              */
  708. /* TYPE                                                                    */
  709. /*      The header type:  0/standard, 1-4/an address list.                 */
  710. /* KEY                                                                     */
  711. /*      The database key to check, usually either the same as TAG or '*'.  */
  712. /* TAG                                                                     */
  713. /*      The header tag ('FROM', 'TO', 'MESSAGE-ID', etc.)                  */
  714. /* LINE                                                                    */
  715. /*      The header line without the tag.                                   */
  716. /*                                                                         */
  717. /* Returns:                                                                */
  718. /* Y,n                                                                     */
  719. /*      The header is accepted with priority 'n'.                          */
  720. /* N,n                                                                     */
  721. /*      The header is rejected with priority 'n'.                          */
  722. /*                                                                         */
  723. /* Globals:                                                                */
  724. /* hdbp                                                                    */
  725. /*      The handle for the general header database.                        */
  726. /*                                                                         */
  727. /* Notes:                                                                  */
  728. /* Priorities are numeric, with 0 as a minimum.  'Y,0' is the default      */
  729. /* return value.  Three priority levels (including 0) are currently used.  */
  730. /*                                                                         */
  731. /* Currently, we perform substring matches.  Some future version will be   */
  732. /* able to use regular expressions.  (I'm still trying to decide whether   */
  733. /* to require Perl or to try to put together a regexp module for REXX.)    */
  734. /*                                                                         */
  735. /* The database is a btree with duplicates allowed; we locate the first    */
  736. /* key match, then iterate until we run out of matching keys.              */
  737. /***************************************************************************/
  738.  
  739. check_mast: procedure expose (globals); parse arg doit, key, tag, val
  740. found = 'Y'
  741. pri = 0
  742. if hdbp \= 0 then do
  743.     what = 'R_CURSOR'
  744.     keyv = key
  745.     do forever
  746.     rc = libDbSeq(hdbp, 'keyv', 'valv', what)
  747.     /* can't use "parse" because spaces are significant in pattern */
  748.     fnd = left(valv, 1)
  749.     valv = substr(valv, 3)
  750.     i = verify(valv, '0123456789')
  751.     prx = left(valv, i - 1)
  752.     pat = substr(valv, i + 1)
  753.     if rc \= 0 | keyv \= key then leave
  754.     /* NB: done this way because val may be big so search may be slow */
  755.     if prx >= pri then do
  756.         /* @@@@ future: valv may be a regexp */
  757.         if pos(pat, ' 'val' ') \= 0 then do
  758.         call log2 1, 'match hdb' tag key '{'pat'} :: {'val'}'
  759.         found = fnd
  760.         pri = prx
  761.         end
  762.     end
  763.     what = 'R_NEXT'
  764.     end
  765. end
  766. return found','pri
  767.  
  768. /***************************************************************************/
  769. /* check_addr(TYPE, TAG, ADDR)                                             */
  770. /*                                                                         */
  771. /* Check the address-like ADDR for well-formed-ness and check against the  */
  772. /* address database.  It is up to the caller to decide whether an address  */
  773. /* that is not well-formed should be rejected as a bad address (e.g. some  */
  774. /* random header contained something vaguely resembling an address).       */
  775. /*                                                                         */
  776. /* Arguments:                                                              */
  777. /* TYPE                                                                    */
  778. /*      The header type:  0/standard, 1-4/an address list.                 */
  779. /* TAG                                                                     */
  780. /*      The header tag ('FROM', 'TO', 'MESSAGE-ID', etc.)                  */
  781. /* ADDR                                                                    */
  782. /*      The address-like string to be checked.                             */
  783. /*                                                                         */
  784. /* Returns:                                                                */
  785. /* Y,n                                                                     */
  786. /*      The address is accepted with priority 'n'.                         */
  787. /* N,n                                                                     */
  788. /*      The address is rejected with priority 'n'.                         */
  789. /*                                                                         */
  790. /* Globals:                                                                */
  791. /* dbp                                                                     */
  792. /*      The address database handle.                                       */
  793. /*                                                                         */
  794. /* Notes:                                                                  */
  795. /* Priorities are numeric, with 0 as a minimum.  'Y,0' is the default      */
  796. /* return value.  Three priority levels (including 0) are currently used.  */
  797. /***************************************************************************/
  798.  
  799. check_addr: procedure expose (globals); parse arg doit, tag, chunk
  800. found = 'Y'
  801. pri = 0
  802. /* deal with route-addr; some systems are stupid */
  803. if left(chunk, 1) = '@' then do
  804.     i = pos(':', chunk)
  805.     if i \= 0 then do
  806.     /* try user part with each route-addr host, then strip routing & pass */
  807.     j = pos('@', chunk, i)
  808.     if j \= 0 then do
  809.         user = substr(chunk, i + 1, j - i - 2)
  810.         k = 1
  811.         do while substr(chunk, k, 1) \= ':'
  812.         j = k + 1
  813.         k = pos(',', chunk, j)
  814.         if k = 0 then k = pos(':', chunk, j)
  815.         nf = check_addr(doit, tag, user'@'substr(chunk, j, k - j - 2))
  816.         parse var nf with nfnd','npri
  817.         if npri >= pri then do
  818.             found = nfnd
  819.             pri = npri
  820.         end
  821.         end
  822.     end
  823.     chunk = substr(chunk, i + 1)
  824.     end
  825. end
  826. i = pos('@', chunk)
  827. if i \= 0 & pri <= 1 then do
  828.     i = pos('@', chunk, i + 1)
  829.     if i \= 0 then do
  830.     call log2 1, 'match badaddr' tag chunk
  831.     pri = 1
  832.     found = 'N'
  833.     end
  834. end
  835. /* sanity-check message ID */
  836. i = lastpos('.', chunk)
  837. j = lastpos('@', chunk)
  838. if doit \= 5 & i \= 0 & j \= 0 & i > j then do
  839.     dom = substr(chunk, i + 1)
  840.     select
  841.     /* Three guesses who breaks the rules just to prove they can... */
  842.     when substr(chunk, j + 1) = 'MAPI.TO.RFC822' then nop
  843.     /* Numeric with brackets is okay, without isn't. */
  844.     when substr(chunk, j + 1, 1) = '[' then nop
  845.     when length(dom) = 2 then nop
  846.     when dom = 'COM' then nop
  847.     when dom = 'MIL' then nop
  848.     when dom = 'GOV' then nop
  849.     when dom = 'NET' then nop
  850.     when dom = 'ORG' then nop
  851.     when dom = 'EDU' then nop
  852.     otherwise do
  853.         call log2 1, 'invalid TLD' dom
  854.         pri = 1
  855.         found = 'N'
  856.     end
  857.     end
  858. end
  859. init = 1
  860. do while chunk \= ''
  861.     i = pos('@', chunk)
  862.     if i = 0 then i = length(chunk) + 1
  863.     adr = left(chunk, i - 1)
  864.     chunk = substr(chunk, i + 1)
  865.     if init then do
  866.     user = adr
  867.     if doit \= 4 & verify(user, '0123456789', 'N') = 0 & pri <= 1 then do
  868.         call log2 1, 'match baduser' tag user'@'adr
  869.         pri = 1
  870.         found = 'N'
  871.     end
  872.     end; else do
  873.     /* special case - all numeric host part dies */
  874.     /* (actually, > 2 as prefix dies) */
  875.     j = pos('.', adr)
  876.     if j = 0 & doit \= 4 & doit \= 5 & pri <= 1 then do
  877.         call log2 1, 'match baddom' tag user'@'adr
  878.         found = 'N'
  879.         pri = 1
  880.     end
  881.     /* oops, allow x.y.z.w format */
  882.     i = verify(left(adr, i - 1), '0123456789', 'N')
  883.     if (i = 0 | i > 2) &,
  884.        verify(adr, '[0123456789.]', 'N') \= 0 & pri <= 1 then do
  885.         call log2 1, 'match badhost' tag user'@'adr
  886.         found = 'N'
  887.         pri = 1
  888.     end
  889.     /* record address for F=T check */
  890.     if doit = 1 | doit = 4 then
  891.         pfx = 'h_from.'
  892.     else if doit = 2 then
  893.         pfx = 'h_to.'
  894.     if (doit = 1 | doit = 2 | doit = 4) & user \= 'ROOT' then do
  895.         i = value(pfx'0')
  896.         i = i + 1
  897.         call value pfx || i, user'@'adr
  898.         call value pfx'0', i
  899.     end
  900.     /* check database */
  901.     if dbp \= 0 then do
  902.         yn = 'Y'
  903.         if pri <= 3 then do
  904.         if libDbGet(dbp, user'@'adr, 'fnd') = 0 then do
  905.             parse var fnd yn .
  906.             call log2 1, 'match pdb' tag user'@'adr yn
  907.             found = yn
  908.             pri = 3
  909.         end
  910.         end
  911.         if pri <= 2 then do
  912.         if libDbGet(dbp, adr, 'fnd') = 0 then do
  913.             parse var fnd yn .
  914.             call log2 1, 'match pdb' tag adr yn
  915.             found = yn
  916.             pri = 2
  917.         end
  918.         end
  919.     end
  920.     end
  921.     init = 0
  922. end
  923. return found','pri
  924.  
  925. /***************************************************************************/
  926. /* foldspaces(STR)                                                         */
  927. /*                                                                         */
  928. /* Convert runs of RFC-specification whitespace to single spaces.          */
  929. /*                                                                         */
  930. /* Arguments:                                                              */
  931. /* STR                                                                     */
  932. /*      A string.                                                          */
  933. /*                                                                         */
  934. /* Returns:                                                                */
  935. /* STR                                                                     */
  936. /*      The string with s/[\10-\15 ]+/ /g                                  */
  937. /*                                                                         */
  938. /* Globals:                                                                */
  939. /* none                                                                    */
  940. /***************************************************************************/
  941.  
  942. foldspaces: procedure expose (globals); parse arg val
  943. l = ''
  944. do forever
  945.     i = verify(val, '08090A0B0C0D20'x, 'M')
  946.     if i = 0 then leave
  947.     if i \= 1 then do
  948.     l = l || left(val, i - 1)
  949.     val = substr(val, i)
  950.     end
  951.     i = verify(val, '08090A0B0C0D20'x, 'N')
  952.     if i = 0 then do
  953.     val = ''
  954.     leave
  955.     end
  956.     val = substr(val, i)
  957.     l = l || ' '
  958. end
  959. return strip(l || val, 'B')
  960.  
  961. /***************************************************************************/
  962. /* getaddrs(STR, STEM)                                                     */
  963. /*                                                                         */
  964. /* Given STR containing an address list, strip trash to create a list of   */
  965. /* machine-usable addresses.                                               */
  966. /*                                                                         */
  967. /* Arguments:                                                              */
  968. /* STR                                                                     */
  969. /*      A string.                                                          */
  970. /* STEM                                                                    */
  971. /*      The name of a stem variable to receive the address list.           */
  972. /*                                                                         */
  973. /* Returns:                                                                */
  974. /* (none)                                                                  */
  975. /*                                                                         */
  976. /* Globals:                                                                */
  977. /* (none)                                                                  */
  978. /***************************************************************************/
  979.  
  980. getaddrs:
  981. globals = arg(2) globals
  982. call getaddrs.1 arg(1), arg(2)
  983. return
  984.  
  985. getaddrs.1: procedure expose (globals); parse arg val!, stp!
  986. globals = subword(globals, 2)
  987. l! = ''
  988. cnt! = 0
  989. dist! = 0
  990. do forever
  991.     i! = verify(val!, ':;"(<,', 'M')
  992.     if i! = 0 then leave
  993.     c! = substr(val!, i!, 1)
  994.     l! = l! || left(val!, i! - 1)
  995.     val! = substr(val!, i! + 1)
  996.     select
  997.     when c! = ':' then do
  998.         /* idiot systems that pass through proprietary syntax */
  999.         if l! = 'SMTP' | l! = 'INET' | l! = 'INTERNET' then
  1000.         nop
  1001.         else if left(val!, 1) = ':' then do
  1002.         /* DECnet... same comment applies, really */
  1003.         l! = l! || '::'
  1004.         val! = substr(val!, 2)
  1005.         end; else do
  1006.         /* distribution list syntax --- we hope */
  1007.         dist! = dist! + 1
  1008.         l! = ''
  1009.         end
  1010.     end
  1011.     when c! = ';' then do
  1012.         if dist! > 0 then
  1013.         dist! = dist! - 1
  1014.         else do
  1015.         call log 'invalid distribution list syntax:' arg(1)
  1016.         l! = l! || c!
  1017.         end
  1018.     end
  1019.     when c! = ',' then do
  1020.         cnt! = cnt! + 1
  1021.         l! = strip(l!, 'B')
  1022.         /* *real* route-addr? trim if same as host */
  1023.         if left(l!, 1) = '@' then do
  1024.         i! = pos(':', l!)
  1025.         if i! \= 0 then do
  1026.             c! = substr(l!, 2, i! - 2)
  1027.             d! = substr(l!, i! + 1)
  1028.             i! = pos('@', d!)
  1029.             if i! \= 0 then if substr(d!, i! + 1) = c! then l! = d!
  1030.         end
  1031.         end
  1032.         if l! \= '' then call value stp! || cnt!, l!
  1033.         l! = ''
  1034.     end
  1035.     when c! = '"' then do
  1036.         do forever
  1037.         j! = verify(val!, '"\', 'M')
  1038.         if j! = 0 then leave
  1039.         if substr(val!, j!, 1) = '"' then do
  1040.             val! = substr(val!, j! + 1)
  1041.             leave
  1042.         end
  1043.         val! = substr(val!, j! + 2)
  1044.         end
  1045.         if j! = 0 then leave
  1046.         l! = l! || ' '
  1047.     end
  1048.     when c! = '<' then do
  1049.         j! = pos('>', val!)
  1050.         /* note we replace the collected crud... */
  1051.         l! = left(val!, j! - 1)
  1052.         val! = ''
  1053.     end
  1054.     when c! = '(' then do
  1055.         depth! = 1
  1056.         do forever
  1057.         j! = verify(val!, '()\', 'M')
  1058.         if j! = 0 then leave
  1059.         if substr(val!, j!, 1) = ')' then do
  1060.             depth! = depth! - 1
  1061.             val! = substr(val!, j! + 1)
  1062.             if depth! = 0 then leave
  1063.         end; else if substr(val!, j!, 1) = '\' then
  1064.             val! = substr(val!, j! + 2)
  1065.         else do
  1066.             val! = substr(val!, j! + 1)
  1067.             depth! = depth! + 1
  1068.         end
  1069.         end
  1070.         if j! = 0 then leave
  1071.         l! = l! || ' '
  1072.     end
  1073.     end
  1074. end
  1075. l! = l! || val!
  1076. if l! \= '' then do
  1077.     cnt! = cnt! + 1
  1078.     l! = strip(l!, 'B')
  1079.     /* *real* route-addr? accept only if same as host */
  1080.     if left(l!, 1) = '@' then do
  1081.     i! = pos(':', l!)
  1082.     if i! \= 0 then do
  1083.         c! = substr(l!, 2, i! - 2)
  1084.         d! = substr(l!, i! + 1)
  1085.         i! = pos('@', d!)
  1086.         if i! \= 0 then if substr(d!, i! + 1) = c! then l! = d!
  1087.     end
  1088.     end
  1089.     call value stp! || cnt!, l!
  1090. end
  1091. if dist! \= 0 then call log 'invalid distribution list syntax:' arg(1)
  1092. call value stp!'0', cnt!
  1093. return
  1094.  
  1095. /***************************************************************************/
  1096. /* isheaderstart(HDR)                                                      */
  1097. /*                                                                         */
  1098. /* Return whether the line is plausibly an RFC-compliant header line.      */
  1099. /*                                                                         */
  1100. /* Arguments:                                                              */
  1101. /* HDR                                                                     */
  1102. /*      A line to be tested                                                */
  1103. /*                                                                         */
  1104. /* Returns:                                                                */
  1105. /* 1                                                                       */
  1106. /*      The line looks like an RFC-compliant header.                       */
  1107. /* 0                                                                       */
  1108. /*      The line is empty, a body line, or a line continuation.            */
  1109. /*                                                                         */
  1110. /* Globals:                                                                */
  1111. /* none                                                                    */
  1112. /***************************************************************************/
  1113.  
  1114. isheaderstart: procedure expose (globals); arg ln
  1115. if ln = '' then return 0
  1116. i = pos(':', ln)
  1117. if i = 0 then return 0
  1118. j = verify(ln, '08090A0B0C0D20'x, 'M')
  1119. if j = 0 then j = length(ln)
  1120. return (j > i)
  1121.  
  1122. /***************************************************************************/
  1123. /* isheadercont(HDR)                                                       */
  1124. /*                                                                         */
  1125. /* Return whether the line is plausibly an RFC-compliant continued header  */
  1126. /* line.  (See WARNING below!)                                             */
  1127. /*                                                                         */
  1128. /* Arguments:                                                              */
  1129. /* HDR                                                                     */
  1130. /*      A line to be tested                                                */
  1131. /*                                                                         */
  1132. /* Returns:                                                                */
  1133. /* 1                                                                       */
  1134. /*      The line looks like an RFC-compliant header continuation.          */
  1135. /* 0                                                                       */
  1136. /*      The line is empty, a body line, or a new header line.              */
  1137. /*                                                                         */
  1138. /* Globals:                                                                */
  1139. /* none                                                                    */
  1140. /*                                                                         */
  1141. /* WARNING:  None of the mail RFCs specifies a *sensible* header structure */
  1142. /* which unambiguously discriminates between header and body; if the first */
  1143. /* line of the body is indented and there is no blank line preceding it,   */
  1144. /* it will be considered a header continuation and "eaten".  (For this     */
  1145. /* program that is likely to be harmless; but in general it is a problem.) */
  1146. /*                                                                         */
  1147. /* (N.B. to mailer implementors:  the current RFCs *require* a blank line  */
  1148. /* between header and body to avoid the above problem.  "Make it so.")     */
  1149. /***************************************************************************/
  1150.  
  1151. isheadercont: procedure expose (globals); arg ln
  1152. return verify(left(ln, 1), '08090A0B0C0D20'x, 'M')
  1153.  
  1154. /***************************************************************************/
  1155. /* addprv(DB, CMDLINE)                                                     */
  1156. /*                                                                         */
  1157. /* Add an entry to the private database.                                   */
  1158. /*                                                                         */
  1159. /* Arguments:                                                              */
  1160. /* DB                                                                      */
  1161. /*      The filename of the private database.                              */
  1162. /* CMDLINE                                                                 */
  1163. /*      The command line passed to the script, parsed as ADDR Y/N.         */
  1164. /*                                                                         */
  1165. /* Returns:                                                                */
  1166. /* 1                                                                       */
  1167. /*      The command was successful.                                        */
  1168. /* 0                                                                       */
  1169. /*      The command failed.                                                */
  1170. /*                                                                         */
  1171. /* Globals:                                                                */
  1172. /* (none)                                                                  */
  1173. /***************************************************************************/
  1174.  
  1175. addprv: procedure expose (globals); arg db, args
  1176. if left(args, 1) \= '"' then
  1177.     parse var args dom yn
  1178. else do
  1179.     args = substr(args, 2)
  1180.     dom = ''
  1181.     do forever
  1182.         i = pos('"', args)
  1183.     if i = 0 then leave
  1184.         dom = dom || left(args, i - 1)
  1185.         args = substr(args, i)
  1186.         if left(args, 2) \= '""' then leave
  1187.         dom = dom || '"'
  1188.         args = substr(args, 3)
  1189.     end
  1190.     if left(args, 1) = '"' then
  1191.         yn = strip(substr(args, 2))
  1192.     else do
  1193.         yn = ''
  1194.         dom = ''
  1195.     end
  1196. end
  1197. if (yn \= 'Y' & yn \= 'N') | dom == '' then do
  1198.     if _what = 'COMMAND' then do
  1199.     sep1 = ' '
  1200.     sep2 = ' '
  1201.     sep3 = ''
  1202.     end; else do
  1203.     sep1 = '('
  1204.     sep2 = ' '
  1205.     sep3 = ')'
  1206.     end
  1207.     call lineout 'STDERR', 'usage:' _myname || sep1 || '/PADD' || sep2 ||,
  1208.                'address Y/N' || sep3
  1209.     return 0
  1210. end
  1211. dbp = open_db(db, 'H')
  1212. if dbp = 0 then do
  1213.     call lineout 'STDERR', _myname': error' libdb_errno 'opening' db
  1214.     return 0
  1215. end
  1216. if libDbPut(dbp, dom, yn date(), '') \= 0 then do
  1217.     call lineout 'STDERR', _myname': error' libdb_errno 'putting key' dom
  1218.     call libDbClose dbp
  1219.     dbp = 0
  1220.     return 0
  1221. end
  1222. call libDbClose dbp
  1223. dbp = 0
  1224. return 1
  1225.  
  1226. /***************************************************************************/
  1227. /* delprv(DB, CMDLINE)                                                     */
  1228. /*                                                                         */
  1229. /* Remove an entry from the private database.                              */
  1230. /*                                                                         */
  1231. /* Arguments:                                                              */
  1232. /* DB                                                                      */
  1233. /*      The filename of the private database.                              */
  1234. /* CMDLINE                                                                 */
  1235. /*      The command line passed to the script, parsed as ADDR.             */
  1236. /*                                                                         */
  1237. /* Returns:                                                                */
  1238. /* 1                                                                       */
  1239. /*      The command was successful.                                        */
  1240. /* 0                                                                       */
  1241. /*      The command failed.                                                */
  1242. /*                                                                         */
  1243. /* Globals:                                                                */
  1244. /* (none)                                                                  */
  1245. /***************************************************************************/
  1246.  
  1247. delprv: procedure expose (globals); parse arg db, args
  1248. if left(args, 1) \= '"' then
  1249.     dom = args
  1250. else do
  1251.     args = substr(args, 2)
  1252.     dom = ''
  1253.     do forever
  1254.         i = pos('"', args)
  1255.     if i = 0 then leave
  1256.         dom = dom || left(args, i - 1)
  1257.         args = substr(args, i)
  1258.         if left(args, 2) \= '""' then leave
  1259.         dom = dom || '"'
  1260.         args = substr(args, 3)
  1261.     end
  1262.     if left(args, 1) \= '"' then do
  1263.     if _what = 'COMMAND' then do
  1264.         sep1 = ' '
  1265.         sep2 = ' '
  1266.         sep3 = ''
  1267.     end; else do
  1268.         sep1 = '('
  1269.         sep2 = ', '
  1270.         sep3 = ')'
  1271.     end
  1272.     call lineout 'STDERR', 'usage:' _myname || sep1 || '/PDELETE' ||,
  1273.                    sep2 || 'address' || sep3
  1274.     return 0
  1275.     end
  1276. end
  1277. dbp = open_db(db, 'H')
  1278. if dbp = 0 then do
  1279.     call lineout 'STDERR', _myname': error' libdb_errno 'opening' db
  1280.     return 0
  1281. end
  1282. if libDbDel(dbp, dom) \= 0 then do
  1283.     call lineout 'STDERR', _myname': error' libdb_errno 'deleting' dom
  1284.     call lineout 'STDERR', '(was it present in the database?  try /PLIST)'
  1285.     call libDbClose dbp
  1286.     dbp = 0
  1287.     return 0
  1288. end
  1289. call libDbClose dbp
  1290. dbp = 0
  1291. return 1
  1292.  
  1293. /***************************************************************************/
  1294. /* addhdr(DB, CMDLINE)                                                     */
  1295. /*                                                                         */
  1296. /* Add an entry to the header database.                                    */
  1297. /*                                                                         */
  1298. /* Arguments:                                                              */
  1299. /* DB                                                                      */
  1300. /*      The filename of the private database.                              */
  1301. /* CMDLINE                                                                 */
  1302. /*      The command line passed to the script, parsed as Y/N PRI HDR STR   */
  1303. /*                                                                         */
  1304. /* Returns:                                                                */
  1305. /* 1                                                                       */
  1306. /*      The command was successful.                                        */
  1307. /* 0                                                                       */
  1308. /*      The command failed.                                                */
  1309. /*                                                                         */
  1310. /* Globals:                                                                */
  1311. /* (none)                                                                  */
  1312. /***************************************************************************/
  1313.  
  1314. addhdr: procedure expose (globals); arg db, yn pri tag val
  1315. err = 0
  1316. if left(val, 1) \= '"' then
  1317.     dom = val
  1318. else do
  1319.     val = substr(val, 2)
  1320.     dom = ''
  1321.     do forever
  1322.         i = pos('"', val)
  1323.     if i = 0 then leave
  1324.         dom = dom || left(val, i - 1)
  1325.         val = substr(val, i)
  1326.         if left(val, 2) \= '""' then leave
  1327.         dom = dom || '"'
  1328.         val = substr(val, 3)
  1329.     end
  1330.     if val \== '"' then err = 1
  1331. end
  1332. if yn \= 'Y' & yn \= 'N' then err = 1
  1333. if tag \= '*' & verify(tag, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-') \= 0 then
  1334.     err = 1
  1335. if verify(pri, '0123456789') \= 0 then err = 1
  1336. if err then do
  1337.     if _what = 'COMMAND' then do
  1338.     sep1 = ' '
  1339.     sep2 = ' '
  1340.     sep3 = ''
  1341.     end; else do
  1342.     sep1 = '('
  1343.     sep2 = ' '
  1344.     sep3 = ')'
  1345.     end
  1346.     call lineout 'STDERR', 'usage:' _myname || sep1 || '/HADD' || sep2 ||,
  1347.                'Y/N priority header string' || sep3
  1348.     return 0
  1349. end
  1350. stem.!flags = 'R_DUP'
  1351. dbp = open_db(db, 'B', 'stem.!')
  1352. if dbp = 0 then do
  1353.     call lineout 'STDERR', _myname': error' libdb_errno 'opening' db
  1354.     return 0
  1355. end
  1356. if libDbPut(dbp, tag, yn pri dom, '') \= 0 then do
  1357.     call lineout 'STDERR', 'error' libdb_errno 'putting key' dom
  1358.     call libDbClose dbp
  1359.     dbp = 0
  1360.     return 0
  1361. end
  1362. call libDbClose dbp
  1363. dbp = 0
  1364. return 1
  1365.  
  1366. /***************************************************************************/
  1367. /* delhdr(DB, CMDLINE)                                                     */
  1368. /*                                                                         */
  1369. /* Remove an entry from the header database.                               */
  1370. /*                                                                         */
  1371. /* Arguments:                                                              */
  1372. /* DB                                                                      */
  1373. /*      The filename of the header database.                               */
  1374. /* CMDLINE                                                                 */
  1375. /*      The command line passed to the script, parsed as TAG [VAL].        */
  1376. /*                                                                         */
  1377. /* Returns:                                                                */
  1378. /* 1                                                                       */
  1379. /*      The command was successful.                                        */
  1380. /* 0                                                                       */
  1381. /*      The command failed.                                                */
  1382. /*                                                                         */
  1383. /* Globals:                                                                */
  1384. /* (none)                                                                  */
  1385. /***************************************************************************/
  1386.  
  1387. delhdr: procedure expose (globals); parse arg db, tag val
  1388. err = 0
  1389. if tag \= '*' & verify(tag, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-') \= 0 then
  1390.     err = 1
  1391. if left(val, 1) \= '"' then
  1392.     dom = val
  1393. else do
  1394.     val = substr(val, 2)
  1395.     dom = ''
  1396.     do forever
  1397.         i = pos('"', val)
  1398.     if i = 0 then leave
  1399.         dom = dom || left(val, i - 1)
  1400.         val = substr(val, i)
  1401.         if left(val, 2) \= '""' then leave
  1402.         dom = dom || '"'
  1403.         val = substr(val, 3)
  1404.     end
  1405.     if val \= '"' then err = 1
  1406. end
  1407. if err then do
  1408.     if _what = 'COMMAND' then do
  1409.     sep1 = ' '
  1410.     sep2 = ' '
  1411.     sep3 = ''
  1412.     end; else do
  1413.     sep1 = '('
  1414.     sep2 = ', '
  1415.     sep3 = ')'
  1416.     end
  1417.     call lineout 'STDERR', 'usage:' _myname || sep1 || '/HDELETE' ||,
  1418.                sep2 || 'tag string' || sep3
  1419.     return 0
  1420. end
  1421. stem.!flags = 'R_DUP'
  1422. dbp = open_db(db, 'B', 'stem.!')
  1423. if dbp = 0 then do
  1424.     call lineout 'STDERR', _myname': error' libdb_errno 'opening' db
  1425.     return 0
  1426. end
  1427. /* it contains dups, so we scan.  if no string, delete *all* matches. */
  1428. what = 'R_CURSOR'
  1429. keyv = tag
  1430. err = -1
  1431. do forever
  1432.     rc = libDbSeq(dbp, 'keyv', 'valv', what)
  1433.     if rc \= 0 | keyv \= tag then leave
  1434.     /* can't use "parse" because spaces are significant in pattern */
  1435.     fnd = left(valv, 1)
  1436.     valv = substr(valv, 3)
  1437.     i = verify(valv, '0123456789')
  1438.     prx = left(valv, i - 1)
  1439.     pat = substr(valv, i + 1)
  1440.     if dom = '' | dom == pat then do
  1441.     if err = -1 then err = 0
  1442.     if libDbDel(dbp, key, 'R_CURSOR') \= 0 then do
  1443.         call lineout 'STDERR', _myname': error' libdb_errno 'deleting' tag,
  1444.                    'pattern' pat
  1445.         err = 1
  1446.     end
  1447.     end
  1448.     what = 'R_NEXT'
  1449. end
  1450. if err = -1 then
  1451.     call lineout 'STDERR', _myname': key' tag 'not found in header database'
  1452. call libDbClose dbp
  1453. dbp = 0
  1454. return (err = 0)
  1455.  
  1456. /***************************************************************************/
  1457. /* showlist(DB)                                                            */
  1458. /*                                                                         */
  1459. /* Format and display the contents of the specified database.              */
  1460. /*                                                                         */
  1461. /* Arguments:                                                              */
  1462. /* DB                                                                      */
  1463. /*      The filename of the database to be displayed.                      */
  1464. /*                                                                         */
  1465. /* Returns:                                                                */
  1466. /* 1                                                                       */
  1467. /*      The command was successful.                                        */
  1468. /* 0                                                                       */
  1469. /*      The command failed.                                                */
  1470. /*                                                                         */
  1471. /* Globals:                                                                */
  1472. /* (none)                                                                  */
  1473. /***************************************************************************/
  1474.  
  1475. showlist: procedure expose (globals); parse arg db
  1476. stem.!openflags = 'O_RDONLY'
  1477. dbp = open_db(db, 'H', 'stem.!')
  1478. if dbp = 0 then do
  1479.     call lineout 'STDERR', _myname': error' libdb_errno 'opening' db
  1480.     return 0
  1481. end
  1482. say 'Listing of address blacklist database:' db
  1483. say ''
  1484. cnt = 0
  1485. what = 'R_FIRST'
  1486. do while libDbSeq(dbp, 'kv', 'vv', what) = 0
  1487.     say left(kv, 40) || vv
  1488.     cnt = cnt + 1
  1489.     what = 'R_NEXT'
  1490. end
  1491. call libDbClose dbp
  1492. dbp = 0
  1493. say ''
  1494. say cnt 'entries in database.'
  1495. return 1
  1496.  
  1497. /***************************************************************************/
  1498. /* showhlist(DB)                                                           */
  1499. /*                                                                         */
  1500. /* Format and display the contents of the specified database.              */
  1501. /*                                                                         */
  1502. /* Arguments:                                                              */
  1503. /* DB                                                                      */
  1504. /*      The filename of the database to be displayed.                      */
  1505. /*                                                                         */
  1506. /* Returns:                                                                */
  1507. /* 1                                                                       */
  1508. /*      The command was successful.                                        */
  1509. /* 0                                                                       */
  1510. /*      The command failed.                                                */
  1511. /*                                                                         */
  1512. /* Globals:                                                                */
  1513. /* (none)                                                                  */
  1514. /***************************************************************************/
  1515.  
  1516. showhlist: procedure expose (globals); parse arg db
  1517. stem.!openflags = 'O_RDONLY'
  1518. stem.!flags = 'R_DUP'
  1519. dbp = open_db(db, 'B', 'stem.!')
  1520. if dbp = 0 then do
  1521.     call lineout 'STDERR', _myname': error' libdb_errno 'opening' db
  1522.     return 0
  1523. end
  1524. say 'Listing of header blacklist database:' db
  1525. say ''
  1526. cnt = 0
  1527. what = 'R_FIRST'
  1528. do while libDbSeq(dbp, 'kv', 'vv', what) = 0
  1529.     say left(kv, 40) || vv
  1530.     cnt = cnt + 1
  1531.     what = 'R_NEXT'
  1532. end
  1533. call libDbClose dbp
  1534. dbp = 0
  1535. say ''
  1536. say cnt 'entries in database.'
  1537. return 1
  1538.  
  1539. /***************************************************************************/
  1540. /* dumplist(DB)                                                            */
  1541. /*                                                                         */
  1542. /* Dump the contents of the specified database as reload commands.         */
  1543. /*                                                                         */
  1544. /* Arguments:                                                              */
  1545. /* DB                                                                      */
  1546. /*      The filename of the database to be dumped.                         */
  1547. /*                                                                         */
  1548. /* Returns:                                                                */
  1549. /* 1                                                                       */
  1550. /*      The command was successful.                                        */
  1551. /* 0                                                                       */
  1552. /*      The command failed.                                                */
  1553. /*                                                                         */
  1554. /* Globals:                                                                */
  1555. /* (none)                                                                  */
  1556. /***************************************************************************/
  1557.  
  1558. dumplist: procedure expose (globals); parse arg db
  1559. stem.!openflags = 'O_RDONLY'
  1560. dbp = open_db(db, 'H', 'stem.!')
  1561. if dbp = 0 then do
  1562.     call lineout 'STDERR', _myname': error' libdb_errno 'opening' db
  1563.     return 0
  1564. end
  1565. say '/* Reload address blacklist database:' db '*/'
  1566. say ''
  1567. what = 'R_FIRST'
  1568. do while libDbSeq(dbp, 'kv', 'vv', what) = 0
  1569.     say 'CALL' _myname "'/PADD'" qq(kv) left(vv, 1)
  1570.     what = 'R_NEXT'
  1571. end
  1572. call libDbClose dbp
  1573. dbp = 0
  1574. return 1
  1575.  
  1576. /***************************************************************************/
  1577. /* dumphlist(DB)                                                           */
  1578. /*                                                                         */
  1579. /* Dump the contents of the specified database as reload commands.         */
  1580. /*                                                                         */
  1581. /* Arguments:                                                              */
  1582. /* DB                                                                      */
  1583. /*      The filename of the database to be dumped.                         */
  1584. /*                                                                         */
  1585. /* Returns:                                                                */
  1586. /* 1                                                                       */
  1587. /*      The command was successful.                                        */
  1588. /* 0                                                                       */
  1589. /*      The command failed.                                                */
  1590. /*                                                                         */
  1591. /* Globals:                                                                */
  1592. /* (none)                                                                  */
  1593. /***************************************************************************/
  1594.  
  1595. dumphlist: procedure expose (globals); parse arg db
  1596. stem.!openflags = 'O_RDONLY'
  1597. stem.!flags = 'R_DUP'
  1598. dbp = open_db(db, 'B', 'stem.!')
  1599. if dbp = 0 then do
  1600.     call lineout 'STDERR', _myname': error' libdb_errno 'opening' db
  1601.     return 0
  1602. end
  1603. say '/* Reload header blacklist database:' db '*/'
  1604. say ''
  1605. what = 'R_FIRST'
  1606. do while libDbSeq(dbp, 'kv', 'vv', what) = 0
  1607.     /* can't use "parse" because spaces are significant in pattern */
  1608.     fnd = left(vv, 1)
  1609.     vv = substr(vv, 3)
  1610.     i = verify(vv, '0123456789')
  1611.     prx = left(vv, i - 1)
  1612.     pat = substr(vv, i + 1)
  1613.     say 'CALL' _myname "'/HADD'" fnd prx sq(kv) qq(pat)
  1614.     what = 'R_NEXT'
  1615. end
  1616. call libDbClose dbp
  1617. dbp = 0
  1618. return 1
  1619.  
  1620. /***************************************************************************/
  1621. /* qq(STRING)                                                              */
  1622. /*                                                                         */
  1623. /* Doublequote the specified string, doubling internal quotes as needed.   */
  1624. /* (More:  it then single-quotes the result.)                              */
  1625. /*                                                                         */
  1626. /* Arguments:                                                              */
  1627. /* STRING                                                                  */
  1628. /*      The string to quote.                                               */
  1629. /*                                                                         */
  1630. /* Returns:                                                                */
  1631. /* STRING                                                                  */
  1632. /*      The quoted string.                                                 */
  1633. /*                                                                         */
  1634. /* Globals:                                                                */
  1635. /* (none)                                                                  */
  1636. /***************************************************************************/
  1637.  
  1638. qq: procedure expose (globals); parse arg str
  1639. /* this is faster in Object Rexx... */
  1640. /* return '"' || changestr('"', str, '""') || '"' */
  1641. res = '"'
  1642. do forever
  1643.     i = pos('"', str)
  1644.     if i = 0 then leave
  1645.     res = res || left(str, i) || '"'
  1646.     str = substr(str, i + 1)
  1647. end
  1648. return sq(res || str || '"')
  1649.  
  1650.  
  1651. /***************************************************************************/
  1652. /* sq(STRING)                                                              */
  1653. /*                                                                         */
  1654. /* Singlequote the specified string, doubling internal quotes as needed.   */
  1655. /*                                                                         */
  1656. /* Arguments:                                                              */
  1657. /* STRING                                                                  */
  1658. /*      The string to quote.                                               */
  1659. /*                                                                         */
  1660. /* Returns:                                                                */
  1661. /* STRING                                                                  */
  1662. /*      The quoted string.                                                 */
  1663. /*                                                                         */
  1664. /* Globals:                                                                */
  1665. /* (none)                                                                  */
  1666. /***************************************************************************/
  1667.  
  1668. sq: procedure expose (globals); parse arg str
  1669. res = "'"
  1670. do forever
  1671.     i = pos("'", str)
  1672.     if i = 0 then leave
  1673.     res = res || left(str, i) || "'"
  1674.     str = substr(str, i + 1)
  1675. end
  1676. return res || str || "'"
  1677.  
  1678. /***************************************************************************/
  1679. /* open_db(DB, TYPE[, MODE])                                               */
  1680. /*                                                                         */
  1681. /* Open a BSD db-1.85 database, retrying if it is locked.                  */
  1682. /*                                                                         */
  1683. /* Arguments: (forwarded to libDbOpen without inspection or modification)  */
  1684. /* DB                                                                      */
  1685. /*      The database file to be opened.                                    */
  1686. /* TYPE                                                                    */
  1687. /*      The type of database: 'BTREE', 'HASH', 'RECNO'                     */
  1688. /* MODE                                                                    */
  1689. /*      An optional stem variable containing OS and db-related modes       */
  1690. /*                                                                         */
  1691. /* Returns:                                                                */
  1692. /* DBP                                                                     */
  1693. /*      A database identifier, or 0 if the open failed.                    */
  1694. /*                                                                         */
  1695. /* Globals:                                                                */
  1696. /* (none)                                                                  */
  1697. /***************************************************************************/
  1698.  
  1699. open_db: procedure expose (globals)
  1700. do forever
  1701.     if arg(3, 'E') then
  1702.     dbp = libDbOpen(arg(1), arg(2), arg(3))
  1703.     else
  1704.     dbp = libDbOpen(arg(1), arg(2))
  1705.     if dbp \= 0 then return dbp
  1706.     if libdb_errno \= 24 then return 0
  1707.     call SysSleep 1
  1708. end
  1709. /* NOTREACHED */
  1710.  
  1711. /***************************************************************************/
  1712. /* log(STRING)                                                             */
  1713. /*                                                                         */
  1714. /* Record the specified string in the logfile, with date/time stamp.  If   */
  1715. /* the log file name is '', no log is kept.                                */
  1716. /*                                                                         */
  1717. /* Arguments:                                                              */
  1718. /* STRING                                                                  */
  1719. /*      The string to record in the logfile.  Additional arguments are not */
  1720. /*      recognized at the present time.                                    */
  1721. /*                                                                         */
  1722. /* Returns:                                                                */
  1723. /* (none)                                                                  */
  1724. /*                                                                         */
  1725. /* Globals:                                                                */
  1726. /* _log                                                                    */
  1727. /*      The name of the log file; if '', logging is not performed.         */
  1728. /***************************************************************************/
  1729.  
  1730. log: procedure expose (globals); parse arg msg
  1731. if _log = '' then return
  1732. /* ORexx appends, CRexx overwrites... sigh */
  1733. /* (can't do this unconditionally because SEEK is incompatible between them) */
  1734. if left(log, 3) \= 'STD' & _rxvsn < 6 then do
  1735.     call stream _log, 'C', 'OPEN'
  1736.     call stream _log, 'C', 'SEEK <0'
  1737. end
  1738. call lineout _log, msg
  1739. if left(log, 3) \= 'STD' then call stream _log, 'C', 'CLOSE'
  1740. return
  1741.  
  1742. /* used for debugging */
  1743. log2: procedure expose (globals); parse arg level, msg
  1744. /*if testing > level then*/ call log msg
  1745. return
  1746.  
  1747. /***************************************************************************/
  1748. /* SIGNAL NAME cleanup                                                     */
  1749. /*                                                                         */
  1750. /* Closes the socket opened by getpage().                                  */
  1751. /***************************************************************************/
  1752.  
  1753. cleanup:
  1754. if symbol('dbp') = 'VAR' & dbp \= 0 then do
  1755.     signal on syntax name cleanup.2
  1756.     call libDbClose dbp
  1757. end
  1758.  
  1759. cleanup.2:
  1760. if symbol('hdbp') = 'VAR' & hdbp \= 0 then do
  1761.     signal on syntax name cleanup.3
  1762.     call libDbClose hdbp
  1763. end
  1764.  
  1765. cleanup.3:
  1766. exit 1
  1767.