Cover V04, I03
Article

may95.tar


Setting Up an HP-UX Mail Hub

Larry Reznick

I had two sendmail(1M) problems to solve on a network of HP-UX systems. First, the site name needed to be hidden. Mail sent from an HP system got stamped as coming from user@localsystem instead of user@centralsystem, and since local systems weren't known outside the company's network, replies never arrived. Second, each HP system effectively stood alone, although they could talk with each other across the network. There was little centralization of files or directories across the systems. However, making mail work uniformly was only one small part of this problem. Users logging in on one system were deposited in a home directory local to that system. If a user sent mail from any HP system while logged in on that system, the mail would go to the receiver's account on that same local system. With eight HP systems, each collecting mail for every user separately, I faced a mess.

A network of Sun systems had been set up with greater uniformity. If mail was sent to any user on a Sun, smail(8) forwarded it to the user's Sun mail account. But smail, although compatible with sendmail, wasn't set up on the HPs. With a limited amount of time to finish this problem and move on to the next, I decided it would be faster to fix the HPs' sendmail.cf files than to hunt for a version of smail that would work on HPs.

HP-UX uses /usr/lib instead of /etc, as its holding directory for sendmail. No HP system would serve mail in my plan. The mail hub, which happened to be a Sun, would do that. First, I had to make sure that none of the slave systems was running the sendmail daemon. I checked the boot script, /etc/netbsdsrc on HP-UX, to be sure that sendmail would not restart at the next reboot. HP uses many scripts for booting that don't correspond with Sun's single rc.local or with System V's rc directory organization. If you aren't sure which boot script starts sendmail, you can discover it with the following command:

egrep sendmail `file /etc/* | egrep text | cut -d: -f1`

This makes the system search through all files identified as text files to find out which script contains sendmail.

If booting has already started the sendmail daemon, I kill it on every system except the hub. Only systems that expect to receive mail from an outside source need to run the daemon. Because none of the HP systems will receive -- they'll only send -- none needs the daemon running. When new mail goes out, the mailer will run sendmail to deliver the message.

My next step is to arrange for every /usr/lib/sendmail.cf to be identical. The following command line shows the differences between two files across two systems (assuming a user already logged in to system1):

remsh system2 cat /usr/lib/sendmail.cf | diff /usr/lib/sendmail.cf -

This command logs onto another system (system2) to deliver that other system's sendmail.cf file to diff. remsh(1), HP-UX's version of rsh(1C on Sun), runs the right side of a pipeline on the local system, not the remote system. If you must run the entire pipeline on the remote system, surround the pipeline argument with quotation marks. Assuming that the system you're currently logged onto contains the sendmail.cf you want to make central, you could put this command inside a loop that changes the system name following remsh. Then, if any differences show up, you can decide whether to incorporate them into the central file. When you've finished with all modifications, including the ones outlined in this article, you'll rcp(1) the central file to all the other systems.

Site Hiding

Arranging site hiding was the simplest part of my task. HP-UX's sendmail.cf already provides a macro for site hiding: DY. By default, DY is not set. The SMTP (Simple Mail Transfer Protocol) TCP/IP delivery agent uses the Y definition when it sends mail out (to see this use, search for the regular expression "^Mtcp" in HP's sendmail.cf). Two rulesets, S11 and S21, associate with the tcp delivery agent via the flags S=11 and R=21. S11 rewrites the sender's address and S21 rewrites the receiver's address. In S11, the last rule, labeled R$+, uses $Y to add the local domain name defined by DY. With DY defined as

DYmysite.com

this rule changes mail sent from larryr@system1 into larryr@mysite.com. People receiving such messages may now simply reply to get their response back to me, no matter what system I'm really logged onto -- once all the mail is accessible centrally, that is.

To put the mail files in a central location, create one directory on the mail hub system. Combine all the mail files from the various systems into one file on the hub system. Check with your users and throw the old mail files out if you can. When in doubt, concatenate the files together on the hub system and notify the users with /etc/motd or news that all users should clean out their mail files. Once all the files are combined on the hub, mount the hub's mail directory or add it to the automount list.

Routing to the Hub

Routing mail to the mail hub turned out to be the tedious part of the job. Essentially, I chose to disable local mailing on every HP. I didn't want to embed the hub system's name in every alias: I thought that would cause more trouble with respect to future maintenance than fixing sendmail. Instead, I decided to route all mail that would have been local through the tcp delivery agent identified in the sendmail.cf file. Once in the tcp agent, sendmail rulesets will route the mail to the mail hub. That plan sounded simple, until I found that the rulesets didn't want to go to the remote mail hub portably.

