Configuring mhn

[previous] [next] [table of contents] [index]

MIME messages are handled by the mhn command. You can change the default configuration of mhn by editing the profiles that it reads. You can configure mhn through two personal profiles; a system-wide profile is used if yours don't have the entries that mhn needs. This section has detailed information and examples of the entries you can put in the profiles.

The Section How mhn Shows a Message has an introduction that you may want to read before you get started with the detailed explanation here.

Profiles that mhn Reads

Like all MH commands, mhn reads an MH profile entry with its own name, mhn:. That entry sets command-line options you want to use every time mhn runs. There's a list of those options in the MH Reference Guide.

But mhn also uses special profile entries that start with mhn-, like mhn-store-text/plain:. These can't be typed on the command line; they're only read from files. This section explains those special profile entries and how to set them.

mhn reads those entries from three files, in this order:

  1. If you've set the MHN environment variable, mhn reads the file that it points to.
  2. If mhn didn't find the entry it needed in the MHN profile (or if there wasn't a MHN profile) it checks your MH profile. If the MH environment variable is set, mhn reads that file; otherwise, it defaults to the .mh_profile in your home directory.
  3. If it didn't find the entry in the first two places, mhn reads the mhn_defaults file in the MH library directory (often /usr/local/lib/mh).
The order that mhn reads those profiles is important.

The person who installed MH on your host also installed the mhn_defaults file. It should have global entries, reasonable defaults for most accounts on the system to use. If you're lucky, mhn_defaults contains entries for all the specialized programs mhn needs to handle different content types. Those programs may be scattered around your filesystem.

The entries in mhn_defaults may not be right for you, though. For instance, most of your system's accounts may use the X Window System, but you always work on an old ASCII terminal. The default profile entries that start X programs will just print errors on your ASCII terminal. In that case, you can add entries to your MH profile that work on your terminal; mhn will use these entries instead of the entries in mhn_defaults. (You don't have to duplicate the mhn_defaults entries that are correct for you. For instance, if the mhn-show-text/enriched: entry in mhn_defaults works on your ASCII terminal, there's no need to put another mhn-show-text/enriched: entry in your MH profile.)

Or, you may use several different types of terminals. You could have X at work and a terminal emulator program on your PC at home. The system defaults might be right on your X system, but not on your PC. So, you'll set the MHN environment variable when you're at home; it can point to an alternate profile with entries that your PC needs.

Of course, a mixture of profile entries might be right for you: some in your MH profile, some in an MHN file for one of your displays, another in a second MHN file for another situation, a third MHN file that's read by a custom shell script you've set up. Who knows? :-)

Making an MHN Profile

If you need the special profile(s) that the MHN variable points to, read this section. It has a few ideas for setting up your account. Remember that, if you only log in from one type of terminal or window system, you probably won't need to set MHN -- you can probably add all the mhn profile entries to your MH profile.

Here's the basic idea. As a process starts, the UNIX environment variables set in the parent process are copied to the child process. The MHN environment variable must be set in the parent process of mhn.

In most cases, the right place to set the environment variable is the startup file, like .login or .profile, that your login shell reads. But if you run MH (and mhn) from a process that isn't started by your login shell -- for instance, in some window systems or as a shell script that runs from cron(8)-- you'll need to set the MHN environment variable there. X Window System users may want to use a file like .xsession.

If you'll have more than one MHN file, it helps to have a system for naming them. I put all of mine in my MH directory with filenames that include the TERM environment variable setting -- like mhn-prf.vt100 and mhn-prf.xterm. You may want to put all of them in a separate directory.

To set MHN automatically from your login shell, you'll need to decide what characteristics of your login session change from place to place. Have your shell setup file execute the correct command to set the environment variable:

setenv MHN /full/pathname/of/file    ...C shell

MHN=/full/pathname/of/file; export MHN   ...Bourne/Korn shell

Here are some ideas. A book about UNIX shells will give the overview of the constructs below. If you need help with nitty-gritty details of your particular system, ask your system administrator:

Mix and match those techniques for your situation. Remember that you can use UNIX links (the ln(1) command) to give the same file several different names. And there's one unfortunate problem: some systems give the answer Not a tty when you run tty(1) from inside backquotes (`tty`).

To be sure the environment variable is being set correctly, type one of the following commands just before you use show or mhn. You're set to go if you see the value you want (which might be an empty answer, if you don't want MHN set in this situation!):

% printenv MHN
/u/jerry/Mail/mhn-prf.vt100
   ...or...
% env | grep MHN=
MHN=/u/jerry/Mail/mhn-prf.vt100
And now you're ready for the real fun! ;-)

What Profile Entries Are There?

Start by finding your system's mhn_defaults file and reading it. These are entries you'll never have to duplicate -- unless you want to override them. Your MH profile (usually named .mh_profile) may also have some mhn entries -- including one, mhn:, for the mhn command itself.

Any profile may have six kinds of mhn profile entries. Entries that start with mhn-show- are used when showing messages. The mhn-compose- entries are used to compose a draft message content when the mhn directive doesn't give a filename. Entries starting with mhn-charset- are used to decide how to handle a particular character set. The mhn-storage: entry and entries starting with mhn-store- tell mhn where and how to store messages. The mhn-cache: and mhn-private-cache: entries tell where external body parts are cached. And a few systems will have the entry mhn-access-ftp: to handle FTP transfers of external body parts.

Here's part of an mhn_defaults file:

