#! /usr/bin/env perl # Program: send_local_smtp # Description # Transport. Sends the report by e-mail through the local mail system, by # piping a complete message to the sendmail-compatible program (sendmail, # msmtp, postfix, exim, FreeBSD's sendmail or dma). No credentials are # needed or read; the local MTA is responsible for authentication. # Needs nothing beyond core Perl. # # Parameters: mailTo (required), mailFrom, mailCC, mailBCC, mailSubject, # attachment (path of a file to attach), sendmailPath (default: searched for). # Recipient lists may be separated by commas, semicolons or spaces. # # Returns 1 on success. Anything else is a failure: # 2 - mailTo missing # 3 - no sendmail program found # 4 - could not start sendmail, or attachment unreadable # 100 + n - sendmail exited with status n # # Revision History # 2026-10-27 Claude v1.0.0 Bug 213: new, replaces the need for sendEmail.pl use warnings; use strict; our $VERSION = '1.0.0'; # helper names are prefixed because transports are all loaded into package main sub sendLocal_clean { my $value = shift; $value = '' unless defined $value; $value =~ s/[\r\n]+/ /g; # a value must never be able to add a header return $value; } sub sendLocal_list { my $value = shift; return grep { length } split /[\s,;]+/, ( defined $value ? $value : '' ); } # builds the complete message (headers, blank line, body). Bcc is deliberately # not a header; the caller puts bcc recipients on the envelope only. sub sendLocal_buildMessage { my ( $parameters, $report ) = @_; my @headers; push @headers, 'From: ' . sendLocal_clean( $$parameters{'mailFrom'} ) if $$parameters{'mailFrom'}; push @headers, 'To: ' . join( ', ', sendLocal_list( $$parameters{'mailTo'} ) ); push @headers, 'Cc: ' . join( ', ', sendLocal_list( $$parameters{'mailCC'} ) ) if $$parameters{'mailCC'}; push @headers, 'Subject: ' . sendLocal_clean( $$parameters{'mailSubject'} || 'sysinfo-client report' ); push @headers, 'Date: ' . do { my @t = gmtime; sprintf( '%s, %02d %s %04d %02d:%02d:%02d +0000', (qw(Sun Mon Tue Wed Thu Fri Sat))[$t[6]], $t[3], (qw(Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec))[$t[4]], $t[5] + 1900, $t[2], $t[1], $t[0] ) }; push @headers, 'MIME-Version: 1.0'; my $attachment = $$parameters{'attachment'}; if ( $attachment ) { open my $in, '<:raw', $attachment or return ( undef, "cannot read attachment $attachment: $!" ); local $/; my $data = <$in>; close $in; require MIME::Base64; ( my $name = $attachment ) =~ s{.*/}{}; $name =~ s/[^\w.\-]/_/g; my $boundary = '=_sysinfo_' . time . '_' . $$; push @headers, qq{Content-Type: multipart/mixed; boundary="$boundary"}; my $body = "--$boundary\nContent-Type: text/plain; charset=UTF-8\nContent-Transfer-Encoding: 8bit\n\n$report\n" . "--$boundary\nContent-Type: application/octet-stream; name=\"$name\"\n" . "Content-Transfer-Encoding: base64\nContent-Disposition: attachment; filename=\"$name\"\n\n" . MIME::Base64::encode_base64( $data ) . "--$boundary--\n"; return ( join( "\n", @headers ) . "\n\n" . $body, '' ); } push @headers, 'Content-Type: text/plain; charset=UTF-8', 'Content-Transfer-Encoding: 8bit'; return ( join( "\n", @headers ) . "\n\n" . $report, '' ); } sub sendLocal_findSendmail { my $parameters = shift; return $$parameters{'sendmailPath'} if $$parameters{'sendmailPath'}; foreach my $path ( qw( /usr/sbin/sendmail /usr/lib/sendmail /usr/local/sbin/sendmail /usr/bin/sendmail /usr/local/bin/sendmail ) ) { return $path if -x $path; } return; } sub doit { my ( $parameters, $report ) = @_; my @to = sendLocal_list( $$parameters{'mailTo'} ); unless ( @to ) { print "send_local_smtp: mailTo not defined\n"; return 2; } my $sendmail = sendLocal_findSendmail( $parameters ); unless ( $sendmail && -x $sendmail ) { print "send_local_smtp: no sendmail program found\n"; return 3; } my ( $message, $error ) = sendLocal_buildMessage( $parameters, $report ); unless ( defined $message ) { print "send_local_smtp: $error\n"; return 4; } my @recipients = ( @to, sendLocal_list( $$parameters{'mailCC'} ), sendLocal_list( $$parameters{'mailBCC'} ) ); my @command = ( $sendmail, '-i' ); push @command, '-f', sendLocal_clean( $$parameters{'mailFrom'} ) if $$parameters{'mailFrom'}; push @command, '--', @recipients; # list form: no shell, so no quoting problems or injection open my $pipe, '|-', @command or do { print "send_local_smtp: could not start $sendmail: $!\n"; return 4; }; binmode $pipe; # message is already UTF-8 bytes print {$pipe} $message; return 1 if close $pipe; my $status = $? >> 8; print "send_local_smtp: $sendmail exited with status $status\n"; return 100 + $status; # never 1, which would read as success } 1;