I wanted to route the mail to mailhost, the common host alias for the mail hub as defined in the NIS hosts map. By using the mailhost alias, I could ensure that if the system hosting mail should change in the future, the sendmail.cf files wouldn't need changing. Instead, I could just change whichever system in the map was aliased as mailhost, push the new map, and sendmail would get it. Everybody's happy.

No such luck. Referring to mailhost works in the rule that calls the tcp agent. Once in the tcp agent's rule, the @host part of the address must have a primary host name, not a host alias. Give that tcp agent rule a host alias, such as mailhost, and the mailer bounces it as an unknown host. Not everything in HP-UX's mail system uses host aliases.

Implementing Hub Routing

HP's sendmail.cf defines a macro for SMTP relay: DS. You can set the S macro to your hub's host name -- for example,

DShostname

where hostname is the first name in the hosts map for the mail host. Don't use a host alias here, such as mailhost: it won't work. Macro S gets used in ruleset 0, which figures out what delivery agent to use based on the address.

By default, any address containing a domain not resolved by earlier rules in ruleset 0 get resolved by a rule that looks like this:

R$+<@$+>        $#tcp$@$2$:$1<@$2>      user@domain

This confusing-looking mess divides into three fields separated by tabs. The first field is the incoming address, in a notation similar to regular expressions. The second field contains instructions for translating the incoming address. Comments go in the third field.

The first field begins with R, which identifies it as a rule. $+ matches one or more tokens up to a less-than (<) symbol followed by an "at" sign (@). After the "at" sign, expect one or more tokens followed by a greater-than (>) symbol. Each $+ match from the first field can be referred to by the second field, by means of a positional notation. $1 is the first match (everything up to the less-than symbol in this rule), and $2 is the second (everything between the "at" sign and the greater-than symbol for this rule).

Previous rules have already turned a simple address, larryr for instance, into a tcp-ready format like larryr<@system1> and sent the address on to other rules. Rules like this domain rule route the address to the delivery agent using the second field. A $# specifies the delivery agent, tcp in this rule. sendmail must now find Mtcp and use its S= or R= rules, depending on whether it is resolving the sender address or the receiver address. $# is the first part of a three-part rule. The second part uses $@ to tell sendmail the name of the receiving host. The third part begins with $:, to identify the receiving user. Assume that the matching address found by the rule's first field is name<@host>. This rule makes the delivery agent tcp, makes the host host, and makes the user name<@host>.

Consider that host is the name of the local system. This rule contains nothing to force mail delivery to the hub system. Delivery agent tcp happily delivers to the local host just as well as it delivers to any host. Look two rules further down in sendmail.cf and you'll find another rule with an identical first field (R$+<@$+>), but it's commented out. This rule's comment in the third field says that the rule relays to SMTP. Without the comment, the rule looks like this:

R$+<@$+>        $#tcp$@$S$:$1<@$2>      user@domain to SMTP relay

Look carefully at the second field. It differs from the previous rule in only one way: it uses $S instead of $2 as the receiving host. That's the S macro. With the S macro's value set to the mail hub's host name, tcp will route it to that hub system. The local system will have nothing more to do with it.

To set this up, comment out the local routing rule containing $@$2 and uncomment the SMTP routing rule containing $@$S instead. So far so good, but local routing hasn't been eliminated.

No More Local Routing

At the end of ruleset 0 is the catchall rule that nothing can get past:

R$+     $#local$:$1     name

The first field contains only $+, to match any incoming address that has at least one token. Every address has at least one token, except an error address typically caught early in sendmail processing by another rule. If some earlier rule hasn't intercepted and modified the address by now, this catchall rule gets it.

According to the catchall rule's second field, an incoming address is submitted to the local delivery agent ($#). No host ($@) is specified, only a user ($:), composed of the entire matching incoming address, is specified. You can find the local delivery agent's handler if you search for the regular expression "^Mlocal" in sendmail.cf.

You'll need to comment out this local rule by starting this rule line with a # sign. If a plain name comes along, it could be a user name or a mail alias. Either way, the mail hub must receive it and figure that out, not the local system. This ultimate, catchall rule must submit the address to the tcp delivery agent. Add the following new rule:

R$+     $#tcp$@$R$:$1   Forward to mail hub

