#!/usr/bin/perl # Nominet automaton script written by James Ponder # # Copyright 2001 James Ponder. All rights reserved. # # Redistribution and use, with or without modification, are permitted # provided that the following conditions are met: # # * Redistributions of this code must retain the above copyright notice, # this list of conditions and the following disclaimer. # # * Neither the name of myself nor the names of its contributors may be used # to endorse or promote products derived from this software without # specific prior written permission. # # * THIS SOFTWARE IS PROVIDED `AS IS'' AND ANY EXPRESSED OR IMPLIED # WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES # OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. # IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, # INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) # HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, # STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED # OF THE POSSIBILITY OF SUCH DAMAGE. use strict; use warnings; use Net::SMTP; use Getopt::Long; use Fcntl; use POSIX qw(tmpnam); use IPC::Open3; use IO::File; my $s = { 'mailserver' => 'localhost', 'tag' => 'YOURTAG', 'from' => 'Your Name ', 'env_from' => 'you@yourdomain.com', 'bcc' => 'copy@yourdomain.com', # optional, remove if you want 'default_uid' => '0x11112222', # your PGP key id 'pgps' => '/usr/local/bin/pgps'}; $ENV{'PGPPASSFD'} = '0'; my ($o, $tmpfile, $passphrase); END { if (defined($tmpfile)) { unlink($tmpfile) or die "Couldn't unlink $tmpfile: $!\n"; } } sub passphrase { my $p; return $passphrase if defined $passphrase; print 'Enter passphrase: '; system('/bin/stty', '-echo') and die "Failed to turn off echo: $?\n"; chop($p = ); print "\n"; system('/bin/stty', 'echo') and die "Failed to turn on echo: $?\n"; return ($passphrase = $p); } sub pgpsend { my ($tmpfile, $options) = @_; my ($m, $pass); my ($stdin, $stdout, $stderr, $pid); local (*FH); $SIG{'PIPE'} = sub { die 'Failed to exec/write to '.$s->{'pgps'}."\n"; }; $m = 'From: '.$options->{'from'}."\n"; $m.= 'To: '.$options->{'to'}."\n"; $m.= 'Subject: '.$options->{'subject'}."\n"; $m.= "\n"; $pass = passphrase(); die $s->{'pgps'}." is not found/executable\n" unless -x $s->{'pgps'}; $stdin = $stdout = ''; $stderr = new IO::File "/dev/null", "w"; # open3 doesn't return on error $pid = open3($stdin, $stdout, $stderr, $s->{'pgps'}, '-at', '-f', '+batchmode=1', defined($o->{'uid'}) ? ('-u', $o->{'uid'}) : () ); print $stdin "$pass\n"; open(FH, $tmpfile) or die "Failed to open '$tmpfile': $!\n"; print $stdin $_ while(); close(FH) or die "Failed to close '$tmpfile': $!\n"; close($stdin) or die "Failed to close stdin to pgps: $!\n"; $m.= $_ while (<$stdout>); close($stdout) or die "Failed to close stdout of pgps: $!\n"; waitpid $pid, 0; die "pgps returned exit code ".($?>>8)." - check passphrase\n" if $?; my $smtp = Net::SMTP->new($s->{'mailserver'}); $smtp->mail($options->{'env_from'}); $smtp->to($options->{'env_to'}) or die "SMTP:".$smtp->message()."\n"; $smtp->to($options->{'bcc'}) or die "SMTP:".$smtp->message()."\n" if (defined($options->{'bcc'})); $smtp->data([$m]) or die "SMTP: ".$smtp->message()."\n"; $smtp->quit() or die "SMTP: ".$smtp->message()."\n"; $stderr->close; } sub autorecipient { my $domain = $_[0]; if ($domain =~ /\.([a-z0-9-]+)\.uk$/i) { return 'auto-'.$1.'@nic.uk'; } die "invalid uk domain: $domain\n"; } sub syntax { print STDERR "Syntax: $0 [--uid=] [--passphrase=]\n\n"; print STDERR <<'!'; --action='list' --month= or --action='modify' --domain= --template= or --action='new' --domain= --template= or --action='retag' --domain= --tag= or --action='query' --domain= or --action='delete' --domain= Any lines beginning 'key:' are skipped in template files. ! exit(1); } syntax() unless GetOptions('uid=s' => \$o->{'uid'}, 'passphrase=s' => \$o->{'passphrase'}, 'action=s' => \$o->{'action'}, 'month=s' => \$o->{'month'}, 'domain=s' => \$o->{'domain'}, 'tag=s' => \$o->{'tag'}, 'template=s' => \$o->{'template'}); syntax() if (!defined($o->{'action'})); # if uid wasn't supplied, use the default $o->{'uid'} = $s->{'default_uid'} if (!defined($o->{'uid'})); # store the passphrase if it was supplied $passphrase = $o->{'passphrase'} if defined($o->{'passphrase'}); my $fh; do { $tmpfile = tmpnam(); } until sysopen($fh, $tmpfile, O_RDWR|O_CREAT|O_EXCL, 0600); if ($o->{'action'} eq 'list') { syntax() if (!defined($o->{'month'}) or defined($o->{'domain'}) or defined($o->{'tag'}) or defined($o->{'template'})); print $fh 'month: '.$o->{'month'}."\n"; close($fh) or die "failed to close temporary file: $!\n"; pgpsend($tmpfile, { 'from' => $s->{'from'}, 'to' => 'auto-co@nic.uk', 'bcc' => $s->{'bcc'}, 'subject' => 'List', 'env_from' => $s->{'env_from'}, 'env_to' => 'auto-co@nic.uk' }); } elsif ($o->{'action'} eq 'query') { syntax() if (defined($o->{'month'}) or !defined($o->{'domain'}) or defined($o->{'tag'}) or defined($o->{'template'})); print $fh 'key: '.$o->{'domain'}."\n"; close($fh) or die "failed to close temporary file: $!\n"; pgpsend($tmpfile, { 'from' => $s->{'from'}, 'to' => autorecipient($o->{'domain'}), 'bcc' => $s->{'bcc'}, 'subject' => 'Query', 'env_from' => $s->{'env_from'}, 'env_to' => autorecipient($o->{'domain'}) }); } elsif ($o->{'action'} eq 'retag') { syntax() if (defined($o->{'month'}) or !defined($o->{'domain'}) or !defined($o->{'tag'}) or defined($o->{'template'})); print $fh 'key: '.$o->{'domain'}."\n"; print $fh 'ips-key: '.$o->{'tag'}."\n"; close($fh) or die "failed to close temporary file: $!\n"; pgpsend($tmpfile, { 'from' => $s->{'from'}, 'to' => autorecipient($o->{'domain'}), 'bcc' => $s->{'bcc'}, 'subject' => 'Release', 'env_from' => $s->{'env_from'}, 'env_to' => autorecipient($o->{'domain'}) }); } elsif ($o->{'action'} eq 'delete') { syntax() if (defined($o->{'month'}) or !defined($o->{'domain'}) or defined($o->{'tag'}) or defined($o->{'template'})); print $fh 'key: '.$o->{'domain'}."\n"; close($fh) or die "failed to close temporary file: $!\n"; pgpsend($tmpfile, { 'from' => $s->{'from'}, 'to' => autorecipient($o->{'domain'}), 'bcc' => $s->{'bcc'}, 'subject' => 'Delete', 'env_from' => $s->{'env_from'}, 'env_to' => autorecipient($o->{'domain'}) }); } elsif ($o->{'action'} eq 'new') { syntax() if (defined($o->{'month'}) or !defined($o->{'domain'}) or defined($o->{'tag'}) or !defined($o->{'template'})); my $template = new IO::File $o->{'template'}, "r" or die "$0: failed to open template file: $!\n"; print $fh 'key: '.$o->{'domain'}."\n"; while(<$template>) { next if /^key:/; print $fh $_; } close($template) or die "$0: failed to close template file: $!\n"; close($fh) or die "$0: failed to close temporary file: $!\n"; pgpsend($tmpfile, { 'from' => $s->{'from'}, 'to' => 'applications@nic.uk', 'bcc' => $s->{'bcc'}, 'subject' => $s->{'tag'}.' Domain Request', 'env_from' => $s->{'env_from'}, 'env_to' => 'applications@nic.uk' }); } elsif ($o->{'action'} eq 'modify') { syntax() if (defined($o->{'month'}) or !defined($o->{'domain'}) or defined($o->{'tag'}) or !defined($o->{'template'})); my $template = new IO::File $o->{'template'}, "r" or die "$0: failed to open template file: $!\n"; print $fh 'key: '.$o->{'domain'}."\n"; while(<$template>) { next if /^key:/; print $fh $_; } close($template) or die "$0: failed to close template file: $!\n"; close($fh) or die "$0: failed to close temporary file: $!\n"; pgpsend($tmpfile, { 'from' => $s->{'from'}, 'to' => autorecipient($o->{'domain'}), 'bcc' => $s->{'bcc'}, 'subject' => 'Modify', 'env_from' => $s->{'env_from'}, 'env_to' => autorecipient($o->{'domain'}) }); } else { die "invalid action type\n"; }