From: Andrew DeFaria Date: Mon, 21 Nov 2016 23:47:08 +0000 (-0800) Subject: Merge branch 'master' of defaria.com:/opt/git/clearscm X-Git-Url: https://defaria.com/gitweb/?a=commitdiff_plain;h=c7fc0ce408c29c87654ece243684c215a56a4d0d;hp=7c4136c55ba83086457197d328b1dfb90a92847d;p=clearscm.git Merge branch 'master' of defaria.com:/opt/git/clearscm --- diff --git a/audience/getPicture.conf b/audience/getPicture.conf new file mode 100644 index 0000000..a04c3b3 --- /dev/null +++ b/audience/getPicture.conf @@ -0,0 +1,21 @@ +################################################################################ +# +# File: getPicture.conf +# Description: Configuration file for getPicture.pl +# Author: Andrew DeFaria +# Version: 1.0 +# Created: Fri Oct 3 18:16:26 PDT 2014 +# Modified: $Date: 2014/10/03 18:17:20 $ +# Language: Conf +# +# This file contains configurable parameters that are unique to your site that +# identify the LDAP parms needed to retrieve users pictures from Active +# Directory. It is assumed that thumbnailPhoto is the attribute name and that +# the user can be found by their unique uid. +# +################################################################################ +AD_HOST: +AD_PORT: +AD_BINDDN: +AD_BINDPW: +AD_BASEDN: diff --git a/audience/getPicture.pl b/audience/getPicture.pl new file mode 100755 index 0000000..e74ee10 --- /dev/null +++ b/audience/getPicture.pl @@ -0,0 +1,202 @@ +#!/usr/bin/env perl +use strict; +use warnings; + +=pod + +=head1 NAME getPicture.pl + +Retrieve thumbnailPhoto for the userid from Active Directory + +=head1 VERSION + +=over + +=item Author + +Andrew DeFaria + +=item Revision + +$Revision: #8 $ + +=item Created + +Fri Oct 3 18:16:26 PDT 2014 + +=item Modified + +$Date: 2014/10/03 18:17:20 $ + +=back + +=head1 DESCRIPTION + +This script will take a userid and search the Active Directory for the user and +return an image file if the user has an image associated with his +thumbnailPhoto attribute. + +This can be configured into Perforce Swarn as documented: + +http://www.perforce.com/perforce/doc.current/manuals/swarm/admin.avatars.html + +One would use something like + + // this block should be a peer of 'p4' + 'avatars' => array( + 'http_url' => 'http:///cgi-bin/getPicture.pl?userid={user}' + 'https_url' => 'http:///cgi-bin/getPicture.pl?userid={user}', + ), + +=cut + +use FindBin; +use Getopt::Long; +use Net::LDAP; +use CGI qw (:standard); + +# Interpolate variable in str (if any) from %opts +sub interpolate ($%) { + my ($str, %opts) = @_; + + # Since we wish to leave undefined $var references in tact the following while + # loop would loop indefinitely if we don't change the variable. So we work + # with a copy of $str changing it always, but only changing the original $str + # for proper interpolations. + my $copyStr = $str; + + while ($copyStr =~ /\$(\w+)/) { + my $var = $1; + + if (exists $opts{$var}) { + $str =~ s/\$$var/$opts{$var}/; + $copyStr =~ s/\$$var/$opts{$var}/; + } elsif (exists $ENV{$var}) { + $str =~ s/\$$var/$ENV{$var}/; + $copyStr =~ s/\$$var/$ENV{$var}/; + } else { + $copyStr =~ s/\$$var//; + } # if + } # while + + return $str; +} # interpolate + +sub _processFile ($%) { + my ($configFile, %opts) = @_; + + while (<$configFile>) { + chomp; + + next if /^\s*[\#|\!]/; # Skip comments + + if (/\s*(.*?)\s*[:=]\s*(.*)\s*/) { + my $key = $1; + my $value = $2; + + # Strip trailing spaces + $value =~ s/\s+$//; + + # Interpolate + $value = interpolate $value, %opts; + + if ($opts{$key}) { + # If the key exists already then we have a case of multiple values for + # the same key. Since we support this we need to replace the scalar + # value with an array of values... + if (ref $opts{$key} eq "ARRAY") { + # It's already an array, just add to it! + push @{$opts{$key}}, $value; + } else { + # It's not an array so make it one + my @a; + + push @a, $opts{$key}; + push @a, $value; + $opts{$key} = \@a; + } # if + } else { + # It's a simple value + $opts{$key} = $value; + } # if + } # if + } # while + + return %opts; +} # _processFile + +sub GetConfig ($) { + my ($filename) = @_; + + my %opts; + + open my $configFile, '<', $filename + or die "Unable to open config file $filename"; + + %opts = _processFile $configFile; + + close $configFile; + + return %opts; +} # GetConfig + +sub checkLDAPError ($$) { + my ($msg, $result) = @_; + + my $code = $result->code; + + die "$msg (Error $code)\n" . $result->error if $code; +} # checkLDAPError + +my ($confFile) = ($FindBin::Script =~ /(.*)\.pl$/); + $confFile = "$confFile.conf"; + +my %opts = GetConfig ($confFile); + +## Main +$| = 1; + +GetOptions ( + \%opts, + 'AD_HOST=s', + 'AD_PORT=s', + 'AD_BINDDN=s', + 'AD_BINDPW=s', + 'AD_BASEDN=s', + 'userid=s', +) || die 'Invalid parameters'; + +$opts{userid} = param 'userid' unless $opts{userid}; + +die "Usage getPicture.pl [userid=]\n" unless $opts{userid}; + +my $ldap = Net::LDAP->new ( + $opts{AD_HOST}, ( + host => $opts{AD_HOST}, + port => $opts{AD_PORT}, + basedn => $opts{AD_BASEDN}, + binddn => $opts{AD_BINDDN}, + bindpw => $opts{AD_BINDPW}, + ), +) or die $@; + +my $result = $ldap->bind ( + dn => $opts{AD_BINDDN}, + password => $opts{AD_BINDPW}, +) or die "Unable to bind\n$@"; + +checkLDAPError ('Unable to bind', $result); + +$result = $ldap->search ( + base => $opts{AD_BASEDN}, + filter => "uid=$opts{userid}", +); + +checkLDAPError ('Unable to search', $result); + +my @entries = ($result->entries); + +if ($entries[0]) { + print header 'image/jpeg'; + print $entries[0]->get_value ('thumbnailPhoto'); +} # if diff --git a/bin/bice.py b/bin/bice.py new file mode 100644 index 0000000..c5bb2f3 --- /dev/null +++ b/bin/bice.py @@ -0,0 +1,153 @@ +#!/usr/bin/python + +import sys +import getopt +import fcntl +import re + +from array import * + +security_logfile = '/var/log/auth.log' +verbose = False +debug = False +update = False +email = False + +def Error (msg = '', errno = 0): + sys.stderr.write ('Error: ' + msg) + + if (errno <> 0): + sys.exit (errno) + +def Verbose (msg, linefeed = True): + global verbose + + if (linefeed): + msg += '\n' + + if (verbose): + print msg + +def Usage (msg = ''): + if msg != '': + print msg + + print """ +Usage: bice.py [-u|sage] [-v|erbose] [-d|ebug] [-nou|pdate] [-nom|ail] + [-f|ilename ] + +Where: + -u|sage Print this usage + -v|erbose: Verbose mode (Default: -verbose) + -nou|pdate: Don't update security logfile file (Default: -update) + -nom|ail: Don't send emails (Default: -mail) + -f|ilename: Open alternate messages file (Default: /var/log/auth.log) + """ + + sys.exit (1) + +def processLogfile (logfile): + violations = {} + + try: + readlog = open (logfile) + + fcntl.flock (readlog, fcntl.LOCK_EX) + except IOError: + Error ("Unable to get exclusive access to " + logfile + " - $!", 1) + + invalid_user = re.compile ("^(\S+\s+\S+\s+\S+)\s+.*Invalid user (\w+) from (\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})") + authentication_failure = re.compile ("^(\S+\s+\S+\s+\S+)\s+.*authentication failure.*ruser=(\S+).*rhost=(\S+)") + failed_password = re.compile ("^(\S+\s+\S+\s+\S+)\s+.*Failed password for (\w+) from (\d{1,3}\.\d{1,3}\.d{1,3}\.d{1,3})") + + for newline in readlog: + violation = {} + newline = newline.strip () + + timestamp = '' + user = '' + ip = '' + + for (timestamp, user, ip) in invalid_user.findall (newline): + continue + + for (timestamp, user, ip) in authentication_failure.findall (newline): + continue + + for (timestamp, user, ip) in failed_password.findall (newline): + continue + + if (ip == ''): + continue + + if (ip in violations): + violation = violations[ip] + + if (user in violation): + violation[user].append (timestamp) + else: + violation[user] = []; + violation[user].append (timestamp) + + violations[ip] = violation + + return violations + +def ReportBreakins (logfile): + violations = processLogfile (logfile) + + nbrViolations = len (violations) + + if (nbrViolations == 0): + Verbose ('No violations found') + elif (nbrViolations == 1): + Verbose ('1 site attempting to violate our perimeter') + else: + Verbose ('{} violations'.format(nbrViolations)) + + for ip in violations: + print 'IP: ' + ip + ' violations:' + for key in sorted (violations[ip].iterkeys ()): + print "\t{}: {}".format (key, violations[ip][key]) + +def dumpargs (): + global verbose, debug, update, email, security_logfile + + print 'Args:' + print 'verbose', verbose + print 'debug', debug + print 'update', update + print 'email', email + print 'file', security_logfile + +def main (argv): + global verbose, debug, update, email, security_logfile + + try: + opts, args = getopt.getopt (argv, "vd", ['verbose', 'debug', 'usage', 'update', 'mail', 'file=']) + except getopt.GetoptError: + Usage (); + sys.exit (1); + + for opt, arg in opts: + if opt in ['-v', '--verbose']: + verbose = 1 + elif opt in ['-d', '--debug']: + debug = 1 + elif opt in ['-u', '--usage']: + Usage + elif opt in ['--update']: + update = 1 + elif opt in ['-m', '--mail']: + email = 1 + elif opt in ['-f', '--file']: + security_logfile = arg + + if security_logfile == '': + Usage ('Must specify filename') + + ReportBreakins (security_logfile) +# dumpargs () + +if __name__ == '__main__': + main (sys.argv [1:]) \ No newline at end of file diff --git a/bin/rexec.new b/bin/rexec.new new file mode 100755 index 0000000..016119e --- /dev/null +++ b/bin/rexec.new @@ -0,0 +1,336 @@ +#!/usr/bin/perl +use strict; +use warnings; + +=pod + +=head1 NAME $RCSfile: rexec,v $ + +Run arbitrary command on a set of machines + +=head1 VERSION + +=over + +=item Author + +Andrew DeFaria + +=item Revision + +$Revision: 1.0 $ + +=item Created: + +Tue Jan 8 15:57:27 MST 2008 + +=item Modified: + +$Date: 2008/02/29 15:09:15 $ + +=back + +=head1 SYNOPSIS + + Usage: rexec [-u|sage] [-v|erbose] [-d|ebug] [-t|ype ] + + + Where: + -u|sage Print this usage + -v|erbose: Verbose mode + -d|ebug: Print debug messages + -t|ype: Machine type (Linux, Windows) + : Command to execute + +=head1 DESCRIPTION + +This script will perform and arbitrary command on a set of machines. + +=cut + +use FindBin; +use Getopt::Long; +use Pod::Usage; +use Term::ANSIColor qw(:constants); +use POSIX ":sys_wait_h"; + +use lib "$FindBin::Bin/../lib", "$FindBin::Bin/../clearadm/lib"; + +use Display; +use Clearadm; +use Logger; +use Rexec; +use Utils; + +my %total; +my ($currentHost, $skip, $log); + +my %opts = ( + usage => sub { pod2usage }, + help => sub { pod2usage (-verbose => 2)}, + verbose => sub { set_verbose }, + debug => sub { set_debug }, + parallel => 0, +); + +my (%workerStatuses, %workerNames); + +sub Interrupted { + use Term::ReadKey; + + display BLUE . "\nInterrupted execution on $currentHost" . RESET; + + Stats \%total; + + display_nolf "Executing on " . YELLOW . $currentHost . RESET . " - " + . GREEN . BOLD . "S" . RESET . GREEN . "kip" . RESET . ", " + . CYAN . BOLD . "C" . RESET . CYAN . "ontinue" . RESET . " or " + . MAGENTA . BOLD . "A" . RESET . MAGENTA . "bort run" . RESET . " (" + . GREEN . BOLD . "s" . RESET . "/" + . CYAN . BOLD . "C" . RESET . "/" + . MAGENTA . BOLD . "a" . RESET . ")?"; + + ReadMode ("cbreak"); + my $answer = ReadKey (0); + ReadMode ("normal"); + + if ($answer eq "\n") { + display "c"; + } else { + display $answer; + } # if + + $answer = lc $answer; + + if ($answer eq "s") { + *STDOUT->flush; + display "Skipping $currentHost"; + $skip = 1; + $total{Skips}++; + } elsif ($answer eq "a") { + display RED . "Aborting run". RESET; + Stats \%total; + exit; + } else { + display "Continuing..."; + $skip = 0; + } # if + + return; +} # Interrupted + +sub workerDeath { + while ((my $worker = waitpid (-1, WNOHANG)) > 0) { + my $status = $?; + + # Ignore all child deaths except for processes we started + next if !exists $workerStatuses{$worker}; + + $workerStatuses{$worker} = $status; + } # while + + $SIG{CHLD} = \&workerDeath; + + return; +} # workerDeath + +sub execute ($$;$) { + my ($cmd, $host, $prompt) = @_; + + my ($remoteHost, @lines); + + # Mark $currentHost for interrupt + $currentHost = $host; + + # Start a log... + $log = Logger->new (name => $host) if $opts{log}; + + verbose_nolf "Connecting to machine $host..."; + + display_nolf YELLOW . "$host:" . RESET; + + eval { + $remoteHost = Rexec->new ( + host => $host, + prompt => $prompt, + ); + }; + + # Problem with creating Rexec object. Log error if logging and return. + if ($@ || !$remoteHost) { + if ($opts{log}) { + $log->err ("Unable to connect to $host to execute command: $cmd") if $opts{log}; + } else { + display RED . 'ERROR:' . RESET . " Unable to connect to $host to execute command: $cmd"; + } # if + + $total{ConnectFailures}++; + + return (1, ()); + } # if + + verbose " connected"; + + display UNDERLINE . "$cmd" . RESET unless $opts{quiet}; + + @lines = $remoteHost->execute ($cmd); + + if ($skip) { + # Kick current connection + kill INT => $remoteHost->{handle}->pid; + } # if + +# if ($opts{parallel} != 0) { +# $log->err ("Unable to connect to $host to execute command\n$cmd") if $opts{log}; +# +# $total{ConnectFailures}++; +# } # if + + verbose "Disconnected from $host"; + + my $status = $remoteHost->status; + + return ($status, @lines); +} # execute + +sub parallelize ($%) { + my ($cmd, %machines) = @_; + + my $thread_count = 1; + + foreach my $host (sort keys %machines) { + if ($thread_count <= $opts{parallel}) { + debug "Processing $host ($thread_count)"; + $thread_count++; + + if (my $pid = fork) { + # In parent process - record this host and its status + $workerNames{$pid} = $host; + } else { + # In spawned child... + $pid = $$; + + debug "Starting process for $host [$pid]"; + + $workerNames{$pid} = $host; + + # Mark currentHost for interrupt (How does this work in the presence + # of parallelization?). + $currentHost = $host; + + my ($status, @lines) = execute $cmd, $host, $machines{$host}; + + $log = Logger->new (name => $host); + + $log->log ($_) foreach (@lines); + + exit $status; + } # if + } else { + # Wait for somebody to finish; + debug "Waiting for somebody to exit..."; + my $reaped = wait; + + debug "Reaped $workerNames{$reaped} [$reaped] (Status: $?)"; + $workerStatuses{$reaped} = $? >> 8 if $reaped != -1; + + $thread_count--; + } # if + } # foreach + + # Wait for all kids + my %threads = %workerNames; + + foreach (keys %threads) { + if (waitpid ($_, 0) == -1) { + delete $threads{$_}; + } else { + $workerStatuses{$_} = $? >> 8; + debug "$threads{$_} [$_] exited with a status of $workerStatuses{$_}"; + } # if + } # foreach + + debug "All processed completed - Status:"; + + if (get_debug) { + foreach (sort keys %workerStatuses) { + debug "$workerNames{$_}\t[$_]:\tStatus: $workerStatuses{$_}"; + } # foreach + } # if + + # Gather output... + display "Output of all executions"; + foreach my $host (sort keys %machines) { + if (-f "$host.log") { + display "$host:$_" foreach (ReadFile ("$host.log")); + + #unlink "$_host.log"; + } else { + warning "Unable to find output for $host ($host.log missing)"; + } # if + } # foreach + + return; +} # parallelize + +# Print the totals if interrupted +$SIG{INT} = \&Interrupted; + +# Get our options +GetOptions ( + \%opts, + 'usage', + 'help', + 'verbose', + 'debug', + 'log', + 'quiet', + 'type=s', + 'parallel=i', +); + +my $cmd = join ' ', @ARGV; + +pod2usage ('No command specified') unless $cmd; + +my $machines = Clearadm->new; + +if ($opts{parallel} > 0) { + #parallelize ($cmd, %machines); + Stats \%total; + exit; +} # if + +display "NOTE: Logging outputs to .log" if $opts{log}; + +my $condition = $opts{type} ? "type=$opts{type}" : ''; + +foreach ($machines->SearchSystem ($condition)) { + my %system = %$_; + $total{Machines}++; + + my ($status, @lines) = execute $cmd, $system{name}; + + if ($skip) { + $skip = 0; + next; + } # if + + if (defined $status) { + if ($status == 0) { + $total{Executions}++; + } else { + $total{Failures}++; + + next; + } # if + } # if + + if ($opts{log}) { + $log->log ($_) foreach (@lines); + } else { + display $_ foreach (@lines); + } # if +} # foreach + +Stats \%total; \ No newline at end of file diff --git a/bin/test.py b/bin/test.py new file mode 100644 index 0000000..f317064 --- /dev/null +++ b/bin/test.py @@ -0,0 +1,98 @@ +#!/usr/bin/python + +import sys, getopt + +security_logfile = '/var/log/auth.log' +verbose = 0 +debug = 0 +update = 0 +email = 0 + +def Usage (msg = ''): + if msg != '': + print msg + + print """ +Usage: bice.py [-u|sage] [-v|erbose] [-d|ebug] [-nou|pdate] [-nom|ail] + [-f|ilename ] + +Where: + -u|sage Print this usage + -v|erbose: Verbose mode (Default: -verbose) + -nou|pdate: Don't update security logfile file (Default: -update) + -nom|ail: Don't send emails (Default: -mail) + -f|ilename: Open alternate messages file (Default: /var/log/auth.log) + """ +def mainold (argv): + global verbose, debug, update, email, security_logfile + + print "in main" + + try: + #opts, args = getopt.getopt (argv, "vd", ['verbose', 'debug', 'usage', 'update', 'mail', 'file=']) + opts, args = getopt.getopt (argv, "hi:o:", ["ifile=","ofile="]) + except getopt.GetoptError: + Usage; + sys.exit (1); + + for opt, arg in opts: + print 'opt: ', opt + print 'arg:', arg + + if opt in ['-v', '-verbose']: + verbose = 1 + elif opt in ['-d', '-debug']: + debug = 1 + elif opt in ['-u', '-usage']: + Usage + elif opt in ['-update']: + update = 1 + elif opt in ['-m', '-mail']: + email = 1 + elif opt in ['-f', '-file']: + security_logfile = arg + + print 'Args:' + print 'verbose', verbose + print 'debug', debug + print 'update', update + print 'email', email + print 'file', security_logfile + +def main(argv): + global verbose, debug, update, email, security_logfile + + inputfile = '' + outputfile = '' + + try: + opts, args = getopt.getopt(argv,"hvi:o:",["verbose", "ifile=","ofile="]) + except getopt.GetoptError: + Usage () + print 'test.py -i -o ' + sys.exit(2) + + for opt, arg in opts: + print 'opt: ', opt + print 'arg: ', arg + if opt in ['-v', '--verbose']: + verbose = 1 + elif opt in ['-u', '-usage']: + Usage () + elif opt == '-h': + print 'test.py -i -o ' + sys.exit() + elif opt in ("-i", "--ifile"): + inputfile = arg + elif opt in ("-o", "--ofile"): + outputfile = arg + + print 'Args:' + print 'verbose', verbose + print 'debug', debug + print 'update', update + print 'email', email + print 'file', security_logfile + +if __name__ == "__main__": + main (sys.argv[1:]) \ No newline at end of file diff --git a/maps/test.msg b/maps/test.msg new file mode 100644 index 0000000..fe92463 --- /dev/null +++ b/maps/test.msg @@ -0,0 +1,171 @@ +Message-ID: <546412AB.7090404@DeFaria.com> +Date: Wed, 12 Nov 2014 18:08:43 -0800 +From: Andrew DeFaria +User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; rv:31.0) Gecko/20100101 Thunderbird/31.2.0 +MIME-Version: 1.0 +To: Yubico Support +Subject: Glutten for punishment... +Content-Type: multipart/alternative; + boundary="------------060602060101040803010204" + +This is a multi-part message in MIME format. +--------------060602060101040803010204 +Content-Type: text/plain; charset=utf-8; format=flowed +Content-Transfer-Encoding: 7bit + +OK I returned the Yubico Security key thing and bought the Yubikey Neo. +I'm on Windows 7 with Chrome 38.0.2125.111 m. I even have the FIDO U2F +(Universal 2nd Factor) extension 0.9.3 installed. + +I go to http://demo.yubico.com/start/u2f/neo?tab=register and insert my +key. Unlike the instructions my yubikey is not a "flashing U2F device" - +the light is on solid. I hit the button anyway. It does nothing but +eventually times out with: + + + Registration failed! + + Make sure you have a U2F device connected, and try again. + + Traceback (most recent call last): + File "/root/python-u2flib-server-demo/examples/yubiauth_server.py", line 159, in __call__ + raise Exception("FIDO Client error: %s" % error) + Exception: FIDO Client error: 5 (TIMEOUT) + + +/root? Really? + +Similarly when I go to my Google account under Security Keys and follow +the instruction there the yubikey doesn't do anything! When I tap the +gold circle the light goes out for a brief second then back on. But the +"Now insert (and tap) your Security Key" with the spinning progress +indicator goes forever... + +Now what? +-- +Andrew DeFaria +ClearSCM, Inc. + +--------------060602060101040803010204 +Content-Type: text/html; charset=utf-8 +Content-Transfer-Encoding: 8bit + + + + + + + + OK I returned the Yubico Security key thing and bought the Yubikey + Neo. I'm on Windows 7 with Chrome  38.0.2125.111 m. I even have the + FIDO U2F (Universal 2nd Factor) extension 0.9.3 installed.
+
+ I go to http://demo.yubico.com/start/u2f/neo?tab=register and insert + my key. Unlike the instructions my yubikey is not a "flashing U2F + device" - the light is on solid. I hit the button anyway. It does + nothing but eventually times out with:
+
+

