#!/usr/bin/perl -w # # Simple script to extract an email address from an MH message, # prompt for an alias for it, and add it to the ~/Mail/aliases # file. # Yes, I could have used a shell script, but I don't really # know enough to do that. Perl isn't much of a problem to use # for this not-very-common task... # 'simple' is no longer as accurate as it once was :-> -- PMM 09/1998 # # For usage information, try 'getaddr --help'. # # If you find this script useful (or find bugs in it :->) please let me know: # email . # Stand by for legal bumph... # Executive summary: this is the XFree86 copyright minus the restriction # about not using the project name in advertising. You can do what # you like with the code, but don't remove my name and don't come whining # to me if it eats your aliases file. ############################################################################ # Copyright (C) 1998 Peter Maydell . # All Rights Reserved. # # Permission is hereby granted, free of charge, to any person obtaining a # copy of this software and associated documentation files (the "Software"), # to deal in the Software without restriction, including without limitation # the rights to use, copy, modify, merge, publish, distribute, sublicense, # and/or sell copies of the Software, and to permit persons to whom the # Software is furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included # in a copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS # OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. # IN NO EVENT SHALL PETER MAYDELL BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER # DEALINGS IN THE SOFTWARE. ############################################################################ # TODO: # maybe we could have a 'getaddr emailaddress alias' form? # (how this would fit into argument parsing: # if 1 arg, as now. if 2 args, check arg1 for msg. if not valid, # must be emailaddress. Allowing 'getaddr emailaddress' is probably # not a terribly good idea...) # see comment below about taking human name from from and email from reply-to # Maybe support creating a new aliases file if none exists? #This causes lots of errors I can't be bothered to fix #(mostly through use of global variables, I think.) use strict; sub parseargs(); sub getprofileentry($); sub usage(); sub help(); sub version(); sub getprogname(); sub getaliasfile(); sub getaddress($$); sub deletealias($$); sub main(); # version string; used in help() and version(). my $VERSION = '1.5 (March 2007)'; # who, me, a C fan? exit main(); sub main() { my($forceoverwrites, $folder, $msg, $alias, $aliasfile, $address, $oldalias); # parse commandline args ($forceoverwrites, $folder, $msg, $alias) = parseargs(); # locate the user's alias file $aliasfile = getaliasfile(); # extract email address from given folder/message $address = getaddress($folder, $msg); # Loop round until we're happy with the alias do { if (!defined $alias) { print "What alias do you want to use for $address?\n"; chop($alias = ); } # Don't permit funny characters. This regexp disallows: # * empty strings # * whitespace # * '<', ';', ':', '*' : these all have special meaning # in the alias file (see mh-alias(5)) # * ''' : this is so that when we say `ali '$alias'` the # shell doesn't get confused. # * '-' : this is because ali treats arguments with a leading '-' # as options. if ($alias !~ /^[^\s<;:*'-]+$/) { print "Sorry, '$alias' isn't a valid alias: please try again.\n"; undef $alias; # What I really want to say here is 'next', but that doesn't # work in a do {..} while() loop for arcane reasons. # (see the Camel Book (2nd ed.) p96) } else { # check for existing alias. Note that 'ali foo' returns foo if # there is no alias, not ''... chop($oldalias = `ali '$alias'`); die "oops, ali $alias failed: $!\n" if $? != 0; if ($forceoverwrites == 0 && $oldalias ne $alias) { my $yn; print "Warning: $alias is already aliased to $oldalias.\n"; do { print "Overwrite? (y/n): "; $yn = ; } while ($yn !~ /^[YyNn]/); $alias = undef if $yn =~ /^[Nn]/; } } } while (!defined $alias); # if we got out of that with $oldalias ne $alias then we must # have confirmed deletion or specified --force on the commandline deletealias($aliasfile, $alias) if ($oldalias ne $alias); open(ALIASFILE, ">>$aliasfile") or die "oops, couldn't append to $aliasfile: $!\n"; print ALIASFILE "$alias: $address\n"; return 0; # success! } sub parseargs() { # parse command line arguments # returns a tuple (forceoverwrites, folder, message, alias) # representing the results. Any or all of folder, message or alias may be # '' or some other default value if the command line args don't specify them. # If --help or --version were specified, or arguments are invalid, # we print an appropriate message and exit rather than returning. # The 'intelligent' argument parsing basically means that we # expect args in the order 'folder msg alias'. If an argument # looks like what we're expecting next we use it up, otherwise # we leave it for the next possible match. # defaults: no overwrite, no folder given, current message, no alias given my($forceoverwrites, $folder, $msg, $alias) = (0, '', 'cur', undef); # First we check for switches that must be the first argument. if (defined $ARGV[0]) { # accept several flavours of help request if ($ARGV[0] eq '--help' || $ARGV[0] eq '-help' || $ARGV[0] eq '-h') { help(); # never returns } elsif ($ARGV[0] eq '--version') { version(); # never returns } elsif ($ARGV[0] eq '--force') { $forceoverwrites = 1; shift @ARGV; } } # now we check the next argument to see if it looks like a folder name if (defined $ARGV[0]) { # if argument looks like '+foldername' then it's a folder if ($ARGV[0] =~ /^\+(.*)/) { $folder = $1; # do syntax checks for metacharacters: # ' is bad because it confuses the shell # spaces are bad on general principle if ($folder !~ /^[^'\s]+$/) { die "$folder is not a valid folder name.\n"; } # Unfortunately there doesn't seem to be any way to suppress # the output of folder, hence the redirection to /dev/null... if (system("folder '+$folder' -fast -nocreate >/dev/null") != 0) { # folder doesn't exist, uhoh die "Folder $folder does not exist.\n"; } shift @ARGV; } } # does next arg look like a message specifier? if (defined $ARGV[0]) { # next argument might be a message number or cur/first/last/next/prev if ($ARGV[0] eq 'cur' || $ARGV[0] eq 'first' || $ARGV[0] eq 'last' || $ARGV[0] eq 'next' || $ARGV[0] eq 'prev' || $ARGV[0] =~ /^\d+$/) { $msg = $ARGV[0]; shift @ARGV; } } # if there's a next arg it must be an alias name if (defined $ARGV[0]) { $alias = $ARGV[0]; shift @ARGV; } # any more arguments is too many; print a usage message if (defined $ARGV[0]) { usage(); } return ($forceoverwrites, $folder, $msg, $alias); } sub help() { # Print detailed help text and exit my $progname = getprogname(); print <] Examples: $progname fredbloggs ; alias 'fredbloggs'; use message 'cur' $progname last ; message 'last'; will prompt for alias $progname --force last ; ditto, but force alias overwriting $progname +friends mike ; make alias 'mike' for sender of current message in the 'friends' folder $progname +friends first ; make alias for first message in 'friends', prompting for alias If you really wanted to make an alias like 'last' then you'll have to say something like '$progname cur last'. MH supports more complex alias formats than this program produces; see mh-aliases(5) for more details. Bugs: This help message is rather long; perhaps we should turn it into a man page? EOT # Tack the version string and copyright onto the end of the help text version(); exit(0); } sub usage() { # Print abbreviated usage string and exit my $progname = getprogname(); print "Usage: $progname [--force] [msg] [alias] | -help | --help | -h | --version\n"; exit(0); } sub version() { my $progname = getprogname(); print < EOT exit(0); } sub getprogname() { my($progname) = $0; $progname =~ s/.*\///; return $progname; } sub getaddress($$) { my($folder, $msg) = @_; # Get the right address from the given (folder,message) my($address, $foldstr); # Set $foldstr to something suitable to put into a command line if ($folder eq '') { $foldstr = ''; } else { $foldstr = "'+$folder'"; } # Use scan to get the address out of the message # We use Reply-To if it exists, otherwise From. # Maybe we could check a reply-to address for a human-readable # part and if it doesn't have one nick the one from From? # This would handle cases like #From: Adrian Bridgett #Reply-To: adrian.bridgett@zetnet.co.uk # more elegantly. Downside: parsing RFC-822 addresses is very hairy. $address = `scan $foldstr $msg -format '%{reply-to}'`; die "oops, scan failed: $!\n" if $? != 0; if ($address eq "\n") { $address = `scan $foldstr $msg -format '%{from}'`; die "oops, scan failed: $!\n" if $? != 0; } chop($address); # paranoia die "oops, message has empty From: field?!?\n" if ($address eq ''); return $address; } sub getaliasfile() { # return the filename the user keeps aliases in, by # reading config details from the user's .mh_profile... my($mhpath,$aliasfile); chop($mhpath = `mhpath +`); die "aargh, 'mhpath +' failed: $!\n" if $? != 0; if ($mhpath eq '') { die "No Path entry in .mh_profile -- it is mandatory! Aborting.\n"; } $aliasfile=getprofileentry('aliasfile'); if ($aliasfile eq '') { # If we want to support adding an Aliasfile entry (ie starting a # new aliases file) then we should do that here... die <$aliasfile.tmp") or die "oops, couldn't write to temporary file $aliasfile.tmp: $!\n"; while () { # if continuing, treat this line the same as the last one, # regardless of whether or not it starts 'alias'... if (!$continuing) { $deleting = 1 if /^$alias/; # check for alias to delete } # Next line will be a continuation of this one if this line # ends in '\' and doesn't begin with ';' (which would make it a comment) $continuing = /^[^\;].*\\\n$/; print ALIASFILE unless $deleting; $deleting = 0 unless $continuing; } close ALIASFILE; close OLDALIASFILE; rename("$aliasfile.tmp", "$aliasfile") || die "oops, couldn't rename temporary file to $aliasfile: $!\n"; # check that we succeeded :-> chop($oldalias = `ali '$alias'`); die "oops, ali $alias failed: $!\n" if $? != 0; die <