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>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
    <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("/<([^<>]*)@([^<]*)>/", "&lt;$1@$2&gt;", $str); // This only does email addresses.
    
    $str = preg_replace('/</', '&lt;', $str); // This will catch more tags. --CBT
    $str = preg_replace("/>/", '&gt;', $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" );
    }

}




?>