Mail: sending attachments

From Mailutils
Revision as of 10:32, 14 January 2017 by Gray (talk | contribs)
Jump to navigationJump to search
The printable version is no longer supported and may have rendering errors. Please update your browser bookmarks and please use the default browser print function instead.

This article describes how to create attachments using the mail utility. It makes special emphasis on using mail from scripts.

The simplest way to attach a file from command line is by using the --attach (-A) option. Its argument specifies the file to attach. For example, the following will attach the content of the file archive.tar:

 $ mail --attach=archive.tar

By default, the content type will be set to application/octet-stream, and the attachment will be encoded using the base64 encoding. To change the content type, use the --content-type option. For example, to send an HTML attachment:

 $ mail --content-type=text/html --attach=in.html

The --content-type option affects all --attach options that follow it. To change the content type, simply add another --content-type option. For example, to send both the HTML file and the archive:

 $ mail --content-type=text/html --attach=in.html \
        --content-type=application/x-tar --attach=archive.tar

Similarly, the encoding to use is set up by the --encoding option. As well as --content-type, this option affects all attachments supplied after it in the command line, until changed by the eventual next appearance of the same option. Extending the above example:

 $ mail --content-type=text/html --encoding=quoted-printable \
        --attach=in.html \
        --content-type=application/x-tar --encoding=base64 \
        --attach=archive.tar

Each attachment can also be assigned a description and a file name. Normally, these are the same as the file name supplied with the --attach option. However, you can change either or both of them using the --content-name and --content-filename, correspondingly. Both of these options affect only the next --attach (or --attach-fd, see below) option.

All the examples above will enter the usual interactive shell, allowing you to compose the body of the message. If that's not needed, the non-interactive use can be forced by redirecting /dev/null to the standard input, e.g.:

 $ mail --attach=archive.tar < /dev/null

This will normally produce a message saying:

 mail: Null message body; hope that's ok

To suppress this message, unset the nullbodymsg variable, as shown in the example below:

 $ mail -E 'set nonullbodymsg' --attach=archive.tar < /dev/null

The option --attach=- forces mail to read the file to be attached from the standard input stream. This option disables the interactive mode and sets nonullbodymsg implicitly, so that the above example can be rewritten as:

 $ mail --attach=- < archive.tar

Special option is provided to facilitate the use of mail in scripts. The --attach-fd=N instructs the program to read the data to be attached from the file descriptor N. The above example is equivalent to:

 $ mail --attach-fd=0 < archive.tar

Attachments created with this option have neither filename nor description set, so normally the use of --content-name and/or --content-filename is advised.

The following Perl program serves as an example of using mail from a script to construct a MIME message on the fly. It scans all mounted file systems for executable files that have setuid or setgid bits set and reports the names of those files in separate attachments. Each attachment is named after the mountpoint it describes.

The script begins with the usual prologue stating the modules that will be used:

  #!/usr/bin/perl
  
  use strict;
  use autodie;

Then global variables are declared. The @rcpt array contains the email addresses of the recipients:

  my @rcpt= 'root@example.com';

The @cmd variable holds the mail command line. It will be augmented for each file system. The initial value is set as follows:

  my @cmd = ('mail',
             '-E set nonullbodymsg',
             '--content-type=text/plain');

The find utility will be used to locate the files. The script will start as many instances as there are mountpoints. Those instances will be run in parallel and their standard output streams will be connected to file descriptors passed to mail invocation in --attach-fd options.

The descriptors will be held in @fds array. This will prevent them from being wiped out by the garbage collector. Furthermore, care should be taken to ensure that the O_CLOEXEC flag be not set for these descriptors. This sample script takes a simplistic approach: it instructs Perl not to close first 255 descriptors when executing another programs:

  my @fds;
  $^F = 255;

The following code obtains the list of mount points:

  open(my $in, '-|', 'mount -t nonfs,noproc,nosysfs,notmpfs');
  while (<$in>) {
      chomp;
      if (/^\S+ on (?<mpoint>.+) type (?<fstype>.+) /) {

For each mountpoint, the find command line is constructed and launched. The file descriptor is pushed to the @fds array to prevent it from being collected by the garbage collector:

  	open(my $fd, '-|',
  	     "find $+{mpoint} -xdev -type f"
               . " \\( -perm -u+x -o -perm -g+x -o -perm -o+x \\)"
               . " \\( -perm -u+s -o -perm -g+s \\) -print");
  	push @fds, $fd;

Now, the mail command is instructed to create next attachment from that file descriptor:

  	my $mpname = $+{mpoint};
  	$mpname =~ tr{/}{%}; 
  	push @cmd,
             "--content-name=Set[ug]id files on $+{mpoint} (type $+{fstype})",
  	     "--content-filename=$mpname.list",
  	     '--attach-fd=' . fileno($fd);
      }
  }
  close $in;

Finally, the emails of the recipients are added to the command line, the standard input is closed to make sure mail won't enter the interactive mode and the constructed command is executed:

  push @cmd, @rcpt;
  close STDIN;
  system(@cmd);