mhn-charset-iso-8859-1: xterm -fn '-*-*-*-*-*-*-*-*-*-*-*-*-iso8859-1' -e %s
mhn-charset-iso-8859-8: xterm -fn '-*-*-*-*-*-*-*-*-*-*-*-*-iso8859-8' -e %s
mhn-compose-audio/basic: cat < /dev/audio
mhn-show-application/PostScript: %plpr -Pps
mhn-show-audio/basic: %pcat > /dev/audio
mhn-show-image/x-pbm: %ppbmtoxwd | /usr/bin/X11/xwud -geometry =-0+0
mhn-show-image/x-pgm: %ppgmtopbm | pbmtoxwd | /usr/bin/X11/xwud -geometry =-0+0
mhn-show-image/xwd: %p/usr/bin/X11/xwud -geometry =-0+0
mhn-show-image/x-xwd: %p/usr/bin/X11/xwud -geometry =-0+0
mhn-show-image: %p/usr/local/bin/xv -geometry =-0+0 '%f'
mhn-store-application/PostScript: %m%P.ps
mhn-store-text: %m%P.txt
mhn-store-video/mpeg: %m%P.mpg
Each entry has two parts. The first part is the entry name, like mhn-store-text:. The rest of the entry has instructions for handling that content type. These instructions are called a string: Most of the entries have a UNIX command. Some entries have escapes like %p. Escapes are used for two things: If an entry holds a UNIX command, mhn passes the command to a Bourne shell for processing. This means you can use Bourne shell features: I/O redirection (operators like <, >, m&>n, and |), quoting, command substitution (backquotes), and so on. You can basically write a small shell script in the mhn profile entry. Or, of course, the entry can call a script written for a shell, Perl, Tcl, and so on.

Here's an example of what you can do. The mhn_defaults file above is installed on a SPARCstation named server with its own built-in audio. If I do a remote login to server to get my email, I don't want audio to be played and recorded on server; it could be hundreds of miles away from me. I want audio to be processed on the host I'm using, slug. So I've added the entries in the next Example to a MHN profile on server. They use the rsh(1) command to send audio across the network, to and from slug:

Example: Server profile entry to handle audio on a remote client

mhn-compose-audio/basic: rsh slug cat \< /dev/audio
mhn-show-audio/basic: %prsh slug 'echo "Sending audio to slug...";
    set t=/tmp/mhn$$; cat > $t; cp $t /dev/audio; rm -f $t'
If I run comp on server to send an audio/basic content, mhn will run the command rsh slug cat \< /dev/audio. Because the input redirection character is escaped (\<), the shell on server will ignore it; the redirection will be done on slug, and the cat command will run on slug and send the audio down the network to server. Next, if a message on server has audio to "show" (play), the profile entry outputs a note to remind me to wait (audio files can be big). Then it stores a temporary filename in a shell variable, sends the audio down the network to the temporary file on slug, and runs cp on slug to send the audio to slug's speaker. I used a temporary file to collect all the audio before playing it; this avoids network delays that could make the audio sound "choppy."

This example wasn't meant to impress you. :-) I wanted to show what you can do if you understand how UNIX shells work. For instance, even though the commands in mhn profile entries are interpreted by the Bourne shell, my account on slug uses the C shell... so I knew that the rsh commands had to use C shell syntax. Your entries probably won't need to be this complex -- but it's nice to know what mhn lets you do!

Next, let's take a close look at each kind of profile entry. The first section below is a tutorial. Sections near the end provide reference information.

Showing Contents: mhn-show-

The Section How mhn Shows a Message introduced the way that mhn displays the message header and body. This section explains, in detail, how the body contents are displayed -- and how you can change the way it's done.

After showing the header, mhn shows the body parts one-by-one. Or, for a multipart/parallel message, mhn will show parts at the same time unless a %e escape (listed in the Table Display String Escapes) prevents it. In a MIME message, each body part has a content-type. To display a content, mhn runs a UNIX command. It finds the commands in profile entries, as explained in the Section Profiles that mhn Reads.

If mhn can't find a profile entry, it has two defaults: the moreproc: entry (in your MH profile) is used for text/plain contents, and the MH show command is used for message/rfc822.

(Yes, mhn can run show. If the message/rfc822 content is itself a MIME message, show can start mhn again. MIME messages can contain other MIME messages, so this behavior makes sense.)

If mhn runs out of choices, it complains and quits with an error like:

mhn: don't know how to display content
     (content application/x-foobar in message 6, part 2)
You might want to add defaults for all content-types -- as a fallback, in case there isn't a command for a particular subtype. Be careful that the defaults won't do anything unexpected with subtypes you haven't heard of (like x-foobar above). Because all MIME-encoded messages are 7-bit ASCII text (for now, at least), a safe way to display unknown content-types is with a pager -- for instance, your moreproc. Then, if mhn shows you garbage, you can just quit the pager and go on to the next part of the message.

(Although I said "all MIME messages are 7-bit for now," some companies use 8-bit or binary content, and more will do that as mail transfer starts to handle 8-bit data. In that case, be sure that the default display program you choose can handle any 8-bit data (including characters like NUL) without locking up your terminal or causing you other grief. It might be better to let mhn complain and quit instead of trying to handle all defaults.)

CAUTION: Some graphic file formats may contain commands that can be dangerous security holes. For instance, PostScript files can hold commands that delete other files.

File viewers are beginning to have security features built in. For instance, GNU ghostscript has a -safer option to disable the PostScript file-deletion "feature." I can't possibly tell you how safe every file viewing program is. But, if you're concerned about security, you should check before you use an unknown file viewer to display an unknown content.

Here are three example entries to get you started:

mhn-show-image: %pxv '%f'
mhn-show-audio/basic: %lraw2audio 2>/dev/null | play
mhn-show-video/mpeg:
Let's look at those three entries.
  1. The first entry displays image content-types if there's no entry for the particular subtype used in your message. For example, let's say that your message has an image/x-pbm part, but the only other entry is for image/gif. mhn would default to the mhn-show-image: display string for general image types.

    mhn would decode the message part (for example, translate 7-bit base64 into an 8-bit binary file) and store the decoded part in a temporary file. Then mhn would use the first entry, replace %f with the temporary filename, and run the command xv 'tempfile'. (xv is a great viewing program for the X Window System; it knows how to display a lot of graphic file formats.)

    The %p in the display string above is an escape that tells mhn to list content information on the terminal (like mhn -list would), then prompt and wait:

    part 3     image/x-pbm                40K Sample waveform
    Press <return> to show content...
    
    When you see that prompt, you can skip the part (not run xv to display it) by pressing your interrupt key -- often, that's CTRL-C. Or, you can skip the rest of the message and get back to a shell prompt by pressing your QUIT key -- typically CTRL-\. If you don't want mhn to pause or print the description of the part, don't put %p in the display command. You also can prevent the pause by giving mhn its -nopause switch.
  2. In the second example, there's no %f to stand for the temporary file. So mhn feeds the decoded part to the standard input of the command line given. As always, mhn starts a Bourne shell to execute the command line. (So, the Bourne shell's 2>/dev/null redirection operator throws away the standard error from the raw2audio command.) In this example, the decoded raw audio data will be fed to the standard input of the raw2audio program. The standard output of raw2audio is piped to the play program.

    The escape %l tells mhn to give a listing, like mhn -list would, before it runs the "display" command. Unlike %p, the %l doesn't prompt and wait for you to press RETURN. So this command will print a one-line listing of the part, then play the sound immediately.

  3. The third entry is empty. It makes mhn complain "don't know how to display content" when you try to display video/mpeg. Use an entry like this in your personal profile when the system default isn't what you want -- and you don't have anything else acceptable.

