|
|
1.1 ! root 1: .so ../ADM/mac ! 2: .XX upas 557 "Upas \(em A Simpler Approach to Network Mail" ! 3: .TL ! 4: Upas \(em a Simpler Approach to Network Mail ! 5: .AU ! 6: David L. Presotto ! 7: William R. Cheswick ! 8: .AI ! 9: .MH ! 10: .AB ! 11: .I Upas* ! 12: is a mail interface that routes messages between existing ! 13: network-specific mailers, users, and user mailboxes. ! 14: It uses a language based on regular expressions describe ! 15: how to convert mail ! 16: addresses into the commands needed to route the mail to the intended ! 17: destination. ! 18: Upas is the mail interface for the Tenth Edition ! 19: .UX ! 20: system. ! 21: .AE ! 22: .2C ! 23: .FS ! 24: * ! 25: .B upas , ! 26: .I "u\(aapas, n" . ! 27: (in full ! 28: .B u\(aapas-tree\(aa ), ! 29: a fabulous Javanese tree that poisoned everything for miles ! 30: around; Javanese tree (\c ! 31: .I "Antiaris toxicara" , ! 32: of the mulberry family): the poison of its latex. ! 33: [Malay, poison.] ! 34: |reference(dictionary chambers) ! 35: .FE ! 36: .NH 1 ! 37: Introduction ! 38: .PP ! 39: Our entry in the `mail race' sprang from events similar to those ! 40: motivating the development of many mail systems. ! 41: For many years a short and simple mailer was used to deliver local mail ! 42: and to route mail via our home-grown networks. ! 43: Although its user interface left a little to be desired, its reliability ! 44: was so high that great trust was put into it. ! 45: However, as we gained access to more and more networks, particularly ones ! 46: over which we had no control, the situation quickly deteriorated. ! 47: Each of these networks had their own mail `standards' and addressing conventions. ! 48: With some trepidation, we absorbed these standards into our mailer. ! 49: Its simplicity was quickly lost along with its fabled reliability. ! 50: Realizing our danger, we decided to step back and see if there was a ! 51: way to get back to a simple, well-understood, and thereby reliable mail system. ! 52: .PP ! 53: The job to be performed by a network mail system is illustrated by Figure 1. ! 54: A mail system is essentially a large switch for handling the routing and ! 55: delivery of messages. ! 56: As a router it must be conversant in the various network protocols, ! 57: be able to decipher destination addresses, and pass messages along ! 58: to the next network. ! 59: Sometimes it actually gets to deliver a piece of mail to a mailbox. ! 60: Also, since there is no common mail format, the mail system must ! 61: convert messages from one format to another as it routes them from ! 62: network to network. ! 63: Because of the number of networks and mail formats, this can easily lead to ! 64: thousands of lines of code. ! 65: Our task was to decide how to partition the task in order to create a ! 66: manageable yet efficient mail system. ! 67: .1C ! 68: .KF ! 69: .PS ! 70: define net | ! 71: [ellipse "network" "$1"; ! 72: arrow -> from last ellipse.s down boxht/3; ! 73: box invis ht boxht/3 "protocol"; ! 74: arrow -> from last box.s down boxht/3; ! 75: box invis ht boxht/3 "convert"; ! 76: arrow -> from last box.s down boxht/3; ! 77: box invis; ! 78: arrow -> down boxht/3; ! 79: box invis ht boxht/3 "queue"; ! 80: arrow -> from last box.s down boxht/3; ! 81: box invis ht boxht/3 "convert"; ! 82: arrow -> from last box.s down boxht/3; ! 83: box invis ht boxht/3 "protocol"; ! 84: arrow -> from last box.s down boxht/3; ! 85: ellipse "network" "$1"; ! 86: ] | ! 87: ! 88: # network mail ! 89: NetA: net(A) ! 90: move right boxwid/4 ! 91: NetB: net(B) ! 92: move right boxwid/4 ! 93: NetC: net(C) ! 94: ! 95: # local mail ! 96: move to NetA.w + 0,boxht/3; ! 97: arrow <- left; ! 98: ellipse "user"; ! 99: move to NetC.e + 0,boxht/3; ! 100: arrow -> right; ! 101: ellipse "mail" "box" ! 102: ! 103: # the router ! 104: box dashed wid 3*boxwid at NetB + 0,boxht/3 "routing" ! 105: .PE ! 106: .sp .5 ! 107: .ce ! 108: \fBFigure 1.\fP The functions to be performed to route network mail. ! 109: .sp ! 110: .KE ! 111: .2C ! 112: .NH 1 ! 113: Some Observations ! 114: .PP ! 115: The task of interfacing to a particular network is often ! 116: messy and arbitrary. ! 117: Fortunately, most entities (corporations, governments, committees) ! 118: that design network protocols also provide code (i.e. mail programs) ! 119: that understand these protocols. ! 120: In our experience, it has ! 121: always been easier to interface one of these mailers to our ! 122: mail system than to incorporate the new protocols ! 123: into our existing mailer. ! 124: Also, code provided by someone else is supported by someone else. ! 125: As network protocols change it is easier to pick up the new version of the ! 126: network mailer than to rewrite our mailer. ! 127: .PP ! 128: Although there are many networks, there are far fewer message formats. ! 129: It is clear that a message needs a destination address and possibly even ! 130: a reply address. ! 131: However, the imposition of further structure on the message is at best ! 132: distasteful, at worst obstructive. ! 133: Imagine what postal delivery would be like if the Postal Service opened ! 134: each piece of mail to ensure that it is correctly dated and signed, ! 135: that the form of address is correct, and that the company letterhead ! 136: obeys some preconceived format, ! 137: refusing delivery if any of these conditions are not met. ! 138: Unfortunately, some networks impose such requirements. ! 139: For a message to obey one standard is difficult enough. ! 140: To expect it to survive a number of conversions between ! 141: restrictive standards constitutes wishful thinking. ! 142: Because of this, most networks adopt standards established by ! 143: older or larger networks. ! 144: Therefore, although there are many networks, there are relatively few ! 145: message formats. ! 146: .PP ! 147: A network address describes a path through a number of machines ! 148: and networks. ! 149: This path may be rather simple, consisting of a single machine ! 150: and user name. ! 151: Often, however, the path crosses a number of administrative domains. ! 152: Each such domain imposes some rules for structuring paths within the ! 153: domain. ! 154: Unfortunately, there is no adhered-to standard ! 155: for binding the path segments from each domain into a single ! 156: address. ! 157: The networks differ on direction of binding (person@machine vs. machine!person), ! 158: delimiters (`.' vs. `@' vs. `%'), quotation marks, and even case sensitivity. ! 159: Therefore, there is no fixed way to correctly parse and understand a ! 160: network address. ! 161: Instead, there are conventions which tend to be very short-lived, ! 162: usually until someone issues a new RFC or a new network appears. ! 163: As a relatively simple example, consider a message sent from one \fIuucp\fP ! 164: |reference(uucp v7man network) ! 165: network, through ARPAnet, to another \fIuucp\fP network. ! 166: The address format might be something like: ! 167: .P1 ! 168: A!B!person%E%D@C ! 169: .P2 ! 170: The rules for parsing such an address are easily defined. ! 171: Unfortunately, the conventions underlying the rules change from day to day. ! 172: Once you've managed to write your code, the administrator ! 173: at B may decide that he won't accept percent signs in an address ! 174: and would really like the address to look like: ! 175: .P1 ! 176: A!B!@d,@e:person@c ! 177: .P2 ! 178: A new set of parsing rules now have to be defined. ! 179: In our experience these changes happen with maddening frequency. ! 180: They are the direct result of there being no single comprehensive ! 181: standard or administrative authority. ! 182: Therefore, we have to treat address parsing rules as ! 183: ephemeral. ! 184: Any network mailer should be able to change its address parsing rules ! 185: frequently and with little difficulty. ! 186: Tying them to one particular standard such as this week's Internet rules is ! 187: equivalent to planned obsolescence. ! 188: .PP ! 189: Finally, we should make a point about reversibility that many other ! 190: mail designers seem to have missed. ! 191: In addition to parsing destination addresses, mailers are expected to ! 192: maintain some form of return address attached to the message. ! 193: This often involves changing the current return address to one ! 194: that the mailer will accept as a reply destination. ! 195: A mailer should parse and modify return addresses using the same rules ! 196: as it does for destination addresses. ! 197: Otherwise, as is too often the case, the mailer will reject the very addresses that ! 198: it has provided for replies. ! 199: .NH 1 ! 200: A Solution ! 201: .PP ! 202: The best solution would have been to throw out all the so-called standards and ! 203: create a single coherent scheme for formatting and addressing mail.|reference(hideous pike weinberger) ! 204: However, since we have no power to impose such a scheme, ! 205: we have tried to use the above-stated requirements and observations ! 206: to build a mail system that makes the best of a bad situation. ! 207: .PP ! 208: The structure of our mail system is depicted in figure 2. ! 209: Each network has its own interface program for message reception ! 210: and transmission. ! 211: In general these are the network-specific mailers provided ! 212: with the networks. ! 213: When a message enters from a network, the network ! 214: specific mailer gives it to Upas. ! 215: Upas then either deposits the mail in a local mail box or routes the ! 216: mail to the next network. ! 217: A format-specific filter may be called to convert the message ! 218: from network format to one Upas understands or vice-versa; ! 219: The ! 220: .UX ! 221: format is built in. ! 222: .1C ! 223: .KF ! 224: .PS 5i ! 225: copy "over.cip" ! 226: .PE ! 227: .sp .5 ! 228: .ce 2 ! 229: \fBFigure 2.\fP The structure of Upas. ! 230: .sp ! 231: .KE ! 232: .2C ! 233: .NH 1 ! 234: Message Routing ! 235: .PP ! 236: The routing of messages is determined by a destination address ! 237: and by a set of rewriting rules kept in the file ! 238: .CW /usr/lib/upas/rewrite . ! 239: Each line of the file is a rule. ! 240: Blank lines and lines beginning with ! 241: .CW # ! 242: are ignored. ! 243: .nr ss \w'conversion ' ! 244: .PP ! 245: Each rewriting rule consists of four fields: ! 246: .IP \fIpattern\fR \n(ssu ! 247: An ! 248: .I ed (1)-like ! 249: regular expression, with simple parentheses playing the role ! 250: of ! 251: .CW \e( ! 252: and ! 253: .CW \e) ! 254: and with the ! 255: .CW + ! 256: and ! 257: .CW ? ! 258: operators of ! 259: .I egrep (1). ! 260: This regular expression must match the entire destination ! 261: address. Case is ignored. ! 262: .IP \fIcommand\fR \n(ssu ! 263: One of the following rewrite commands: ! 264: .I alias , ! 265: .I auth , ! 266: .I translate . ! 267: .I | , ! 268: or ! 269: .I >> . ! 270: .IP \fIparameter\fR \n(ssu ! 271: An ! 272: .I ed (1)-style ! 273: replacement string to generate a ! 274: parameter to the ! 275: .I command . ! 276: .IP \fIaddress-list\fR \n(ssu ! 277: A list of addresses that might be shipped with a single command. ! 278: .PP ! 279: The ! 280: .I pattern , ! 281: .I parameter , ! 282: and ! 283: .I address-list ! 284: fields may use the following: ! 285: .KS ! 286: .IP \f(CW\es\fP \n(ssu ! 287: The address of the sender. ! 288: .IP \f(CW\el\fP \n(ssu ! 289: The name of the local machine. ! 290: .IP \f(CW&\fP \n(ssu ! 291: The entire destination address. ! 292: .KE ! 293: .PP ! 294: The ! 295: .I parameters ! 296: and ! 297: .I address-list ! 298: fields may use ! 299: .CW \e0 ! 300: through ! 301: .CW \e9 ! 302: to match the first ten parenthesized groups matched in the ! 303: .I pattern ! 304: field. ! 305: .PP ! 306: When rewriting a destination address, ! 307: Upas starts with the first rule and continues ! 308: down the list until a pattern ! 309: matches the destination address. ! 310: The command on that line is executed. ! 311: If no match is found, the ! 312: mail is returned to sender with an error. ! 313: If the command does not result in mail delivery ! 314: (i.e is not ! 315: .CW | ! 316: or ! 317: .CW >> ), ! 318: Upas scans the rules again with the latest version of ! 319: the destination address, starting from the first rule. ! 320: .1C ! 321: .KF ! 322: .P1 ! 323: # local mail ! 324: [^!@%]+ translate "exec translate '&'" ! 325: local!([^!@%]+) >> /usr/spool/mail/\e1 ! 326: \el!(.+) alias \e1 ! 327: ! 328: # convert %@ format to ! format ! 329: (_822_)!((.+)!)?([^!]+)[%@]([^!%@]+) alias \e1!\e2\e5!\e4 ! 330: ([^!]+)[%@]([^!@%]+) alias _822_!\e2!\e1 ! 331: _822_!(.+) alias \e1 ! 332: ! 333: # special domain names ! 334: ([^!.]+)\e.(att\e.com|uucp)!(.+) alias \e1!\e3 ! 335: ! 336: ([^!]+)!(.+) | "/usr/lib/upas/route '\es' '\e1'" "'\e2'" ! 337: .P2 ! 338: .sp .5 ! 339: .ce 2 ! 340: \fBFigure 3.\fP Sample rewrite file for a machine using \fIuucp\fP only. ! 341: .sp ! 342: .KE ! 343: .2C ! 344: .PP ! 345: There are five rewrite commands: ! 346: .IP \fIalias\fR \n(ssu ! 347: Rewrites the address with the pattern ! 348: in the ! 349: .I parameter ! 350: field. ! 351: .IP \fIauth\fR \n(ssu ! 352: Calls ! 353: .I parameter ! 354: to authorize the mail. ! 355: A zero exit status approves the mail, non-zero rejects it. ! 356: The ! 357: .I auth ! 358: command is called only once per message. ! 359: If it is never called, the mail is approved. ! 360: .IP \fItranslate\fR \n(ssu ! 361: Calls ! 362: .I parameter ! 363: to rewrite the address. The program must write the new ! 364: address(es) to standard output. This command is used to implement ! 365: mailing lists. ! 366: .IP \f(CW|\fP \n(ssu ! 367: Pipe the message to the mail delivery agent ! 368: .I parameter . ! 369: The ! 370: .I address-list ! 371: parameter is a list of recipients with the same destination ! 372: machine. If the delivery agent fails, the message is ! 373: returned to the sender with the error message from the ! 374: delivery program's standard error file. ! 375: .IP \f(CW>>\fP \n(ssu ! 376: Deliver the message to a local mailbox. The file given in ! 377: .I parameter ! 378: must either exist and appear to be a valid mailbox, ! 379: or the last name in the path must be a user name found in ! 380: .CW /etc/passwd . ! 381: .PP ! 382: Rules for most networks can be specified in one or two lines. ! 383: In addition, the rules are in a language familiar to most ! 384: experienced ! 385: .UX ! 386: programmers: the regular expressions ! 387: seen in many editors, languages, and utilities. ! 388: By using such a mini-language, it becomes an easy task to build or ! 389: modify Upas configuration files. ! 390: The result is that configuration files rarely contain gross ! 391: mistakes and take very little time to create ! 392: and to edit when addressing conventions change. ! 393: Further, the rewrite file is reread for each new mail delivery, so a ! 394: change to the rewrite file will take effect immediately. ! 395: .NH 1 ! 396: SMTP Message Format Conversion ! 397: .PP ! 398: Upas uses only the ! 399: .I uucp -style ! 400: addressing internally. ! 401: The mail delivery program must convert between this ! 402: form and its own, if different. For example, the ! 403: .I smtpd ! 404: daemon must convert incoming RFC822 addresses to ! 405: .I uucp ! 406: form when calling ! 407: Upas, and the ! 408: .I smtp ! 409: program generates header lines on outgoing mail. ! 410: .PP ! 411: The outbound conversion to SMTP format is required by RFC822. Specifically, ! 412: three header lines are required: ! 413: .CW Date: , ! 414: .CW To: , ! 415: and one of several variants of ! 416: .CW From: . ! 417: If the message appears to have these header lines, and the lines are ! 418: formatted properly, the message is sent unaltered. ! 419: For example, if there is an original ! 420: .CW From: ! 421: line with an address in the requested domain, it is left alone. Otherwise, we ! 422: generate a ! 423: .CW From: ! 424: line and turn any existing one into ! 425: .CW Original-From: . ! 426: Missing information is filled in from the Unix-style ! 427: .CW From ! 428: line. ! 429: .PP ! 430: We do not add other header lines to mail. ! 431: These provide extra bulk (over ten percent ! 432: in one of our surveys) with little added utility. ! 433: In particular, ! 434: .CW Received: ! 435: lines are only rarely useful, and the information they provide appears ! 436: in our log files. ! 437: .PP ! 438: Incoming SMTP destination addresses are derived from the ! 439: envelope addresses and header information. ! 440: The senders address is extracted from the first of the following header lines found: ! 441: .UX ! 442: .CW From , ! 443: .CW Reply-to: , ! 444: .CW Sender: , ! 445: .CW From: , ! 446: and the sender given in the SMTP ! 447: .CW "MAIL FROM:" ! 448: command. ! 449: .PP ! 450: The early versions ! 451: handled uucp and SMTP addressing internally. Later, SMTP was broken ! 452: out into two pairs of filters: ! 453: .I smtpd ! 454: and ! 455: .I fromsmtp , ! 456: and ! 457: .I tosmtp ! 458: and ! 459: .I smtp . ! 460: .I Fromsmtp ! 461: and ! 462: .I tosmtp ! 463: were filters that extracted and created RFC822 addressing and headers, ! 464: respectively. Recently, these filters were folded into ! 465: .I smtpd ! 466: and ! 467: .I smtp ! 468: for efficiency reasons. ! 469: .NH 1 ! 470: User Control ! 471: .PP ! 472: Users often wish to specify alternate ways to dispose of their mail. ! 473: Upas offers two choices. ! 474: The first line of a user's mail ! 475: file is interpreted as a command to the mail system. ! 476: If the line is of the format ! 477: .P1 ! 478: Forward to \fIlist-of-addresses ! 479: .P2 ! 480: the mail is forwarded to each recipient in ! 481: .I list-of-addresses. ! 482: While this can be used to forward a single user's mail, it ! 483: can be also be used to create mailing lists. ! 484: To do this, one creates a file in the mail directory whose name is ! 485: that of the mailing list and which consists of ! 486: .CW "Forward to" ! 487: followed by the list of recipients. ! 488: .PP ! 489: If the first line is of the format is ! 490: .P1 ! 491: Pipe to \fIshell-command ! 492: .P2 ! 493: .I shell-command ! 494: is executed when mail is delivered, with the message as standard input. ! 495: .NH 1 ! 496: Concealing Machine Names ! 497: .PP ! 498: It is often useful to hide several machines behind a single mail machine. ! 499: For example, our center has over 50 machines, but all mail is directed ! 500: through the machine named ! 501: .CW research . ! 502: The files ! 503: .CW /usr/lib/upas/names.* ! 504: contain routing information for each user. A sample entry ! 505: might be: ! 506: .P1 ! 507: andrew pipe!andrew ! 508: .P2 ! 509: Mail sent to ! 510: .CW research!andrew ! 511: will be directed to ! 512: .CW pipe , ! 513: .CW andrew 's ! 514: home machine. But mail from ! 515: .CW andrew ! 516: should appear to come from ! 517: .CW research , ! 518: not ! 519: .CW pipe . ! 520: .PP ! 521: To hide names, Upas attempts to translate the last field of the ! 522: sender's address. If the translation exactly matches the entire ! 523: sending address, the sending address is truncated to the last field. ! 524: .NH 1 ! 525: Loop Detection ! 526: .PP ! 527: Detecting forward loops, like those provoked by ! 528: .CW "Forward to" ! 529: is difficult. ! 530: It involves combining the forwarding lists of all involved machines ! 531: into a single directed graph and then performing a search or ! 532: partitioning to detect cycles. ! 533: However, if we allow a detection algorithm to reject some legal although ! 534: highly-unlikely cases along with real loops, we greatly simplify the problem. ! 535: .PP ! 536: In the case of a single machine, ! 537: an infinite forwarding loop corresponds to ! 538: infinite recursion of the mailer. ! 539: If a mailer rejects any message that results in recursion past a ! 540: certain depth, it will reject all loops and some small number of legal ! 541: but very long mail redirections. ! 542: In our case a depth is 32 and to date, no legal forwarding loop has been ! 543: more than 3 steps long. ! 544: .PP ! 545: In the case of a multi-machine loop, the recursion technique is not valid. ! 546: However, we can still use a similar method. ! 547: Instead of counting recursion, we scan the ! 548: .CW From ! 549: line to see the number ! 550: of times the local machine name occurs in the path. ! 551: If this exceeds a limit (in our case 8), the mail is returned to the sender. ! 552: .NH 1 ! 553: Installation ! 554: .PP ! 555: Upas has been ported to most major versions of the ! 556: .UX ! 557: system. ! 558: The source contains a ! 559: .CW config ! 560: directory where the working Upas directories are specified. ! 561: Each variant of Upas is made from a separate directory with ! 562: .I make (1). ! 563: The ! 564: .CW makefile ! 565: may require some editing to select the needed programs. ! 566: The ! 567: .CW config ! 568: directory contains a number of sample ! 569: rewrite and routing files. ! 570: .NH 1 ! 571: A Comparison With Sendmail ! 572: .PP ! 573: Upas is an attempt to solve the same problem previously attacked by Sendmail ! 574: |reference(sendmail). ! 575: Upas owes much of its design and success to Sendmail. ! 576: The idea of designing Upas as a central switcher ! 577: communicating with network-specific mailers comes directly from Sendmail. ! 578: The reasons we wrote Upas and didn't just adopt Sendmail are: ! 579: .IP \(bu ! 580: We strongly favor messages whose only formatted portion are the ! 581: destination and reply addresses. ! 582: Sendmail has an unfortunate predilection for verbose and rigidly-structured ! 583: messages that we would like to avoid. ! 584: .IP \(bu ! 585: Sendmail configuration files are famous for their inscrutability. ! 586: We wanted a system that had simpler and therefore more easily ! 587: verifiable rewriting rules. ! 588: .IP \(bu ! 589: Sendmail combines the functions of routing, queuing, aliasing, ! 590: transmission, header ! 591: processing, delivery, translation, etc., into a single large program. ! 592: This extra design makes Sendmail more complicated and harder ! 593: to understand and support. Upas's modular design simplifies these ! 594: tasks. ! 595: .IP \(bu ! 596: The size of sendmail has left it prone to several security ! 597: problems, some intentional. It is easier to understand and ! 598: check a smaller, more modular program. ! 599: .NH 1 ! 600: Lessons ! 601: .PP ! 602: The philosophy behind Upas has not changed much since its original ! 603: description in |reference(upas presotto), ! 604: but there have been many implementation changes. ! 605: The rewrite file now has five commands compared to the original ! 606: generalized command execution. Removing case sensitivity, and ! 607: anchoring the pattern matches by default has made them more versatile ! 608: and easier to read. ! 609: .PP ! 610: The early Upas understood ! 611: .I uucp ! 612: and SMTP addresses and formatting. ! 613: The SMTP portions are now broken out in separate programs, ! 614: simplifying the processing. The ! 615: .I uucp -style ! 616: address has ! 617: proven quite easy to teach and to use. For example, ! 618: .P1 2n ! 619: bitnet!templevm!rdk ! 620: .P2 ! 621: is much easier to teach and use than ! 622: .P1 2n ! 623: rdk%[email protected] ! 624: .P2 ! 625: .PP ! 626: Authorization was implemented with a file lookup of trusted machines. ! 627: Now, a command can implement arbitrary policies. ! 628: .NH 1 ! 629: Summary ! 630: .PP ! 631: We have presented a simple yet flexible network mail system. ! 632: It gains its simplicity from a number of assumptions which are ! 633: valid in most networked computers. ! 634: By using existing network-specific mailers as expert systems ! 635: that deal with network details, Upas itself remains relatively ! 636: simple and understandable. ! 637: Finally, by using a mini-language already ! 638: familiar to most ! 639: .UX ! 640: programmers, Upas is easily modified ! 641: to respond to changes in the name space and topology of the ! 642: network. ! 643: .PP ! 644: Upas has run at Research and on the AT&T Internet gateway for ! 645: nearly two years now. It has performed well in these demanding ! 646: environments, adjusting nicely to the changes. Its flexibility ! 647: comes at the cost of efficiency. Even so, we have handled nearly ! 648: four thousand messages per day on a VAX 750 with reasonable, if ! 649: not spectacular, throughput. ! 650: .NH 1 ! 651: Acknowledgements ! 652: .PP ! 653: Many people have contributed to the success of Upas. MIT supplied the ! 654: original SMTP code, which was improved by many people. ! 655: Bill Cheswick, Geoff Collyer, Ian Darwin, Peter Honeyman, Dave Presotto, ! 656: and Dennis Ritchie have all had a hand in the code. We have received ! 657: helpful feedback from ! 658: Steven Bellovin, ! 659: Jonathan Clark, ! 660: and ! 661: Marcel Frank-Simon. ! 662: .NH 1 ! 663: References ! 664: .LP ! 665: |reference_placement
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.