home *** CD-ROM | disk | FTP | other *** search
/ Source Code 1994 March / Source_Code_CD-ROM_Walnut_Creek_March_1994.iso / compsrcs / misc / volume38 / procmail / part08 < prev    next >
Encoding:
Text File  |  1993-07-04  |  48.0 KB  |  1,329 lines

  1. Newsgroups: comp.sources.misc
  2. From: berg@pool.informatik.rwth-aachen.de (Stephen R. van den Berg)
  3. Subject: v38i027:  procmail - mail processing package v2.90, Part08/11
  4. Message-ID: <1993Jul1.151300.21721@sparky.imd.sterling.com>
  5. X-Md4-Signature: 33617c3b0d3b6a96000a1dd00762c63a
  6. Sender: kent@sparky.imd.sterling.com (Kent Landfield)
  7. Organization: Sterling Software
  8. Date: Thu, 1 Jul 1993 15:13:00 GMT
  9. Approved: kent@sparky.imd.sterling.com
  10.  
  11. Submitted-by: berg@pool.informatik.rwth-aachen.de (Stephen R. van den Berg)
  12. Posting-number: Volume 38, Issue 27
  13. Archive-name: procmail/part08
  14. Environment: sendmail, smail, MMDF, mailsurr, UNIX, POSIX
  15. Supersedes: procmail: Volume 35, Issue 21-32,124,125
  16.  
  17. #! /bin/sh
  18. # This is a shell archive.  Remove anything before this line, then unpack
  19. # it by saving it into a file and typing "sh file".  To overwrite existing
  20. # files, type "sh file -c".  You can also feed this as standard input via
  21. # unshar, or by typing "sh <file", e.g..  If this archive is complete, you
  22. # will see the following message at the end:
  23. #        "End of archive 8 (of 11)."
  24. # Contents:  procmail/mailinglist/Manual
  25. #   procmail/mailinglist/bin/digest procmail/src/mailfold.c
  26. #   procmail/src/regexp.c
  27. # Wrapped by berg@tubastos on Thu Jul  1 14:06:17 1993
  28. PATH=/bin:/usr/bin:/usr/ucb ; export PATH
  29. if test -f 'procmail/mailinglist/Manual' -a "${1}" != "-c" ; then 
  30.   echo shar: Will not clobber existing file \"'procmail/mailinglist/Manual'\"
  31. else
  32. echo shar: Extracting \"'procmail/mailinglist/Manual'\" \(15180 characters\)
  33. sed "s/^X//" >'procmail/mailinglist/Manual' <<'END_OF_FILE'
  34. X$Id: Manual,v 1.11 1993/06/21 14:23:42 berg Exp $
  35. X
  36. X        Written by Stephen R. van den Berg.
  37. X                    berg@pool.informatik.rwth-aachen.de
  38. X                    berg@physik.tu-muenchen.de
  39. X
  40. XContents:
  41. X---------    1. Creating and removing mailinglists or archive servers
  42. X        2. Remote maintenance of mailinglists
  43. X        3. Customisation
  44. X        3a.Digest processing
  45. X        3b.Moderated lists
  46. X        3c.Schematic overview of what goes on behind the scenes
  47. X        4. The archive server
  48. X        5. The format of the dist file
  49. X        6. Multigram and the thresholds in rc.init/rc.custom
  50. X
  51. X
  52. X1. Creating and removing mailinglists or archive servers
  53. X   -----------------------------------------------------
  54. X
  55. XMake sure that the .bin directory is in your PATH.  Now you can issue
  56. Xcommands like:
  57. X
  58. X    createlist testing
  59. X    createlist testing joe@somewhere.edu
  60. X    createlist -a testing joe@somewhere.edu
  61. X    removelist testing
  62. X
  63. XThe first command creates a mailinglist with two useful addresses:
  64. X
  65. X    testing
  66. X    testing-request
  67. X
  68. XThe second command does the same, but it also specifies joe@somewhere.edu
  69. Xto be the responsible contact person for this list.
  70. X
  71. XThe third command does the same, except instead of creating a mailinglist,
  72. Xit creates an archive server.
  73. X
  74. XThe fourth command removes all traces of the "testing" mailinglist again.
  75. X
  76. XThere are two other convenience-utilitis that can be used:
  77. X    delink
  78. X        It will unlink a file from its hardlinked counterpart(s).
  79. X    showlink
  80. X        It will display what groups of files are linked together.
  81. X
  82. X
  83. X2. Remote maintenance of mailinglists
  84. X   ----------------------------------
  85. X
  86. XTo facilitate remote maintenance of some mailinglists by their maintainers
  87. XI have created the .bin/x_command script.  It parses mails sent to the
  88. X-request address and can execute some administrative commands.
  89. X
  90. XThe mail should be sent to the -request address of a mailinglist and
  91. Xshould contain a field in the header looking like this:
  92. X
  93. XX-Command: joe@somewhere.edu password command
  94. X
  95. X"command" can be anything of the following:
  96. X
  97. X    subscribe mailaddress
  98. X    unsubscribe mailaddress
  99. X    showdist            To list the distfile
  100. X    showlog                To list the log
  101. X    wipelog                To clear the log
  102. X    help                To show this command summary
  103. X    info                Ditto
  104. X
  105. XThe exact fieldname defaults to "X-Command", but can be customised to
  106. Xwhatever you want.
  107. X
  108. XThe password defaults to "password", but can/should be changed.
  109. X
  110. XThe "joe@somewhere.edu" is always the mail address of the maintainer.  Note
  111. Xthat this has to match what was specified on the command line of
  112. X"createlist" when the list was created.
  113. X
  114. XNote that the X-Command: field has to be part of the header, when it's
  115. Xin the body of the mail, it has no effect.
  116. X
  117. XAnytime an X-Command: mail has been processed, the results will be
  118. Xmailed back to the maintainer of the list, and the X-Command: field
  119. Xwill have been renamed to X-Processed:.
  120. X
  121. XAlthough this remote-facility is convenient, some might argue that it
  122. Xpresents a security hole.  Well, in order to make this hole as small as
  123. Xpossible, you can keep the password secret.  Also, the exact mailaddress
  124. Xof the maintainer might not be publicly known.    You can simply change
  125. Xthe X-Command field into something else like X-MyCommand.  Above all, since
  126. Xfaking mail is a well known possibility it would be ridiculous to take
  127. Xmore precautions than these.  Besides, if someone indeed manages to sneek in
  128. Xa bogus X-Command:, it will never go unnoticed since the mailing list
  129. Xmaintainer (and only the maintainer) will always receive the X-Processed:
  130. Xmail.
  131. X
  132. X
  133. X3. Customisation
  134. X   -------------
  135. X
  136. XThe mailinglists can be customised in several ways:
  137. X
  138. X- For all the lists:
  139. X    - Since all the lists share the same help.txt, subscibe.txt, rc.init,
  140. X      rc.submit and rc.request files (hardlinked), any change to them
  141. X      will affect all lists.
  142. X    - Since all the lists have the .bin directory in their PATH, any
  143. X      change to one of the Bourne shell scripts in there will affect
  144. X      them all.
  145. X- Per list:
  146. X    - Every list directory contains an "rc.custom" rcfile which can
  147. X      be edited to your hearts content to customise certain parameters
  148. X      for this list only.
  149. X    - For graver customisation you can remove the hardlink (using
  150. X      .bin/delink for example) to any of the files in a list directory and
  151. X      provide that list with its own copy in order to edit that to taste.
  152. X    - Since the current directory is in the PATH before the .bin
  153. X      directory you can create per-list copies of any of the Bourne shell
  154. X      scripts in .bin which can then be changed without affecting the
  155. X      other lists.
  156. X- Per group of lists:
  157. X    - The same applies as when customising per list, but you should
  158. X      then hardlink the appropriate files among the group of list
  159. X      directories.
  160. X
  161. XIf you are not using the remote-maintenance facility and you start editing
  162. Xor customising scripts/files by hand, then you should make sure that there
  163. Xdoesn't arrive any mail to those lists that are affected by your changes.
  164. X
  165. XIf you are editing while the system is running you can temporarily put
  166. Xincoming mails on hold;     you can do this:
  167. X
  168. X- for all the lists by creating the file:    .etc/rc.lock
  169. X- only for one list by creating the file:    rc.lock
  170. X  in the list directory of that list.
  171. X
  172. XThe .bin/flist command checks to see if these rc.lock files exist AND are
  173. Xnot older than 17 minutes before delivering the mail.  So, if you create
  174. Xan rc.lock file, mails to that (or all) lists will stall for the next
  175. X17 minutes.  If you need more time, touch the file every so often.
  176. XYou should remove the rc.lock files again after finishing your editing.
  177. X
  178. X
  179. X3a.Digest processing
  180. X   -----------------
  181. X
  182. XYou can configure a list to send out digests of accumulated submissions.
  183. XIn order to do so, simply uncomment the appropriate assignment to
  184. Xdigest_flag in rc.init (if you want all lists to be digested) or rc.custom
  185. X(if you only want this list to be digested).  Digests are then sent out
  186. Xevery so often depending on size and age of the accumulated messages.
  187. X
  188. XThe conditions for sending out a digest are checked during the arrival
  189. Xof every submission.  If however traffic on the list sometimes is very low
  190. X(i.e. less often than the maximum age of a digest) a digest could be laying
  191. Xaround for longer than the specified maximum period (3 days by default).
  192. X
  193. XIn order to make sure that the digest gets sent out anyway, you should be
  194. Xrunning the .bin/flush_digests program every so often.    The recommended
  195. Xprocedure is to create a cron job (under the list account) that contains
  196. Xsomething like the following entry:
  197. X
  198. X0 6 * * * /home/list/.bin/flush_digests
  199. X
  200. XThis will ensure that at six o'clock in the morning all the overdue digests
  201. Xwill be sent out.  Beware: call flush_digests with an absolute or relative
  202. Xpath, do not rely on PATH to find it for you (flush_digests uses $0 to
  203. Xfind the location of the lists).
  204. X
  205. XIf you want to give your subscribers the choice of receiving digests or not.
  206. XThis is what you can do:
  207. X
  208. X    Create two lists.  E.g. if your list would be called "thelist", then
  209. X    you have the `real' list called "thelist" (created and used like
  210. X    a regular list) and the `digested' list called "thelist-d".
  211. X
  212. X    In the distfile of thelist you should include thelist-d as one of
  213. X    the subscribers.  In the rc.custom file of thelist-d you should
  214. X    edit the assignment to undigested_list to read
  215. X    "undigested_list =    thelist@$domain".
  216. X
  217. X    After you've done this, you're all set.     People that want digests
  218. X    simply subscribe to thelist-d and people that don't, subscribe to
  219. X    thelist.
  220. X
  221. X
  222. X3b.Moderated lists
  223. X   ---------------
  224. X
  225. XThe easiest way to set up a moderated list would be to rename the "thelist"
  226. Xalias into "thelist-approved", and to create a new "thelist" alias that
  227. Xpoints to the moderator.  This way the moderator receives all submissions,
  228. Xwhich he/she then can forward to thelist-approved to finally distribute it
  229. X(after some editing of the message perhaps).
  230. X
  231. X
  232. X3c.Schematic overview of what goes on behind the scenes
  233. X   ----------------------------------------------------
  234. X
  235. XSuppose you have two entries in the aliases file, one for thelist@domain
  236. Xand one for thelist-request@domain.
  237. X
  238. XWhenever mail arrives for either address, the following happens:
  239. X    - flist is started suid root with thelist as its argument
  240. X        - changes its uid and gid to that of the list account
  241. X        - changes its current directory to that of thelist
  242. X        - waits until both ../.etc/rc.lock and rc.lock are gone or
  243. X          are old enough (17 minutes)
  244. X
  245. XThen, if it was a regular submission to thelist@domain:
  246. X    - flist execs procmail with rcfile rc.submit
  247. X        - pulls in rc.init that sets up the defaults
  248. X        - pulls in rc.custom that overrides some defaults (if any)
  249. X        - checks the submission format and fires of sendmail to
  250. X          send the mail to thelist-dist@domain
  251. X    - If the mail was an administrative request, it does not get
  252. X      passed on to the list, instead, procmail pulls in rc.request
  253. X
  254. XBut, if it was an administrative mail for thelist-request@domain:
  255. X    - flist execs procmail with rcfile rc.request
  256. X        - pulls in rc.init that sets up the defaults
  257. X        - pulls in rc.custom that overrides some defaults (if any)
  258. X        - performs the necessary actions, depending on the content
  259. X        - if the content was undecipherable, it gets passed on to
  260. X          the maintainer of thelist
  261. X
  262. XIf there are grave system failures during all this, the catch-all script
  263. Xrc.post will kick in and make sure that the mail is stashed away somewhere
  264. Xor forwarded to the maintainer, whatever works.     This to ensure that no
  265. Xmail gets lost.
  266. X
  267. X
  268. X4. The archive server
  269. X   ------------------
  270. X
  271. XAll mail (except mail being forwarded from another mailinglist) sent to any
  272. Xof the lists is archived.  The archiving is fairly straightforward.
  273. XE.g. if you have a list called "scuba", then all submissions are archived
  274. Xin scuba/archive/latest/.  The mails will be stored one-mail-per-file each.
  275. XThe files will be numbered.
  276. X
  277. XNow, normally, only the last two mails will be kept around, the others
  278. Xare periodically removed.  This in order to keep down the archiving costs
  279. Xfor people with limited diskspace.  To disable archiving completely,
  280. Xedit the rc.submit file.  To simply make the archive-history longer,
  281. Xedit the rc.custom file.  To get more sophisticated archiving, like grouping
  282. Xsubmissions monthly, you should either create a cron job or edit the
  283. X.bin/arch_trunc file.
  284. X
  285. XThe archive server can be accessed per mailinglist by sending mail
  286. Xto the -request address with the following Subject:
  287. X
  288. X    Subject: archive
  289. X
  290. XThe body of the mail or the rest of the subject line can then be
  291. Xfiled with requests to the archive server.  It basically understands
  292. Xthree commands:
  293. X
  294. X    get file ...
  295. X    ls directory ...
  296. X    help
  297. X
  298. XThe archive server does a thorough check on the commands and the files
  299. Xthat are requested.  This to ensure that it does not access any files
  300. Xoutside the "scuba/archive" directory.    Any text-file that you put below
  301. Xthe "scuba/archive" directory can now be retrieved by the archive commands.
  302. X
  303. XThe whole archive server can be found in the .bin/arch_retrieve script.
  304. X
  305. X
  306. X5. The format of the dist file
  307. X   ---------------------------
  308. X
  309. XYou do not need to know this, unless you edit the dist file by hand or want
  310. Xto incorporate an existing list of addresses.
  311. X
  312. XIn order to distribute incoming submissions the dist file is fed to sendmail
  313. Xwith the regular :include: alias.  So the format of this file must
  314. Xbe in accordance with what sendmail would expect.  In addition to that
  315. Xthis file is searched and edited by multigram in order to find particular
  316. Xsubscribers.  The format which multigram expects is a bit more rigid than
  317. Xwhat sendmail allows.
  318. X
  319. XThe following conditions apply:
  320. X- One subscriber per line.
  321. X- Empty lines are allowed.
  322. X- The mail address of the subscriber must be the first word on the line.
  323. X- Comments may follow the address (but separated from the address by
  324. X  at least one whitespace character).
  325. X- Everything preceding the line containing:
  326. X    (Only addresses below this line can be automatically removed)
  327. X  is write protected from changes by multigram (i.e. these addresses can
  328. X  never be automatically/accidentally unsubscribed).
  329. X- If the line:
  330. X    (Only addresses below this line can be automatically removed)
  331. X  is not present at all, automatic unsubscriptions to this list are impossible.
  332. X- Whenever multigram automatically removes an address from the list, it
  333. X  rewrites the dist file `in situ'.  This means that the dist file will be
  334. X  contracted at that point, any excess slack at the end will be overwritten
  335. X  by newlines (i.e. the dist file never shrinks, this because ANSI-C does not
  336. X  provide a truncate() command of some kind).  I choose to write in situ in
  337. X  order to avoid copying the dist file every time it changes (a real life
  338. X  saver if the list grows too big).
  339. X- Multigram always adds new subscribers on the line immediately following the
  340. X  last filled entry in the dist file.
  341. X
  342. XSome sample entries (the preferred format):
  343. X    joe@some.where
  344. X    joe@some.where (some comment)
  345. X    joe@some.where (some comment) (some more comment)
  346. X
  347. XDepreciated, but allowed:
  348. X    <joe@some.where>
  349. X    <joe@some.where> some comment
  350. X    <joe@some.where> (some comment)
  351. X
  352. XNot allowed by multigram (although sendmail doesn't mind):
  353. X    (some comment) joe@some.where
  354. X    some comment <joe@some.where>
  355. X
  356. X
  357. X6. Multigram and the thresholds in rc.init/rc.custom
  358. X   -------------------------------------------------
  359. X
  360. XThe rc.init and rc.custom scripts define some threshold values:
  361. X
  362. X    match_threshold, off_threshold, reject_threshold, submit_threshold.
  363. X
  364. XThese values are fed to multigram as a cut-off value with which to decide
  365. Xif a certain mail address is on a list.
  366. XThe higher the threshold, the better the match must be.     The thresholds
  367. Xhave a scale from -16383 to 32767.  This means that, for instance a threshold
  368. Xof 30730 can be used to find only mailaddresses that are almost verbatim
  369. Xon the list.  A value of 24476 on the other hand allows for some error
  370. X(like mailaddresses munged by gateways etc.) in finding matches to the
  371. Xlist.
  372. X
  373. XThe values 30730 and 24476 are somewhat arbitrary values which seem
  374. Xto work well for the particular problems at hand.
  375. X
  376. XTo get a feeling for the values computed by multigram you can do
  377. Xthe following test:
  378. X
  379. X    Create a file with the same format as the distfile, fill it with
  380. X    any number of addresses you like (e.g. you could take an existing
  381. X    distfile).
  382. X    Now make a copy of this `distfile' and alter some of the addresses
  383. X    a bit (like omit one character, or add some gateway information,
  384. X    switch two words, change it into an uucp address, etc.).
  385. X    Next you should call up multigram with the following command line:
  386. X
  387. X        multigram -l-16000 -b300 pseudo_distfile <altered_distfile
  388. X
  389. X    Multigram will display up the 300 best matches it found after
  390. X    crossreferencing altered_distfile and pseudo_distfile.
  391. X    The output produced by multigram can be disected as follows:
  392. X
  393. X        lineno. name1 goodness name2
  394. X
  395. X    Lineno. and name1 refer to the line number in pseudo_distfile which
  396. X    contains the mailaddress name1.     Goodness is the metric that
  397. X    corresponds to the aforementioned threshold values, and name2 is
  398. X    the matching mailaddress from altered_distfile (which is usually
  399. X    the incoming mail).
  400. X
  401. X    Once you get the hang of it you can play around a bit with the
  402. X    entries in altered_distfile by mutilating them more and more in
  403. X    order to see what multigram makes of it (try inserting some non-
  404. X    existing addresses as well).
  405. END_OF_FILE
  406. if test 15180 -ne `wc -c <'procmail/mailinglist/Manual'`; then
  407.     echo shar: \"'procmail/mailinglist/Manual'\" unpacked with wrong size!
  408. fi
  409. # end of 'procmail/mailinglist/Manual'
  410. fi
  411. if test -f 'procmail/mailinglist/bin/digest' -a "${1}" != "-c" ; then 
  412.   echo shar: Will not clobber existing file \"'procmail/mailinglist/bin/digest'\"
  413. else
  414. echo shar: Extracting \"'procmail/mailinglist/bin/digest'\" \(2415 characters\)
  415. sed "s/^X//" >'procmail/mailinglist/bin/digest' <<'END_OF_FILE'
  416. X#! /bin/sh
  417. X:
  418. X#$Id: digest,v 1.4 1993/06/02 15:30:44 berg Exp $
  419. X
  420. Xtest=test        # /bin/test
  421. Xecho=echo        # /bin/echo
  422. Xls=ls            # /bin/ls
  423. Xawk=awk            # /usr/bin/awk
  424. Xcat=cat            # /bin/cat
  425. Xdate=date        # /bin/date
  426. Xrm=rm            # /bin/rm
  427. Xsed=sed            # /bin/sed
  428. Xexpr=expr        # /bin/expr
  429. Xformail=formail        # /usr/local/bin/formail
  430. X
  431. X$test -z "$listaddr" &&
  432. X $echo "Don't start this script directly, it is used in rc.submit" && exit 64
  433. X
  434. Xtest -z "$foreign_submit$METOO" && exit 1
  435. X
  436. Xtmprequest=tmp.request
  437. Xtmpfrom=tmp.from
  438. X
  439. Xdigestheader=archive/latest/digest.header
  440. Xdigestadmin=digest.admin
  441. Xdigestadmin2=archive/latest/$digestadmin
  442. Xdigestbody=archive/latest/digest.body
  443. Xdigesttrailer=archive/latest/digest.trailer
  444. X
  445. X$echo "$digest_age $digest_size $archive_hist $UMASK
  446. X$SENDMAIL $sendmailOPT $listdist" >.digest.params
  447. X
  448. X$awk '
  449. XBEGIN                    { lines=0;mode=0; }
  450. X                    { match=0; }
  451. X/^$/                    { match=1; }
  452. X/^[     ][     ]*$/            { match=2; }
  453. X/^------------------------------$/    { match=3; }
  454. X{ if(mode==0)
  455. X   { print($0) >"'$tmpfrom'";
  456. X     if(match==1)
  457. X    mode=1;
  458. X     next;
  459. X   }
  460. X  if(mode==1 && match)
  461. X     next;
  462. X  mode=2;
  463. X  if(match==1)
  464. X   { ++lines;next;
  465. X   }
  466. X  while(lines)
  467. X   { print("");--lines;
  468. X   }
  469. X  if(match==3)
  470. X     print(" -----------------------------");
  471. X  else
  472. X     print($0);
  473. X}
  474. XEND { print("");print("------------------------------");print(""); }
  475. X' >$tmprequest
  476. X
  477. Xflush_digests -c
  478. X
  479. Xfor a in Date From To Cc Subject Message-ID Keywords Summary
  480. Xdo
  481. X  b="`$formail -X $a: <$tmpfrom`"
  482. X  if $test ! -z "$b"
  483. X  then
  484. X     $echo "$b" >>$digestbody
  485. X  fi
  486. Xdone
  487. X
  488. X$echo "" >>$digestbody
  489. X$cat $tmprequest >>$digestbody
  490. X
  491. Xif $test ! -f $digesttrailer
  492. Xthen
  493. X
  494. X  Year=`$date +%y`
  495. X  Issue=0
  496. X
  497. X  if $test -f $digestheader
  498. X  then
  499. X     set dummy `$sed -n \
  500. X      -e '1,/^$/ s/^Subject:.*Digest V\([0-9]*\) #\([0-9]*\)/\1 \2/p' \
  501. X      <$digestheader`
  502. X     $test $Year = "$2" && Issue=$3
  503. X  fi
  504. X
  505. X  Issue=`$expr 1 + $Issue`
  506. X
  507. X  $cat >$digestheader <<HERE
  508. XFrom: $listreq
  509. XReply-To: $undigested_list
  510. XSubject: $list Digest V$Year #$Issue
  511. XX-Loop: $listaddr
  512. XPrecedence: list
  513. XTo: $listaddr
  514. X
  515. X$list Digest                Volume $Year : Issue $Issue
  516. X
  517. XToday's Topics:
  518. XHERE
  519. X
  520. X  $echo "End of $list Digest V$Year Issue #$Issue" >$digesttrailer
  521. X  b=`$sed -e 's/./*/g' <$digesttrailer`
  522. X  $echo "$b" >>$digesttrailer
  523. Xfi
  524. X
  525. X$echo  "    `$formail -x Subject: <$tmpfrom`" >>$digestheader
  526. X$cat /dev/null >$tmprequest 2>$tmpfrom
  527. X
  528. X#
  529. X# Check again, maybe we exceed the time or size limits now already.
  530. X#
  531. X
  532. Xflush_digests -c
  533. Xexit 0
  534. END_OF_FILE
  535. if test 2415 -ne `wc -c <'procmail/mailinglist/bin/digest'`; then
  536.     echo shar: \"'procmail/mailinglist/bin/digest'\" unpacked with wrong size!
  537. fi
  538. chmod +x 'procmail/mailinglist/bin/digest'
  539. # end of 'procmail/mailinglist/bin/digest'
  540. fi
  541. if test -f 'procmail/src/mailfold.c' -a "${1}" != "-c" ; then 
  542.   echo shar: Will not clobber existing file \"'procmail/src/mailfold.c'\"
  543. else
  544. echo shar: Extracting \"'procmail/src/mailfold.c'\" \(12269 characters\)
  545. sed "s/^X//" >'procmail/src/mailfold.c' <<'END_OF_FILE'
  546. X/************************************************************************
  547. X *    Routines that deal with the mailfolder(format)            *
  548. X *                                    *
  549. X *    Copyright (c) 1990-1992, S.R. van den Berg, The Netherlands    *
  550. X *    #include "README"                        *
  551. X ************************************************************************/
  552. X#ifdef RCS
  553. Xstatic /*const*/char rcsid[]=
  554. X "$Id: mailfold.c,v 1.32 1993/07/01 11:58:32 berg Exp $";
  555. X#endif
  556. X#include "procmail.h"
  557. X#include "sublib.h"
  558. X#include "robust.h"
  559. X#include "shell.h"
  560. X#include "misc.h"
  561. X#include "pipes.h"
  562. X#include "common.h"
  563. X#include "exopen.h"
  564. X#include "goodies.h"
  565. X#include "locking.h"
  566. X#include "mailfold.h"
  567. X#ifndef NO_COMSAT
  568. X#include "network.h"
  569. X
  570. Xconst char scomsat[]="COMSAT";
  571. X#endif
  572. Xint logopened,tofile;
  573. Xoff_t lasttell;
  574. Xstatic long lastdump;
  575. Xstatic volatile mailread;    /* if the mail is completely read in already */
  576. Xstatic struct dyna_long escFrom_,confield;      /* escapes, concatenations */
  577. X                   /* inserts escape characters on outgoing mail */
  578. Xstatic long getchunk(s,fromw,len)const int s;const char*fromw;const long len;
  579. X{ long dist,dif;int i;static const char esc[]=ESCAP;
  580. X  dist=fromw-themail;            /* where are we now in transmitting? */
  581. X  for(dif=len,i=0;i<escFrom_.filled;)        /* let's see if we can find this */
  582. X     if(!(dif=escFrom_.offs[i++]-dist))             /* this exact spot? */
  583. X      { rwrite(s,esc,STRLEN(esc));lastdump++;            /* escape it */
  584. X    if(i>=escFrom_.filled)                      /* last block? */
  585. X       return len;                /* yes, give all what's left */
  586. X    dif=escFrom_.offs[i]-dist;break;         /* the whole next block */
  587. X      }
  588. X     else if(dif>0)                /* passed this spot already? */
  589. X    break;
  590. X  return dif<len?dif:len;
  591. X}
  592. X
  593. Xlong dump(s,source,len)const int s;const char*source;long len;
  594. X{ int i;long part;
  595. X  lasttell=i= -1;
  596. X  if(s>=0)
  597. X   { if(tofile&&(lseek(s,(off_t)0,SEEK_END),fdlock(s)))
  598. X    nlog("Kernel-lock failed\n");
  599. X     lastdump=len;part=tofile==to_FOLDER?getchunk(s,source,len):len;
  600. X     lasttell=lseek(s,(off_t)0,SEEK_END);smboxseparator(s);     /* optional */
  601. X#ifndef NO_NFS_ATIME_HACK                    /* separator */
  602. X     if(part&&tofile)               /* if it is a file, trick NFS into an */
  603. X    len--,part--,rwrite(s,source++,1),sleep(1);        /* a_time<m_time */
  604. X#endif
  605. X     goto jin;
  606. X     do
  607. X      { part=getchunk(s,source,len);
  608. Xjin:    while(part&&(i=rwrite(s,source,BLKSIZ<part?BLKSIZ:(int)part)))
  609. X     { if(i<0)
  610. X          goto writefin;
  611. X       part-=i;len-=i;source+=i;
  612. X     }
  613. X      }
  614. X     while(len);
  615. X     if(!len&&(lastdump<2||!(source[-1]=='\n'&&source[-2]=='\n')))
  616. X    lastdump++,rwrite(s,newline,1);           /* message always ends with a */
  617. X     emboxseparator(s);         /* newline and an optional custom separator */
  618. Xwritefin:
  619. X     if(tofile&&fdunlock())
  620. X    nlog("Kernel-unlock failed\n");
  621. X     i=rclose(s);
  622. X   }               /* return an error even if nothing was to be sent */
  623. X  tofile=0;return i&&!len?-1:len;
  624. X}
  625. X
  626. Xstatic dirfile(chp,linkonly)char*const chp;const int linkonly;
  627. X{ if(chp)
  628. X   { long i=0;                 /* first let us try to prime i with the */
  629. X#ifndef NOopendir             /* highest MH folder number we can find */
  630. X     long j;DIR*dirp;struct dirent*dp;char*chp2;
  631. X     if(dirp=opendir(buf))
  632. X      { while(dp=readdir(dirp))        /* there still are directory entries */
  633. X       if((j=strtol(dp->d_name,&chp2,10))>i&&!*chp2)
  634. X          i=j;                /* yep, we found a higher number */
  635. X    closedir(dirp);                     /* aren't we neat today */
  636. X      }
  637. X     else
  638. X    readerr(buf);
  639. X#endif /* NOopendir */
  640. X     ;{ int ok;
  641. X    do ultstr(0,++i,chp);               /* find first empty MH folder */
  642. X    while((ok=link(buf2,buf))&&errno==EEXIST);
  643. X    if(linkonly)
  644. X     { if(ok)
  645. X          goto nolnk;
  646. X       goto ret;
  647. X     }
  648. X      }
  649. X     unlink(buf2);goto opn;
  650. X   }
  651. X  ;{ struct stat stbuf;
  652. X     stat(buf2,&stbuf);
  653. X     ultoan((unsigned long)stbuf.st_ino,      /* filename with i-node number */
  654. X      strchr(strcat(buf,tgetenv(msgprefix)),'\0'));
  655. X   }
  656. X  if(linkonly)
  657. X   { yell("Linking to",buf);
  658. X     if(link(buf2,buf))       /* hardlink the new file, it's a directory folder */
  659. Xnolnk:    nlog("Couldn't make link to"),logqnl(buf);
  660. X     goto ret;
  661. X   }
  662. X  if(!myrename(buf2,buf))           /* rename it, we need the same i-node */
  663. Xopn: return opena(buf);
  664. Xret:
  665. X  return -1;
  666. X}
  667. X
  668. Xstatic ismhdir(chp)char*const chp;
  669. X{ if(chp-1>=buf&&chp[-1]==*MCDIRSEP_&&*chp==chCURDIR)
  670. X   { chp[-1]='\0';return 1;
  671. X   }
  672. X  return 0;
  673. X}
  674. X                       /* open file or new file in directory */
  675. Xdeliver(boxname,linkfolder)char*boxname,*linkfolder;
  676. X{ struct stat stbuf;char*chp;int mhdir;mode_t cumask;
  677. X  umask(cumask=umask(0));cumask=UPDATE_MASK&~cumask;tofile=to_FILE;
  678. X  asgnlastf=1;
  679. X  if(boxname!=buf)
  680. X     strcpy(buf,boxname);         /* boxname can be found back in buf */
  681. X  if(*(chp=buf))                  /* not null length target? */
  682. X     chp=strchr(buf,'\0')-1;             /* point to just before the end */
  683. X  mhdir=ismhdir(chp);                      /* is it an MH folder? */
  684. X  if(!stat(boxname,&stbuf))                    /* it exists */
  685. X   { if(cumask&&!(stbuf.st_mode&UPDATE_MASK))
  686. X    chmod(boxname,stbuf.st_mode|UPDATE_MASK);
  687. X     if(!S_ISDIR(stbuf.st_mode))     /* it exists and is not a directory */
  688. X    goto makefile;                /* no, create a regular file */
  689. X   }
  690. X  else if(!mhdir||mkdir(buf,NORMdirperm))    /* shouldn't it be a directory? */
  691. Xmakefile:
  692. X   { if(linkfolder)      /* any leftovers?  Now is the time to display them */
  693. X    concatenate(linkfolder),skipped(linkfolder);
  694. X     tofile=strcmp(devnull,buf)?to_FOLDER:0;return opena(boxname);
  695. X   }
  696. X  if(linkfolder)            /* any additional directories specified? */
  697. X   { size_t blen;
  698. X     if(blen=Tmnate-linkfolder)               /* copy the names into safety */
  699. X    Tmnate=(linkfolder=tmemmove(malloc(blen),linkfolder,blen))+blen;
  700. X     else
  701. X    linkfolder=0;
  702. X   }
  703. X  if(mhdir)                /* buf should contain directory name */
  704. X     *chp='\0',chp[-1]= *MCDIRSEP_,strcpy(buf2,buf);       /* it ended in /. */
  705. X  else                     /* fixup directory name, append a / */
  706. X     strcat(chp,MCDIRSEP_),strcpy(buf2,buf),chp=0;
  707. X  ;{ int fd= -1;        /* generate the name for the first directory */
  708. X     if(unique(buf2,strchr(buf2,'\0'),NORMperm,verbose)&&
  709. X      (fd=dirfile(chp,0))>=0&&linkfolder)     /* save the file descriptor */
  710. X    for(strcpy(buf2,buf),boxname=linkfolder;boxname!=Tmnate;)
  711. X     { strcpy(buf,boxname);        /* go through the list of other dirs */
  712. X       if(*(chp=buf))
  713. X          chp=strchr(buf,'\0')-1;
  714. X       mhdir=ismhdir(chp);                  /* is it an MH folder? */
  715. X       if(stat(boxname,&stbuf))             /* it doesn't exist */
  716. X          mkdir(buf,NORMdirperm);                /* create it */
  717. X       else if(cumask&&!(stbuf.st_mode&UPDATE_MASK))
  718. X          chmod(buf,stbuf.st_mode|UPDATE_MASK);
  719. X       if(mhdir)
  720. X          *chp='\0',chp[-1]= *MCDIRSEP_;
  721. X       else                 /* fixup directory name, append a / */
  722. X          strcat(chp,MCDIRSEP_),chp=0;
  723. X       dirfile(chp,1);        /* link it with the original in buf2 */
  724. X       while(*boxname++);          /* skip to the next directory name */
  725. X     }
  726. X     if(linkfolder)                       /* free our cache */
  727. X    free(linkfolder);
  728. X     return fd;                  /* return the file descriptor we saved */
  729. X   }
  730. X}
  731. X
  732. Xvoid logabstract(lstfolder)const char*const lstfolder;
  733. X{ if(lgabstract>0||logopened&&lgabstract)  /* don't mail it back unrequested */
  734. X   { char*chp,*chp2;int i;static const char sfolder[]=FOLDER;
  735. X     if(mailread)              /* is the mail completely read in? */
  736. X      { *thebody='\0';               /* terminate the header, just in case */
  737. X    if(eqFrom_(chp=themail))               /* any "From " header */
  738. X     { if(chp=strchr(themail,'\n'))
  739. X          *chp++='\0';
  740. X       else
  741. X          chp=thebody;              /* preserve mailbox format */
  742. X       elog(themail);elog(newline);                 /* (any length) */
  743. X     }
  744. X    if(!(lcking&lck_ALLOCLIB)&&        /* don't reenter malloc/free */
  745. X     (chp=egrepin(NSUBJECT,chp,(long)(thebody-chp),0)))
  746. X     { for(chp2= --chp;*--chp2!='\n'&&*chp2;);
  747. X       if(chp-++chp2>MAXSUBJECTSHOW)        /* keep it within bounds */
  748. X          chp2[MAXSUBJECTSHOW]='\0';
  749. X       *chp='\0';detab(chp2);elog(" ");elog(chp2);elog(newline);
  750. X     }
  751. X      }
  752. X     elog(sfolder);
  753. X     i=strlen(strncpy(buf,lstfolder,MAXfoldlen))+STRLEN(sfolder);
  754. X     buf[MAXfoldlen]='\0';detab(buf);elog(buf);i-=i%TABWIDTH;    /* last dump */
  755. X     do elog(TABCHAR);
  756. X     while((i+=TABWIDTH)<LENoffset);
  757. X     ultstr(7,lastdump,buf);elog(buf);elog(newline);
  758. X   }
  759. X#ifndef NO_COMSAT
  760. X  ;{ int s;struct sockaddr_in addr;char*chp,*chad;         /* @ seperator? */
  761. X     if(chad=strchr(chp=(char*)tgetenv(scomsat),SERV_ADDRsep))
  762. X    *chad++='\0';              /* split it up in service and hostname */
  763. X     else if(!renvint(-1L,scomsat))        /* or is it a false boolean? */
  764. X    return;                           /* ok, no comsat then */
  765. X     if(!chad||!*chad)                          /* no host */
  766. X#ifndef IP_localhost
  767. X    chad=COMSAThost;                      /* use default */
  768. X#else /* IP_localhost */
  769. X      { static const unsigned char ip_localhost[]=IP_localhost;
  770. X    addr.sin_family=AF_INET;
  771. X    tmemmove(&addr.sin_addr,ip_localhost,sizeof ip_localhost);
  772. X      }
  773. X     else
  774. X#endif /* IP_localhost */
  775. X      { const struct hostent*host;          /* what host?  paranoid checks */
  776. X    if(!(host=gethostbyname(chad))||!host->h_0addr_list)
  777. X     { endhostent();return;             /* host can't be found, too bad */
  778. X     }
  779. X    addr.sin_family=host->h_addrtype;         /* address number found */
  780. X    tmemmove(&addr.sin_addr,host->h_0addr_list,host->h_length);
  781. X    endhostent();
  782. X      }
  783. X     if(!*chp)                               /* no service */
  784. X    chp=BIFF_serviceport;                      /* use default */
  785. X     s=strtol(chp,&chad,10);
  786. X     if(chp==chad)                   /* the service is not numeric */
  787. X      { const struct servent*serv;
  788. X    if(!(serv=getservbyname(chp,COMSATprotocol)))       /* so get its no. */
  789. X     { endservent();return;
  790. X     }
  791. X    addr.sin_port=serv->s_port;endservent();
  792. X      }
  793. X     else
  794. X    addr.sin_port=htons((short)s);                /* network order */
  795. X     cat(tgetenv(lgname),"@");             /* should always fit in buf */
  796. X     if(lasttell>=0)                       /* was it a file? */
  797. X    ultstr(0,(unsigned long)lasttell,buf2),catlim(buf2);          /* yep */
  798. X     catlim(COMSATxtrsep);                 /* custom seperator */
  799. X     if(lasttell>=0&&!strchr(dirsep,*lstfolder))       /* relative filename? */
  800. X    catlim(tgetenv(maildir)),catlim(MCDIRSEP_);   /* prepend current dir */
  801. X     catlim(lstfolder);s=socket(AF_INET,SOCK_DGRAM,UDP_protocolno);
  802. X     sendto(s,buf,strlen(buf),0,(const void*)&addr,sizeof(addr));rclose(s);
  803. X     yell("Notified comsat:",buf);
  804. X   }
  805. X#endif /* NO_COMSAT */
  806. X}
  807. X
  808. Xstatic concnd;                     /* last concatenation value */
  809. X
  810. Xvoid concon(ch)const int ch;   /* flip between concatenated and split fields */
  811. X{ size_t i;
  812. X  if(concnd!=ch)                   /* is this run redundant? */
  813. X   { concnd=ch;                  /* no, but note this one for next time */
  814. X     for(i=confield.filled;i;)           /* step through the saved offsets */
  815. X    themail[confield.offs[--i]]=ch;               /* and flip every one */
  816. X   }
  817. X}
  818. X
  819. Xvoid readmail(rhead,tobesent)const long tobesent;
  820. X{ char*chp,*pastend,*realstart;
  821. X  ;{ long dfilled;
  822. X     if(rhead)                    /* only read in a new header */
  823. X      { dfilled=mailread=0;chp=readdyn(malloc(1),&dfilled);filled-=tobesent;
  824. X    if(tobesent<dfilled)           /* adjust buffer size (grow only) */
  825. X       themail=realloc(themail,dfilled+filled);
  826. X    tmemmove(themail+dfilled,thebody,filled);tmemmove(themail,chp,dfilled);
  827. X    free(chp);themail=realloc(themail,1+(filled+=dfilled));
  828. X      }
  829. X     else
  830. X      { if(!mailread||!filled)
  831. X       rhead=1;     /* yup, we read in a new header as well as new mail */
  832. X    mailread=0;dfilled=thebody-themail;themail=readdyn(themail,&filled);
  833. X      }
  834. X     pastend=filled+(thebody=themail);
  835. X     while(thebody<pastend&&*thebody++=='\n');         /* skip leading garbage */
  836. X     realstart=thebody;
  837. X     if(rhead)                  /* did we read in a new header anyway? */
  838. X      { confield.filled=0;concnd='\n';
  839. X    while(thebody=
  840. X     egrepin("[^\n]\n[\n\t ]",thebody,(long)(pastend-thebody),1))
  841. X       if(thebody[-1]!='\n')          /* mark continuated fields */
  842. X          app_val(&confield,(off_t)(--thebody-1-themail));
  843. X       else
  844. X          goto eofheader;           /* empty line marks end of header */
  845. X    thebody=pastend;      /* provide a default, in case there is no body */
  846. Xeofheader:;
  847. X      }
  848. X     else                   /* no new header read, keep it simple */
  849. X    thebody=themail+dfilled; /* that means we know where the body starts */
  850. X   }
  851. X  ;{ int f1stchar;    /* to make sure that the first From_ line is uninjured */
  852. X     f1stchar= *realstart;*(chp=realstart)='\0';escFrom_.filled=0;
  853. X     while(chp=egrepin(FROM_EXPR,chp,(long)(pastend-chp),1))
  854. X      { while(*--chp!='\n');               /* where did this line start? */
  855. X    app_val(&escFrom_,(off_t)(++chp-themail));chp++;       /* bogus! */
  856. X      }
  857. X     *realstart=f1stchar;mailread=1;
  858. X   }
  859. X}
  860. END_OF_FILE
  861. if test 12269 -ne `wc -c <'procmail/src/mailfold.c'`; then
  862.     echo shar: \"'procmail/src/mailfold.c'\" unpacked with wrong size!
  863. fi
  864. # end of 'procmail/src/mailfold.c'
  865. fi
  866. if test -f 'procmail/src/regexp.c' -a "${1}" != "-c" ; then 
  867.   echo shar: Will not clobber existing file \"'procmail/src/regexp.c'\"
  868. else
  869. echo shar: Extracting \"'procmail/src/regexp.c'\" \(14162 characters\)
  870. sed "s/^X//" >'procmail/src/regexp.c' <<'END_OF_FILE'
  871. X/************************************************************************
  872. X *    Custom regular expression library, *fully* egrep compatible    *
  873. X *                                    *
  874. X *    Seems to be perfect.                        *
  875. X *                                    *
  876. X *    Copyright (c) 1991-1992, S.R. van den Berg, The Netherlands    *
  877. X *    #include "README"                        *
  878. X ************************************************************************/
  879. X#ifdef RCS
  880. Xstatic /*const*/char rcsid[]=
  881. X "$Id: regexp.c,v 1.27 1993/06/30 16:14:06 berg Exp $";
  882. X#endif
  883. X#include "procmail.h"
  884. X#include "robust.h"
  885. X#include "shell.h"
  886. X#include "misc.h"
  887. X#include "regexp.h"
  888. X
  889. X#define R_BEG_GROUP    '('
  890. X#define R_OR        '|'
  891. X#define R_END_GROUP    ')'
  892. X#define R_0_OR_MORE    '*'
  893. X#define R_0_OR_1    '?'
  894. X#define R_1_OR_MORE    '+'
  895. X#define R_DOT        '.'
  896. X#define R_SOL        '^'
  897. X#define R_EOL        '$'
  898. X#define R_BEG_CLASS    '['
  899. X#define R_NOT_CLASS    '^'
  900. X#define R_RANGE        '-'
  901. X#define R_END_CLASS    ']'
  902. X#define R_ESCAPE    '\\'
  903. X
  904. X#define BITS_P_CHAR        8
  905. X#define OPB            (1<<BITS_P_CHAR)
  906. X#define DONE_NODE        (OPB<<1)
  907. X#define DONE_MASK        (DONE_NODE-1)
  908. X#define LOOPL_NODE        (OPB<<2)
  909. X#define LOOPR_NODE        (OPB<<3)
  910. X#define LOOP_MASK        (LOOPL_NODE-1)
  911. X#define OPC_SEMPTY        OPB
  912. X#define OPC_TSWITCH        (OPB+1)
  913. X#define OPC_DOT            (OPB+2)
  914. X#define OPC_BOTEXT        (OPB+3)
  915. X#define OPC_EPS            (OPB+4)
  916. X#define OPC_JUMP        (OPB+5)
  917. X#define OPC_CLASS        (OPB+6)
  918. X#define OPC_FIN            (OPB+7)
  919. X#define OPC_FILL        (OPB+8)
  920. X          /* Don't change any opcode above without checking skplen[] */
  921. X#define bit_type        unsigned
  922. X#define bit_bits        (sizeof(bit_type)*8)
  923. X#define bit_index(which)    ((unsigned)(which)/bit_bits)
  924. X#define bit_mask(which)        ((unsigned)1<<(unsigned)(which)%bit_bits)
  925. X#define bit_toggle(name,which)    (name[bit_index(which)]^=bit_mask(which))
  926. X#define bit_test(name,which)    (!!(name[bit_index(which)]&bit_mask(which)))
  927. X#define bit_set(name,which,value)    \
  928. X (value?(name[bit_index(which)]|=bit_mask(which)):\
  929. X (name[bit_index(which)]&=~bit_mask(which)))
  930. X#define bit_field(name,size)    bit_type name[((size)+bit_bits-1)/bit_bits]
  931. X
  932. X#define SZ(x)        (sizeof(struct x))
  933. X#define Ceps        (struct eps*)
  934. X#define epso(to,add)    (Ceps((char*)(to)+(add)))
  935. X#define ii        (aleps.topc)
  936. X#define jj        (aleps.au.jju)
  937. X#define spawn        sp.awn
  938. X
  939. Xstatic struct eps*r;
  940. Xstatic struct{unsigned topc;union{struct eps*tnext;unsigned jju;}au;}aleps;
  941. Xstatic uchar*p,*cachea,*cachep;
  942. Xstatic size_t cacher;
  943. Xstatic case_ignore,errorno;
  944. X
  945. Xstruct jump {unsigned opcj_;struct eps*nextj;};
  946. Xstruct mchar {unsigned opcc_;struct eps*next1_,*p1_,*p2_;};
  947. Xstruct chclass {unsigned opc_;struct eps*next_,*pos1,*pos2;
  948. X bit_field(c,OPB);};
  949. X                      /* length array, used by skiplen() */
  950. Xstatic /*const*/char skplen[]=           /* it SHOULD have been const, but */
  951. X {SZ(eps),SZ(jump),SZ(chclass),0,0};   /* some !@#$%^&*() compilers disagree */
  952. X                               /* epsilon transition */
  953. Xstatic void puteps(spot,to)struct eps*const spot;const struct eps*const to;
  954. X{ spot->opc=OPC_EPS;spot->next=Ceps to;spot->spawn=0;
  955. X}
  956. X
  957. X#define Cc(p,memb)    (((struct chclass*)(p))->memb)
  958. X#define rAc        Cc(r,c)
  959. X
  960. Xstatic void bseti(i,j)unsigned i;const int j;
  961. X{ bit_set(rAc,i,j);               /* mark 'i' as being in the class */
  962. X  if(case_ignore)                  /* mark the other case too */
  963. X   { if(i-'A'<'Z'-'A')                        /* uppercase */
  964. X    i+='a'-'A';
  965. X     else if(i-'a'<'z'-'a')                    /* lowercase */
  966. X    i-='a'-'A';
  967. X     else return;                          /* no case */
  968. X     bit_set(rAc,i,j);
  969. X   }
  970. X}
  971. X                       /* general purpose length routine */
  972. Xstatic struct eps*skiplen(ep)const struct eps*const ep;
  973. X{ return epso(ep,(ep->opc&DONE_MASK)<OPC_EPS?
  974. X   SZ(mchar):skplen[(ep->opc&DONE_MASK)-OPC_EPS]);
  975. X}
  976. X
  977. Xstatic por P((const struct eps*const e));
  978. X
  979. Xstatic void psimp(e)const struct eps*const e;
  980. X{ switch(*p)
  981. X   { case R_BEG_GROUP:p++;              /* not so simple after all */
  982. X    if(por(e))
  983. X       errorno=1;
  984. X    return;
  985. X     case R_BEG_CLASS:                       /* a simple class */
  986. X      { unsigned i,j=R_NOT_CLASS==*++p;
  987. X    if(e)
  988. X     { r->opc=OPC_CLASS;r->next=Ceps e;Cc(r,pos1)=Cc(r,pos2)=0;
  989. X       i=maxindex(rAc);
  990. X       do rAc[i]=j?~0:0;                 /* preset the bit field */
  991. X       while(i--);
  992. X     }
  993. X    if(j)                      /* skip the 'not' modifier */
  994. X     { p++;
  995. X       if(e)
  996. X          bit_toggle(rAc,'\n');
  997. X     }
  998. X    if(*p==R_END_CLASS)      /* right at the start, cannot mean the end */
  999. X     { p++;
  1000. X       if(e)
  1001. X          i=R_END_CLASS,bit_toggle(rAc,R_END_CLASS);
  1002. X     }
  1003. X    else if(*p==R_RANGE)                /* take it literally */
  1004. X     { p++;
  1005. X       if(e)
  1006. X          i=R_RANGE,bit_toggle(rAc,R_RANGE);
  1007. X     }
  1008. X    for(;;p++)
  1009. X     { switch(*p)
  1010. X        { case R_END_CLASS:p++;
  1011. X          case '\0':r=epso(r,SZ(chclass));return;
  1012. X          case R_RANGE:
  1013. X         switch(*++p)
  1014. X          { default:
  1015. X               if(e)
  1016. X              while(++i<*p)            /* mark all in the range */
  1017. X                 bseti(i,!j);
  1018. X               break;
  1019. X            case '\0':case R_END_CLASS:p--;        /* literally */
  1020. X          }
  1021. X        }
  1022. X       if(e)
  1023. X          bseti(i= *p,!j);              /* a normal character, mark it */
  1024. X     }
  1025. X      }
  1026. X     case '\0':return;
  1027. X     case R_DOT:             /* matches everything but a newline */
  1028. X    if(e)
  1029. X     { r->opc=OPC_DOT;goto fine;
  1030. X     }
  1031. X    goto fine2;
  1032. X     case R_EOL:case R_SOL:              /* match a newline (in effect) */
  1033. X    if(p[1]==R_SOL)
  1034. X     { p++;
  1035. X       if(e)
  1036. X        {  r->opc=OPC_BOTEXT;goto fine;
  1037. X        }
  1038. X     }
  1039. X    else if(e)
  1040. X     { r->opc='\n';goto fine;
  1041. X     }
  1042. X    goto fine2;
  1043. X     case R_ESCAPE:                      /* quote something */
  1044. X    if(!*++p)                     /* nothing to quote */
  1045. X       p--;
  1046. X   }
  1047. X  if(e)                              /* a regular character */
  1048. X   { r->opc=case_ignore&&(unsigned)*p-'A'<'Z'-'A'?*p+'a'-'A':*p;
  1049. Xfine:
  1050. X     r->next=Ceps e;Cc(r,pos1)=Cc(r,pos2)=0;
  1051. X   }
  1052. Xfine2:
  1053. X  p++;r=epso(r,SZ(mchar));
  1054. X}
  1055. X
  1056. X#define EOS(x)    (jj?Ceps e:(x))
  1057. X
  1058. Xstatic void pnorm(e)const struct eps*const e;
  1059. X{ void*pold;struct eps*rold;
  1060. X  for(;;)
  1061. X   { pold=p;rold=r;psimp(Ceps 0);ii= *p;            /* skip it first */
  1062. X     jj=p[1]==R_OR||p[1]==R_END_GROUP||!p[1];
  1063. X     if(e)
  1064. X    p=pold,pold=r;
  1065. X     switch(ii)               /* check for any of the postfix operators */
  1066. X      { case R_0_OR_MORE:r++;
  1067. X       if(e)              /* first an epsilon, then the rest */
  1068. X          puteps(rold,EOS(r)),r=rold+1,psimp(rold);
  1069. X       goto incagoon;
  1070. X    case R_1_OR_MORE:                   /* first the rest */
  1071. X       if(e)                      /* and then an epsilon */
  1072. X        { puteps(r,rold);
  1073. X          if(jj)
  1074. X         (r+1)->opc=OPC_JUMP,(r+1)->next=Ceps e;
  1075. X          r=rold;psimp(Ceps pold);
  1076. X        }
  1077. X       r++;
  1078. X       if(p[1]==R_OR||p[1]==R_END_GROUP||!p[1])
  1079. X          r=epso(r,SZ(jump));
  1080. X       goto incagoon;
  1081. X    case R_0_OR_1:r++;
  1082. X       if(e)              /* first an epsilon, then the rest */
  1083. X          puteps(rold,r=EOS(r)),pold=r,r=rold+1,psimp(Ceps pold);
  1084. Xincagoon:  switch(*++p)            /* at the end of this group already? */
  1085. X        { case R_OR:case R_END_GROUP:case '\0':return;
  1086. X        }
  1087. X       continue;                 /* regular end of the group */
  1088. X    case R_OR:case R_END_GROUP:case '\0':
  1089. X       if(e)
  1090. X          r=rold,psimp(e);
  1091. X       return;
  1092. X      }
  1093. X     if(e)            /* no fancy postfix operators, plain vanilla */
  1094. X    r=rold,psimp(Ceps pold);
  1095. X   }
  1096. X}
  1097. X
  1098. Xstatic por(e)const struct eps*const e;
  1099. X{ uchar*pvold;struct eps*rvold;
  1100. X  if(!e)
  1101. X   { rvold=r;
  1102. X     if(cachea==(pvold=p))
  1103. X      { p=cachep;r=epso(rvold,cacher);goto ret0;
  1104. X      }
  1105. X   }
  1106. X  for(;;)
  1107. X   { uchar*pold;struct eps*rold;
  1108. X     for(pold=p,rold=r;;)
  1109. X      { switch(*p)
  1110. X     { default:pnorm(Ceps 0);r=rold;continue;     /* still in this group */
  1111. X       case '\0':case R_END_GROUP:           /* found the end of the group */
  1112. X          if(p==pold)                 /* empty 'or' group */
  1113. X           { if(e)
  1114. X            r->opc=OPC_JUMP,r->next=Ceps e;
  1115. X         r=epso(r,SZ(jump));
  1116. X           }
  1117. X          else
  1118. X         p=pold,pnorm(e);            /* normal last group */
  1119. X          if(!e)
  1120. X           { if(*p)
  1121. X            p++;
  1122. X         cachea=pvold;cachep=p;cacher=(char*)r-(char*)rvold;goto ret0;
  1123. X           }
  1124. X          if(*p)
  1125. X           { p++;
  1126. Xret0:         return 0;
  1127. X           }
  1128. X          return 1;
  1129. X       case R_OR:r++;
  1130. X          if(p==pold)                 /* empty 'or' group */
  1131. X           { if(e)
  1132. X            puteps(rold,e);              /* special epsilon */
  1133. X           }
  1134. X          else
  1135. X           { p=pold;pnorm(e);          /* normal 'or' group, first an */
  1136. X         if(e)                   /* epsilon, then the rest */
  1137. X            puteps(rold,r);
  1138. X           }
  1139. X          p++;
  1140. X     }
  1141. X    break;
  1142. X      }
  1143. X   }
  1144. X}
  1145. X          /* go down recursively, mark loopbacks on the way up again */
  1146. Xstatic struct eps*maxback(down)struct eps*down;
  1147. X{ ii=0;                   /* didn't find a loop at this level (yet) */
  1148. X  for(;;)
  1149. X   { switch(down->opc&LOOP_MASK)            /* chase JUMP chains */
  1150. X      { default:goto ret0;             /* oops, not an EPS, return */
  1151. X    case OPC_JUMP:down->opc=OPC_JUMP|DONE_NODE;    /* mark them as used */
  1152. X    case OPC_JUMP|DONE_NODE:down=down->next;continue;
  1153. X    case OPC_EPS|DONE_NODE:ii=1;   /* used EPS found, return loop number */
  1154. X       return down->spawn==Ceps&aleps?down:down->spawn;
  1155. X    case OPC_EPS:;            /* unused EPS found, the work starts */
  1156. X      }
  1157. X     break;
  1158. X   }
  1159. X  if(!down->spawn)     /* has it been visited (belongs to previous group?) */
  1160. X   { struct eps*left;                    /* no, so process it */
  1161. X     down->opc=OPC_EPS|DONE_NODE;down->spawn=Ceps&aleps;     /* mark as used */
  1162. X     left=maxback(down->next);           /* init loop no. and recurse left */
  1163. X     if(ii)                    /* loop found directly below us? */
  1164. X    down->opc|=LOOPL_NODE;                 /* mark a left-loop */
  1165. X     ;{ struct eps*right;         /* recurse right, take the smallest */
  1166. X    if((right=maxback(down+1))&&(char*)left>(char*)right)     /* loop no. */
  1167. X       left=right;
  1168. X      }
  1169. X     if(ii)                       /* loop found directly below? */
  1170. X      { down->opc|=LOOPR_NODE;                /* mark a right-loop */
  1171. X    if(!(down->opc&LOOPL_NODE))    /* if we didn't also had a left-loop */
  1172. X       ii=0;        /* we tell our predecessor we are not a loop */
  1173. X      }
  1174. X     if(!left)                        /* found no loop at all? */
  1175. X      { down->spawn=down;goto ret0;  /* then give ourselves our own loop no. */
  1176. X      }
  1177. X     if((down->spawn=left)!=down)     /* save the loop no., check if it's us */
  1178. X    return left;                   /* if not, pass the number up */
  1179. X   }                     /* otherwise we are the end of the loop */
  1180. Xret0:
  1181. X  return 0;                           /* no loop whatsoever */
  1182. X}
  1183. X
  1184. Xstruct eps*bregcomp(a,ign_case)const char*const a;
  1185. X{ struct eps*st;size_t i;      /* first a trial run, determine memory needed */
  1186. X  skplen[OPC_FILL-OPC_EPS]=SZ(eps)-ioffsetof(struct eps,sp);  /* a constant! */
  1187. X  errorno=0;p=(uchar*)a;case_ignore=ign_case;r=Ceps&aleps;cachea=0;por(Ceps 0);
  1188. X  st=r=
  1189. X   malloc((i=(char*)r-(char*)&aleps)+sizeof r->opc);
  1190. X  p=(uchar*)a;
  1191. X  if(!por(epso(st,i)))                       /* really compile */
  1192. X     errorno=1;
  1193. X  r->opc=OPC_FIN;
  1194. X  if(errorno)
  1195. X     nlog("Invalid regexp"),logqnl(a);
  1196. X  for(r=st;;st=skiplen(st))         /* simplify the compiled code (i.e. */
  1197. X     switch(st->opc)              /* take out cyclic epsilon references) */
  1198. X      { case OPC_FIN:return r;                     /* finished */
  1199. X    case OPC_EPS:             /* check for any closed epsilon circles */
  1200. X       if(!st->spawn)               /* they can't be executed */
  1201. X        { maxback(st);     /* if not visited yet, recurse and mark loops */
  1202. X          ;{ register struct eps*i;
  1203. X         for(i=r;;i=skiplen(i))         /* search the whole program */
  1204. X          { switch(i->opc&LOOP_MASK)
  1205. X             { default:                /* renumber regulars */
  1206. X            { register struct eps*f;        /* if needed */
  1207. X              if(((f=i->next)->opc&DONE_MASK)==OPC_EPS&&f->spawn)
  1208. X               { for(;f->spawn!=f;f=f->spawn);   /* search start */
  1209. X                 i->next=f;                  /* of loop */
  1210. X               }
  1211. X            }           /* spare the used nodes in this group */
  1212. X               case OPC_EPS|DONE_NODE:case OPC_JUMP|DONE_NODE:
  1213. X               case OPC_FILL:continue;
  1214. X               case OPC_FIN:;
  1215. X             }
  1216. X            break;
  1217. X          }
  1218. X           }
  1219. X          ;{ register struct eps*i;
  1220. X         for(i=r;;i=skiplen(i))         /* search the whole program */
  1221. X          { switch(i->opc)      /* unmark/transform the used nodes */
  1222. X             { case OPC_EPS|DONE_NODE|LOOPL_NODE:i->next=i+1;
  1223. X               case OPC_EPS|DONE_NODE|LOOPR_NODE:i->sp.sopc=OPC_FILL;
  1224. X               case OPC_JUMP|DONE_NODE:i->opc=OPC_JUMP;continue;
  1225. X               case OPC_EPS|DONE_NODE|LOOPL_NODE|LOOPR_NODE:
  1226. X               case OPC_EPS|DONE_NODE:i->opc=OPC_EPS;
  1227. X               default:continue;
  1228. X               case OPC_FIN:;
  1229. X             }
  1230. X            break;
  1231. X          }
  1232. X           }
  1233. X        }
  1234. X      }
  1235. X}
  1236. X
  1237. X#define XOR1        \
  1238. X (ioffsetof(struct chclass,pos1)^ioffsetof(struct chclass,pos2))
  1239. X#define PC(this,t)    (*(struct eps**)((char*)(this)+(t)))
  1240. X
  1241. Xchar*bregexec(code,text,len,ign_case)struct eps*code;const uchar*const text;
  1242. X size_t len;
  1243. X{ register struct eps*reg,*stack,*other,*thiss;unsigned i,th1,ot1;
  1244. X  const uchar*str;struct eps*initstack,*initcode;
  1245. X  static struct mchar tswitch={OPC_TSWITCH,Ceps&tswitch};
  1246. X  static struct eps sempty={OPC_SEMPTY,&sempty};
  1247. X  sempty.spawn=initstack= &sempty;
  1248. X  if((initcode=code)->opc==OPC_EPS)
  1249. X     initcode=(initstack=code)->next,code->spawn= &sempty;
  1250. X  thiss=Ceps&tswitch;th1=ioffsetof(struct chclass,pos1);
  1251. X  ot1=ioffsetof(struct chclass,pos2);str=text-1;len++;i='\n';goto setups;
  1252. X  do                  /* make sure any beginning-of-line-hooks catch */
  1253. X   { i= *++str;                 /* get the next real-text character */
  1254. X     if(ign_case&&i-'A'<'Z'-'A')
  1255. X    i+='a'-'A';                 /* transmogrify it to lowercase */
  1256. Xlastrun:                     /* switch this & other pc-stack */
  1257. X     th1^=XOR1;ot1^=XOR1;thiss=other;
  1258. Xsetups:
  1259. X     other=Ceps&tswitch;stack=initstack;reg=initcode;goto nostack;
  1260. X     for(;;)                 /* pop next entry off this pc-stack */
  1261. X      { thiss=PC(reg=thiss,th1);PC(reg,th1)=0;reg=reg->next;goto nostack;
  1262. X    for(;;)                /* pop next entry off the work-stack */
  1263. X       for(reg=stack->next,stack=stack->spawn;;)
  1264. Xnostack:    { switch(reg->opc-OPB)
  1265. X           { default:
  1266. X            if(i==reg->opc)          /* regular character match */
  1267. X               goto yep;
  1268. X            break;        /* push spawned branch on the work-stack */
  1269. X         case OPC_EPS-OPB:reg->spawn=stack;reg=(stack=reg)+1;continue;
  1270. X         case OPC_JUMP-OPB:reg=reg->next;continue;
  1271. X         case OPC_FIN-OPB:return(char*)str;    /* one past the match */
  1272. X         case OPC_SEMPTY-OPB:goto empty_stack;
  1273. X         case OPC_TSWITCH-OPB:goto pcstack_switch;
  1274. X         case OPC_BOTEXT-OPB:
  1275. X            if(str<text)           /* only at the very beginning */
  1276. X               goto yep;
  1277. X            break;
  1278. X         case OPC_CLASS-OPB:
  1279. X            if(bit_test(((struct chclass*)reg)->c,i))
  1280. X               goto yep;               /* character in class */
  1281. X            break;
  1282. X         case OPC_DOT-OPB:                 /* dot-wildcard */
  1283. X            if(i!='\n')
  1284. Xyep:               if(!PC(reg,ot1))             /* state not yet pushed */
  1285. X              PC(reg,ot1)=other,other=reg; /* push location onto */
  1286. X           }                       /* other pc-stack */
  1287. X          break;
  1288. X        }
  1289. Xempty_stack:;                      /* the work-stack is empty */
  1290. X      }
  1291. Xpcstack_switch:;                   /* this pc-stack is empty */
  1292. X   }
  1293. X  while(--len);                         /* still text to search */
  1294. X  if(ign_case!=2)                          /* out of text */
  1295. X   { ign_case=2;len=1;str++;goto lastrun;     /* check if we just matched */
  1296. X   }
  1297. X  return 0;                             /* no match */
  1298. X}
  1299. END_OF_FILE
  1300. if test 14162 -ne `wc -c <'procmail/src/regexp.c'`; then
  1301.     echo shar: \"'procmail/src/regexp.c'\" unpacked with wrong size!
  1302. fi
  1303. # end of 'procmail/src/regexp.c'
  1304. fi
  1305. echo shar: End of archive 8 \(of 11\).
  1306. cp /dev/null ark8isdone
  1307. MISSING=""
  1308. for I in 1 2 3 4 5 6 7 8 9 10 11 ; do
  1309.     if test ! -f ark${I}isdone ; then
  1310.     MISSING="${MISSING} ${I}"
  1311.     fi
  1312. done
  1313. if test "${MISSING}" = "" ; then
  1314.     echo You have unpacked all 11 archives.
  1315.     rm -f ark[1-9]isdone ark[1-9][0-9]isdone
  1316. else
  1317.     echo You still need to unpack the following archives:
  1318.     echo "        " ${MISSING}
  1319. fi
  1320. ##  End of shell archive.
  1321. exit 0
  1322. -- 
  1323. Sincerely,                                  berg@pool.informatik.rwth-aachen.de
  1324.            Stephen R. van den Berg (AKA BuGless).    berg@physik.tu-muenchen.de
  1325.  
  1326. "Always look on the bright side of life!"
  1327.  
  1328. exit 0 # Just in case...
  1329.