home *** CD-ROM | disk | FTP | other *** search
/ OS/2 Shareware BBS: 35 Internet / 35-Internet.zip / mail19.zip / mail.cmd < prev    next >
OS/2 REXX Batch file  |  1993-05-26  |  33KB  |  975 lines

  1. /* Tiny Mail..  by turgut@ege.edu.tr (or turgut@frmop11.cnusc.fr)
  2.  
  3.    NOTE: Please read MAIL.CFG file, and configure MAIL.CMD before
  4.   using it.
  5.  
  6. Version: 1.8  -- Adds more RexxUtil functions for speed.
  7.  
  8. Version: 1.7a -- added support for signature file as g.signature
  9.                - fixed @erase of out.fil to g.outfile
  10.                - fixed variable g.AllNotebookall to g.AllNotebook
  11.                - acquired tcpip\etc from environment
  12.                - update bindir with info from etcdir
  13.                - fixed bug in location of all.notebook which
  14.                  could appear in working directory
  15.                - if g.AllNotebook is null then don't log
  16.                - 1.7a updated made by Lionel Dyck
  17.                  ldyck@osreq48.rockwell.com
  18.  
  19. This little program allows you to mail using TCP/IP's
  20. SENDMAIL command. To receive mail, you need to have SENDMAIL
  21. alive, but LAMAIL is not required to be active.
  22.  
  23. If you just type MAIL, it will display you the current mail
  24. items on your \tcpip\etc\mail box. It assumes the default
  25. drive.
  26.  
  27. You can also type MAIL userid@address    to send mail messages.
  28.  
  29. Comments? Suggestions? Please let me know. Let's improve this
  30. little program!
  31. **/
  32.  
  33. Parse arg destination '(' options
  34.  
  35. Call RxFuncAdd 'SysLoadFuncs','RexxUtil','SysLoadFuncs'
  36. Call SysLoadFuncs
  37.  
  38. /* Locate TCP directory, so that 'tcp' contains something like
  39.    'C:\TCPIP'
  40. */
  41. Parse value value("ETC",,"OS2ENVIRONMENT") with tcp'\ETC'
  42.  
  43. /* DO NOT modify them here. Modify MAIL.CFG instead! */
  44. g. = ''
  45. g.myself = 'turgut@earn-ps.circe.fr'
  46. g.signature = tcp'\signatur.txt'
  47. g.screensize = word(SysTextScreenSize(),1)-4 /* lines */
  48. g.UseCLS = 1
  49. g.namefile = tcp'\turgut.nam'
  50. g.defaultdomain = '.BITNET'
  51. g.etcdir = tcp'\ETC'
  52. g.bindir = tcp'\BIN'
  53. g.editor = ''
  54. g.detachSendmail = 0
  55. g.displayAgent = ''
  56. g.AllNotebook = 'All.Notebook'
  57. g.OutFile = 'C:\Mailout.fil'
  58.  
  59.  
  60. z = 'ETC DPATH PATH'
  61. do i=1 to words(z)
  62.    conf = SysSearchPath(subword(z,i,1),'MAIL.CFG')
  63.    if conf¬='' Then Leave
  64. End
  65. If conf = '' Then Say 'Warning: MAIL.CFG is not found in PATH!'
  66.  
  67. /* Load lines with equal signs.. */
  68. Parse value SysCurPos() with row .
  69. Say
  70. If row>4 Then row=row-1
  71. Call SysCurPos row,0
  72. Say 'Reading' conf
  73. Call SysCurPos row,0
  74. Call SysFileSearch "=",conf,'conf.'
  75. If conf.0 < 1 Then
  76.    Say 'Warning: NO lines were read from' conf
  77. Say 'Interpreting' conf'      '
  78. Call SysCurPos row,0
  79. Do i=1 to conf.0
  80.    Interpret conf.i
  81. End
  82. drop conf.
  83. If ¬exist(g.bindir'\SENDMAIL.EXE') Then Do
  84.    Say 'The program could not find' g.bindir'\SENDMAIL.EXE.'
  85.    Say 'MAIL.CMD requires IBM TCP/IP 1.2.1 or above.'
  86.    exit 1
  87. end
  88.  
  89. Do while options ¬= ''
  90.    parse upper var options option optionss
  91.    If left(option,4) = 'FILE' Then parse var options inputfile options
  92. End
  93.  
  94. /* did they use MAIL user@node syntax? */
  95. If destination ¬= '' Then do
  96.    subject = ''
  97.    Call MailSend
  98.    exit
  99. end
  100.  
  101. Say 'Scanning for mail..                         '
  102. Call SysCurPos row,0
  103.  
  104. Call Load_Mail
  105. i=1
  106. do nextmail=1 while mails¬=''
  107.    fn = subword(mails,i,1)
  108.    if fn = '' Then Exit
  109.    Do show=1 to 999
  110.      call display fn
  111.      Say '<S>endNewMail  <#>Skip  <R>eply  <D>elete  <K>eep  <F>orward',
  112.       ' <HX>Exit'
  113.      pull option
  114.      Select
  115.        when option = '' Then Leave /* blank return */
  116.        When option = '#' Then Do
  117.            Say 'The # is not a real option - it simply means that you can'
  118.            Say 'enter a number to skip to the message with that number.'
  119.            call pressany
  120.        End
  121.        When option = 'R' Then Call Reply
  122.        When option = 'D' Then Do
  123.           Call Delete
  124.           Leave
  125.        End
  126.        When option = 'K' Then Leave
  127.        When option = 'F' Then Call Forward
  128.        When option = 'S' Then do 1
  129.            Say 'Enter destination address for new message:'
  130.            parse pull destination
  131.            if destination = '' Then Leave
  132.            Call MailSend
  133.        End
  134.        When option = 'HX' Then exit
  135.        When datatype(option,'N') Then Do
  136.           i = option
  137.           Leave
  138.        End
  139.        Otherwise nop
  140.      End
  141.    End /* keep showing */
  142.    i = i + 1
  143. End
  144. exit 0
  145.  
  146. Reply:
  147.    /* first lines are iffy */
  148.    do i=1 to 4
  149.      line = LINEIN(fn)
  150.    end
  151.    Do while lines(fn)>0
  152.        line = LINEIN(fn)
  153.        if line = '' then leave
  154.        Queue line
  155.    End
  156.    rc = LINEOUT(fn,,) /* FINIS */
  157.    Parse value '' with date subject origin replyto cc from
  158.    Call LSV822IN queued(),'FROM DATE SUBJECT REPLYTO ORIGIN RCPT SENDER'
  159.    Parse var result retcode . '15'x data
  160.    said = 0
  161.    Do while data¬=''
  162.       Parse var data kwd value'15'x data
  163.       Select
  164.         When kwd = 'DATE'    Then date = value
  165.         When kwd = 'SUBJECT' Then subject = value
  166.         When kwd = 'REPLYTO' Then replyto = replyto,
  167.            word(value,1)'@'word(value,2)
  168.         When kwd = 'ORIGIN'  Then origin  = origin,
  169.            word(value,1)'@'word(value,2)
  170.         When kwd = 'CC'      Then cc = cc word(value,1)'@'word(value,2)
  171.         When kwd = 'FROM'    Then from = from word(value,1)'@'word(value,2)
  172.         When kwd = 'TO' Then Nop
  173.         When kwd = 'TAG' Then Nop
  174.         Otherwise Do
  175.            Say kwd value
  176.            said = 1
  177.         End
  178.       End /* select */
  179.    End
  180.    If said then Call pressany
  181.  
  182.    destination = from
  183.    If replyto ¬= '' Then destination = replyto
  184.    If cc ¬= '' Then destination = destination cc
  185.    If strip(destination) = '' Then Do
  186.       Say 'Cannot determine mail origin.'
  187.       Return
  188.    End
  189.    If translate(left(subject,3))='RE:' Then subject=substr(subject,4)
  190.      Else subject = 'Re:'subject
  191.    Call MailSend subject
  192.    Return
  193.  
  194. Delete:
  195.    '@ERASE 'fn
  196.    /* fix INBOX now.. */
  197.    inbox  = g.etcdir'\MAIL\INBOX.NDX'
  198.    outbox = g.etcdir'\MAIL\INBOX.TMP'
  199.    If ¬exist(inbox) Then Return /* all done! */
  200.    If exist(outbox) Then '@ERASE' outbox
  201.    do while lines(inbox)
  202.       line = LINEIN(inbox)
  203.       Parse var line 28 dfn dft .
  204.       if g.etcdir'\MAIL\'dfn'.'dft = fn Then Iterate
  205.       rc = LINEOUT(outbox,line)
  206.       if rc¬=0 Then Call Fatal 'Error writing' outbox 'rc='rc
  207.    end
  208.    rc = LINEOUT(inbox,,)
  209.    rc = LINEOUT(outbox,,)
  210.    '@ERASE' inbox
  211.    '@REN' outbox 'INBOX.NDX'
  212.    Return
  213.  
  214.  
  215. Forward:
  216.    Say 'Enter complete destination address or nickname:'
  217.    parse pull un
  218.    if un = '' Then Return
  219.    if pos('@',un) = 0 Then
  220.        Parse value SearchNickName(un) with un .
  221.    if pos('.',un)=0  then un=un||g.defaultdomain
  222.    If exist('MAIL.TMP') Then '@ERASE MAIL.TMP'
  223.    line = LINEIN(fn)
  224.    do while (lines(fn)>0)
  225.        line = LINEIN(fn)
  226.        rc = LINEOUT("MAIL.TMP",line)
  227.        if rc¬=0 Then Do
  228.           Say 'Error' rc 'writing MAIL.TMP'
  229.           Exit rc
  230.        End
  231.    end
  232.    rc = LINEOUT(fn,,)
  233.    rc = LINEOUT('MAIL.TMP',,)
  234.  
  235.    If g.DetachSendmail Then
  236.       '@DETACH 'g.bindir'\SENDMAIL -af MAIL.TMP -f' g.myself un
  237.    Else
  238.       g.bindir'\SENDMAIL -af MAIL.TMP -f' g.myself un
  239.    if rc¬=0 then
  240.       Say 'Warning:' g.bindir'\SENDMAIL failed.'
  241.    else say 'Successful delivery.'
  242.    '@ERASE MAIL.TMP'
  243.    Return
  244.  
  245. Load_Mail:
  246.   mails = '' /* keeps filenames.. */
  247.   rc = SysFileTree(g.etcdir"\MAIL\*.*",s,'B')
  248.   If rc¬=0 Then Call Fatal("Cannot load mailbox")
  249.   If s.0 = 0 Then Do
  250.      Say 'No mail in your mailbox.'
  251.      Return
  252.   End
  253.   Do i=1 to s.0
  254.       fn = word(s.i,5)
  255.       x = lastpos('\',fn)
  256.       rest = substr(fn,x + 1)
  257.       if ¬datatype(rest,'N') then iterate
  258.       mails = mails fn
  259.   End
  260.   z = words(mails)
  261.   s=''
  262.   if z>1 then s = 's'
  263.   if z = 0 then z = 'No'
  264.   Say z 'new mail message's'.                '
  265.   Return
  266.  
  267. Display:
  268. procedure expose g.
  269. arg fn
  270.   lines = 0
  271.   if g.useCLS Then Call SysCLS
  272.   If g.DisplayAgent ¬='' Then Do
  273.      '@'g.DisplayAgent fn
  274.      Return
  275.   End
  276.   Say 'File:'fn
  277.   do while lines(fn)>0
  278.     line = LINEIN(fn)
  279.     Say line
  280.     count = trunc(length(line) / 80)
  281.     if count < 1 then count = 1
  282.     lines = lines + count
  283.     if lines >= g.screensize then do
  284.        say 'More? (Y/n/hx)'
  285.        pull a
  286.        if a = 'N' | a = 'NO' then leave
  287.        If a = 'HX' Then do
  288.          dummy = lineout(fn,,)  /* close file */
  289.          Exit
  290.        end
  291.        lines = 0
  292.        if g.useCLS then Call SysCLS
  293.     end
  294.   end
  295.   dummy = lineout(fn,,)  /* close file */
  296.    return
  297.  
  298. Exist:
  299. procedure
  300. arg fn
  301.    rc  = SysFileTree(fn,s,'B')
  302.    return s.0 > 0
  303.  
  304. /* Immediate commands are handled here */
  305. Immediate_Command:
  306. procedure expose g. fn SMdest destination typedany subject
  307. Arg cmd options .
  308.   Select
  309.      when cmd = '/HELP' | cmd = '/?' then Do
  310.         Say 'Available immediate commands are:'
  311.         Say '       /MERGE fn.ft  - to append a file'
  312.         Say '       /REDISP       - to redisplay mail'
  313.         Say '       /ADD u@n      - add/display a recipient'
  314.         Say '       /REMOVE u@n   - remove/display a recipient'
  315.         Say '       /EXIT         - send message'
  316.         Say '       /QUIT         - abort message'
  317.         Say 'Any other line starting with a slash is left as-is.'
  318.      End
  319.      When cmd = '/MERGE' Then Do
  320.         lines=0
  321.         do while lines(options) > 0
  322.           line = LINEIN(options)
  323.           rc = LINEOUT(fn,line)
  324.           if rc¬=0 then call fatal 'Error writing line to' fn
  325.           lines=lines+1
  326.         end
  327.         rc = LINEOUT(options,,)
  328.         Say 'Merge completed.' lines 'appended.'
  329.         if typedany = 0 & lines>0 then typedany = 1
  330.      End
  331.      When cmd = '/REDISP' Then Do
  332.         if g.useCLS Then Call SysCLS
  333.         Say 'To:' destination
  334.         If subject ¬= '' Then Say 'Subject:' subject
  335.         Say 'Date:  'date() time()
  336.         Say
  337.         If exist(fn) Then '@TYPE' fn
  338.      End
  339.      When cmd = '/ADD' Then Do
  340.        un = options; name = ''
  341.        If un ¬= '' Then Do
  342.          if pos('@',un)=0 Then
  343.             parse value SearchNickName(options) with un name
  344.          SMdest = SMdest','un
  345.          if name¬='' then un= '"'name'" <'un'>'
  346.          destination = destination','un
  347.        End
  348.        If left(destination,1)=',' Then destination = substr(destination,2)
  349.        Call Immediate_Command '/REDISP'
  350.      End
  351.      When cmd = '/REMOVE' Then Do 1
  352.        un = options; name = ''
  353.        If un = '' Then Leave
  354.        If Find(translate(SMdest),un)>0 Then
  355.           SMdest = delword(SMdest,find(translate(SMdest),un),1)
  356.        If Find(translate(destination),un)>0 Then
  357.           destination = delword(destination,Find(translate(destination),un),1)
  358.        Call Immediate_Command '/REDISP'
  359.      End
  360.      When cmd = '/QUIT' Then Return -1
  361.      When cmd = '/EXIT' Then Return 2
  362.      Otherwise Return 1 /* unknown cmd */
  363.    End /* select */
  364.    Return 0 /* command processed. */
  365.  
  366. /***********/
  367. MailSend:
  368. Procedure expose destination g.
  369. Parse Arg subject
  370.  
  371. if g.useCLS then Call SysCLS
  372.  
  373. /* remove commas */
  374. destination = translate(destination,' ',',')
  375. newdest = ''
  376. SMdest = '' /* sendmail doesnt accept full names ".." stuff */
  377. do words(destination)
  378.    parse var destination un destination
  379.    if pos('@',un) = 0 Then
  380.        Parse value SearchNickName(un) with un name
  381.    else name = ''
  382.    If pos('.',un) = 0 then un = un||g.defaultdomain
  383.    SMdest = smdest','un
  384.    If name¬='' Then un = '"'name'" <'un'>'
  385.    newdest = newdest','un
  386. end
  387. destination = substr(newdest,2)
  388. SMdest = substr(SMdest,2)
  389.  
  390. Say
  391. Say 'To:' destination
  392. If subject = '' Then Do
  393.    Say 'Subject? (optional)'
  394.    parse pull subject
  395. End
  396. Else
  397.    Say 'Subject:' subject
  398. Say 'Date:  'date() time()
  399.  
  400. fn = '\mailfile.tmp'
  401. if Exist(fn) then '@ERASE' fn
  402. fn2 = '\mailfile.tm1'
  403. if Exist(fn2) then '@ERASE' fn2
  404.  
  405.  
  406. If g.editor ¬='' Then Do /* external editor specified? */
  407.    '@'g.editor fn
  408.    If ¬exist(fn) Then Return
  409.    Say 'Send message?'
  410.    Pull yn
  411.    If left(yn,1) ¬= 'Y' Then Return
  412.    Signal DoneEdit
  413. End
  414.  
  415. Say
  416. Say 'Compose your mail, hit CTRL-K - ENTER when done. Use /? for help.'
  417. Say
  418. typedany = 0
  419. Do forever
  420.    parse pull blurb
  421.    if left(blurb,1) = '/' Then Do
  422.       rc = immediate_command(blurb)
  423.       if rc = -1 Then do /* quit */
  424.          typedany = 0
  425.          leave
  426.       End
  427.       if rc = 0 Then Iterate /* command done */
  428.       if rc = 2 Then Leave /* /exit */
  429.    End
  430.    t = c2d(left(blurb,1))
  431.    if t<28 then leave /* control char?*/
  432.       else typedany = 1
  433.    rc = LINEOUT(fn,blurb,)
  434.    if rc¬=0 then call fatal 'Error writing line to' fn
  435. End
  436.  
  437. If ¬typedany Then Do
  438.    Say 'Empty mailfile.. Not sent.'
  439.    Return
  440. End
  441. rc = LINEOUT(fn,,)
  442.  
  443. DoneEdit:
  444.  
  445. /* Append header info */
  446. rc = LINEOUT(fn2,'To:    'destination)
  447. If subject¬='' then rc = LINEOUT(fn2,'Subject:' subject,)
  448. rc = LINEOUT(fn2,'Date:  'date() time())
  449. Do i=1 while g.Header.i¬=''
  450.    rc = LINEOUT(fn2,g.Header.i)
  451. End
  452. rc = LINEOUT(fn2,'  ')
  453. rc = LINEOUT(fn2,,)
  454. '@COPY/B' fn2'+'fn g.outfile '> NUL'
  455. '@ERASE' fn
  456. '@ERASE' fn2
  457.  
  458. if exist(g.signature) = 1 then do
  459.    '@COPY/B' g.outfile'+'g.signature fn2 '> NUL:'
  460.    '@COPY/B' fn2 g.outfile '> NUL:'
  461.    '@Erase' fn2
  462.    end
  463.  
  464. blurb = 'Mail send to' SMdest
  465. /* log note if g.allnotebook is not null */
  466. if g.AllNotebook <> "" then do
  467.    blurb = blurb',saved in' g.etcdir'\mail\'g.AllNotebook
  468.    If exist(g.etcdir'\mail\'g.AllNotebook) then do
  469.       fn2 = g.etcdir'\mail'fn2
  470.       '@COPY/B' g.etcdir'\MAIL\'g.ALLNOTEBOOK'+'g.outfile fn2 '> NUL:'
  471.       '@Del' g.etcdir'\mail\'g.allnotebook
  472.       '@Rename' fn2 g.allnotebook
  473.       end
  474.    else
  475.       '@COPY/B' g.outfile g.etcdir'\MAIL\'g.ALLNOTEBOOK '> NUL:'
  476. end
  477. Say blurb
  478.  
  479. if g.detachSendmail Then
  480.   '@DETACH SENDMAIL -af' g.outfile '-f' g.myself SMdest
  481. Else do
  482.   'SENDMAIL -af' g.outfile '-f' g.myself SMdest
  483.    If rc = 0 Then '@ERASE' g.outfile
  484. End
  485. Return
  486.  
  487. SearchNickName:
  488. Procedure expose g.
  489. arg nick .
  490.  
  491. load = 0
  492. parse value '' with user node name
  493. do both=1 while lines(g.namefile)>0
  494.    line = strip(LINEIN(g.namefile))
  495.    do while length(line)>1
  496.       Parse var line ':'tag'.'value':'line
  497.       tag = translate(tag)
  498.       if tag = 'NICK' Then Do
  499.          If load then Leave Both
  500.          If translate(value) = nick Then load = 1 /* start loading */
  501.          line=':'line
  502.          Iterate
  503.       End
  504.       If ¬load then iterate
  505.       Select
  506.         When tag = 'USERID' Then user = value
  507.         When tag = 'NODE'   Then node = value
  508.         When tag = 'NAME'   Then name = value
  509.         Otherwise Nop
  510.       End
  511.       line = ':'line
  512.    End
  513. End
  514. rc = LINEOUT(g.namefile,,)
  515. if words(user node)<2 then return nick
  516. Return strip(user)'@'strip(node) name
  517.  
  518. Fatal:
  519. parse arg blurb
  520.    say blurb
  521.    exit
  522.  
  523. isdelimiter:
  524. parse arg argh
  525. Return (pos(argh,'."%@!')>0)
  526.  
  527. Find:
  528. Parse arg one,another
  529.   Return pos(another,one)
  530.  
  531. pressany:
  532.   say 'Press ENTER..'
  533.   parse pull
  534.   return
  535.  
  536.  
  537. /**********************************************************************
  538. *                                                                     *
  539. * LSV822IN -- LISTEARN system, RFC822 input header parsing            *
  540. *                                                                     *
  541. *              LISTEARN List Processor, Release 1                     *
  542. *           ----------------------------------------                  *
  543. *        LISTEARN 1.0  (c) EARN Association 1989 is derived from:     *
  544. *        LISTSERV 1.5o (c) Eric Thomas 1986,1987,1988,1989            *
  545. *                                                                     *
  546. *                                                                     *
  547. * This program is public domain. It can be used in any academic, non- *
  548. * commercial program without charge provided that the author is noti- *
  549. * fied of the use (so that he can send fixes if need arise).          *
  550. *                                                                     *
  551. *                                                                     *
  552. * Syntax: Call LSV822IN numlines<,options>                            *
  553. *                                                                     *
  554. * 'numlines' is  the number  of lines  that have  been placed  in the *
  555. * program stack and constitute the input to LSV822IN. The recommended *
  556. * approach is to place the  complete mailfile contents in the program *
  557. * stack and make this number of  lines available to LSV822IN. It will *
  558. * automatically stop  when the end of  the mail header (ie  the first *
  559. * blank line)  is encountered,  and will report  how many  lines were *
  560. * extracted from the program stack (see below).                       *
  561. *                                                                     *
  562. * 'options' is a string of  options controlling the amount and nature *
  563. * of the  output generated  by LSV822IN. The  default value  is empty *
  564. * string.                                                             *
  565. *                                                                     *
  566. *                                                                     *
  567. * The result is of the following form:                                *
  568. *                                                                     *
  569. *     rc numread reserved '15'x field1 <'15'x field2 <'15'x...>>      *
  570. *                                                                     *
  571. * 'rc' is a return code.  0 indicates successful completion, 4 stands *
  572. * for "warning  messages have  been issued but  the input  mail might *
  573. * still be processable", and 8  indicates an error which should cause *
  574. * rejection of the input file.                                        *
  575. *                                                                     *
  576. * 'numread' is the  number of lines that have been  obtained from the *
  577. * program stack in the process of extracting the mail header from it. *
  578. *                                                                     *
  579. * 'reserved' is  one or  more word positions  which are  reserved for *
  580. * future  use  and  should  be  discarded  by  the  caller  to  avoid *
  581. * compatibility problems with future versions of the program.         *
  582. *                                                                     *
  583. *                                                                     *
  584. * Each  "field" contains  some  form of  information  about the  mail *
  585. * header or an error message. There  can be any number of fields, and *
  586. * the caller  should not assume anything  on the order in  which they *
  587. * appear. The format of a 'field' is the following:                   *
  588. *                                                                     *
  589. *           fieldname field-data                                      *
  590. *                                                                     *
  591. * Example:  W Duplicate 'To:' field encountered.                      *
  592. *                                                                     *
  593. * 'fieldname' is  an uppercase "name"  associated with the  field and *
  594. * describing its contents.                                            *
  595. *                                                                     *
  596. * 'field-data' is  a mixed case  string which represent the  value of *
  597. * the field.                                                          *
  598. *                                                                     *
  599. *                                                                     *
  600. * The following fields are presently generated:                       *
  601. *                                                                     *
  602. * - I: informational message. These  are non-severe messages which do *
  603. *   not cause the return code to be changed. The recommended disposal *
  604. *   of these  messages is to  echo them on  the console log  file and *
  605. *   discard them. They should not be sent back to the mail originator *
  606. *   (but  it would  be acceptable  to  do so  if desired).  It is  an *
  607. *   acceptable implementation  to discard all  informational messages *
  608. *   without any further processing; however,  it is NOT an acceptable *
  609. *   implementation  to reject  a  mailfile  because an  informational *
  610. *   message has been issued.                                          *
  611. *                                                                     *
  612. * - W: warning message. These messages are issued whenever a possible *
  613. *   error has  been detected in the  input data stream. It  should be *
  614. *   echoed to the  console log file and it is  recommended to echo it *
  615. *   to the mail originator as  well. The implementation can choose to *
  616. *   reject  or process  the  mailfile  as desired,  but  there is  no *
  617. *   warranty  that the  mail  header information  integrity has  been *
  618. *   preserved. For example, a gateway  might have moved one line from *
  619. *   the mail body  to the header, possibly causing  a warning message *
  620. *   to be  issued by LSV822IN. The  mailfile might, or might  as well *
  621. *   not, be meaningful to the calling program.                        *
  622. *                                                                     *
  623. * - E: error message. This is a severe error in the mail header which *
  624. *   should cause the mailfile to be rejected. The message ought to be *
  625. *   displayed on  the console log  file and  echoed back to  the mail *
  626. *   originator if at all possible (a  good example of an E message is *
  627. *   precisely "E Missing sender field ('From:'/'Sender:')" -- in that *
  628. *   case the message ought to be echoed to a human operator instead). *
  629. *                                                                     *
  630. * - DATE: this is  the 'Resent-Date:' or 'Date:' field  from the mail *
  631. *   message. It is automatically generated  if missing. Its format is *
  632. *   exactly  what the  mailing system  had put  in the  corresponding *
  633. *   field. It is supplied only if the 'DATE' option was specified.    *
  634. *                                                                     *
  635. * - FROM:  this is  the  'Resent-From:'/'From:'  field, in  "address" *
  636. *   format (see below). It is provided  only if the 'FROM' option was *
  637. *   specified. Note that there may  be several 'FROM' fields if there *
  638. *   is a 'Resent-Sender:'/'Sender:' specification.                    *
  639. *                                                                     *
  640. * - SENDER:   this  is   the  'Resent-Sender:'/'Sender:'   field,  in *
  641. *   "address" format (see below). It is provided only if the 'SENDER' *
  642. *   option was specified.                                             *
  643. *                                                                     *
  644. * - ORIGIN:  this  is the  'Resent-Sender:'/'Resent-From:'/'Sender:'/ *
  645. *   'From:'  field,  in  "address"   format.  This  field  is  unique *
  646. *   and is always provided.                                           *
  647. *                                                                     *
  648. * - TO,  CC  and BCC:  this  is  one  recipient  out of  the  various *
  649. *   'Resent-To:'/'To:',  'Resent-cc:'/'cc:' and  'Resent-bcc:'/'bcc:' *
  650. *   fields, in  "address" format.  There may be  any number  of those *
  651. *   fields.  They  are  only  provided  when  the  'RCPT'  option  is *
  652. *   specified.                                                        *
  653. *                                                                     *
  654. * - SUBJECT: this is  the 'Subject:' field as it appears  in the mail *
  655. *   header. It corresponds to the 'SUBJECT' option.                   *
  656. *                                                                     *
  657. * - REPLYTO:  this is  the  'Resent-Reply-To:'/'Reply-To:' field,  in *
  658. *   "address" format. There  may be any number of  these fields. They *
  659. *   correspond to the 'REPLYTO' option.                               *
  660. *                                                                     *
  661. * - MSGID: this  is the 'Resent-Message-ID:'/'Message-ID:'  field, as *
  662. *   it appeared in the original tag.  This data is only provided when *
  663. *   the 'MSGID' option is specified.                                  *
  664. *                                                                     *
  665. * - TAG:  these  fields  are  generated when  the  'COPY'  option  is *
  666. *   present, and represent the contents of one of the original RFC822 *
  667. *   fields from the input mail header, unfolded and in the same order *
  668. *   as they were specified in the original header.                    *
  669. *                                                                     *
  670. *                                                                     *
  671. *                                                                     *
  672. * "address" format is defined as follows:                             *
  673. *                                                                     *
  674. *      userid domain name                                             *
  675. *                                                                     *
  676. *                                                                     *
  677. * 'userid' is the "local portion" of the RFC822 address.              *
  678. *                                                                     *
  679. * 'domain'  is the  "domain portion"  of the  RFC822 address.  If the *
  680. * 'BITNET'  option  is  specified,  any trailing  ".BITNET"  will  be *
  681. * removed from 'domain'.                                              *
  682. *                                                                     *
  683. * 'name',  if  present,  is  the  person's  full  name  in  canonical *
  684. * representation, ie with all quoting characters removed.             *
  685. *                                                                     *
  686. *                                                                     *
  687. * This program is system-independent and  can run under any operating *
  688. * system that supports REXX.                                          *
  689. *                                                                     *
  690. ***********************************************************************
  691. Update History:
  692. 10/29/91: Corrected wrapping of multi-line addresses. Spaces are added
  693.           based on need, not blindly.
  694. 01/14/92: Corrected line>250 character problem.
  695. 07/24/92: FIX00119: Adds support for "user"@node format
  696. 11/18/92: FIX00137: corrects "user" <user@node> parsing
  697. */
  698.  
  699. LSV822IN:
  700. procedure
  701.  
  702.  Arg numlines .,options
  703.  output = ''
  704.  numread = 0
  705.  retcode = 0
  706.  
  707.  If numlines < 1 | ¬Datatype(numlines,'W') Then
  708.   Do
  709.     Call LSVerror 'Invalid parameter list -- "'Arg(1)'","'Arg(2)'".'
  710.     Signal LSVexit
  711.   End
  712.  
  713.  Do numlines
  714.     numread = numread+1
  715.     Parse pull line
  716.     l.numread = Strip(Translate(line,,'0515'x),'T')
  717.     If l.numread = '' Then Leave
  718.  End
  719.  
  720.  i = 1
  721.  BITNET = (Find(options,'BITNET') ¬== 0)
  722.  copy = (Find(options,'COPY') ¬== 0)
  723.  n. = 0
  724.  k. = ''
  725.  Do until i > numread
  726.     line = l.i
  727.     i = i+1
  728.     Do while Left(l.i,1) == ' '
  729.        t = i-1
  730.        If length(l.t)<79 & ¬isdelimiter(left(strip(l.i),1)) Then
  731.           line = line Strip(l.i)
  732.        Else
  733.           line = line||Strip(l.i)
  734.        i = i+1
  735.     End
  736.     If Left(line,1) == ' ' Then
  737.      Do
  738.        Call Warning 'RFC822 field starting with a blank.',
  739.            'Field ignored. Line:' i
  740.        Iterate
  741.      End
  742.     Parse var line keyword':'data
  743.     data = Strip(data)
  744.     keyword = Translate(Strip(keyword))
  745.     If length(keyword)>250 Then Do
  746.        Call LSVerror 'Keyword too long line:' i
  747.        Signal LSVexit
  748.     End
  749.  
  750.     If Words(keyword) ¬== 1 Then
  751.      Do
  752.         Call Warning 'Invalid RFC822 field -- "'keyword'"'
  753.         Iterate
  754.      End
  755.     If copy Then Call Outfield 'TAG' Strip(line)
  756.     k.keyword = data
  757.     n.keyword = n.keyword + 1
  758.  End
  759.  Drop l.
  760.  
  761.  If Find(options,'SUBJECT') ¬== 0 Then
  762.   Do
  763.     dolr = 'SUBJECT'; If n.dolr > 1 Then Call Duplicate 'Subject:'
  764.     Call Outfield 'SUBJECT' k.dolr
  765.   End
  766.  
  767.  If Find(options,'REPLYTO') ¬== 0 Then
  768.   Do 1
  769.      tag = First('RESENT-REPLY-TO REPLY-TO')
  770.      If tag == ':' Then Leave
  771.      If n.tag > 1 Then Call Duplicate 'Resent-Reply-To:/Reply-To:'
  772.      input = k.tag
  773.      Do while input ¬= ''
  774.         Call Getaddress
  775.         If result ¬== '' Then Call Outfield 'REPLYTO' result
  776.      End
  777.   End
  778.  
  779.  If Find(options,'DATE') ¬== 0 Then
  780.   Do
  781.     tag = First('RESENT-DATE DATE')
  782.     If n.tag > 1 Then Call Duplicate 'Resent-Date:/Date:'
  783.     If k.tag ¬= '' Then Call Outfield 'DATE' k.tag
  784.                    Else Call Outfield 'DATE' Gendate()
  785.   End
  786.  
  787.  If Find(options,'MSGID') ¬== 0 Then
  788.   Do
  789.     tag = First('RESENT-MESSAGE-ID MESSAGE-ID')
  790.     If n.tag > 1 Then Call Duplicate 'Resent-Message-ID:/Message-ID:'
  791.     If k.tag ¬= '' Then Call Outfield 'MSGID' k.tag
  792.   End
  793.  
  794.  If Find(options,'RCPT') ¬== 0 Then
  795.   Do
  796.     Call Gendest 'TO','To'
  797.     Call Gendest 'CC','cc'
  798.     Call Gendest 'BCC','bcc'
  799.   End
  800.  
  801.  If Find(options,'FROM') ¬== 0 Then Call Gendest 'FROM','From'
  802.  If Find(options,'SENDER') ¬== 0 Then Call Gendest 'SENDER','Sender'
  803.  
  804.  tag = First('RESENT-SENDER RESENT-FROM SENDER FROM')
  805.  If tag == ':' Then
  806.   Do
  807.     Call LSVerror '"From:"/"Sender:" field is missing.'
  808.     Signal LSVexit
  809.   End
  810.  
  811.  If n.tag > 1 Then Call Duplicate tag':'
  812.  input = k.tag
  813.  Call Getaddress
  814.  If result = '' Then
  815.   Do
  816.     Call LSVerror 'Mail origin cannot be determined.'
  817.     Call LSVerror 'Original tag was ->' tag':' k.tag
  818.     Signal LSVexit
  819.   End
  820.  
  821.  Call Outfield 'ORIGIN' result
  822.  If input ¬= '' Then
  823.      Call Warning 'More than one sender was specified.',
  824.          'Second and following senders discarded.'
  825.  
  826. LSVexit:
  827.  Return retcode numread '15'x||output
  828.  
  829. Inform:
  830.  Call Outfield 'I' Arg(1)
  831.  Return
  832.  
  833. Warning:
  834.  Call Outfield 'W' Arg(1)
  835.  retcode = Max(retcode,4)
  836.  Return
  837.  
  838. LSVerror:
  839.  Call Outfield 'E' Arg(1)
  840.  retcode = Max(retcode,8)
  841.  Return
  842.  
  843. Duplicate:
  844.  Call Warning 'Field "'Arg(1)'" duplicated.',
  845.      'Last occurence was retained.'
  846.  Return
  847.  
  848. Outfield:
  849.  If output == ''
  850.   Then output = Arg(1)
  851.   Else output = output||'15'x||Arg(1)
  852.  Return
  853.  
  854. First:
  855.  Parse arg search
  856.  Do Words(search)
  857.     Parse var search keyword search
  858.     If n.keyword ¬== 0 Then Return keyword
  859.  End
  860.  Return ':' /* This keyword can not exist and will yield null string */
  861.  
  862. Gendate:
  863.  Return Left(Date('W'),3)',' Subword(Date(),1,2),
  864.      Left(Date('O'),2) Time() 'LCL'
  865. Gendest:
  866.  Parse arg tagname .,nicetag
  867.  tag = First('RESENT-'tagname tagname)
  868.  If tag == ':' Then Return
  869.  If n.tag > 1 Then Call Duplicate 'Resent-'nicetag':/'nicetag':'
  870.  input = k.tag
  871.  Do while input ¬= ''
  872.     Call Getaddress
  873.     If result ¬= '' Then Call Outfield tagname result
  874.  End
  875.  Return
  876.  
  877. Getaddress: Procedure expose input output retcode options BITNET
  878.  If input = '' Then Return ''
  879.  userid = ''
  880.  domain = ''
  881.  name = ''
  882.  string = ''
  883.  quote = 0
  884.  saved = 0
  885.  special.0 = '\"@<>():;,'
  886.  special.1 = '\"'
  887.  special.2 = 'E0'x||'()'
  888.  oinput = input
  889.  Do while input ¬= ''
  890.     i = Verify(input,special.quote,'M')
  891.     If i == 0 Then i = Length(input)+1
  892.     string = string||Left(input,i-1)
  893.     Parse Value Substr(input,i) with c +1 input
  894.     Select
  895.       When c == '\' Then Call Backslash
  896.       When c == '"' Then quote = ¬quote
  897.       When Pos(c,'@<>():;,') == 0 Then string = string||c
  898.       When c == ',' Then Leave
  899.       When c == '<' Then Call Append 'name'
  900.       When c == '>' Then Call Append 'domain'
  901.       When c == '(' Then Call LSVsave
  902.       When c == ')' Then Call Restore
  903.       When c == '@' Then Do
  904.          If pos('"'string'"@',space(oinput,0))>0 Then
  905.             string='"'string'"'
  906.          Call Append 'userid'
  907.       End
  908.       When c == ':' Then string = ''
  909.       When c == ';' Then nop
  910.     End
  911.  End
  912.  If saved ¬== 0 Then
  913.      Call LSVerror 'Unmatched parenthesis in address field.'
  914.  If domain = '' Then domain = string
  915.                 Else name = name string
  916.  If BITNET & Translate(Right(domain,7)) == '.BITNET' Then
  917.      domain = Left(domain,Length(domain)-7)
  918.  userid = Space(userid)
  919.  domain = Space(domain)
  920.  If CheckDomain(userid) | CheckDomain(domain) Then Return ''
  921.  If userid ¬== '' & domain ¬== '' Then
  922.      Return Space(userid,0) Space(domain,0) Space(name)
  923.  Call Inform 'Empty address field found and ignored.'
  924.  If input = '' Then Return ''
  925.  Return Getaddress()
  926.  
  927. Append:
  928.  Arg appto
  929.  If saved ¬== 0 Then Return
  930.  Select
  931.    When appto == 'NAME' Then name = name string
  932.    When Value(appto) == '' Then Interpret appto '= string'
  933.    Otherwise name = name string
  934.  End
  935.  string = ''
  936.  Return
  937.  
  938. Backslash:
  939.  Parse var input c +1 input
  940.  string = string||c
  941.  Return
  942.  
  943. LSVsave:
  944.  saved = saved+1
  945.  If saved ¬== 1 Then Return
  946.  savestr = string
  947.  string = ''
  948.  quote = 2
  949.  Return
  950.  
  951. Restore:
  952.  saved = saved-1
  953.  If saved ¬== 0 Then Return
  954.  name = name string
  955.  string = savestr
  956.  quote = 0
  957.  Return
  958.  
  959. CheckDomain:
  960.  Arg string
  961.  If BITNET Then splitters = '%.'
  962.            Else splitters = '.'
  963.  Do forever
  964.     i = Pos(' ',string)
  965.     If i == 0 Then Return 0
  966.     If Pos(Substr(string,i-1,1),splitters) == 0 &,
  967.         Pos(Substr(string,i+1,1),splitters) == 0 Then Return 1
  968.     string = Substr(string,i+1)
  969.  End
  970.  
  971. Isdelimiter:
  972. parse arg argh
  973.    Return (pos(argh,'."%@!')>0)
  974.  
  975.