socket_mail.php
<?php
################################################
#
# Tested under PHP/4.2.2
#
# Cole Tierney - colet at putnamhill dot net
# 2003.07.14
#
################################################
if (!is_readable ('./.acl')) {
// If there is no external access-list, then enter
// permissions here.
$access_list[] = array(); // Default to no access.
####### Begin user definable section: ##########
#
# $access_list[] = new net ('0.0.0.0', '0.0.0.0'); // Allow from any network.
# $access_list[] = new net ('10.0.0.24', '255.255.255.255'); // Allow access to a specific host.
# $access_list[] = new net ('64.30.15.240', '255.255.255.240'); // Allow from a specific external subnet.
# $access_list[] = new net ('192.168.1.0', '255.255.255.0'); // Allow from a class C internal network.
#
####### End user definable section #############
} // Or read from an external access-list with the following format, named .acl:
/*
0.0.0.0 0.0.0.0 ; Allow from any network.
10.0.0.24 255.255.255.255 ; Allow access to a specific host.
64.30.15.240 255.255.255.240 ; Allow from a specific external subnet.
192.168.1.0 255.255.255.0 ; Allow from a class C internal network.
*/
else $access_list = read_access_list('./.acl');
# Validate source ip address.
$remote_address = $_SERVER["REMOTE_ADDR"];
if ( permitted( $access_list, $remote_address ) ) {
if ( strpos($_SERVER['HTTP_USER_AGENT'], 'Mac') === false ) {
$font_family = 'Lucida Console';
$font_size = '10px';
} else {
$font_family = 'Monaco';
$font_size = '9px';
}
// Write openning html.
?>
<html>
<head>
<title>socket mailer</title>
<style type="text/css">
* { font-family: <?php echo($font_family) ?>; font-size: <?php echo($font_size) ?>; }
.grey { font-family: <?php echo($font_family) ?>; font-size: <?php echo($font_size) ?>; color: gray; }
.red { font-family: <?php echo($font_family) ?>; font-size: <?php echo($font_size) ?>; color: red; }
</style>
</head>
<body>
<?php // Continue php...
if ($_SERVER["REQUEST_METHOD"]=='GET') { // Set some defaults...
$xsender = '';
$date = '';
$server = '';
$to = '';
$from = '';
$subject = '';
$body = '';
$hexchars = '';
$filename = ''; // Optional attached filename for testing virus filters.
}
else { // POST so reset our values
$xsender = $_POST['xsender'];
$date = $_POST['date'];
$server = $_POST['server'];
$to = $_POST['to'];
$from = $_POST['from'];
$subject = $_POST['subject'];
$body = stripslashes($_POST['body']);
/* Now, isolate any naked newlines and replace with carriage return linefeed pairs.
(to test filters for stray linefeeds, enter them as hex values in the field provided.) */
$body = preg_replace("/\r\n/", "\n", $body); // First temporarily replace valid \r\n pairs with \n.
$body = preg_replace("/\r/", "\n", $body); // Now replace any remaining \r with \n.
$body = preg_replace("/\n/", "\r\n", $body); // Finally, replace all \n with \r\n.
$hexchars = $_POST['hexchars'];
$filename = $_POST['filename']; // Optional attached filename for testing virus filters.
}
?> <!-- Start normal html and serve a form... -->
<form action="<?php echo($php_self); ?>" method=post>
<p><b>Enter email fields:</b></p>
X-Sender:..(optional).....<input type=text name="xsender" value="<?php echo($xsender); ?>" size=45 maxlength=100><br>
Date:......(optional).....<input type=text name="date" value="<?php echo($date); ?>" size=45 maxlength=100><br>
Server IP:.(optional).....<input type=text name="server" value="<?php echo($server); ?>" size=45 maxlength=100><br>
To:.......................<input type=text name="to" value="<?php echo($to); ?>" size=45 maxlength=100><br>
From:.....................<input type=text name="from" value="<?php echo($from); ?>" size=45 maxlength=100><br>
Subject:..................<input type=text name="subject" value="<?php echo($subject); ?>" size=45 maxlength=100><br><br>
Body:.....................<br>
<textarea name="body" rows=4 cols=46 wrap value="<?php echo($body); ?>"></textarea><br><br>
Additional Hex Data:......<input type=text name="hexchars" value="<?php echo($hexchars); ?>" size=45 maxlength=100><br>
Attached Dummy Filename:..<input type=text name="filename" value="<?php echo($filename); ?>" size=45 maxlength=100><br>
<p><input type=submit name="submit" value="Send"></p>
</form> <?php // Continue php...
if ($_SERVER["REQUEST_METHOD"]=='POST') { // Process posted data...
echo('<pre>');
// If there is an additional hex string, add it the to the body. (Stip non hex vals, 1st.)
if ($hexchars) {
$hexchars = preg_replace("/[^a-fA-F0-9]/", "", $hexchars);
$body .= pack("H*", $hexchars);
}
// Extract domain from email address.
$arr = explode('@',$to);
$domain = $arr[1];
// Lookup MX hosts for given to domain. Returns FALSE if none found.
if (!$server) $hosts = find_mailhosts($domain);
if ($hosts||$server) {
$fp = open_smptsocket($hosts,$server);
if ($fp) {
// Set up some variables for later.
if (!$date) $date = date("D M j G:i:s T Y");
if (!$xsender) $xsender = $_SERVER["SERVER_SOFTWARE"];
$sendinghost = $_SERVER["HTTP_HOST"];
$timeStamp = time();
$boundary = '============_-' . md5(uniqid(mt_rand(), 1)) . '==_============--';
$mid = "<p$timeStamp@[$remote_address]>";
$cid = "<p$timeStamp@[$remote_address].0.0>";
get_lines($fp);
put_line ($fp, "EHLO $sendinghost");
if (get_lines ($fp)) { // get_lines returns TRUE if text begins with 250 -- CBT
put_line ($fp, "MAIL FROM:<$from>");
if (get_lines ($fp)) {
put_line ($fp, "RCPT TO:<$to>");
if (get_lines ($fp)) {
put_line ($fp, "DATA");
get_lines ($fp); // Don't check results of this since DATA does not respond with 250.
put_line ($fp, 'Mime-Version: 1.0');
put_line ($fp, "X-Sender: $xsender");
put_line ($fp, "Message-Id: $mid"); // Send message id.
put_line ($fp, "Date: $date");
put_line ($fp, "From: $from");
put_line ($fp, "To: $to");
if ($subject) put_line ($fp, "Subject: $subject");
if (!$filename) { // If no test attachment, then do plain mail.
put_line ($fp, '');
put_line ($fp, $body);
} // end if no attachment
else {
$file_contents = 'sample file contents';
put_line ($fp, "Content-Type: multipart/mixed; boundary=\"$boundary\"");
put_line ($fp, '');
put_line ($fp, "--$boundary");
put_line ($fp, "Content-Type: text/plain; charset=\"us-ascii\" ; format=\"flowed\"");
put_line ($fp, '');
put_line ($fp, $body);
put_line ($fp, "--$boundary");
put_line ($fp, "Content-Id: $cid");
put_line ($fp, "Content-Type: application/octet-stream; name=\"$filename\"");
put_line ($fp, "Content-Disposition: attachment; filename=\"$filename\"");
put_line ($fp, 'Content-Transfer-Encoding: base64');
put_line ($fp, '');
put_line ($fp, chunk_split(base64_encode($file_contents)) );
put_line ($fp, '');
put_line ($fp, "--$boundary");
} // end if else send attachment
// Close out message.
put_line ($fp, '.');
get_lines ($fp);
} // end if RCPT TO: accepted.
} // end if MAIL FROM: accepted.
} // end if EHLO accepted.
put_line ($fp, 'QUIT');
get_lines ($fp);
fclose ($fp);
} // end if socket openned successfully
else {
echo ("Could not connect to any mail server for domain $domain.\n");
} // end else failed to open socket to mail host
} // end if mail hosts retrieved successfully
else {
echo ("No mail servers found for domain $domain.\n");
} // end else no mail hosts found
echo ('</pre>'); // Close out the html for method POST.
} // end else process posted data
echo ("\n</body>\n</html>"); // Close out the html
} // end if valid member of network
else {
echo ('Not authorized.');
} // end if NOT a member of trusted network
################################################
#
# Utilities follow...
#
################################################
// This function will add the carriage return and linefeed for us.
// It will also echo to the browser.
function put_line($sock, $ln) {
fputs ($sock, "$ln\r\n");
$html_str = format_pre($ln);
echo("<span class='grey'>$html_str</span>\n");
}
// This function gathers the response from the server and returns TRUE if it begins with 250.
// It will also echo results to the browser.
function get_lines($sock) {
$data = '';
while($str = fgets($sock,515)) {
$data .= "$str";
// if the 4th character is a space then we are done reading
// so just break the loop
if(substr($str,3,1) == ' ') { break; }
}
$formated_data = format_pre($data);
if (substr($data,0,3)=='550') echo("<span class='red'>$formated_data</span>");
else echo($formated_data);
return (substr($data,0,3)=='250'); // Usually the response will start with 250 if the mail server is happy.
}
function format_pre ($str) { // Call this to format email address to be displayed in browser.
// $str = preg_replace("/<([^<>]*)@([^<]*)>/", "<$1@$2>", $str); // This only does email addresses.
$str = preg_replace('/</', '<', $str); // This will catch more tags. --CBT
$str = preg_replace("/>/", '>', $str);
if (!$str) $str = '<br>';
return $str;
}
function find_mailhosts ($domain) {
$domain=$domain.'.';
if (getmxrr($domain, $mxhosts, $mxprefs) == FALSE ) {
$mailhosts = FALSE;
}
else {
// Combine arrays:
$mailhosts = array_combine( $mxprefs, $mxhosts );
ksort($mailhosts); // Sort hosts by MX preference.
} // end else valid email
return $mailhosts;
}
function open_smptsocket ($hosts,$server) {
if ($server) {
echo "Trying host $server...\n";
$fp = fsockopen ($server, 25, $errno, $errstr, 30);
if ($fp) return $fp;
else echo("$errstr ($errno) \n");
} else {
foreach ($hosts as $host) {
echo "Trying host $host...\n";
$address = gethostbyname ($host);
$fp = fsockopen ($address, 25, $errno, $errstr, 30);
if ($fp) return $fp;
else echo("$errstr ($errno) \n");
}
}
return FALSE;
}
# PHP 5 & above include the following function.
function array_combine ($keys, $vals) {
$i = 0;
foreach ($keys as $key) $newarray[$key] = $vals[$i++];
return $newarray;
}
function permitted ($acl, $ip) {
foreach ($acl as $net) { // Accept first match.
if (is_object($net)) {
if ($net->is_member($ip)) return TRUE;
}
}
// If no acl match, then fall through and try basic authentication...
if ( !isset($_SERVER['PHP_AUTH_USER'] ) || !isset( $_SERVER['PHP_AUTH_PW'] ) ) {
/****************************************************
If here then the user either did not provide a
username or password. So display not authorized.
--CBT
****************************************************/
$realm = 'Socket Mailer ' . strftime("%c",time());
Header( "WWW-authenticate: Basic realm=\"$realm\"");
Header( 'HTTP/1.0 401 Unauthorized');
return FALSE;
} else {
// Just going to hardwire this for now.
return (($_SERVER['PHP_AUTH_USER']=='username') && ($_SERVER['PHP_AUTH_PW']=='somepassword'));
}
}
function read_access_list ($filename) {
$fp = fopen ($filename, "r");
$fstring = fread ($fp, filesize ($filename));
fclose ($fp);
$lines = explode("\n", $fstring);
$len = count($lines);
foreach ($lines as $line) {
$pos = strpos($line, ';');
if ($pos > 0) $line = substr($line, 0, $pos-1); // Strip any comments.
if (strlen($line) > 0) { // If we've got a real ACL entry, then parse it.
$addresses = preg_split ("/[\s,]+/", $line);
$acl[] = new net ($addresses[0], $addresses[1]);
}
}
return $acl;
}
class net {
var $net_address;
var $net_mask;
// Constructor method.
function net ( $network, $mask ) {
$this->net_address = $network;
$this->net_mask = $mask;
}
function is_member( $ip ) {
$network_long = ip2long ($this->net_address);
$mask_long = ip2long ($this->net_mask);
$ip_long = ip2long ($ip);
return (($ip_long & $mask_long) == $network_long);
}
function get_props() {
return ( "$this->net_address $this->net_mask" );
}
}
?>