This new catchall rule uses an R macro, defined as

DRmailhost

where mailhost is literally the alias name of the mail hub host. I recommend placing this definition near the S macro's definition.

Strictly, I could have used the S macro in this rule, too. Initially, I tried setting S to mailhost and using $S throughout the changes outlined in this article. Unfortunately, sendmail didn't cooperate. While this catchall tcp rule did work with mailhost, other rules wouldn't accept that mailhost alias. While debugging the problem, I defined the R (for Remote) macro to use mailhost and set the S macro to the host's primary name. This worked. Not wanting to challenge Murphy any further, I kept it this way. If you're uncomfortable with yet another macro, substitute $S for $R. If anyone knows a rule combination that uses the mailhost alias throughout, please write.

The tcp delivery agent will receive all that previously went to the local delivery agent. With a receiving host specified by $R, tcp will route the address to the mailhost. Local systems using these rules will not attempt any further processing of the address except according to the tcp agent's rules.

There's no need to modify or comment out the local delivery agent or its rules. Some X.400 rules still use the local agent, but the HPs I administer aren't using those.

Fixing the tcp Delivery Agent

Find the tcp delivery agent line (search for regular expression "^Mtcp"). The receiver rule, which modifies the receiver's address, is identified with R=21 in my HP sendmail.cf file. Ruleset 21 (search for regular expression "^S21") should be several lines below the tcp delivery agent line.

Ruleset 21 consists of only two rules. The first rule handles addresses already containing a domain name. Such addresses didn't come to the tcp delivery agent from the catchall rule that sends addresses to the local agent. The second rule is important for routing to the mail hub. Any addresses that used to go to the local agent get caught by this second rule. Before my modification, this rule read:

R$+     $:$1<@$w>       add local domain

As before, the first field ($+) acts as a catchall. The $: at the beginning of the second field doesn't have the same interpretation as it has in the three-part $# set. Here, $: means "Rewrite Once." Typically, rules are applied repeatedly, as the input data needs. Using $: prevents more than one application of the same rule. Once this $: rule finishes rewriting the address, sendmail must go to the next phase.

A sendmail intrinsic macro, $w, is set to the local system's host name. If larryr is the matched token, this rule turns it into larryr<@localhost>, where localhost refers to the local system's host name. When a sender logged in on system1 mails to larryr, this rule changes larryr to larryr<@system1>. This is definitely wrong for mail hub routing. This rule must refer to the mail host, not the local host.

To fix this, comment out the existing rule that uses $w and add the following new rule:

R$+     $:$1<@$S>       add mailhost

The only difference in this rule is its use of $S. Again, I would have liked to use mailhost in $R but that didn't work here.

Conclusion

Once I had finished and tested all sendmail.cf modifications, I rcp'd /usr/lib/sendmail.cf to all the other HP systems that will route to the mail hub. With these changes in place, both mail addressing problems were solved.

All sender addresses appear to originate from the company's domain name server. Replies go back to that name server, which knows how to get to the mail hub for proper routing to the user's central mail file.

All receiver addresses, whether including domain references, using simple user names, or using mail aliases, are routed to the mail hub. As a side effect, local systems don't have to know the mail aliases. Any alias is routed to the mail hub. That mail hub resolves the alias and routes the mail. When given an unknown alias, the hub system has a proper sender address to return the mail.

Afterword

I strongly recommend the book sendmail, by Bryan Costales, published by O'Reilly and Associates, 1993, ISBN: 1-56592-056-2. This book was indispensable. Documentation on sendmail is hard to find and sometimes virtually unreadable. HP-UX's sendmail.cf has extensive comments that help, but often each answer raises more questions.

Costales' book gives detailed instructions on how sendmail works, explains the meanings of sendmail's macros, rules, and other definitions, identifies debugging methods, and includes handy tables throughout. The index is thorough, with every intrinsic macro and expression symbol identified. At times, the index acted as a quick reference list for me.

Finally, sendmail doesn't suffer from the SunOS bias that several O'Reilly books have. As much as I like Suns, there are other systems out there and I work on a variety of them. Costales' book details variations in sendmail across many versions. Without this book, I couldn't have solved the problems outlined in this article as quickly.

About the Author

Larry Reznick has been programming professionally since 1978. He is currently working on systems programming in UNIX, MS-DOS, and OS/2. He teaches C, C++, and UNIX language courses at American River College and at the University of California, Davis extension.