home *** CD-ROM | disk | FTP | other *** search
/ Source Code 1992 March / Source_Code_CD-ROM_Walnut_Creek_March_1992.iso / usenet / altsrcs / 1 / 1894 < prev    next >
Text File  |  1990-12-28  |  13KB  |  439 lines

  1. Newsgroups: comp.lang.perl,alt.sources
  2. From: vixie@wrl.dec.com (Paul Vixie)
  3. Subject: Re: Anyone written a mail-server in perl?.
  4. Message-ID: <1990Oct3.062122.15323@wrl.dec.com>
  5. Date: Wed, 3 Oct 90 06:21:22 GMT
  6.  
  7. In article <1990Sep26.024048.1757@hades.ausonics.oz.au>,
  8. greyham@hades.ausonics.oz.au (Greyham Stoney) writes:
  9. # Perl looks like a totally awesome language to write a mail-server in; has
  10. # anyone tried doing it?. If so, could they send me a copy please?. It doesn't
  11. # need to be a polished work; anything will do.
  12.  
  13. Well, since it doesn't have to be polished, here's mine.  It's three files:
  14.     archivist    - collects a mail message, stashes it in an MH folder
  15.               runs out of sendmail's /usr/lib/aliases, as in:
  16.                 <|/usr/lib/mail/archivist listandfoldername>
  17.     listserv    - stupid name, no relation to BITnet program; collects
  18.               commands on stdin and executes them.  intended to
  19.               access archive built by "archivist"
  20.     listserv.help    - what "listserv" says if you send it a "help" command
  21.  
  22. A larger mail server, based on this one but with the intent of letting people
  23. remotely FTP files and have them mailed back to them, is in final testing now.
  24. It will appear here and elsewhere when it's done.
  25.  
  26. Paul Vixie
  27. DEC WRL
  28.  
  29. #! /bin/sh
  30. # This is a shell archive.  Remove anything before this line, then unpack
  31. # it by saving it into a file and typing "sh file".  To overwrite existing
  32. # files, type "sh file -c".  You can also feed this as standard input via
  33. # unshar, or by typing "sh <file", e.g..  If this archive is complete, you
  34. # will see the following message at the end:
  35. #        "End of shell archive."
  36. # Contents:  listserv listserv.help archivist
  37. # Wrapped by vixie@vixie.sf.ca.us on Tue Oct  2 23:15:24 1990
  38. PATH=/bin:/usr/bin:/usr/ucb ; export PATH
  39. if test -f 'listserv' -a "${1}" != "-c" ; then 
  40.   echo shar: Will not clobber existing file \"'listserv'\"
  41. else
  42. echo shar: Extracting \"'listserv'\" \(5857 characters\)
  43. sed "s/^X//" >'listserv' <<'END_OF_FILE'
  44. X#! /usr/local/bin/perl
  45. X
  46. X$| = 1;
  47. X$mhdir = "/usr/new/mh";
  48. X$listservdir = "/usr/lib/mail";
  49. X$sendmail = "/usr/lib/sendmail -oi";
  50. chop($hostname = `/bin/hostname`);
  51. X
  52. X# this is where the real archive is, we'll just symlink into it
  53. X$archivedir = "/var/mail-archive";
  54. X
  55. X# silly way to get last argument
  56. X$folder = "nonspecific";
  57. foreach (@ARGV) {
  58. X    $folder = $_;
  59. X}
  60. X
  61. X# this is our fake home directory, where MH can put its goop
  62. X$dir = "/tmp/listserv.$$";
  63. X
  64. X# these are the valid args to an MH command and the number of subargs for each
  65. X$mh_argc{"-and"} = 1;
  66. X$mh_argc{"-or"} = 1;
  67. X$mh_argc{"-not"} = 1;
  68. X$mh_argc{"-lbrace"} = 1;
  69. X$mh_argc{"-rbrace"} = 1;
  70. X$mh_argc{"-cc"} = 2;
  71. X$mh_argc{"-date"} = 2;
  72. X$mh_argc{"-from"} = 2;
  73. X$mh_argc{"-search"} = 2;
  74. X$mh_argc{"-subject"} = 2;
  75. X$mh_argc{"-after"} = 2;
  76. X$mh_argc{"-before"} = 2;
  77. X
  78. X#
  79. X# make a place to work.  MH will mess it up, then we'll nuke it.
  80. X#
  81. system("mkdir $dir");
  82. chdir($dir) || die "couldn't chdir $dir: $!";;
  83. X$ENV{"HOME"} = $dir;
  84. symlink("$archivedir/$folder", "$folder") || die "symlink $folder: $!";
  85. open(context, ">context") || die "context: $!";
  86. print context "Current-Folder: $folder\n";
  87. close(context);
  88. open(profile, ">.mh_profile") || die ".mh_profile: $!";
  89. print profile "Path: .\n";
  90. close(profile);
  91. X
  92. X#
  93. X# grab headers. this is standard code that ought to be in a library
  94. X#
  95. X$full_header = "";
  96. X$prev_header = "";
  97. while (<stdin>) {
  98. X    if (/^\n$/) { last; }        # blank line ends headers
  99. X    $full_header .= $_;
  100. X    if (/^[ \t]/) {
  101. X        # leading whitespace means continuation
  102. X        $header = $prev_header;
  103. X        $value = $_;
  104. X    } else {
  105. X        /^([\w-]*):(.*\n)$/;
  106. X        $header = $1;
  107. X        $value = $2;
  108. X    }
  109. X    $prev_header = $header;
  110. X    $header =~ tr/A-Z/a-z/;        # make header lower-case
  111. X    $headers{$header} .= $value;
  112. X}
  113. X#
  114. X# got headers, next line read will be first body line, blank line was eaten
  115. X#
  116. X
  117. X# --- find default reply address ---
  118. X#
  119. X$reply = "owner-$folder";
  120. if ($headers{"reply-to"} ne undef) {
  121. X    $reply = $headers{"reply-to"};
  122. X} elsif ($headers{"from"} ne undef) {
  123. X    $reply = $headers{"from"};
  124. X} elsif ($headers{"sender"} ne undef) {
  125. X    $reply = $headers{"sender"};
  126. X}
  127. chop $reply;  $reply =~ s/^[ ]+//;
  128. X
  129. X# if ($headers{"subject"} ne undef) {
  130. X#     do command($headers{"subject"});
  131. X# }
  132. X
  133. while (<stdin>) {
  134. X    do command($_);
  135. X}
  136. X
  137. X#
  138. X# session is over, send the transcript to the reply address
  139. X#
  140. open(sm, "|$sendmail '-f$reply' -t -v")
  141. X    || die "can't start sendmail: !$\n";
  142. print sm "From: $folder list server on $hostname <listserv@$hostname>\n";
  143. print sm "To: $reply\n";
  144. print sm "Cc: $folder-request\n";
  145. print sm "Subject: results of your request\n";
  146. foreach $hdr ("date", "from", "message-id") {
  147. X    if ($headers{$hdr} ne undef) {
  148. X        print sm "X-orig-".$hdr.":".$headers{$hdr};
  149. X    }
  150. X}
  151. print sm "\n";
  152. open(xs,"<transcript") || die "can't reopen transcript: $!\n";
  153. while (read(xs,$buf,2048)) {
  154. X    print sm $buf;
  155. X}
  156. close(sm);
  157. close(xs);
  158. X
  159. unlink "transcript", "context", ".mh_profile", $folder;
  160. chdir "/tmp";  rmdir $dir;
  161. X
  162. exit 0;
  163. X
  164. sub command {
  165. X    local($_) = @_;
  166. X
  167. X    chop;  s/^[ ]+//;
  168. X    return if (/^$/ || /^#/);
  169. X
  170. X    open(xs, ">>transcript") || die "can't open transcript: $!";
  171. X    select(xs); $| = 1; select(stdout);
  172. X
  173. X    ($cmd, @args) = split;
  174. X    $cmd =~ y/A-Z/a-z/;
  175. X    print xs "<<< $_\n";
  176. X    if ($cmd eq "scan") {
  177. X        if ($#args < $[) {
  178. X            @args = ("last:10");
  179. X        }
  180. X        do mh("scan", @args);
  181. X    } elsif ($cmd eq "show") {
  182. X        if ($#args < $[) {
  183. X            @args = ("last");
  184. X        }
  185. X        do mh("show", @args);
  186. X    } elsif ($cmd eq "reply") {
  187. X        $reply = join(" ", @args);
  188. X        $reply =~ s/^[ <]+//;
  189. X        $reply =~ s/[ >]+$//;
  190. X        print xs ">>> OK, will reply to <$reply>\n";
  191. X    } elsif ($cmd eq "listsubs") {
  192. X        system("cat $listservdir/lists/$folder >>transcript");
  193. X    } elsif ($cmd eq "subscribe" || $cmd eq "unsubscribe") {
  194. X        open(sm, "|".$sendmail." -t") || die "can't run sendmail";
  195. X        print sm "From: listserv for $folder <".$folder."-listserv>\n";
  196. X        print sm "To: ".$folder."-request (list maintainer)\n";
  197. X        print sm "Subject: subscription-related request\n";
  198. X        print sm "\n";
  199. X        print sm $cmd." ".join(" ",@args)."\n";
  200. X        close(sm);
  201. X        print xs ">>> request forwarded to list maintainer\n";
  202. X    } elsif ($cmd eq "help") {
  203. X        system("cat $listservdir/listserv.help >>transcript");
  204. X    } else {
  205. X        print xs ">>> command unrecognized, try 'help'.\n";
  206. X    }
  207. X    close(xs);
  208. X}
  209. X
  210. sub mh {
  211. X    local($cmd, @args) = @_;
  212. X    local(@picks) = ();
  213. X    local($search) = "";
  214. X
  215. X    for ($n = $]; $n <= $#args; $n++) {
  216. X        $arg = $args[$n];
  217. X        if (!($arg =~ /^-/)) {
  218. X            push(@picks, do mh_msgsel($arg));
  219. X            next;
  220. X        }
  221. X        if ($mh_argc{$arg} == undef) {
  222. X            print xs ">>> unrecognized argument: '$arg'\n";
  223. X            return;
  224. X        }
  225. X        $search .= $arg." ";
  226. X        for ($nn = 1; $nn < $mh_argc{$arg}; $nn++) {
  227. X            $search .= $args[++$n]." ";
  228. X        }
  229. X    }
  230. X    if (length($search) > 0) {
  231. X        chop $search;
  232. X        push(@picks, $search);
  233. X    }
  234. X
  235. X    # 'tis time
  236. X    local($zero, $pick, $pickcmd, $npicks) = ("-zero", "", "", 0);
  237. X    foreach $pick (@picks) {
  238. X        next if (length($pick) == 0);
  239. X        do syscmd($mhdir."/pick ".$pick." ".$zero." -sequence listserv");
  240. X        $zero = "-nozero";
  241. X        $npicks++;
  242. X    }
  243. X    if ($npicks > 0) {
  244. X        do syscmd($mhdir."/".$cmd." listserv");
  245. X    }
  246. X}
  247. X
  248. sub syscmd {
  249. X    local($cmd) = @_;
  250. X    local($_);
  251. X
  252. X    $cmd =~ y/~/ /;
  253. X    print xs ">>> ".$cmd."\n";
  254. X    close(xs);
  255. X    if (fork() == 0) {
  256. X        open(STDOUT, ">>transcript");    # output straight to xs
  257. X        open(STDERR, ">&STDOUT");    # make it follow pipe
  258. X        exec split(/[ \t]+/, $cmd);    # don't use sh -c
  259. X    }
  260. X    wait();
  261. X    open(xs,">>transcript") || die "can't reopen transcript: $!\n";
  262. X}
  263. X
  264. sub mh_msgsel {
  265. X    local($sel) = @_;
  266. X    local(@sel) = split(/,/, $sel);
  267. X    local(@ret) = ();
  268. X    local($errors) = 0;
  269. X    local($_);
  270. X
  271. X    foreach $_ (@sel) {
  272. X        if (/(first|last|\d+)-(first|last|\d+)/) {
  273. X            push(@ret, "$1-$2");
  274. X        } elsif (/(first|last|\d+):([\+\-])(\d+)/) {
  275. X            push(@ret, "$1:$2$3");
  276. X        } elsif (/(first|last|\d+)/) {
  277. X            push(@ret, "$1");
  278. X        } else {
  279. X            print xs ">>> bad message selector: '$_'\n";
  280. X            $errors++;
  281. X        }
  282. X    }
  283. X    if ($errors) {
  284. X        print xs ">>> $errors errors in '$sel'\n";
  285. X        return ();
  286. X    }
  287. X    return @sel;
  288. X}
  289. END_OF_FILE
  290. if test 5857 -ne `wc -c <'listserv'`; then
  291.     echo shar: \"'listserv'\" unpacked with wrong size!
  292. fi
  293. chmod +x 'listserv'
  294. # end of 'listserv'
  295. fi
  296. if test -f 'listserv.help' -a "${1}" != "-c" ; then 
  297.   echo shar: Will not clobber existing file \"'listserv.help'\"
  298. else
  299. echo shar: Extracting \"'listserv.help'\" \(2104 characters\)
  300. sed "s/^X//" >'listserv.help' <<'END_OF_FILE'
  301. Welcome to listserv!        $Date$  $Revision$
  302. X
  303. X=========================================================================
  304. X                                COMMANDS
  305. X
  306. general
  307. X-------------------------------------------------------------------------
  308. help        you're reading it
  309. reply ADDR    server should reply to ADDR instead of guessing. (recommended)
  310. X
  311. subscription utilities
  312. X-------------------------------------------------------------------------
  313. listsubs        list the subscribers of the mailing list
  314. subscribe ADDR        subscribe to the mailing list
  315. unsubscribe ADDR    unsubscribe from the mailing list
  316. X
  317. archive utilities
  318. X-------------------------------------------------------------------------
  319. scan ARGS    show summary of messages, one message per line (def: 'last:5')
  320. show ARGS    show text of messages, can be a lot of text (def: 'last')
  321. X
  322. X=========================================================================
  323. X                                DETAILS
  324. X
  325. ARGS is passed more or less directly to an MH "pick" command:
  326. X
  327. X    -and                -cc  PATTERN
  328. X    -or                -date  PATTERN
  329. X    -not                -from  PATTERN
  330. X    -lbrace                -search  PATTERN
  331. X    -rbrace                -subject  PATTERN
  332. X    START-END            -to  PATTERN
  333. X    BASE:-OFFSET            -after  DATE
  334. X    BASE:+OFFSET            -before  DATE
  335. X
  336. X=========================================================================
  337. X                                EXAMPLES
  338. X
  339. X    reply <vixie@decwrl.dec.com>
  340. X    subscribe <eyal@coyote.stanford.edu> Eyal Moses
  341. X    scan -from eyal
  342. X    scan -from eyal -or -from mehuld
  343. X    scan -after 1dec89 -and -before 1jan90 -and -subject liability
  344. X    scan first:100, 100-104, 110:5
  345. X    show -from mehuld -and -subject killing
  346. X    show 1,3-40,last:10
  347. X
  348. X=========================================================================
  349. X                                NOTES
  350. X
  351. Note that a selector (such as "last:100") is mixed with any of the search
  352. operands (such as "-from eyal"), the effect is "or" rather than the more
  353. intuitive "and".  Your best bet is to use one or the other exclusively, and
  354. to experiement liberally with "scan" before you start using "show".
  355. X
  356. X"show first-last" is almost certainly a mistake, but the server will let you
  357. do it -- so be careful!
  358. X
  359. Comments on this list server are welcome, send to <listserv@vixie.sf.ca.us>.
  360. END_OF_FILE
  361. if test 2104 -ne `wc -c <'listserv.help'`; then
  362.     echo shar: \"'listserv.help'\" unpacked with wrong size!
  363. fi
  364. # end of 'listserv.help'
  365. fi
  366. if test -f 'archivist' -a "${1}" != "-c" ; then 
  367.   echo shar: Will not clobber existing file \"'archivist'\"
  368. else
  369. echo shar: Extracting \"'archivist'\" \(1250 characters\)
  370. sed "s/^X//" >'archivist' <<'END_OF_FILE'
  371. X#! /usr/local/bin/perl
  372. X
  373. X$ENV{"HOME"} = "/var/mail-archive";
  374. X$rcvstore = "/usr/new/lib/mh/rcvstore";
  375. X
  376. X# cheap and silly way to get last argument
  377. X$folder = "nonspecific";
  378. foreach $x (@ARGV) {
  379. X    $folder = $x;
  380. X}
  381. X
  382. X$full_header = "";
  383. X$prev_header = "";
  384. while (<stdin>) {
  385. X    if (/^\n$/) { last; }        # blank line ends headers
  386. X    $full_header .= $_;
  387. X    if (/^[ \t]/) {
  388. X        # leading whitespace means continuation
  389. X        $header = $prev_header;
  390. X        $value = $_;
  391. X    } else {
  392. X        /^([\w-]*):(.*\n)$/;
  393. X        $header = $1;
  394. X        $value = $2;
  395. X    }
  396. X    $prev_header = $header;
  397. X    $header =~ tr/A-Z/a-z/;        # make header lower-case
  398. X    $headers{$header} .= $value;
  399. X}
  400. X
  401. X#
  402. X# got headers, next line read will be first body line, blank line eaten
  403. X#
  404. X
  405. if ($headers{"date"} =~ /[ \t]+(Sun|Mon|Tue|Wed|Thu|Fri|Sat) (Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec) ([ \d]\d) (\d\d:\d\d:\d\d) 19(\d\d)/) {
  406. X    $headers{"date"} = "$1, $3 $2 $5 $4 GMT";
  407. X}
  408. X
  409. open(rcv, "|".$rcvstore." +".$folder) || die "rcvstore";
  410. X
  411. print rcv "Date:    " . $headers{"date"};
  412. print rcv "From:    " . $headers{"from"};
  413. print rcv "To:      " . $headers{"to"};
  414. if ($headers{"cc"}) {
  415. X    print rcv "Cc:      " . $headers{"cc"};
  416. X}
  417. print rcv "Subject: " . $headers{"subject"};
  418. X
  419. print rcv "\n";
  420. while (<stdin>) {
  421. X    print rcv $_;
  422. X}
  423. close(rcv);
  424. X
  425. exit 0;
  426. END_OF_FILE
  427. if test 1250 -ne `wc -c <'archivist'`; then
  428.     echo shar: \"'archivist'\" unpacked with wrong size!
  429. fi
  430. chmod +x 'archivist'
  431. # end of 'archivist'
  432. fi
  433. echo shar: End of shell archive.
  434. exit 0
  435. --
  436. Paul Vixie
  437. DEC Western Research Lab    <vixie@wrl.dec.com>
  438. Palo Alto, California        ...!decwrl!vixie
  439.