NOTE: A good general-purpose viewer for text, especially text with 8-bit characters, is less. Add moreproc: less to your MH profile. MH 6.8.3 comes with less version 177. The latest versions of less are even better at handling international text. Check an up-to-date archive site like ftp.uu.net or ask Archie.

If you use less, read its manual page for configuration information. In less version 177, you can display the ISO-8859-1 character set by setting the LESSCHARSET environment variable to latin1 and the LESS environment variable to r.

Now is a good time to edit your MH profile (or an MHN file, if you'd rather) and experiment. The steps below will lead you through the basics of display strings.

The examples above showed the %f, %l and %p escapes. The Table below lists all of the escapes for display strings.

Table: Display String Escapes

%a
Insert parameters from Content-type: field.
%d
Insert content description.
%e
Exclusive execution.
%f
Insert filename with content. Take standard input from content.
%F
Insert filename with content. Take standard input from terminal. Exclusive execution.
%l
Display listing, then display content.
%p
Display listing, prompt, then display content.
%s
Insert content subtype.
The %a, %d, and %s escapes are the most straightforward. They pass information about the content to the display program. Let's start with a simple profile entry. Add the following entry to your profile. The command head -3 should display the first three lines of the content. If your system doesn't have the head(1) command, use the command sed 3q instead:
mhn-show-text/plain: %phead -3
Find a junk text/plain message that you'll want to remove. If your folders are full of various MIME messages, use mhn -list to find a message that has the content types you want. (If you don't have any, send yourself one now.) When you show the message, you should see the header, the prompt from %p, and the first three lines of the body:
% show
(Message inbox:172)
Date:    Thu, 25 Aug 1994 06:37:23 PDT
To:      Randy Wyse <randy@xmaaw.com>
From:    Jerry Peek <jerry@ora.com>
Subject: Re: Book outline
MIME-Version: 1.0

part       text/plain                2441
Press <return> to show content...
First three lines
of the body
appear on your screen
%
Of course, that's not a useful way to display most messages: you'd usually like to see all of the content. But don't delete that entry in your profile yet.

Notice that you didn't give a filename to that head command. mhn passed the content to the standard input of the head command. You can give a filename wih the %f or %F escapes. We'll try that next.

Let's add a more complex entry to your profile. This one runs the pr(1) command, which formats a file for printing. The pr -h option specifies a heading you want to print at the top of a page. But your entry won't send the content to a printer. Instead, it will display the pr-formatted version on your terminal -- using the more(1) pager program. If you don't have more, substitute the pager you use -- like pg or less -- in the command. Be sure to use the escape %F (uppercase letter "F"), not %f:

mhn-show-text: pr -h '%d (content-type: text/%s)' '%F' | more
That display string is a command like one you might type at a shell prompt. In fact, before you show a message, try typing that command at a shell prompt to see what it does. Replace the %F escape with the filename of a text file in your current directory:
% pr -h '%d (content-type: text/%s)' 'somefile' | more

Jul 31 09:17 1994  %d (content-type: text/%s) Page 1

    first screenful of somefile appears...

 -- More -- 
Because mhn wasn't interpreting the %s and %d escapes, the pr command displays them literally in its heading.

Next, let's see if we can make that profile entry work on a real message. (Note: I'm trying to surprise you here.) Let's show the same message with the content-type text/plain that you did in the previous example:

% show
    ...
What happens? You should see the first three lines of the body; that's all. mhn should not have used your new mhn-show-text: display string! Why? Because your profile entry, mhn-show-text:, is the default for text contents. It's used when there isn't a profile entry for the particular subtype (in this example, text/plain). But you also have a mhn-show-text/plain: entry, so it's used instead.

Now, let's make your profile entry take effect. Change the content-type of the message by editing it with your favorite editor (emacs, vi, etc.):

% emacs `mhpath cur`
(The `mhpath cur` command, in backquotes, puts the absolute pathname of the current message on your editor command line.) Find the Content-type: header field and change it to the unlikely value text/x-haha. If there's a ; charset=something parameter on that Content-type: header field, remove it. Also, if the message doesn't have a Content-Description: field, add one. The two fields should look like this:
Content-type: text/x-haha
Content-Description: Test message!
Okay? Now your test message has a different content-type that (probably!) no other profile entry will match. Let's try it:
% show

Jan 09 09:17 1995  Test message! (content-type: text/x-haha) Page 1

    first screenful of content appears...

 -- More -- 
Please do not read the rest of the message yet. Think: Which command is in control of your display at this moment? It's the command started by the shell, which was started by mhn. The shell's command line looks like this:
pr -h 'Test message! (content-type: text/x-haha)' 'filename' | more
mhn filled in the escapes as the Table Display String Escapes showed: %d got the description, %s got the subtype, and %F got the temporary filename (shown here as filename) that mhn chose for holding the content.

The more pager is printing a prompt. You can give more's "quit" command (usually, q) to make the pager stop. Or, you can finish displaying the message and quit the pager. You wouldn't use mhn's "quit" command (CTRL-C or CTRL-\) at the -- More -- prompt because mhn isn't in control now. When your display command finishes, mhn is in control again: it can show the next part of the message, if any, or quit.

Ready for another example? Before you get started, make a note of the message number you used in the previous example. You'll be using it again in the Section Displaying Other Character Sets: mhn-charset-.

The Metamail package is a group of programs for working with MIME mail. If it isn't on your system, The Section Metamail explains where to get it. mhn does some of the things that Metamail was written to do -- mhn knows how to create and understand MIME messages and how to deal with multipart contents. But mhn doesn't know how to display other contents. For instance, mhn can't handle enriched text -- the text/enriched content type. The Metamail package comes with a program named richtext that was written for handling the obsolete text/richtext content. The -e option makes versions 2.7 and above of richtext display enriched text.