Registration failed!

+

Make sure + you have a U2F device connected, and try again.

+
 Traceback (most recent call last):
+  File "/root/python-u2flib-server-demo/examples/yubiauth_server.py", line 159, in __call__
+    raise Exception("FIDO Client error: %s" % error)
+Exception: FIDO Client error: 5 (TIMEOUT)
+ 
+
+
/root? Really?
+
+ Similarly when I go to my Google account under Security Keys and + follow the instruction there the yubikey doesn't do anything! When + I tap the gold circle the light goes out for a brief second then + back on. But the "Now insert (and tap) your Security Key" with the + spinning progress indicator goes forever...
+
+ Now what?
+ --
+ Andrew DeFaria
+ ClearSCM, Inc.
+
+ + + +--------------060602060101040803010204-- diff --git a/maps/test.pl b/maps/test.pl new file mode 100644 index 0000000..0310205 --- /dev/null +++ b/maps/test.pl @@ -0,0 +1,42 @@ +#!/usr/bin/perl +use strict; +use warnings; + +use lib "/opt/clearscm/lib"; + +use Display; +use Utils; +use Net::SMTP; + +my %config = ( + SMTPHOST => 'defaria.com', +); + +sub DeliverMsg ($) { + my ($msg) = @_; + + my $smtp = Net::SMTP->new ($config{SMTPHOST}) + or error "Unable to connect to mail server: $config{SMTPHOST}", 1; + + $smtp->mail ('Andrew@DeFaria.com'); + + $smtp->to ('adefaria@gmail.com'); + + $smtp->data; + $smtp->datasend ('From: Andrew@DeFaria.com'); + $smtp->datasend ('To: adefaria@gmail.com'); + $smtp->datasend ('Subject: Forwarded mail'); + $smtp->datasend ('Content-Type: plain/text'); + $smtp->datasend ("\n"); + $smtp->datasend ($msg); + $smtp->dataend; + $smtp->quit; + + return; +} # DeliverMsg + +my $msg = ReadFile 'test.msg'; + +DeliverMsg ($msg); + +display 'done'; \ No newline at end of file diff --git a/test/test.py b/test/test.py new file mode 100755 index 0000000..981ea9b --- /dev/null +++ b/test/test.py @@ -0,0 +1,10 @@ +#!/usr/bin/env python +a, b = 0, 1 + +while b < 100: + print b + a, b = b, a+b + +print "done" + +quit()