Let's make a profile entry that runs richtext -e. You may already have a profile entry for displaying enriched text. Search for it with grep -i, which ignores the difference between upper- and lowercase letters as it searches. Fill in the filenames below (though you can leave $MHN as it is, if you've created that file; your shell will replace it with the filename from the environment variable):

% grep -i "text/enriched" $MHN .mh_profile /path/to/library/directory/mhn_defaults
If you didn't see a matching line, you're ready to go. If you saw a matching line in your system mhn_defaults file, remember that your personal entry will override it. Make an entry like the one below. If the richtext program isn't in a standard system directory (in your shell's search path), you'll need to use an absolute pathname:
mhn-show-text/enriched: %prichtext -e -t '%F'
The -t option is just for this exercise. It puts asterisks around bold text (*bold*) and underscores around italic text (_italic_).

Now, you'll need an enriched text message. You can search for a non-multipart message by using pick and its --content-type switch. If that doesn't match a message, you can search all the messages, multipart included, with pick -search:

pick --content-type text/enriched   ...single-part messages
pick -search "^content-type: text/enriched"   ...multipart, too

No luck? Just create a simple enriched text message with a text editor. Section Sending MIME Mail has an example. Include at least one word in boldface and one word in italic text.

Next, show the message. The %p escape should make mhn pause before the text/enriched content. The whole body should be shown at once, without stopping, because you haven't used a pager. richtext has a simple pager built in; you can get it by using the -p option. Now that you've seen what the -t option does, you can remove it (unless you have a dumb terminal, in which case you might want to keep it). If you have the richtext(1) manual page on your system, check it for other interesting options that you might want. Your entry should look something like this:

mhn-show-text/enriched: %prichtext -e -p '%F'
Try it again. Optimize the boldface and italic handling for your display. If you have other displays, you can try showing the message there, too; if you need different settings, make a MHN profile for one display or the other.

I haven't shown the %a escape, the Content-Type: parameters, yet. There's an example in the getrich script in Section Composing Content: mhn-compose-.

At this point, if you haven't read the mhn(1) manual page, you're ready to read the first few pages of it. Read through the end of the section called "Showing the Contents." It mentions character sets, which will be a good introduction to the next section of this tutorial.

Displaying Other Character Sets: mhn-charset-

Not everyone in the world uses the same character set you do (whatever that is). English-speaking people probably use the us-ascii character set, a series of 128 characters that include every letter used in the English language. Other people use other character sets. One of the most common is iso-8859-1, a 256-character set that handles many non-English languages.

(MH has other support for "international" characters -- that is, non-English characters. See Section International Character Support.)

There are three things mhn needs to know to work with multiple character sets:

For comparison, here are two typical mhn-charset-iso-8859-1: entries:
mhn-charset-iso-8859-1: xterm -fn '-*-*-medium-r-normal-*-*-120-*-*-c-*-iso8859-1' -e '%s'

mhn-charset-iso-8859-1: %s | iso2asc 0
Both use the %s escape; it is required. The first one starts xterm, an X Window System terminal emulator. This opens a new window on your X display with a font that includes all iso-8859-1 characters. (Although I haven't seen the problems myself, I've heard that some X fonts which are labelled as iso8859 don't contain all of the ISO-8859 characters.) The second entry is for people who can't display iso-8859-1 because they have an ASCII-only terminal. It uses the iso2asc ISOto-ASCII converter program. (If iso2asc isn't on your system, you can get a copy from ftp://ftp.ora.com/published/oreilly/nutshell/MHxmh/iso2asc.tar.Z.

To set up for the next exercise, please put the following two entries in your profile. (If you did the tutorial in the previous section, you probably already have the text/plain entry.)

mhn-show-text/plain: %phead -3
mhn-charset-x-upperlower: %s | tr '[a-z]' '[A-Z]'
If you know the UNIX tr(1) command, the second entry should look familiar. It translates all lowercase characters to uppercase.

Next, edit the text/plain message you used in the previous section. Change the Content-type: field to read:

Content-type: text/plain; charset="x-upperlower"
If the message body has any non-ASCII characters, please delete them for this example. (Sorry.) The message should have only upper- and lowercase English letters (A-Z and a-z), digits and punctuation.

Now on to the example. "What is this x-upperlower?," you're probably asking. To make a simpler example for people who are new to the idea of displaying other character sets -- and to be sure no one can accuse me of character-set favoritism :-) -- I've just invented a new character set named x-upperlower. The x-upperlower character set has all the English letters, both upper- and lowercase. (Actually, it's the us-ascii character set. But we'll ignore that.)

Here's our problem in this exercise. You'll play the part of a person with an old computer display. Your terminal can't display lowercase characters. It's ALL UPPERCASE. When people with these old terminals display a message that has this field:

Content-type: text/something; charset="x-upperlower"
they can't see the lowercase letters. They need a mhn-charset-x-upperlower: profile entry to translate the lowercase letters into something they can see: that is, to translate the lowercase letters into ALL UPPERCASE.

Show the text/plain message that you just edited. The content (the body, that is) should come out ALL UPPERCASE. That's because the message has the parameter charset=x-upperlower in it. But the MM_CHARSET environment variable on your terminal isn't set to x-upperlower. So, mhn uses your mhn-charset-x-upperlower: profile entry to translate the x-upperlower characters to a character set you can see. (Please ignore those lowercase letters in the header. ;-) The MIME RFC 1521 spec only applies to the message body.)

% show
(Message inbox:172)
Date:    Thu, 25 Aug 1994 06:37:23 PDT
To:      Randy Wyse <randy@xmaaw.com>
From:    Jerry Peek <jerry@ora.com>
Subject: Re: Book outline
MIME-Version: 1.0

part       text/plain                2441
Press <return> to show content...
FIRST THREE LINES
OF THE BODY
APPEAR ON YOUR SCREEN IN UPPERCASE
%
To build its command line for displaying the message in a different character set, mhn:
  1. Finds the mhn-show- entry for this content type (here, mhn-show-text/plain:).
  2. Finds the mhn-charset- entry for this character set. It replaces the %s with the command to show the content.
In this example, the command line mhn uses with your test message is:
head -3 | tr '[a-z]' '[A-Z]'
(Also look back at the profile entries, if you'd like.) The head -3 command displays the first three lines of the content. That output is piped to tr '[a-z]' '[A-Z]', which translates the content into uppercase for your ancient uppercase-only terminal.

Now, let's say you've moved to a terminal that can show both upper- and lowercase letters. To tell mhn about this, you need to set the MM_CHARSET environment variable to the name of your character set -- that is, to x-upperlower:

setenv MM_CHARSET "x-upperlower"    ...C shell

MM_CHARSET="x-upperlower"      ...Bourne/Korn shell
$ export MM_CHARSET

Now when you show your text/plain message, two interesting things should happen. Try it:

% show
(Message inbox:172)
Date:    Thu, 25 Aug 1994 06:37:23 PDT
To:      Randy Wyse <randy@xmaaw.com>
From:    Jerry Peek <jerry@ora.com>
Subject: Re: Book outline
MIME-Version: 1.0

All the lines
of the body
appear on your screen
with no "Press <return>..." prompt.
%
Because your terminal can display x-upperlower, and because the message is plain text, MH doesn't need a special display command to show it on your terminal. It ignores the %phead -3 command for text/plain. It shows the message as it would show a non-MIME message. In fact, show doesn't even invoke mhn for this message.

If you want to see what mhn would do with the message if show had invoked it, try it. (Just type mhn at the shell prompt.) mhn should use your mhn-show-text/plain: profile entry, prompt you to Press <return>..., and run %phead -3 to show the first three lines.

To avoid confusion later, you should probably take the mhn-show-text/plain: display string out of your profile. Also set the MM_CHARSET environment variable to the character set your terminal can actually handle -- for example, us-ascii or iso-8859-1.

Composing Content: mhn-compose-

The Sections Sending MIME Mail and Composing and Sending MIME Messages introduced message composition with mhn. If you haven't used mhn from a What now? prompt, please do that before you work through this section of the tutorial. In this section, to show you more of what happens "under the hood" as mhn composes a draft, you'll start by running mhn from a shell prompt instead of from the usual whatnow program prompt. Doing things this way should also help people who want to use mhn to build draft messages for their own email programs.

If you use a window system, open a new window for this part of the tutorial. Otherwise, start a subshell by typing the name of your shell at the prompt. For example, if you use the C shell:

% csh
%
The separate window, or subshell, will make it easy to undo the changes to your environment in this tutorial.

Make a little shell script named showenv that shows the UNIX environment. If your system has the env(1) command, use it; otherwise, you probably have printenv(1). Put that command in the showenv file and make it executable. Then run the shell script:

% vi showenv
...put these two lines in the file:
#!/bin/sh
env
% chmod 755 showenv
% showenv
DISPLAY=:0.0
   ...
VISUAL=/usr/ucb/vi
(If the current directory isn't in your search path, you may need to type ./showenv to run the script.)

Next, compose a draft message in the usual way, with the comp program. Make a multipart message with three parts: enriched text entered from the draft (a #< directive), a non-text file, and the output of the who(1) program. Then get to a What now? prompt and wait; do not run mhn to encode the draft.

% comp
To: someone
cc:
Subject: Testing mhn
------
#<text/enriched
The stuff in this message is <bold>junk</bold>,
just some stuff I'm using to test <italic>mhn</italic>.
#text/plain [The output of who(1)] |who
#application/octet-stream [The who(1) program binary] /bin/who



CTRL-D

What now?
At this point, the whatnow program is prompting you. If you haven't read the overview of what's happening at this point, you should probably look at the Section What now? -- and the whatnow Program. Now, a question for you: What makes mhn act the way it does when you run it from a What now? prompt? (Note: don't run mhn yet!) When you run mhn from a shell prompt, it displays a message. But when you run mhn from a What now? prompt, it encodes a message. What's happening? The difference is in the UNIX environment set by the whatnow program. Let's look at the environment. Tell whatnow to run your showenv script:
What now? edit showenv
DISPLAY=:0.0
    ...
mhdraft=/u/joe/Mail/drafts/3
    ...
(If you get a "command not found" error, use a pathname -- for example, edit ./showenv.) The whatnow program has stored the absolute pathname of the draft file in the mhdraft environment variable. When you run mhn from within whatnow, mhn sees the mhdraft environment variable and becomes a message-encoder instead of a message-displayer.

Now quit and leave your unencoded draft message where it is. Then change your current directory to the one where your draft file is. (Note that older versions of whatnow don't show the draft pathname as they quit; if you don't know where your draft is, look back at the value of the mhdraft variable.) List the directory and look for your draft:

What now? q
whatnow: draft left on /u/joe/Mail/drafts/3
% cd /u/joe/Mail/drafts
% ls
...    3   ...
If you run showenv again, you'll see that the mhdraft variable isn't set. Because we need mhn to encode the draft, add mhdraft to your environment. Use the pathname to your draft message:

$ mhdraft=/u/joe/Mail/drafts/3   ...Bourne/Korn shells
$ export mhdraft

% setenv mhdraft /u/joe/Mail/drafts/3    ...C shells

Now you're ready to encode the draft. Run mhn:

% mhn /u/joe/Mail/drafts/3
composing content text/plain from command
        who
If you get errors -- for example, mhn can't read the file /bin/who because your system administrator has denied read permission -- edit the draft file and change the directives. (Use your favorite editor and the draft filename, like this: vi 3.) Then rerun mhn -- and be sure you don't get the error.

Now list your directory again. You should see the encoded draft and another file: the original draft with the directives in it. Read them both with a pager program like more(1):

% ls
...   ,3.orig  ...  3   ...
% more ,3.orig 3
::::::::::::::
,3.orig
::::::::::::::
To: someone
Subject: Testing mhn
------
#<text/enriched
The stuff in this message is <bold>junk</bold>,
just some stuff I'm using to test <italic>mhn</italic>.
#text/plain [The output of who(1)] |who
#application/octet-stream [The who(1) program binary] /bin/who
 -- More-- (Next file: 3)
::::::::::::::
3
::::::::::::::
To: someone
Subject: Testing mhn
MIME-Version: 1.0
Content-Type: multipart/mixed; boundary="----- =_aaaaaaaaaa0"
Content-ID: <904.783368683.0@joebob.kermit.tx.us>

------- =_aaaaaaaaaa0
Content-Type: text/enriched; charset="us-ascii"
Content-ID: <904.783368683.1@joebob.kermit.tx.us>

The stuff in this message is <bold>junk</bold>,
just some stuff I'm using to test <italic>mhn</italic>.

------- =_aaaaaaaaaa0
Content-Type: text/plain; charset="us-ascii"
Content-ID: <904.783368683.2@joebob.kermit.tx.us>
Content-Description: The output of who(1)

joe      console Oct 28 06:23
joe      ttyp0   Oct 28 06:24   (:0.0)
joe      ttyp1   Oct 28 06:24   (:0.0)
joe      ttyp2   Oct 28 06:24   (:0.0)

------- =_aaaaaaaaaa0
Content-Type: application/octet-stream
Content-ID: <904.783368683.3@joebob.kermit.tx.us>
Content-Description: The who(1) program binary
Content-Transfer-Encoding: base64

gQMBCAAACZAAAARAAAAASAAAAAAAACAAAAAAAAAAAAC8ECAA0AOgQJIDoESVKiAClAKgBJQCQAoX
   ...
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==

------- =_aaaaaaaaaa0--
Now assume you've changed your mind about your draft. You'd like to include another part: your signature file. Editing the draft (the file 3) can be a hassle; it isn't always easy to create MIME-format messages by hand. But you can replace the encoded draft with the original file that has the directives. Then edit the draft and re-run mhn. Let's do it:
% mv ,3.orig 3
% vi 3
    ...add this directive to the end:
#text/plain [signature] /u/joe/.signature
% mhn /u/joe/Mail/drafts/3
composing content text/plain from command
        who
% more 3
    ...re-encoded draft appears...
That's enough experimenting with mhn at a shell prompt. Unless you want to keep working from a shell prompt, close the separate window or end the subshell by typing exit:
% exit
%
You should be back at your original shell in the original current directory. The mhdraft environment variable should not be set. (If you want to check, run showenv again.) Now you're ready to experiment with mhn directives.

Let's start by looking at the type directives, #type/subtype. (If you need a reminder of the syntax, check Tables 1-3 in the Section Syntax of mhn Directives.) mhn gets the content for those directives by looking at the last argument:

The Table below lists the escapes that work in composition strings.

Table: Composition String Escapes

%a
Insert parameters (example: x-conversions=compress) from directive.
%f
Insert temporary filename where content should be written.
%F
Insert temporary filename where content should be written. Don't redirect standard output to content.
%s
Insert content subtype.
To make some use of those parameters, let's assume that you have a program for entering and editing enriched text. You'd like mhn to run your editor, let you compose the part, and then insert the edited part when the editor quits. Call the editor program getrich.

How do you give the editor a filename to edit from the composition string? The %f and %F escapes do that. Both escapes give the filename, but one of them changes the way that mhn handles output. Normally -- if you don't use any escapes, or if you use any escape except %F -- mhn directs the standard output of the composition command to the message content. This is good; it lets the output of the command be sent in the mail message. But you won't want that to happen when you use an interactive text editor. If it did, you couldn't see the screen or prompts that the editor makes: those would be redirected into the content, instead of shown on your screen! Notice in the previous Table that the %F escape is replaced with a filename and it tells mhn not to redirect the composition command standard output to the content. That's what you want in a case like this.

Add a composition string to your profile that runs getrich. The script will need some parameters, so add the escapes shown:

mhn-compose-text/enriched: getrich '%F' %a
If getrich was supposed to make more than one subtype (as in an mhn-compose-text: composition string), it would probably need to know what subtype it should produce. In that case, you would also put a %s parameter in the composition string. In this example, though, %s would be useless because getrich will always make text/enriched content.

To get started, make a simple getrich that displays the parameters mhn passed to it and then outputs some dummy enriched text. (It won't start a text editor yet.) Here's the script. You can write it in Perl or some other language if you prefer:

#!/bin/sh
### getrich - create enriched text for mhn
### Usage (in mhn profile):   mhn-compose-text/enriched: getrich '%F' %a

# show command line arguments.  Write to standard error so 
# mhn won't add this text to the message content:
echo "getrich: got these arguments: $*" 1>&2
# Output two lines of enriched text:
echo 'This is a <bold>bold</bold> word from the "getrich" script.
This one is <italic>italic</italic>.' > "$1"
# Make sure mhn keeps the output:
exit 0
The two-line echo command at the end of the script writes two lines into $1, which is the filename from the first %F parameter. Use chmod +x to make the getrich script executable.

Next, make a short file named sampletext with two or three lines of enriched text in it.

Now, make a new draft with three different flavors of enriched text directive. One includes text you type into the draft, one reads the short enriched text you created above, and the third runs getrich. (If your directive doesn't give a pathname to another directory, mhn will read sampletext from the current directory.)

#<text/enriched [text typed into the draft]
The stuff in this part is <bold>junk</bold>,
some text I'm using to test <italic>mhn</italic>.
#text/enriched [text from a file] sampletext
#text/enriched [text from getrich]
After you run mhn, take a look at the draft. It should contain the text from all three parts.
What now? edit mhn
composing content text/enriched from command
        /tmp/getrich '/tmp/mhnb00987'
getrich: got these arguments: /tmp/mhnb00987

What now? list
   ...
------- =_aaaaaaaaaa0
Content-Type: text/enriched; charset="us-ascii"
Content-ID: <987.783374275.1@ora.com>
Content-Description: text typed into the draft

The stuff in this part is <bold>junk</bold>,
some text I'm using to test <italic>mhn</italic>.

------- =_aaaaaaaaaa0
Content-Type: text/enriched; charset="us-ascii"
Content-ID: <987.783374275.2@ora.com>
Content-Description: text from a file

This is sample enriched text from the <italic>sampletext</italic>
file.  Are we having fun yet???

------- =_aaaaaaaaaa0
Content-Type: text/enriched; charset="us-ascii"
Content-ID: <987.783374275.3@ora.com>
Content-Description: text from getrich

This is a <bold>bold</bold> word from the "getrich" script.
This one is <italic>italic</italic>.

------- =_aaaaaaaaaa0--

What now? quit delete
(If your lproc is set to use show when you list the draft, you won't see the encoded draft. In that case, you can see the encoded draft with the more(1) pager by typing edit more.)

The last line of getrich sets a zero exit status. This is important: if the composition command returns a nonzero status, mhn will abort and not encode the draft. If you'd like to experiment, change the exit 0 to exit 1, compose a new message for getrich and run mhn:

   ...
#text/enriched [output from getrich script?]
CTRL-D

What now? e mhn
composing content text/enriched from command
        /tmp/getrich '/tmp/mhnb00993' 
getrich: got these arguments: /tmp/mhnb00993
Exit 1

What now?
Let's hack getrich to use the parameters passed from the %a escape and to actually run a text editor. The order of the composition string parameters is important. You put the content filename (%F) first, before the optional parameters. The script will grab the filename from its first argument; the rest of the arguments will be parameters from the directive. Here's the revised getrich:
#!/bin/sh
### getrich - create enriched text for mhn
### Usage (in mhn profile):   mhn-compose-text/enriched: getrich '%F' %a

# Grab content filename:
file="$1"; shift
# If there are parameters, "handle" them:
for param
do
        echo "getrich: what is this '$param' parameter for??" 1>&2
done
# Create content by running enriched text editor (actually, vi).
vi "$file"
# Make sure mhn keeps the output:
exit 0
If you use a window system, you might change the editor line to open a separate window with a text editor in it. For instance, in the X Window System you could open a new xterm running the vi editor with:
xterm -e "vi $file"
Now compose a draft with a directive that will run getrich. Add some parameters if you'd like to. (If you don't add parameters, the for loop in getrich won't run to process the parameters -- and you won't see the messages about parameters.)
% comp
   ...
#text/enriched; x-temperature="98.6 F"; x-foo=bar [text from getrich]
CTRL-D

What now? edit mhn

composing content text/enriched from command
        /tmp/getrich '/tmp/mhna01003' x-temperature="98.6 F" x-foo="bar"
getrich: what is this 'x-temperature=98.6 F' parameter for??
getrich: what is this 'x-foo=bar' parameter for??
   ...editor starts on the empty /tmp/mhna01003 file
What now? list
    ...
MIME-Version: 1.0
Content-Type: text/enriched; x-temperature="98.6 F"; x-foo="bar";
        charset="us-ascii"
Content-ID: <1003.783375452.1@ora.com>
Content-Description: text from getrich

This is the enriched text I'm entering with <italic>vi</italic>.

What now?
When you list the draft, you should see the enriched text that you entered with the editor.

This simple version of getrich displayed the x-temperature and x-foo parameters you passed to it. The script could have used them for something -- for example, post-processing the output with a command like compress -- if you set it up that way. Notice that mhn also copied the parameters to the Content-Type: header field (and added a charset parameter, too).

Storing Content: mhn-storage and mhn-store-

As the Section Decoding and Storing MIME Messages explained, mhn can decode message contents and store them in a file. Note that storing contents isn't the same as the same as caching contents. The cache is used for local copies of external body parts; it's intended for mhn's use. On the other hand, storing contents has the same purpose as storing a standard UNIX file: so you can use the stored content with any program you want to.

Because the Section Decoding and Storing MIME Messages introduced mhn storage with examples from configuration files, this section will be mostly reference.

mhn -store uses the profile entry mhn-store-type/subtype:, otherwise it uses mhn-store-type:. If it can't find a profile entry, mhn tries these defaults:

The string can be one of several types, as the Table below shows.

Table: mhn Storage Formatting Strings

|command
Pass content to standard input of command. Can use escapes in Table mhn Storage Escapes.
+folder
Write content to folder (default: current folder)
@folder
Write content to subfolder folder (see the Section Relative Folder Names)
-
Write content to standard output of mhn (Bug: this does not work in MH 6.8.3. See the workaround in the mimecat script.)
pathname
Write file to pathname. If pathname starts with /, that directory is used; else, directory named by mhn-storage:; else current directory. Can use escapes in the Table mhn Storage Escapes.
The next Table lists the escapes you can use in the | (pipe) and pathname storage formatting strings.

Table: mhn Storage Escapes

%a
Parameters from Content-type: (only for |-type formatting strings).
%m
Message number
%P
Part number with leading dot. (Usually used in middle of formatting string.) Ignored if message not multipart.
%p
Part number without leading dot. Ignored if message not multipart.
%s
Content subtype.
Here are some examples.

Caching External Body Parts: mhn-cache and mhn-private-cache

When you run mhn (or show does), and the message has an external body part with a Content-ID: field, the content might be written to or read from a cache. The Section Cached Contents has a listing of a cache and examples of mhn using caches. This section has tips for configuring the caches.

Each user can access two cache directories, public and private.

The Public Cache

The public cache directory is usually named in your system's mhn_defaults file. That lets everyone on the system share the public cache. If you need to set a different public cache directory, put an mhn-cache: entry in your MH profile or MHN profile. And, of course, any user can ignore the public cache by setting the -rcache and -wcache switches to private or never.

If everyone on the system is allowed to write to the public cache, the system administrator needs to make its directory world-writable (mode 777). mhn will create all the cache files read-only (mode 444), but that doesn't stop people from removing cached files when they shouldn't. On many UNIX systems, the owner of a directory can set its sticky bit by using chmod 1777. On those systems, people will be able to add files to the directory, but they can't remove or rename files that were created by someone else.

Computers with networked filesystems can share a public cache between hosts. Any user on a host sharing that filesystem can save a public content. The mhn_defaults files on all those systems need to point to the right directory. Of course, if the network connections are slow, you may be undoing some of the benefit of cached contents: fast access to message parts without network delays.

The Private Cache

Each user can have her own private cache. Unless you name one in the mhn-private-cache: profile entry, the default will be a subdirectory of your MH directory named .cache. The default name starts with a dot (.) to "hide" it from the ls command. But the default cache will still show up in the folders command output. I put my cache somewhere else:

mhn-private-cache: /home/jerry/tmp/mhn-private-cache
If you don't want to use space in your home directory, you could try putting the cache in a subdirectory of a system temporary-file directory like /tmp/mhn-cache-yourname. Remember to protect the directory (chmod 700) if you might be storing private messages. Also remember that a cache directory in /tmp may be deleted or emptied by the system. That may be just what you want, of course. But if you want your cache to be more permanent, put it somewhere else and use the cleanup scripts below.

Cleaning the Caches

Cached copies of external body parts can get out of date. For instance, a cached copy of today's weather map probably wouldn't be much use by next week. mhn is good at writing to the caches, but it doesn't have a way to clean them up. This section has a few ideas.

The MIME standard, RFC 1521, describes an optional expiration= parameter for external body parts. The parameter says that after the date given, the content might be missing or invalid. Unfortunately, not all messages use that parameter. Even if they do, mhn doesn't save that date anywhere that I can see. Tidy users can get the expiration date from mhn -list -verbose and remove old cached contents. (If your system has many cache "housekeepers" like that, setting the sticky bit on the public cache directory might be a bad idea!) You'll probably still get plenty of old files in your caches.

Unless someone goes through the caches by hand every week or two to clean out old files, the best way to clean up is with a script run by cron(8) or at(1). The script can use find(1) to search for and remove files that are more than some number of days old. With its -mtime switch, find will remove the files based on the date they were stored. Or, if you use the -atime switch, the test will be on when the file was last accessed. If a cached file hasn't been accessed for a while, it might be a safe bet to remove. Finally, you might use the size of the file (find -size) as a factor. Big files take longer to copy over the network, so you could keep them longer. Of course, big files take more disk space, so you might want to delete them sooner. Whatever. :-)

If you have a better scheme for cleaning out caches, please tell me about it!

Getting External Body Parts by FTP: mhn-access-ftp

mhn doesn't run ftp(1) to get external body parts with ftp access types. Instead, mhn has its own build-in FTP code. The manual page says that if your host doesn't have a "sockets" interface to the Internet file transfer protocol (FTP), mhn will check the mhn-access-ftp: profile entry for an FTP program to run. That doesn't seem to be quite right -- at least, not for MH 6.8.3. My mhn seems to use the program named in mhn-access-ftp:, if there is one, before trying its internal FTP code.

Why would you want to write your own program? You might be on a site that's behind an Internet firewall -- and need access beyond the firewall. You might be on a site with no Internet access -- and need to send a request to an email-to-FTP gateway like ftpmail. Or your host might not support a "sockets" interface.

The mhnftpmail shell script below is an example of a program you can run from an mhn-access-ftp: profile entry. It uses the seven parameters passed from mhn to build an email message to an ftpmail server. You can customize it for any ftpmail server you want to use, including the popular (but overloaded) server ftpmail@decwrl.dec.com. The script is set up for Lee McLoughlin's ftpmail server from the host src.doc.ic.ac.uk. (It's explained in detail in O'Reilly & Associates' book Managing Internet Information Services.)

This example shows the script being called as I show a message. The script doesn't have to be this verbose: edit it if you'd like to.

% next
   ...
Retrieve book.catalog (content 1.2)
    using anonymous FTP from site ftp.ora.com? y
mhnftpmail: sending mail to 'ftpmail@online.ora.com':

open ftp.ora.com anonymous jerry@foobar.ora.com
binary
mime
chdir pub
get book.catalog

The 'Exit 1' error below makes sure mhn won't try to cache the missing response:
Exit 1
mhn: don't know how to display any of the contents
     (content multipart/alternative in message 55)
The script returns a nonzero exit status to be sure that mhn won't think that the FTP job succeeded. You'll have to manually handle the file that ftpmail sends.

Here's the mhnftpmail Bourne shell script:

#!/bin/sh
### mhnftpmail - submit ftpmail request for mhn
### Usage (in mhn profile):   mhn-access-ftp: mhnftpmail
# Customize this script for your site and ftpmail server.

mhmail=/usr/local/mh/mhmail     # Absolute path to mhmail program

ftpmail='ftpmail@online.ora.com'
#ftpmail=yourname@yourhost      # Uncomment for debugging

# Build message to send in $msg (multi-line) shell variable.
# arguments passed by mhn:  $1 - domain name of ftp site, $2 - username,
# $3 - password, $4 - remote directory, $5 - remote filename,
# $6 - local filename (not used here), $7 - "ascii" or "binary":
msg="open $1 $2 $3
$7
mime
chdir $4
get $5"

# Say what's happening (on standard error):
cat << END_OF_STUFF 1>&2
`basename $0`: sending mail to '$ftpmail':

$msg

The 'Exit 1' error below makes sure mhn won't try to cache the missing response:
END_OF_STUFF

# Send mail, exit with status 1 so mhn won't think we have the content.
$mhmail "$ftpmail" -body "$msg"
exit 1

The automhnproc

By default, when you compose a message, MH doesn't run mhn. If you forget to run mhn, you can send a message full of directives instead of the encoded body parts you want. The easy way to take care of this is by adding an automhnproc: profile entry. The program you name will be run when you type send or push at the What now? prompt. (If you use push, the automhnpoc runs before the message delivery is moved to the background. You'll be able to handle any interactive prompts or problems first.)

Most people probably use mhn:

automhnproc: mhn
You can use any program you want. For instance, if you've switched to MH from another MIME MUA, you could use the familiar Metamail program and your own .mailcap file. The program you use should be able to take the absolute pathname of the draft from its first argument, encode the draft, and write it back. It should also return a zero exit status if it succeeded; if whatnow gets a nonzero status from the automhnproc, it will prompt you again with What now?. So, there's nothing to stop you from writing a script -- in, say, Perl or the Bourne shell -- that does whatever you want (and, maybe, calls mhn when it's done). The mysend script might give you some ideas.

[Table of Contents] [Index] [Previous: An MH Profile, in General] [Next: International Character Support]


Last change $Date: 1996/06/06 15:09:28 $

This file is from the third edition of the book MH & xmh: Email for Users & Programmers, ISBN 1-56592-093-7, by Jerry Peek. Copyright 1991, 1992, 1995 by O'Reilly & Associates, Inc. This file is freely-available; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation. For more information, see the file copying.htm.

Suggestions are welcome: Jerry Peek <jerry@ora.com>

Questions or problems regarding this web site should be directed to Steve Gielda.
Copyright 1999 www.cotse.com.  All rights reserved.
Last modified: Friday April 02, 1999.