├── .gitignore ├── support ├── st_functions.php ├── Net │ └── license.txt ├── str_basics.php ├── ipaddr.php ├── mime_parser.php ├── utf8.php ├── pop3.php ├── cli.php ├── utf_utils.php └── smtp.php ├── install.php ├── run.php ├── plugins └── email_notify.php └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | /config.* 2 | /input/* 3 | /logs/* 4 | /test.php 5 | -------------------------------------------------------------------------------- /support/st_functions.php: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /support/Net/license.txt: -------------------------------------------------------------------------------- 1 | Net_DNS2 - DNS Library for handling lookups and updates. 2 | 3 | Copyright (c) 2010-2020, Mike Pultz . 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions 8 | are met: 9 | 10 | * Redistributions of source code must retain the above copyright 11 | notice, this list of conditions and the following disclaimer. 12 | 13 | * Redistributions in binary form must reproduce the above copyright 14 | notice, this list of conditions and the following disclaimer in 15 | the documentation and/or other materials provided with the 16 | distribution. 17 | 18 | * Neither the name of Mike Pultz nor the names of his contributors 19 | may be used to endorse or promote products derived from this 20 | software without specific prior written permission. 21 | 22 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 23 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 24 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 25 | FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 26 | COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 27 | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 28 | BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 29 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 30 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 31 | LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 32 | ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 33 | POSSIBILITY OF SUCH DAMAGE. 34 | -------------------------------------------------------------------------------- /install.php: -------------------------------------------------------------------------------- 1 | array( 23 | "?" => "help" 24 | ), 25 | "rules" => array( 26 | "help" => array("arg" => false) 27 | ) 28 | ); 29 | $args = CLI::ParseCommandLine($options); 30 | 31 | if (isset($args["opts"]["help"])) 32 | { 33 | echo "Status tracker installer\n"; 34 | echo "Purpose: Installs status tracker.\n"; 35 | echo "\n"; 36 | echo "This tool is question/answer enabled. Just running it will provide a guided interface. It can also be run entirely from the command-line if you know all the answers.\n"; 37 | echo "\n"; 38 | echo "Syntax: " . $args["file"] . " [options]\n"; 39 | echo "\n"; 40 | echo "Examples:\n"; 41 | echo "\tphp " . $args["file"] . "\n"; 42 | 43 | exit(); 44 | } 45 | 46 | $plugins = ST_LoadPlugins(); 47 | foreach ($plugins as $plugin) 48 | { 49 | $plugin->Install(); 50 | } 51 | 52 | ST_SaveConfig($config); 53 | 54 | if (!is_dir($rootpath . "/input")) @mkdir($rootpath . "/input", 0775); 55 | if (!is_dir($rootpath . "/logs")) @mkdir($rootpath . "/logs", 0775); 56 | 57 | echo "\n"; 58 | echo "**********\n"; 59 | echo "Configuration file is located at '" . $rootpath . "/config.dat'. It can be manually edited.\n\n"; 60 | echo "Now you can set up cron to run 'run.php' every minute.\n"; 61 | echo "Read the documentation on what to do next.\n"; 62 | echo "**********\n\n"; 63 | 64 | echo "Done.\n"; 65 | ?> -------------------------------------------------------------------------------- /support/str_basics.php: -------------------------------------------------------------------------------- 1 | $val) 10 | { 11 | if (is_string($val)) $_REQUEST[$key] = trim($val); 12 | else if (is_array($val)) 13 | { 14 | $_REQUEST[$key] = array(); 15 | foreach ($val as $key2 => $val2) $_REQUEST[$key][$key2] = (is_string($val2) ? trim($val2) : $val2); 16 | } 17 | else $_REQUEST[$key] = $val; 18 | } 19 | } 20 | 21 | // Cleans up all PHP input issues so that $_REQUEST may be used as expected. 22 | public static function ProcessAllInput() 23 | { 24 | self::ProcessSingleInput($_COOKIE); 25 | self::ProcessSingleInput($_GET); 26 | self::ProcessSingleInput($_POST); 27 | } 28 | 29 | public static function ExtractPathname($dirfile) 30 | { 31 | $dirfile = str_replace("\\", "/", $dirfile); 32 | $pos = strrpos($dirfile, "/"); 33 | if ($pos === false) $dirfile = ""; 34 | else $dirfile = substr($dirfile, 0, $pos + 1); 35 | 36 | return $dirfile; 37 | } 38 | 39 | public static function ExtractFilename($dirfile) 40 | { 41 | $dirfile = str_replace("\\", "/", $dirfile); 42 | $pos = strrpos($dirfile, "/"); 43 | if ($pos !== false) $dirfile = substr($dirfile, $pos + 1); 44 | 45 | return $dirfile; 46 | } 47 | 48 | public static function ExtractFileExtension($dirfile) 49 | { 50 | $dirfile = self::ExtractFilename($dirfile); 51 | $pos = strrpos($dirfile, "."); 52 | if ($pos !== false) $dirfile = substr($dirfile, $pos + 1); 53 | else $dirfile = ""; 54 | 55 | return $dirfile; 56 | } 57 | 58 | public static function ExtractFilenameNoExtension($dirfile) 59 | { 60 | $dirfile = self::ExtractFilename($dirfile); 61 | $pos = strrpos($dirfile, "."); 62 | if ($pos !== false) $dirfile = substr($dirfile, 0, $pos); 63 | 64 | return $dirfile; 65 | } 66 | 67 | // Makes an input filename safe for use. 68 | // Allows a very limited number of characters through. 69 | public static function FilenameSafe($filename) 70 | { 71 | return preg_replace('/\s+/', "-", trim(trim(preg_replace('/[^A-Za-z0-9_.\-]/', " ", $filename), "."))); 72 | } 73 | 74 | public static function ReplaceNewlines($replacewith, $data) 75 | { 76 | $data = str_replace("\r\n", "\n", $data); 77 | $data = str_replace("\r", "\n", $data); 78 | $data = str_replace("\n", $replacewith, $data); 79 | 80 | return $data; 81 | } 82 | 83 | public static function LineInput($data, &$pos) 84 | { 85 | $CR = ord("\r"); 86 | $LF = ord("\n"); 87 | 88 | $result = ""; 89 | $y = strlen($data); 90 | if ($pos > $y) $pos = $y; 91 | while ($pos < $y && ord($data[$pos]) != $CR && ord($data[$pos]) != $LF) 92 | { 93 | $result .= $data[$pos]; 94 | $pos++; 95 | } 96 | if ($pos + 1 < $y && ord($data[$pos]) == $CR && ord($data[$pos + 1]) == $LF) $pos++; 97 | if ($pos < $y) $pos++; 98 | 99 | return $result; 100 | } 101 | 102 | // Constant-time string comparison. Ported from CubicleSoft C++ code. 103 | public static function CTstrcmp($secret, $userinput) 104 | { 105 | $sx = 0; 106 | $sy = strlen($secret); 107 | $uy = strlen($userinput); 108 | $result = $sy - $uy; 109 | for ($ux = 0; $ux < $uy; $ux++) 110 | { 111 | $result |= ord($userinput[$ux]) ^ ord($secret[$sx]); 112 | $sx = ($sx + 1) % $sy; 113 | } 114 | 115 | return $result; 116 | } 117 | 118 | public static function ConvertUserStrToBytes($str) 119 | { 120 | $str = trim($str); 121 | $num = (double)$str; 122 | if (strtoupper(substr($str, -1)) == "B") $str = substr($str, 0, -1); 123 | switch (strtoupper(substr($str, -1))) 124 | { 125 | case "P": $num *= 1024; 126 | case "T": $num *= 1024; 127 | case "G": $num *= 1024; 128 | case "M": $num *= 1024; 129 | case "K": $num *= 1024; 130 | } 131 | 132 | return $num; 133 | } 134 | 135 | public static function ConvertBytesToUserStr($num) 136 | { 137 | $num = (double)$num; 138 | 139 | if ($num < 0) return "0 B"; 140 | if ($num < 1024) return number_format($num, 0) . " B"; 141 | if ($num < 1048576) return str_replace(".0 ", "", number_format($num / 1024, 1)) . " KB"; 142 | if ($num < 1073741824) return str_replace(".0 ", "", number_format($num / 1048576, 1)) . " MB"; 143 | if ($num < 1099511627776.0) return str_replace(".0 ", "", number_format($num / 1073741824.0, 1)) . " GB"; 144 | if ($num < 1125899906842624.0) return str_replace(".0 ", "", number_format($num / 1099511627776.0, 1)) . " TB"; 145 | 146 | return str_replace(".0 ", "", number_format($num / 1125899906842624.0, 1)) . " PB"; 147 | } 148 | 149 | public static function JSSafe($data) 150 | { 151 | return str_replace(array("'", "\r", "\n"), array("\\'", "\\r", "\\n"), $data); 152 | } 153 | } 154 | ?> -------------------------------------------------------------------------------- /run.php: -------------------------------------------------------------------------------- 1 | InitRun(); 28 | } 29 | 30 | // Retrieve new input files. 31 | $files = array(); 32 | $dir = @opendir($rootpath . "/input"); 33 | if ($dir) 34 | { 35 | while (($file = readdir($dir)) !== false) 36 | { 37 | if ($file !== "." && $file !== "..") 38 | { 39 | $filename = $rootpath . "/input/" . $file; 40 | 41 | if (substr($file, -5) === ".json" && strpos($file, "-") !== false) 42 | { 43 | $ts = filemtime($filename); 44 | if ($ts + 1 < time()) 45 | { 46 | $data = @file_get_contents($filename); 47 | $data = @json_decode($data, true); 48 | if (is_array($data)) 49 | { 50 | // Set the timestamp of the starting point for status duration calculations. 51 | $data["start"] = $ts; 52 | $data["start_utc"] = date("Y-m-d H:i:s", $data["start"]); 53 | 54 | $files[$file] = $data; 55 | } 56 | } 57 | } 58 | 59 | @unlink($filename); 60 | } 61 | } 62 | 63 | closedir($dir); 64 | } 65 | 66 | uksort($files, "strnatcmp"); 67 | 68 | // Retrieve existing names. 69 | $names = array(); 70 | $dir = @opendir($rootpath . "/logs"); 71 | if ($dir) 72 | { 73 | while (($file = readdir($dir)) !== false) 74 | { 75 | if (substr($file, -5) === ".json") 76 | { 77 | $filename = $rootpath . "/logs/" . $file; 78 | $data = @file_get_contents($filename); 79 | $data = @json_decode($data, true); 80 | if (is_array($data)) $names[substr($file, 0, -5)] = $data; 81 | } 82 | } 83 | 84 | closedir($dir); 85 | } 86 | 87 | function NotifyUsers($name, $data, $prevstatus, $ts) 88 | { 89 | global $plugins; 90 | 91 | foreach ($plugins as $plugin) 92 | { 93 | $plugin->NotifyUsers($name, $data, $prevstatus, $ts); 94 | } 95 | } 96 | 97 | function AppendLog($name, $data, $endts) 98 | { 99 | global $rootpath; 100 | 101 | $data["end"] = $endts; 102 | $data["end_utc"] = date("Y-m-d H:i:s", $endts); 103 | 104 | $fp = fopen($rootpath . "/logs/" . $name . ".log", "ab"); 105 | if ($fp !== false) 106 | { 107 | fwrite($fp, json_encode($data, JSON_UNESCAPED_SLASHES) . "\n"); 108 | 109 | fclose($fp); 110 | } 111 | } 112 | 113 | // Process incoming data. 114 | foreach ($files as $filename => $data) 115 | { 116 | if (isset($data["status"]) && is_string($data["status"]) && isset($data["notify"]) && (is_string($data["notify"]) || is_array($data["notify"]))) 117 | { 118 | $pos = strpos($filename, "-"); 119 | $name = substr($filename, 0, $pos); 120 | 121 | // Calculate timeout. 122 | if (isset($data["timeout"])) $data["timeout"] = time() + ((int)$data["timeout"] > 0 ? (int)$data["timeout"] : 1); 123 | 124 | // Revert 'start' if it already existed and the status has not changed. 125 | if (isset($names[$name]) && $data["status"] === $names[$name]["status"]) 126 | { 127 | $data["start"] = $names[$name]["start"]; 128 | $data["start_utc"] = $names[$name]["start_utc"]; 129 | } 130 | 131 | if (is_string($data["notify"])) $data["notify"] = array($data["notify"]); 132 | 133 | file_put_contents($rootpath . "/logs/" . $name . ".json", json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES)); 134 | 135 | // Only notify users if the status has changed. 136 | if (!isset($names[$name])) NotifyUsers($name, $data, false, $data["start"]); 137 | else if ($data["status"] !== $names[$name]["status"]) 138 | { 139 | AppendLog($name, $names[$name], $data["start"]); 140 | 141 | NotifyUsers($name, $data, $names[$name]["status"], $data["start"]); 142 | } 143 | 144 | $names[$name] = $data; 145 | } 146 | } 147 | 148 | // Evaluate remaining names for timeout expiration. 149 | foreach ($names as $name => $data) 150 | { 151 | if (isset($data["timeout"]) && $data["timeout"] <= time()) 152 | { 153 | // Copy the data, set the status, and clear the timeout. 154 | $data2 = $data; 155 | $data2["status"] = "status-tracker-timeout"; 156 | unset($data2["timeout"]); 157 | 158 | // Set the timestamp of the starting point for status duration calculation. 159 | $data2["start"] = time(); 160 | $data2["start_utc"] = date("Y-m-d H:i:s", $data2["start"]); 161 | 162 | file_put_contents($rootpath . "/logs/" . $name . ".json", json_encode($data2, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES)); 163 | 164 | AppendLog($name, $data, $data2["start"]); 165 | 166 | NotifyUsers($name, $data2, $data["status"], $data2["start"]); 167 | } 168 | } 169 | ?> -------------------------------------------------------------------------------- /plugins/email_notify.php: -------------------------------------------------------------------------------- 1 | "; 50 | 51 | $message .= "

The status for " . $name . ($prevstatus !== false ? " has been changed from '" . $prevstatus . "' to '" . $data["status"] . "'." : " is now '" . $data["status"] . "' and was previously unregistered.") . "

\n\n"; 52 | 53 | if (isset($data["info"])) 54 | { 55 | $message .= "
";
56 | 				$message .= htmlspecialchars(json_encode($data["info"], JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));
57 | 				$message .= "
\n\n"; 58 | } 59 | 60 | $message .= "

"; 61 | $message .= "Host: " . gethostname() . "
\n"; 62 | $message .= "Time (local): " . date("D, F j, Y g:i:s A", $ts) . "
\n"; 63 | $message .= "Time (UTC): " . gmdate("D, F j, Y g:i:s A", $ts); 64 | $message .= "

\n\n"; 65 | 66 | $message .= ""; 67 | 68 | $smtpoptions = array( 69 | "headers" => SMTP::GetUserAgent("Thunderbird"), 70 | "htmlmessage" => $message, 71 | "textmessage" => SMTP::ConvertHTMLToText($message), 72 | "usemail" => $config["emailinfo"]["usemail"], 73 | "server" => $config["emailinfo"]["server"], 74 | "port" => $config["emailinfo"]["port"], 75 | "secure" => $config["emailinfo"]["secure"], 76 | "username" => $config["emailinfo"]["username"], 77 | "password" => $config["emailinfo"]["password"] 78 | ); 79 | 80 | $fromaddr = $config["emailinfo"]["from"]; 81 | 82 | foreach ($data["notify"] as $toaddr) 83 | { 84 | if (substr($toaddr, 0, 7) === "mailto:" || substr($toaddr, 0, 7) === "email:") 85 | { 86 | $toaddr = str_replace(array("mailto:", "email:"), "", $toaddr); 87 | 88 | // SMTP only. No POP before SMTP support. 89 | $result = SMTP::SendEmail($fromaddr, $toaddr, $subject, $smtpoptions); 90 | if (!$result["success"]) CLI::DisplayError("Unable to send notification e-mail from '" . $fromaddr . "' to '" . $toaddr . "'.", $result, false); 91 | } 92 | } 93 | } 94 | } 95 | ?> -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Status Tracker 2 | ============== 3 | 4 | A simple and elegant cron script and web application health status tracker written in PHP. This tool allows cron scripts and web applications to rapidly report their status using a very simple JSON file storage mechanism. The tool runs via cron and processes each report as it comes in. Whenever the status changes, users are notified about the change. 5 | 6 | If you have cron scripts that run on a regular basis (e.g. every minute) and flood your inbox with a bajillion e-mails when things like network failures occur, then this tool is for you. It is a superior system monitor because you only register the script changes you are actually interested in receiving. Notifications sent out only when the status changes, which results in only two or three e-mails being sent instead of a zillion e-mails. 7 | 8 | Quiet inboxes are happy inboxes. 9 | 10 | NOTE: This product is considered largely obsolete in favor of [xcron](https://github.com/cubiclesoft/xcron), which can do the same thing as this product but far more elegantly and with a much richer feature set. 11 | 12 | [![Donate](https://cubiclesoft.com/res/donate-shield.png)](https://cubiclesoft.com/donate/) [![Discord](https://img.shields.io/discord/777282089980526602?label=chat&logo=discord)](https://cubiclesoft.com/product-support/github/) 13 | 14 | Features 15 | -------- 16 | 17 | * Powerful status tracking in a very small solution. 18 | * Blazing fast performance. Simply write a JSON file to the /input directory to report current status. No complex remote API to set up. 19 | * Language agnostic. As long as a language can create JSON and write it to a file, then it is supported. 20 | * Tracks status start and end times in a JSON log file. Other tools can then be used make pretty color-coded graphs. 21 | * Extensible. Add Slack or other notification systems via plugins. 22 | * Also has a liberal open source license. MIT or LGPL, your choice. 23 | * Designed for relatively painless integration into your environment. 24 | * Sits on GitHub for all of that pull request and issue tracker goodness to easily submit changes and ideas respectively. 25 | 26 | Installation 27 | ------------ 28 | 29 | To install, download this repository and put it somewhere secure. Put all desired plugins into the 'plugins/' directory. Then run: 30 | 31 | `php install.php` 32 | 33 | And follow the prompts. 34 | 35 | To secure the installation, create a new user group (e.g. `addgroup statustracker` on Ubuntu) and then set the group of the /input directory to the new user group (e.g. `chgrp statustracker input`) and then add the group to one or more users (e.g. `adduser www-data statustracker' on Ubuntu). 36 | 37 | From an application or cron script, write a serialized JSON object to the /input directory containing: 38 | 39 | * status (Required) - A string containing the current status. 40 | * notify (Required) - A string or array containing one or more users to notify. Plugins expect certain prefixes to be applied to each user. For example, the "mailto:" prefix will be handled by the e-mail notification plugin. 41 | * timeout (Optional) - An integer containing the number of seconds to wait before automatically setting the status to 'status-tracker-timeout' and notifying users about the change. Useful for automatically discovering cron scripts that stop working for some unknown reason. 42 | 43 | Example: 44 | 45 | ```` 46 | { 47 | "status": "normal", 48 | "timeout": 900, 49 | "notify": [ 50 | "mailto:you@somewhere.com", 51 | "mailto:helpdesk@somewhere.com", 52 | "mailto:1235555555@txt.att.net" 53 | ] 54 | } 55 | ```` 56 | 57 | In the example, the status is set to 'normal' (you can use whatever statuses work for you), the timeout is set to 15 minutes (15 * 60 = 900), and three e-mail addresses are notified (one of which will translate to a SMS message for a cellphone on the AT&T cell network). 58 | 59 | The filename must contain a hyphen, end in '.json', and be in the format: 60 | 61 | `name-sortingmetric.json` 62 | 63 | The 'name' portion can be whatever you want. The 'sortingmetric' is probably best as a UNIX timestamp of some sort (e.g. `time()` or `microtime(true)` under PHP). 64 | 65 | Once you have your first JSON object in the /input directory, manually run: 66 | 67 | `php run.php` 68 | 69 | And verify that all users receive their first notification from the system. 70 | 71 | Finally, add the script to cron or other system task scheduler so that it runs every minute: 72 | 73 | `* * * * * /usr/bin/php /var/scripts/script-tracker/run.php > /dev/null 2>&1` 74 | 75 | About /logs 76 | ----------- 77 | 78 | For every 'name' dropped into the /input directory, there are two associated files in the /logs directory. 79 | 80 | The first associated file ends in '.json' and stores the previous state as recorded by status tracker. It represents the current status and will simply be updated repeatedly until the 'status' changes. 81 | 82 | The second associated file ends in '.log' and contains past statuses in JSON format, one per line. The duration of a status can easily be calculated as `end - start`. This makes it fairly straightforward to create a visual chart or graph of the changes in status over time to quickly identify problem areas that need to be resolved. Analyzing the logs is beyond the scope of this tool. 83 | 84 | Writing Plugins 85 | --------------- 86 | 87 | Look at the 'plugins/email_notify.php' plugin to see a simple example of a plugin. Support files should go into the /support directory. Most plugins, when correctly written, will be about the same size as the e-mail notification plugin, which weighs in at less than 100 lines of code. 88 | -------------------------------------------------------------------------------- /support/ipaddr.php: -------------------------------------------------------------------------------- 1 | $segment) 40 | { 41 | $segment = trim($segment); 42 | if ($segment != "") $ipaddr2[] = $segment; 43 | else if ($foundpos === false && count($ipaddr) > $num + 1 && $ipaddr[$num + 1] != "") 44 | { 45 | $foundpos = count($ipaddr2); 46 | $ipaddr2[] = "0000"; 47 | } 48 | } 49 | // Convert ::ffff:123.123.123.123 format. 50 | if (strpos($ipaddr2[count($ipaddr2) - 1], ".") !== false) 51 | { 52 | $x = count($ipaddr2) - 1; 53 | if ($ipaddr2[count($ipaddr2) - 2] != "ffff") $ipaddr2[$x] = "0"; 54 | else 55 | { 56 | $ipaddr = explode(".", $ipaddr2[$x]); 57 | if (count($ipaddr) != 4) $ipaddr2[$x] = "0"; 58 | else 59 | { 60 | $ipaddr2[$x] = str_pad(strtolower(dechex($ipaddr[0])), 2, "0", STR_PAD_LEFT) . str_pad(strtolower(dechex($ipaddr[1])), 2, "0", STR_PAD_LEFT); 61 | $ipaddr2[] = str_pad(strtolower(dechex($ipaddr[2])), 2, "0", STR_PAD_LEFT) . str_pad(strtolower(dechex($ipaddr[3])), 2, "0", STR_PAD_LEFT); 62 | } 63 | } 64 | } 65 | $ipaddr = array_slice($ipaddr2, 0, 8); 66 | if ($foundpos !== false && count($ipaddr) < 8) array_splice($ipaddr, $foundpos, 0, array_fill(0, 8 - count($ipaddr), "0000")); 67 | foreach ($ipaddr as $num => $segment) 68 | { 69 | $ipaddr[$num] = substr(str_pad(strtolower(dechex(hexdec($segment))), 4, "0", STR_PAD_LEFT), -4); 70 | } 71 | $ipv6addr = implode(":", $ipaddr); 72 | 73 | // Extract IPv4 address. 74 | if (substr($ipv6addr, 0, 30) == "0000:0000:0000:0000:0000:ffff:") $ipv4addr = hexdec(substr($ipv6addr, 30, 2)) . "." . hexdec(substr($ipv6addr, 32, 2)) . "." . hexdec(substr($ipv6addr, 35, 2)) . "." . hexdec(substr($ipv6addr, 37, 2)); 75 | 76 | // Make a short IPv6 address. 77 | $shortipv6 = $ipv6addr; 78 | $pattern = "0000:0000:0000:0000:0000:0000:0000"; 79 | do 80 | { 81 | $shortipv6 = str_replace($pattern, ":", $shortipv6); 82 | $pattern = substr($pattern, 5); 83 | } while (strlen($shortipv6) == 39 && $pattern != ""); 84 | $shortipv6 = explode(":", $shortipv6); 85 | foreach ($shortipv6 as $num => $segment) 86 | { 87 | if ($segment != "") $shortipv6[$num] = strtolower(dechex(hexdec($segment))); 88 | } 89 | $shortipv6 = implode(":", $shortipv6); 90 | 91 | return array("ipv6" => $ipv6addr, "shortipv6" => $shortipv6, "ipv4" => $ipv4addr); 92 | } 93 | 94 | public static function GetRemoteIP($proxies = array()) 95 | { 96 | $ipaddr = self::NormalizeIP(isset($_SERVER["REMOTE_ADDR"]) ? $_SERVER["REMOTE_ADDR"] : "127.0.0.1"); 97 | 98 | // Check for trusted proxies. Stop at first untrusted IP in the chain. 99 | if (isset($proxies[$ipaddr["ipv6"]]) || ($ipaddr["ipv4"] != "" && isset($proxies[$ipaddr["ipv4"]]))) 100 | { 101 | $xforward = (isset($_SERVER["HTTP_X_FORWARDED_FOR"]) ? explode(",", $_SERVER["HTTP_X_FORWARDED_FOR"]) : array()); 102 | $clientip = (isset($_SERVER["HTTP_CLIENT_IP"]) ? explode(",", $_SERVER["HTTP_CLIENT_IP"]) : array()); 103 | 104 | do 105 | { 106 | $found = false; 107 | 108 | if (isset($proxies[$ipaddr["ipv6"]])) $header = $proxies[$ipaddr["ipv6"]]; 109 | else $header = $proxies[$ipaddr["ipv4"]]; 110 | 111 | $header = strtolower($header); 112 | if ($header == "xforward" && count($xforward) > 0) 113 | { 114 | $ipaddr = self::NormalizeIP(array_pop($xforward)); 115 | $found = true; 116 | } 117 | else if ($header == "clientip" && count($clientip) > 0) 118 | { 119 | $ipaddr = self::NormalizeIP(array_pop($clientip)); 120 | $found = true; 121 | } 122 | } while ($found && (isset($proxies[$ipaddr["ipv6"]]) || ($ipaddr["ipv4"] != "" && isset($proxies[$ipaddr["ipv4"]])))); 123 | } 124 | 125 | return $ipaddr; 126 | } 127 | 128 | public static function IsMatch($pattern, $ipaddr) 129 | { 130 | if (is_string($ipaddr)) $ipaddr = self::NormalizeIP($ipaddr); 131 | 132 | if (strpos($pattern, ":") !== false) 133 | { 134 | // Pattern is IPv6. 135 | $pattern = explode(":", strtolower($pattern)); 136 | $ipaddr = explode(":", $ipaddr["ipv6"]); 137 | if (count($pattern) != 8 || count($ipaddr) != 8) return false; 138 | foreach ($pattern as $num => $segment) 139 | { 140 | $found = false; 141 | $pieces = explode(",", $segment); 142 | foreach ($pieces as $piece) 143 | { 144 | $piece = trim($piece); 145 | $piece = explode(".", $piece); 146 | if (count($piece) == 1) 147 | { 148 | $piece = $piece[0]; 149 | 150 | if ($piece == "*") $found = true; 151 | else if (strpos($piece, "-") !== false) 152 | { 153 | $range = explode("-", $piece); 154 | $range[0] = hexdec($range[0]); 155 | $range[1] = hexdec($range[1]); 156 | $val = hexdec($ipaddr[$num]); 157 | if ($range[0] > $range[1]) $range[0] = $range[1]; 158 | if ($val >= $range[0] && $val <= $range[1]) $found = true; 159 | } 160 | else if ($piece === $ipaddr[$num]) $found = true; 161 | } 162 | else if (count($piece) == 2) 163 | { 164 | // Special IPv4-like notation. 165 | $found2 = false; 166 | $found3 = false; 167 | $val = hexdec(substr($ipaddr[$num], 0, 2)); 168 | $val2 = hexdec(substr($ipaddr[$num], 2, 2)); 169 | 170 | if ($piece[0] == "*") $found2 = true; 171 | else if (strpos($piece[0], "-") !== false) 172 | { 173 | $range = explode("-", $piece[0]); 174 | if ($range[0] > $range[1]) $range[0] = $range[1]; 175 | if ($val >= $range[0] && $val <= $range[1]) $found2 = true; 176 | } 177 | else if ($piece[0] == $val) $found2 = true; 178 | 179 | if ($piece[1] == "*") $found3 = true; 180 | else if (strpos($piece[1], "-") !== false) 181 | { 182 | $range = explode("-", $piece[1]); 183 | if ($range[0] > $range[1]) $range[0] = $range[1]; 184 | if ($val >= $range[0] && $val <= $range[1]) $found3 = true; 185 | } 186 | else if ($piece[1] == $val2) $found3 = true; 187 | 188 | if ($found2 && $found3) $found = true; 189 | } 190 | 191 | if ($found) break; 192 | } 193 | 194 | if (!$found) return false; 195 | } 196 | } 197 | else 198 | { 199 | // Pattern is IPv4. 200 | $pattern = explode(".", strtolower($pattern)); 201 | $ipaddr = explode(".", $ipaddr["ipv4"]); 202 | if (count($pattern) != 4 || count($ipaddr) != 4) return false; 203 | foreach ($pattern as $num => $segment) 204 | { 205 | $found = false; 206 | $pieces = explode(",", $segment); 207 | foreach ($pieces as $piece) 208 | { 209 | $piece = trim($piece); 210 | 211 | if ($piece == "*") $found = true; 212 | else if (strpos($piece, "-") !== false) 213 | { 214 | $range = explode("-", $piece); 215 | if ($range[0] > $range[1]) $range[0] = $range[1]; 216 | if ($ipaddr[$num] >= $range[0] && $ipaddr[$num] <= $range[1]) $found = true; 217 | } 218 | else if ($piece == $ipaddr[$num]) $found = true; 219 | 220 | if ($found) break; 221 | } 222 | 223 | if (!$found) return false; 224 | } 225 | } 226 | 227 | return true; 228 | } 229 | } 230 | ?> -------------------------------------------------------------------------------- /support/mime_parser.php: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /support/utf8.php: -------------------------------------------------------------------------------- 1 | = 0x20 && $tempchr <= 0x7E) || $tempchr == 0x09 || $tempchr == 0x0A || $tempchr == 0x0D) 20 | { 21 | // ASCII minus control and special characters. 22 | $result .= chr($tempchr); 23 | $x++; 24 | } 25 | else 26 | { 27 | if ($y - $x > 1) $tempchr2 = ord($data[$x + 1]); 28 | else $tempchr2 = 0x00; 29 | if ($y - $x > 2) $tempchr3 = ord($data[$x + 2]); 30 | else $tempchr3 = 0x00; 31 | if ($y - $x > 3) $tempchr4 = ord($data[$x + 3]); 32 | else $tempchr4 = 0x00; 33 | 34 | if (($tempchr >= 0xC2 && $tempchr <= 0xDF) && ($tempchr2 >= 0x80 && $tempchr2 <= 0xBF)) 35 | { 36 | // Non-overlong (2 bytes). 37 | $result .= chr($tempchr); 38 | $result .= chr($tempchr2); 39 | $x += 2; 40 | } 41 | else if ($tempchr == 0xE0 && ($tempchr2 >= 0xA0 && $tempchr2 <= 0xBF) && ($tempchr3 >= 0x80 && $tempchr3 <= 0xBF)) 42 | { 43 | // Non-overlong (3 bytes). 44 | $result .= chr($tempchr); 45 | $result .= chr($tempchr2); 46 | $result .= chr($tempchr3); 47 | $x += 3; 48 | } 49 | else if ((($tempchr >= 0xE1 && $tempchr <= 0xEC) || $tempchr == 0xEE || $tempchr == 0xEF) && ($tempchr2 >= 0x80 && $tempchr2 <= 0xBF) && ($tempchr3 >= 0x80 && $tempchr3 <= 0xBF)) 50 | { 51 | // Normal/straight (3 bytes). 52 | $result .= chr($tempchr); 53 | $result .= chr($tempchr2); 54 | $result .= chr($tempchr3); 55 | $x += 3; 56 | } 57 | else if ($tempchr == 0xED && ($tempchr2 >= 0x80 && $tempchr2 <= 0x9F) && ($tempchr3 >= 0x80 && $tempchr3 <= 0xBF)) 58 | { 59 | // Non-surrogates (3 bytes). 60 | $result .= chr($tempchr); 61 | $result .= chr($tempchr2); 62 | $result .= chr($tempchr3); 63 | $x += 3; 64 | } 65 | else if ($tempchr == 0xF0 && ($tempchr2 >= 0x90 && $tempchr2 <= 0xBF) && ($tempchr3 >= 0x80 && $tempchr3 <= 0xBF) && ($tempchr4 >= 0x80 && $tempchr4 <= 0xBF)) 66 | { 67 | // Planes 1-3 (4 bytes). 68 | $result .= chr($tempchr); 69 | $result .= chr($tempchr2); 70 | $result .= chr($tempchr3); 71 | $result .= chr($tempchr4); 72 | $x += 4; 73 | } 74 | else if (($tempchr >= 0xF1 && $tempchr <= 0xF3) && ($tempchr2 >= 0x80 && $tempchr2 <= 0xBF) && ($tempchr3 >= 0x80 && $tempchr3 <= 0xBF) && ($tempchr4 >= 0x80 && $tempchr4 <= 0xBF)) 75 | { 76 | // Planes 4-15 (4 bytes). 77 | $result .= chr($tempchr); 78 | $result .= chr($tempchr2); 79 | $result .= chr($tempchr3); 80 | $result .= chr($tempchr4); 81 | $x += 4; 82 | } 83 | else if ($tempchr == 0xF4 && ($tempchr2 >= 0x80 && $tempchr2 <= 0x8F) && ($tempchr3 >= 0x80 && $tempchr3 <= 0xBF) && ($tempchr4 >= 0x80 && $tempchr4 <= 0xBF)) 84 | { 85 | // Plane 16 (4 bytes). 86 | $result .= chr($tempchr); 87 | $result .= chr($tempchr2); 88 | $result .= chr($tempchr3); 89 | $result .= chr($tempchr4); 90 | $x += 4; 91 | } 92 | else if ($open && $x + 4 > $y) break; 93 | else $x++; 94 | } 95 | } 96 | 97 | $prefix = substr($data, $x); 98 | 99 | return $result; 100 | } 101 | 102 | public static function MakeValid($data) 103 | { 104 | $prefix = ""; 105 | 106 | if (!is_string($data)) $data = (string)$data; 107 | 108 | return self::MakeValidStream($prefix, $data, false); 109 | } 110 | 111 | public static function IsValid($data) 112 | { 113 | $x = 0; 114 | $y = strlen($data); 115 | while ($x < $y) 116 | { 117 | $tempchr = ord($data[$x]); 118 | if (($tempchr >= 0x20 && $tempchr <= 0x7E) || $tempchr == 0x09 || $tempchr == 0x0A || $tempchr == 0x0D) $x++; 119 | else if ($tempchr < 0xC2) return false; 120 | else 121 | { 122 | $left = $y - $x; 123 | if ($left > 1) $tempchr2 = ord($data[$x + 1]); 124 | else return false; 125 | 126 | if (($tempchr >= 0xC2 && $tempchr <= 0xDF) && ($tempchr2 >= 0x80 && $tempchr2 <= 0xBF)) $x += 2; 127 | else 128 | { 129 | if ($left > 2) $tempchr3 = ord($data[$x + 2]); 130 | else return false; 131 | 132 | if ($tempchr3 < 0x80 || $tempchr3 > 0xBF) return false; 133 | 134 | if ($tempchr == 0xE0 && ($tempchr2 >= 0xA0 && $tempchr2 <= 0xBF)) $x += 3; 135 | else if ((($tempchr >= 0xE1 && $tempchr <= 0xEC) || $tempchr == 0xEE || $tempchr == 0xEF) && ($tempchr2 >= 0x80 && $tempchr2 <= 0xBF)) $x += 3; 136 | else if ($tempchr == 0xED && ($tempchr2 >= 0x80 && $tempchr2 <= 0x9F)) $x += 3; 137 | else 138 | { 139 | if ($left > 3) $tempchr4 = ord($data[$x + 3]); 140 | else return false; 141 | 142 | if ($tempchr4 < 0x80 || $tempchr4 > 0xBF) return false; 143 | 144 | if ($tempchr == 0xF0 && ($tempchr2 >= 0x90 && $tempchr2 <= 0xBF)) $x += 4; 145 | else if (($tempchr >= 0xF1 && $tempchr <= 0xF3) && ($tempchr2 >= 0x80 && $tempchr2 <= 0xBF)) $x += 4; 146 | else if ($tempchr == 0xF4 && ($tempchr2 >= 0x80 && $tempchr2 <= 0x8F)) $x += 4; 147 | else return false; 148 | } 149 | } 150 | } 151 | } 152 | 153 | return true; 154 | } 155 | 156 | // Locates the next UTF8 character in a UTF8 string. 157 | // Set Pos and Size to 0 to start at the beginning. 158 | // Returns false at the end of the string or bad UTF8 character. Otherwise, returns true. 159 | public static function NextChrPos(&$data, $datalen, &$pos, &$size) 160 | { 161 | $pos += $size; 162 | $size = 0; 163 | $x = $pos; 164 | $y = $datalen; 165 | if ($x >= $y) return false; 166 | 167 | $tempchr = ord($data[$x]); 168 | if (($tempchr >= 0x20 && $tempchr <= 0x7E) || $tempchr == 0x09 || $tempchr == 0x0A || $tempchr == 0x0D) $size = 1; 169 | else if ($tempchr < 0xC2) return false; 170 | else 171 | { 172 | $left = $y - $x; 173 | if ($left > 1) $tempchr2 = ord($data[$x + 1]); 174 | else return false; 175 | 176 | if (($tempchr >= 0xC2 && $tempchr <= 0xDF) && ($tempchr2 >= 0x80 && $tempchr2 <= 0xBF)) $size = 2; 177 | else 178 | { 179 | if ($left > 2) $tempchr3 = ord($data[$x + 2]); 180 | else return false; 181 | 182 | if ($tempchr3 < 0x80 || $tempchr3 > 0xBF) return false; 183 | 184 | if ($tempchr == 0xE0 && ($tempchr2 >= 0xA0 && $tempchr2 <= 0xBF)) $size = 3; 185 | else if ((($tempchr >= 0xE1 && $tempchr <= 0xEC) || $tempchr == 0xEE || $tempchr == 0xEF) && ($tempchr2 >= 0x80 && $tempchr2 <= 0xBF)) $size = 3; 186 | else if ($tempchr == 0xED && ($tempchr2 >= 0x80 && $tempchr2 <= 0x9F)) $size = 3; 187 | else 188 | { 189 | if ($left > 3) $tempchr4 = ord($data[$x + 3]); 190 | else return false; 191 | 192 | if ($tempchr4 < 0x80 || $tempchr4 > 0xBF) return false; 193 | 194 | if ($tempchr == 0xF0 && ($tempchr2 >= 0x90 && $tempchr2 <= 0xBF)) $size = 4; 195 | else if (($tempchr >= 0xF1 && $tempchr <= 0xF3) && ($tempchr2 >= 0x80 && $tempchr2 <= 0xBF)) $size = 4; 196 | else if ($tempchr == 0xF4 && ($tempchr2 >= 0x80 && $tempchr2 <= 0x8F)) $size = 4; 197 | else return false; 198 | } 199 | } 200 | } 201 | 202 | return true; 203 | } 204 | 205 | // Converts a numeric value to a UTF8 character (code point). 206 | public static function chr($num) 207 | { 208 | if ($num < 0 || ($num >= 0xD800 && $num <= 0xDFFF) || ($num >= 0xFDD0 && $num <= 0xFDEF) || ($num & 0xFFFE) == 0xFFFE) return ""; 209 | 210 | if ($num <= 0x7F) $result = chr($num); 211 | else if ($num <= 0x7FF) $result = chr(0xC0 | ($num >> 6)) . chr(0x80 | ($num & 0x3F)); 212 | else if ($num <= 0xFFFF) $result = chr(0xE0 | ($num >> 12)) . chr(0x80 | (($num >> 6) & 0x3F)) . chr(0x80 | ($num & 0x3F)); 213 | else if ($num <= 0x10FFFF) $result = chr(0xF0 | ($num >> 18)) . chr(0x80 | (($num >> 12) & 0x3F)) . chr(0x80 | (($num >> 6) & 0x3F)) . chr(0x80 | ($num & 0x3F)); 214 | else $result = ""; 215 | 216 | return $result; 217 | } 218 | 219 | public static function MakeChr($num) 220 | { 221 | return self::chr($num); 222 | } 223 | 224 | // Converts a UTF8 code point to a numeric value. 225 | public static function ord($str) 226 | { 227 | $tempchr = ord($str[0]); 228 | if ($tempchr <= 0x7F) return $tempchr; 229 | else if ($tempchr < 0xC2) return false; 230 | else 231 | { 232 | $y = strlen($str); 233 | if ($y > 1) $tempchr2 = ord($str[1]); 234 | else return false; 235 | 236 | if (($tempchr >= 0xC2 && $tempchr <= 0xDF) && ($tempchr2 >= 0x80 && $tempchr2 <= 0xBF)) return (($tempchr & 0x1F) << 6) | ($tempchr2 & 0x3F); 237 | else 238 | { 239 | if ($y > 2) $tempchr3 = ord($str[2]); 240 | else return false; 241 | 242 | if ($tempchr3 < 0x80 || $tempchr3 > 0xBF) return false; 243 | 244 | if (($tempchr == 0xE0 && ($tempchr2 >= 0xA0 && $tempchr2 <= 0xBF)) || ((($tempchr >= 0xE1 && $tempchr <= 0xEC) || $tempchr == 0xEE || $tempchr == 0xEF) && ($tempchr2 >= 0x80 && $tempchr2 <= 0xBF)) || ($tempchr == 0xED && ($tempchr2 >= 0x80 && $tempchr2 <= 0x9F))) 245 | { 246 | return (($tempchr & 0x0F) << 12) | (($tempchr2 & 0x3F) << 6) | ($tempchr3 & 0x3F); 247 | } 248 | else 249 | { 250 | if ($y > 3) $tempchr4 = ord($str[3]); 251 | else return false; 252 | 253 | if ($tempchr4 < 0x80 || $tempchr4 > 0xBF) return false; 254 | 255 | if (($tempchr == 0xF0 && ($tempchr2 >= 0x90 && $tempchr2 <= 0xBF)) || (($tempchr >= 0xF1 && $tempchr <= 0xF3) && ($tempchr2 >= 0x80 && $tempchr2 <= 0xBF)) || ($tempchr == 0xF4 && ($tempchr2 >= 0x80 && $tempchr2 <= 0x8F))) 256 | { 257 | return (($tempchr & 0x07) << 18) | (($tempchr2 & 0x3F) << 12) | (($tempchr3 & 0x3F) << 6) | ($tempchr4 & 0x3F); 258 | } 259 | } 260 | } 261 | } 262 | 263 | return false; 264 | } 265 | 266 | // Checks a numeric value to see if it is a combining code point. 267 | public static function IsCombiningCodePoint($val) 268 | { 269 | return (($val >= 0x0300 && $val <= 0x036F) || ($val >= 0x1DC0 && $val <= 0x1DFF) || ($val >= 0x20D0 && $val <= 0x20FF) || ($val >= 0xFE20 && $val <= 0xFE2F)); 270 | } 271 | 272 | // Determines if a UTF8 string can also be viewed as ASCII. 273 | public static function IsASCII($data) 274 | { 275 | $pos = 0; 276 | $size = 0; 277 | $y = strlen($data); 278 | while (self::NextChrPos($data, $y, $pos, $size) && $size == 1) {} 279 | if ($pos < $y || $size > 1) return false; 280 | 281 | return true; 282 | } 283 | 284 | // Returns the number of characters in a UTF8 string. 285 | public static function strlen($data) 286 | { 287 | $num = 0; 288 | $pos = 0; 289 | $size = 0; 290 | $y = strlen($data); 291 | while (self::NextChrPos($data, $y, $pos, $size)) $num++; 292 | 293 | return $num; 294 | } 295 | 296 | // Converts a UTF8 string to ASCII and drops bad UTF8 and non-ASCII characters in the process. 297 | public static function ConvertToASCII($data) 298 | { 299 | $result = ""; 300 | 301 | $pos = 0; 302 | $size = 0; 303 | $y = strlen($data); 304 | while ($pos < $y) 305 | { 306 | if (self::NextChrPos($data, $y, $pos, $size) && $size == 1) $result .= $data[$pos]; 307 | else if (!$size) $size = 1; 308 | } 309 | 310 | return $result; 311 | } 312 | 313 | // Converts UTF8 characters in a string to HTML entities. 314 | public static function ConvertToHTML($data) 315 | { 316 | return preg_replace_callback('/([\xC0-\xF7]{1,1}[\x80-\xBF]+)/', __CLASS__ . '::ConvertToHTML__Callback', $data); 317 | } 318 | 319 | protected static function ConvertToHTML__Callback($data) 320 | { 321 | $data = $data[1]; 322 | $num = 0; 323 | $data = str_split(strrev(chr((ord(substr($data, 0, 1)) % 252 % 248 % 240 % 224 % 192) + 128) . substr($data, 1))); 324 | foreach ($data as $k => $v) $num += (ord($v) % 128) * pow(64, $k); 325 | 326 | return "&#" . $num . ";"; 327 | } 328 | } 329 | ?> -------------------------------------------------------------------------------- /support/pop3.php: -------------------------------------------------------------------------------- 1 | fp = false; 12 | $this->messagelist = array(); 13 | $this->debug = false; 14 | } 15 | 16 | public function __destruct() 17 | { 18 | $this->Disconnect(); 19 | } 20 | 21 | public static function GetSSLCiphers($type = "intermediate") 22 | { 23 | $type = strtolower($type); 24 | 25 | // Cipher list last updated May 3, 2017. 26 | if ($type == "modern") return "ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256"; 27 | else if ($type == "old") return "ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:ECDHE-RSA-DES-CBC3-SHA:ECDHE-ECDSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:AES:DES-CBC3-SHA:HIGH:SEED:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!RSAPSK:!aDH:!aECDH:!EDH-DSS-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA:!SRP"; 28 | 29 | return "ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA:ECDHE-ECDSA-DES-CBC3-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:DES-CBC3-SHA:!DSS"; 30 | } 31 | 32 | public static function GetSafeSSLOpts($cafile = true, $cipherstype = "intermediate") 33 | { 34 | // Result array last updated May 3, 2017. 35 | $result = array( 36 | "ciphers" => self::GetSSLCiphers($cipherstype), 37 | "disable_compression" => true, 38 | "allow_self_signed" => false, 39 | "verify_peer" => true, 40 | "verify_depth" => 5 41 | ); 42 | 43 | if ($cafile === true) $result["auto_cainfo"] = true; 44 | else if ($cafile !== false) $result["cafile"] = $cafile; 45 | 46 | return $result; 47 | } 48 | 49 | private static function ProcessSSLOptions(&$options, $key, $host) 50 | { 51 | if (isset($options[$key]["auto_cainfo"])) 52 | { 53 | unset($options[$key]["auto_cainfo"]); 54 | 55 | $cainfo = ini_get("curl.cainfo"); 56 | if ($cainfo !== false && strlen($cainfo) > 0) $options[$key]["cafile"] = $cainfo; 57 | else if (file_exists(str_replace("\\", "/", dirname(__FILE__)) . "/cacert.pem")) $options[$key]["cafile"] = str_replace("\\", "/", dirname(__FILE__)) . "/cacert.pem"; 58 | } 59 | 60 | if (isset($options[$key]["auto_peer_name"])) 61 | { 62 | unset($options[$key]["auto_peer_name"]); 63 | 64 | $options[$key]["peer_name"] = $host; 65 | } 66 | 67 | if (isset($options[$key]["auto_cn_match"])) 68 | { 69 | unset($options[$key]["auto_cn_match"]); 70 | 71 | $options[$key]["CN_match"] = $host; 72 | } 73 | 74 | if (isset($options[$key]["auto_sni"])) 75 | { 76 | unset($options[$key]["auto_sni"]); 77 | 78 | $options[$key]["SNI_enabled"] = true; 79 | $options[$key]["SNI_server_name"] = $host; 80 | } 81 | } 82 | 83 | protected static function GetIDNAHost($host) 84 | { 85 | $y = strlen($host); 86 | for ($x = 0; $x < $y && ord($host[$x]) <= 0x7F; $x++); 87 | 88 | if ($x < $y) 89 | { 90 | if (!class_exists("UTFUtils", false)) require_once str_replace("\\", "/", dirname(__FILE__)) . "/utf_utils.php"; 91 | 92 | $host2 = UTFUtils::ConvertToPunycode($host); 93 | if ($host2 !== false) $host = $host2; 94 | } 95 | 96 | return $host; 97 | } 98 | 99 | public function Connect($username, $password, $options = array()) 100 | { 101 | if ($this->fp !== false) $this->Disconnect(); 102 | 103 | $server = (isset($options["server"]) ? self::GetIDNAHost(trim($options["server"])) : "localhost"); 104 | if ($server == "") return array("success" => false, "error" => self::POP3_Translate("Invalid server specified."), "errorcode" => "invalid_server"); 105 | $secure = (isset($options["secure"]) ? $options["secure"] : false); 106 | $protocol = ($secure ? (isset($options["protocol"]) ? strtolower($options["protocol"]) : "ssl") : "tcp"); 107 | if (function_exists("stream_get_transports") && !in_array($protocol, stream_get_transports())) return array("success" => false, "error" => self::POP3_Translate("The desired transport protocol '%s' is not installed.", $protocol), "errorcode" => "transport_not_installed"); 108 | $port = (isset($options["port"]) ? (int)$options["port"] : -1); 109 | if ($port < 0 || $port > 65535) $port = ($secure ? 995 : 110); 110 | 111 | $this->debug = (isset($options["debug"]) ? $options["debug"] : false); 112 | $errornum = 0; 113 | $errorstr = ""; 114 | if (!isset($options["connecttimeout"])) $options["connecttimeout"] = 10; 115 | if (!function_exists("stream_socket_client")) $this->fp = @fsockopen($protocol . "://" . $server, $port, $errornum, $errorstr, $options["connecttimeout"]); 116 | else 117 | { 118 | $context = @stream_context_create(); 119 | if (isset($options["source_ip"])) $context["socket"] = array("bindto" => $options["source_ip"] . ":0"); 120 | if ($secure) 121 | { 122 | if (!isset($options["sslopts"]) || !is_array($options["sslopts"])) 123 | { 124 | $options["sslopts"] = self::GetSafeSSLOpts(); 125 | $options["sslopts"]["auto_peer_name"] = true; 126 | } 127 | self::ProcessSSLOptions($options, "sslopts", (isset($options["sslhostname"]) ? self::GetIDNAHost($options["sslhostname"]) : $server)); 128 | foreach ($options["sslopts"] as $key => $val) @stream_context_set_option($context, "ssl", $key, $val); 129 | } 130 | $this->fp = @stream_socket_client($protocol . "://" . $server . ":" . $port, $errornum, $errorstr, $options["connecttimeout"], STREAM_CLIENT_CONNECT, $context); 131 | 132 | $contextopts = stream_context_get_options($context); 133 | if ($secure && isset($options["sslopts"]) && is_array($options["sslopts"]) && isset($contextopts["ssl"]["peer_certificate"])) 134 | { 135 | if (isset($options["debug_callback"])) $options["debug_callback"]("peercert", @openssl_x509_parse($contextopts["ssl"]["peer_certificate"]), $options["debug_callback_opts"]); 136 | } 137 | } 138 | 139 | if ($this->fp === false) return array("success" => false, "error" => self::POP3_Translate("Unable to establish a POP3 connection to '%s'.", $protocol . "://" . $server . ":" . $port), "errorcode" => "connection_failure", "info" => $errorstr . " (" . $errornum . ")"); 140 | 141 | // Get the initial connection data. 142 | $result = $this->GetPOP3Response(false); 143 | if (!$result["success"]) return array("success" => false, "error" => self::POP3_Translate("Unable to get initial POP3 data."), "errorcode" => "no_response", "info" => $result); 144 | $rawrecv = $result["rawrecv"]; 145 | $rawsend = ""; 146 | 147 | // Extract APOP information (if any). 148 | $pos = strpos($result["response"], "<"); 149 | $pos2 = strpos($result["response"], ">", (int)$pos); 150 | if ($pos !== false && $pos2 !== false) $apop = substr($result["response"], $pos, $pos2 - $pos + 1); 151 | else $apop = ""; 152 | 153 | // // Determine authentication capabilities. 154 | // fwrite($this->fp, "CAPA\r\n"); 155 | 156 | if ($apop != "" && (!isset($options["use_apop"]) || $options["use_apop"])) 157 | { 158 | $result = $this->POP3Request("APOP " . $username . " " . md5($apop . $password), $rawsend, $rawrecv); 159 | if (!$result["success"]) return array("success" => false, "error" => self::POP3_Translate("The POP3 login request failed (APOP failed)."), "errorcode" => "invalid_response", "info" => $result); 160 | } 161 | else 162 | { 163 | $result = $this->POP3Request("USER " . $username, $rawsend, $rawrecv); 164 | if (!$result["success"]) return array("success" => false, "error" => self::POP3_Translate("The POP3 login username is invalid (USER failed)."), "errorcode" => "invalid_response", "info" => $result); 165 | 166 | $result = $this->POP3Request("PASS " . $password, $rawsend, $rawrecv); 167 | if (!$result["success"]) return array("success" => false, "error" => self::POP3_Translate("The POP3 login password is invalid (PASS failed)."), "errorcode" => "invalid_response", "info" => $result); 168 | } 169 | 170 | return array("success" => true, "rawsend" => $rawsend, "rawrecv" => $rawrecv); 171 | } 172 | 173 | public function GetMessageList() 174 | { 175 | $rawrecv = ""; 176 | $rawsend = ""; 177 | $result = $this->POP3Request("LIST", $rawsend, $rawrecv, true); 178 | if (!$result["success"]) return array("success" => false, "error" => self::POP3_Translate("The message list request failed (LIST failed)."), "errorcode" => "invalid_response", "info" => $result); 179 | 180 | $ids = array(); 181 | foreach ($result["data"] as $data) 182 | { 183 | $data = explode(" ", $data); 184 | if (count($data) > 1) $ids[(int)$data[0]] = (int)$data[1]; 185 | } 186 | 187 | return array("success" => true, "ids" => $ids, "rawsend" => $rawsend, "rawrecv" => $rawrecv); 188 | } 189 | 190 | public function GetMessage($id) 191 | { 192 | $rawrecv = ""; 193 | $rawsend = ""; 194 | $result = $this->POP3Request("RETR " . (int)$id, $rawsend, $rawrecv, true); 195 | if (!$result["success"]) return array("success" => false, "error" => self::POP3_Translate("The message retrieval request failed (RETR %d failed).", (int)$id), "errorcode" => "invalid_response", "info" => $result); 196 | 197 | return array("success" => true, "message" => implode("\r\n", $result["data"]) . "\r\n", "rawsend" => $rawsend, "rawrecv" => $rawrecv); 198 | } 199 | 200 | public function DeleteMessage($id) 201 | { 202 | $rawrecv = ""; 203 | $rawsend = ""; 204 | $result = $this->POP3Request("DELE " . (int)$id, $rawsend, $rawrecv); 205 | if (!$result["success"]) return array("success" => false, "error" => self::POP3_Translate("The message deletion request failed (DELE %d failed).", (int)$id), "errorcode" => "invalid_response", "info" => $result); 206 | 207 | return array("success" => true, "rawsend" => $rawsend, "rawrecv" => $rawrecv); 208 | } 209 | 210 | public function Disconnect() 211 | { 212 | if ($this->fp === false) return true; 213 | 214 | $rawrecv = ""; 215 | $rawsend = ""; 216 | $this->POP3Request("QUIT", $rawsend, $rawrecv); 217 | 218 | fclose($this->fp); 219 | $this->fp = false; 220 | 221 | return true; 222 | } 223 | 224 | private function POP3Request($command, &$rawsend, &$rawrecv, $multiline = false) 225 | { 226 | if ($this->fp === false) return array("success" => false, "error" => self::POP3_Translate("Not connected to a POP3 server."), "errorcode" => "not_connected"); 227 | 228 | fwrite($this->fp, $command . "\r\n"); 229 | if ($this->debug) $rawsend .= $command . "\r\n"; 230 | 231 | $result = $this->GetPOP3Response($multiline); 232 | if ($this->debug) $rawrecv .= $result["rawrecv"]; 233 | 234 | return $result; 235 | } 236 | 237 | private function GetPOP3Response($multiline) 238 | { 239 | $rawrecv = ""; 240 | $currline = fgets($this->fp); 241 | if ($currline === false) return array("success" => false, "error" => self::POP3_Translate("Connection terminated."), "errorcode" => "connection_terminated", "rawrecv" => $rawrecv); 242 | if ($this->debug) $rawrecv .= $currline; 243 | $currline = rtrim($currline); 244 | if (strtoupper(substr($currline, 0, 5)) == "-ERR ") 245 | { 246 | $data = substr($currline, 5); 247 | return array("success" => false, "error" => self::POP3_Translate("POP3 server returned an error."), "errorcode" => "error_response", "info" => $data, "rawrecv" => $rawrecv); 248 | } 249 | 250 | $response = substr($currline, 4); 251 | if (feof($this->fp)) return array("success" => false, "error" => self::POP3_Translate("Connection terminated."), "errorcode" => "connection_terminated", "info" => $response, "rawrecv" => $rawrecv); 252 | $data = array(); 253 | if ($multiline) 254 | { 255 | do 256 | { 257 | $currline = fgets($this->fp); 258 | if ($currline === false) return array("success" => false, "error" => self::POP3_Translate("Connection terminated."), "errorcode" => "connection_terminated", "rawrecv" => $rawrecv); 259 | if ($this->debug) $rawrecv .= $currline; 260 | $currline = rtrim($currline); 261 | if ($currline == ".") break; 262 | if ($currline == "..") $currline = "."; 263 | $data[] = $currline; 264 | } while (!feof($this->fp)); 265 | } 266 | 267 | if (feof($this->fp)) return array("success" => false, "error" => self::POP3_Translate("Connection terminated."), "errorcode" => "connection_terminated", "info" => $response, "rawrecv" => $rawrecv); 268 | 269 | return array("success" => true, "response" => $response, "data" => $data, "rawrecv" => $rawrecv); 270 | } 271 | 272 | private static function POP3_Translate() 273 | { 274 | $args = func_get_args(); 275 | if (!count($args)) return ""; 276 | 277 | return call_user_func_array((defined("CS_TRANSLATE_FUNC") && function_exists(CS_TRANSLATE_FUNC) ? CS_TRANSLATE_FUNC : "sprintf"), $args); 278 | } 279 | } 280 | ?> -------------------------------------------------------------------------------- /support/cli.php: -------------------------------------------------------------------------------- 1 | $val) 15 | { 16 | if (!isset($options["rules"][$val])) unset($options["shortmap"][$key]); 17 | } 18 | foreach ($options["rules"] as $key => $val) 19 | { 20 | if (!isset($val["arg"])) $options["rules"][$key]["arg"] = false; 21 | if (!isset($val["multiple"])) $options["rules"][$key]["multiple"] = false; 22 | } 23 | 24 | if ($args === false) $args = $_SERVER["argv"]; 25 | else if (is_string($args)) 26 | { 27 | $args2 = $args; 28 | $args = array(); 29 | $inside = false; 30 | $currarg = ""; 31 | $y = strlen($args2); 32 | for ($x = 0; $x < $y; $x++) 33 | { 34 | $currchr = substr($args2, $x, 1); 35 | 36 | if ($inside === false && $currchr == " " && $currarg != "") 37 | { 38 | $args[] = $currarg; 39 | $currarg = ""; 40 | } 41 | else if ($currchr == "\"" || $currchr == "'") 42 | { 43 | if ($inside === false) $inside = $currchr; 44 | else if ($inside === $currchr) $inside = false; 45 | else $currarg .= $currchr; 46 | } 47 | else if ($currchr == "\\" && $x < $y - 1) 48 | { 49 | $x++; 50 | $currarg .= substr($args2, $x, 1); 51 | } 52 | else if ($inside !== false || $currchr != " ") 53 | { 54 | $currarg .= $currchr; 55 | } 56 | } 57 | 58 | if ($currarg != "") $args[] = $currarg; 59 | } 60 | 61 | $result = array("success" => true, "file" => array_shift($args), "opts" => array(), "params" => array()); 62 | 63 | // Look over shortmap to determine if options exist that are one byte (flags) and don't have arguments. 64 | $chrs = array(); 65 | foreach ($options["shortmap"] as $key => $val) 66 | { 67 | if (isset($options["rules"][$val]) && !$options["rules"][$val]["arg"]) $chrs[$key] = true; 68 | } 69 | 70 | $allowopt = true; 71 | $y = count($args); 72 | for ($x = 0; $x < $y; $x++) 73 | { 74 | $arg = $args[$x]; 75 | 76 | // Attempt to process an option. 77 | $opt = false; 78 | $optval = false; 79 | if ($allowopt && substr($arg, 0, 1) == "-") 80 | { 81 | $pos = strpos($arg, "="); 82 | if ($pos === false) $pos = strlen($arg); 83 | else $optval = substr($arg, $pos + 1); 84 | $arg2 = substr($arg, 1, $pos - 1); 85 | 86 | if (isset($options["rules"][$arg2])) $opt = $arg2; 87 | else if (isset($options["shortmap"][$arg2])) $opt = $options["shortmap"][$arg2]; 88 | else if ($x == 0) 89 | { 90 | // Attempt to process as a set of flags. 91 | $y2 = strlen($arg2); 92 | if ($y2 > 0) 93 | { 94 | for ($x2 = 0; $x2 < $y2; $x2++) 95 | { 96 | $currchr = substr($arg2, $x2, 1); 97 | 98 | if (!isset($chrs[$currchr])) break; 99 | } 100 | 101 | if ($x2 == $y2) 102 | { 103 | for ($x2 = 0; $x2 < $y2; $x2++) 104 | { 105 | $opt = $options["shortmap"][substr($arg2, $x2, 1)]; 106 | 107 | if (!$options["rules"][$opt]["multiple"]) $result["opts"][$opt] = true; 108 | else 109 | { 110 | if (!isset($result["opts"][$opt])) $result["opts"][$opt] = 0; 111 | $result["opts"][$opt]++; 112 | } 113 | } 114 | 115 | continue; 116 | } 117 | } 118 | } 119 | } 120 | 121 | if ($opt === false) 122 | { 123 | // Is a parameter. 124 | if (substr($arg, 0, 1) === "\"" || substr($arg, 0, 1) === "'") $arg = substr($arg, 1); 125 | if (substr($arg, -1) === "\"" || substr($arg, -1) === "'") $arg = substr($arg, 0, -1); 126 | 127 | $result["params"][] = $arg; 128 | 129 | if (!$options["allow_opts_after_param"]) $allowopt = false; 130 | } 131 | else if (!$options["rules"][$opt]["arg"]) 132 | { 133 | // Is a flag by itself. 134 | if (!$options["rules"][$opt]["multiple"]) $result["opts"][$opt] = true; 135 | else 136 | { 137 | if (!isset($result["opts"][$opt])) $result["opts"][$opt] = 0; 138 | $result["opts"][$opt]++; 139 | } 140 | } 141 | else 142 | { 143 | // Is an option. 144 | if ($optval === false) 145 | { 146 | $x++; 147 | if ($x == $y) break; 148 | $optval = $args[$x]; 149 | } 150 | 151 | if (substr($optval, 0, 1) === "\"" || substr($optval, 0, 1) === "'") $optval = substr($optval, 1); 152 | if (substr($optval, -1) === "\"" || substr($optval, -1) === "'") $optval = substr($optval, 0, -1); 153 | 154 | if (!$options["rules"][$opt]["multiple"]) $result["opts"][$opt] = $optval; 155 | else 156 | { 157 | if (!isset($result["opts"][$opt])) $result["opts"][$opt] = array(); 158 | $result["opts"][$opt][] = $optval; 159 | } 160 | } 161 | } 162 | 163 | return $result; 164 | } 165 | 166 | public static function CanGetUserInputWithArgs(&$args, $prefix) 167 | { 168 | return (($prefix !== false && isset($args["opts"][$prefix]) && is_array($args["opts"][$prefix]) && count($args["opts"][$prefix])) || count($args["params"])); 169 | } 170 | 171 | // Gets a line of input from the user. If the user supplies all information via the command-line, this could be entirely automated. 172 | public static function GetUserInputWithArgs(&$args, $prefix, $question, $default, $noparamsoutput = "", $suppressoutput = false, $callback = false, $callbackopts = false) 173 | { 174 | if (!self::CanGetUserInputWithArgs($args, $prefix) && $noparamsoutput != "") 175 | { 176 | echo "\n" . rtrim($noparamsoutput) . "\n\n"; 177 | 178 | $suppressoutput = false; 179 | $noparamsoutput = ""; 180 | } 181 | 182 | do 183 | { 184 | $prompt = ($suppressoutput ? "" : $question . ($default !== false ? " [" . $default . "]" : "") . ": "); 185 | 186 | if ($prefix !== false && isset($args["opts"][$prefix]) && is_array($args["opts"][$prefix]) && count($args["opts"][$prefix])) 187 | { 188 | $line = array_shift($args["opts"][$prefix]); 189 | if ($line === "") $line = $default; 190 | if (!$suppressoutput) echo $prompt . $line . "\n"; 191 | } 192 | else if (count($args["params"])) 193 | { 194 | $line = array_shift($args["params"]); 195 | if ($line === "") $line = $default; 196 | if (!$suppressoutput) echo $prompt . $line . "\n"; 197 | } 198 | else if (strtoupper(substr(php_uname("s"), 0, 3)) != "WIN" && function_exists("readline") && function_exists("readline_add_history")) 199 | { 200 | $line = readline($prompt); 201 | if ($line === false) exit(); 202 | 203 | $line = trim($line); 204 | if ($line === "") $line = $default; 205 | if ($line !== false && $line !== "") readline_add_history($line); 206 | } 207 | else 208 | { 209 | echo $prompt; 210 | fflush(STDOUT); 211 | $line = fgets(STDIN); 212 | if ($line === false || ($line === "" && feof(STDIN))) exit(); 213 | 214 | $line = trim($line); 215 | if ($line === "") $line = $default; 216 | } 217 | 218 | if ($line === false || (is_callable($callback) && !call_user_func_array($callback, array($line, &$callbackopts)))) 219 | { 220 | if ($line !== false) $line = false; 221 | else echo "Please enter a value.\n"; 222 | 223 | if (!self::CanGetUserInputWithArgs($args, $prefix) && $noparamsoutput != "") 224 | { 225 | echo "\n" . $noparamsoutput . "\n"; 226 | 227 | $noparamsoutput = ""; 228 | } 229 | 230 | $suppressoutput = false; 231 | } 232 | } while ($line === false); 233 | 234 | return $line; 235 | } 236 | 237 | // Obtains a valid line of input. 238 | public static function GetLimitedUserInputWithArgs(&$args, $prefix, $question, $default, $allowedoptionsprefix, $allowedoptions, $loop = true, $suppressoutput = false, $multipleuntil = false) 239 | { 240 | $noparamsoutput = $allowedoptionsprefix . "\n\n"; 241 | $size = 0; 242 | foreach ($allowedoptions as $key => $val) 243 | { 244 | if ($size < strlen($key)) $size = strlen($key); 245 | } 246 | 247 | foreach ($allowedoptions as $key => $val) 248 | { 249 | $newtab = str_repeat(" ", 2 + $size + 3); 250 | $noparamsoutput .= " " . $key . ":" . str_repeat(" ", $size - strlen($key)) . " " . str_replace("\n\t", "\n" . $newtab, $val) . "\n"; 251 | } 252 | 253 | $noparamsoutput .= "\n"; 254 | 255 | if ($default === false && count($allowedoptions) == 1) 256 | { 257 | reset($allowedoptions); 258 | $default = key($allowedoptions); 259 | } 260 | 261 | $results = array(); 262 | do 263 | { 264 | $displayed = (!count($args["params"])); 265 | $result = self::GetUserInputWithArgs($args, $prefix, $question, $default, $noparamsoutput, $suppressoutput); 266 | if (is_array($multipleuntil) && $multipleuntil["exit"] === $result) break; 267 | $result2 = false; 268 | if (!count($allowedoptions)) break; 269 | foreach ($allowedoptions as $key => $val) 270 | { 271 | if (!strcasecmp($key, $result) || !strcasecmp($val, $result)) $result2 = $key; 272 | } 273 | if ($loop) 274 | { 275 | if ($result2 === false) 276 | { 277 | echo "Please select an option from the list.\n"; 278 | 279 | $suppressoutput = false; 280 | } 281 | else if (is_array($multipleuntil)) 282 | { 283 | $results[$result2] = $result2; 284 | 285 | $question = $multipleuntil["nextquestion"]; 286 | $default = $multipleuntil["nextdefault"]; 287 | } 288 | } 289 | 290 | if ($displayed) $noparamsoutput = ""; 291 | } while ($loop && ($result2 === false || is_array($multipleuntil))); 292 | 293 | return (is_array($multipleuntil) ? $results : $result2); 294 | } 295 | 296 | // Obtains Yes/No style input. 297 | public static function GetYesNoUserInputWithArgs(&$args, $prefix, $question, $default, $noparamsoutput = "", $suppressoutput = false) 298 | { 299 | $default = (substr(strtoupper(trim($default)), 0, 1) === "Y" ? "Y" : "N"); 300 | 301 | $result = self::GetUserInputWithArgs($args, $prefix, $question, $default, $noparamsoutput, $suppressoutput); 302 | $result = (substr(strtoupper(trim($result)), 0, 1) === "Y"); 303 | 304 | return $result; 305 | } 306 | 307 | public static function GetHexDump($data) 308 | { 309 | $result = ""; 310 | 311 | $x = 0; 312 | $y = strlen($data); 313 | if ($y <= 256) $padwidth = 2; 314 | else if ($y <= 65536) $padwidth = 4; 315 | else if ($y <= 16777216) $padwidth = 6; 316 | else $padwidth = 8; 317 | 318 | $pad = str_repeat(" ", $padwidth); 319 | 320 | $data2 = str_split(strtoupper(bin2hex($data)), 32); 321 | foreach ($data2 as $line) 322 | { 323 | $result .= sprintf("%0" . $padwidth . "X", $x) . " | "; 324 | 325 | $line = str_split($line, 2); 326 | array_splice($line, 8, 0, ""); 327 | $result .= implode(" ", $line) . "\n"; 328 | 329 | $result .= $pad . " |"; 330 | $y2 = $x + 16; 331 | for ($x2 = 0; $x2 < 16 && $x < $y; $x2++) 332 | { 333 | $result .= " "; 334 | if ($x2 === 8) $result .= " "; 335 | 336 | $tempchr = ord($data[$x]); 337 | if ($tempchr === 0x09) $result .= "\\t"; 338 | else if ($tempchr === 0x0D) $result .= "\\r"; 339 | else if ($tempchr === 0x0A) $result .= "\\n"; 340 | else if ($tempchr === 0x00) $result .= "\\0"; 341 | else if ($tempchr < 32 || $tempchr > 126) $result .= " "; 342 | else $result .= " " . $data[$x]; 343 | 344 | $x++; 345 | } 346 | 347 | $result .= "\n"; 348 | } 349 | 350 | return $result; 351 | } 352 | 353 | // Outputs a JSON array (useful for captured output). 354 | public static function DisplayResult($result, $exit = true) 355 | { 356 | if (is_array($result)) echo json_encode($result, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES) . "\n"; 357 | else echo $result . "\n"; 358 | 359 | if ($exit) exit(); 360 | } 361 | 362 | // Useful for reparsing remaining parameters as new arguments. 363 | public static function ReinitArgs(&$args, $newargs) 364 | { 365 | // Process the parameters. 366 | $options = array( 367 | "shortmap" => array( 368 | "?" => "help" 369 | ), 370 | "rules" => array( 371 | ) 372 | ); 373 | 374 | foreach ($newargs as $arg) $options["rules"][$arg] = array("arg" => true, "multiple" => true); 375 | $options["rules"]["help"] = array("arg" => false); 376 | 377 | $args = self::ParseCommandLine($options, array_merge(array(""), $args["params"])); 378 | 379 | if (isset($args["opts"]["help"])) self::DisplayResult(array("success" => true, "options" => array_keys($options["rules"]))); 380 | } 381 | 382 | // Tracks messages for a command-line interface app. 383 | private static $messages = array(); 384 | 385 | public static function LogMessage($msg, $data = null) 386 | { 387 | if (isset($data)) $msg .= "\n\t" . trim(str_replace("\n", "\n\t", json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES))); 388 | 389 | self::$messages[] = $msg; 390 | 391 | fwrite(STDERR, $msg . "\n"); 392 | } 393 | 394 | public static function DisplayError($msg, $result = false, $exit = true) 395 | { 396 | self::LogMessage(($exit ? "[Error] " : "") . $msg); 397 | 398 | if ($result !== false && is_array($result) && isset($result["error"]) && isset($result["errorcode"])) self::LogMessage("[Error] " . $result["error"] . " (" . $result["errorcode"] . ")", (isset($result["info"]) ? $result["info"] : null)); 399 | 400 | if ($exit) exit(); 401 | } 402 | 403 | public static function GetLogMessages($filters = array()) 404 | { 405 | if (is_string($filters)) $filters = array($filters); 406 | 407 | $result = array(); 408 | foreach (self::$messages as $message) 409 | { 410 | $found = (!count($filters)); 411 | foreach ($filters as $filter) 412 | { 413 | if (preg_match($filter, $message)) $found = true; 414 | } 415 | 416 | if ($found) $result[] = $message; 417 | } 418 | 419 | return $result; 420 | } 421 | 422 | public static function ResetLogMessages() 423 | { 424 | self::$messages = array(); 425 | } 426 | 427 | 428 | private static $timerinfo = array(); 429 | 430 | public static function StartTimer() 431 | { 432 | $ts = microtime(true); 433 | 434 | self::$timerinfo = array( 435 | "start" => $ts, 436 | "diff" => $ts 437 | ); 438 | } 439 | 440 | public static function UpdateTimer() 441 | { 442 | $ts = microtime(true); 443 | $diff = $ts - self::$timerinfo["diff"]; 444 | self::$timerinfo["diff"] = $ts; 445 | 446 | $result = array( 447 | "success" => true, 448 | "diff" => sprintf("%.2f", $diff), 449 | "total" => sprintf("%.2f", $ts - self::$timerinfo["start"]) 450 | ); 451 | 452 | return $result; 453 | } 454 | } 455 | ?> -------------------------------------------------------------------------------- /support/utf_utils.php: -------------------------------------------------------------------------------- 1 | = 0x0300 && $val <= 0x036F) || ($val >= 0x1DC0 && $val <= 0x1DFF) || ($val >= 0x20D0 && $val <= 0x20FF) || ($val >= 0xFE20 && $val <= 0xFE2F)); 21 | } 22 | 23 | public static function Convert($data, $srctype, $desttype) 24 | { 25 | $arr = is_array($data); 26 | if ($arr) $srctype = self::UTF32_ARRAY; 27 | $x = 0; 28 | $y = ($arr ? count($data) : strlen($data)); 29 | $result = ($desttype === self::UTF32_ARRAY ? array() : ""); 30 | if (!$arr && $srctype === self::UTF32_ARRAY) return $result; 31 | 32 | $first = true; 33 | 34 | if ($srctype === self::UTF8_BOM) 35 | { 36 | if (substr($data, 0, 3) === "\xEF\xBB\xBF") $x = 3; 37 | 38 | $srctype = self::UTF8; 39 | } 40 | 41 | if ($srctype === self::UTF16_BOM) 42 | { 43 | if (substr($data, 0, 2) === "\xFE\xFF") 44 | { 45 | $srctype = self::UTF16_BE; 46 | $x = 2; 47 | } 48 | else if (substr($data, 0, 2) === "\xFF\xFE") 49 | { 50 | $srctype = self::UTF16_LE; 51 | $x = 2; 52 | } 53 | else 54 | { 55 | $srctype = self::UTF16_LE; 56 | } 57 | } 58 | 59 | if ($srctype === self::UTF32_BOM) 60 | { 61 | if (substr($data, 0, 4) === "\x00\x00\xFE\xFF") 62 | { 63 | $srctype = self::UTF32_BE; 64 | $x = 4; 65 | } 66 | else if (substr($data, 0, 4) === "\xFF\xFE\x00\x00") 67 | { 68 | $srctype = self::UTF32_LE; 69 | $x = 4; 70 | } 71 | else 72 | { 73 | $srctype = self::UTF32_LE; 74 | } 75 | } 76 | 77 | while ($x < $y) 78 | { 79 | // Read the next valid code point. 80 | $val = false; 81 | 82 | switch ($srctype) 83 | { 84 | case self::UTF8: 85 | { 86 | $tempchr = ord($data[$x]); 87 | if ($tempchr <= 0x7F) 88 | { 89 | $val = $tempchr; 90 | $x++; 91 | } 92 | else if ($tempchr < 0xC2) $x++; 93 | else 94 | { 95 | $left = $y - $x; 96 | if ($left < 2) $x++; 97 | else 98 | { 99 | $tempchr2 = ord($data[$x + 1]); 100 | 101 | if (($tempchr >= 0xC2 && $tempchr <= 0xDF) && ($tempchr2 >= 0x80 && $tempchr2 <= 0xBF)) 102 | { 103 | $val = (($tempchr & 0x1F) << 6) | ($tempchr2 & 0x3F); 104 | $x += 2; 105 | } 106 | else if ($left < 3) $x++; 107 | else 108 | { 109 | $tempchr3 = ord($data[$x + 2]); 110 | 111 | if ($tempchr3 < 0x80 || $tempchr3 > 0xBF) $x++; 112 | else 113 | { 114 | if (($tempchr == 0xE0 && ($tempchr2 >= 0xA0 && $tempchr2 <= 0xBF)) || ((($tempchr >= 0xE1 && $tempchr <= 0xEC) || $tempchr == 0xEE || $tempchr == 0xEF) && ($tempchr2 >= 0x80 && $tempchr2 <= 0xBF)) || ($tempchr == 0xED && ($tempchr2 >= 0x80 && $tempchr2 <= 0x9F))) 115 | { 116 | $val = (($tempchr & 0x0F) << 12) | (($tempchr2 & 0x3F) << 6) | ($tempchr3 & 0x3F); 117 | $x += 3; 118 | } 119 | else if ($left < 4) $x++; 120 | else 121 | { 122 | $tempchr4 = ord($data[$x + 3]); 123 | 124 | if ($tempchr4 < 0x80 || $tempchr4 > 0xBF) $x++; 125 | else if (($tempchr == 0xF0 && ($tempchr2 >= 0x90 && $tempchr2 <= 0xBF)) || (($tempchr >= 0xF1 && $tempchr <= 0xF3) && ($tempchr2 >= 0x80 && $tempchr2 <= 0xBF)) || ($tempchr == 0xF4 && ($tempchr2 >= 0x80 && $tempchr2 <= 0x8F))) 126 | { 127 | $val = (($tempchr & 0x07) << 18) | (($tempchr2 & 0x3F) << 12) | (($tempchr3 & 0x3F) << 6) | ($tempchr4 & 0x3F); 128 | $x += 4; 129 | } 130 | else 131 | { 132 | $x++; 133 | } 134 | } 135 | } 136 | } 137 | } 138 | } 139 | 140 | break; 141 | } 142 | case self::UTF16_LE: 143 | { 144 | if ($x + 1 >= $y) $x = $y; 145 | else 146 | { 147 | $val = unpack("v", substr($data, $x, 2))[1]; 148 | $x += 2; 149 | 150 | if ($val >= 0xD800 && $val <= 0xDBFF) 151 | { 152 | if ($x + 1 >= $y) 153 | { 154 | $x = $y; 155 | $val = false; 156 | } 157 | else 158 | { 159 | $val2 = unpack("v", substr($data, $x, 2))[1]; 160 | 161 | if ($val2 < 0xDC00 || $val2 > 0xDFFF) $val = false; 162 | else 163 | { 164 | $val = ((($val - 0xD800) << 10) | ($val2 - 0xDC00)) + 0x10000; 165 | $x += 2; 166 | } 167 | } 168 | } 169 | } 170 | 171 | break; 172 | } 173 | case self::UTF16_BE: 174 | { 175 | if ($x + 1 >= $y) $x = $y; 176 | else 177 | { 178 | $val = unpack("n", substr($data, $x, 2))[1]; 179 | $x += 2; 180 | 181 | if ($val >= 0xD800 && $val <= 0xDBFF) 182 | { 183 | if ($x + 1 >= $y) 184 | { 185 | $x = $y; 186 | $val = false; 187 | } 188 | else 189 | { 190 | $val2 = unpack("n", substr($data, $x, 2))[1]; 191 | 192 | if ($val2 < 0xDC00 || $val2 > 0xDFFF) $val = false; 193 | else 194 | { 195 | $val = ((($val - 0xD800) << 10) | ($val2 - 0xDC00)) + 0x10000; 196 | $x += 2; 197 | } 198 | } 199 | } 200 | } 201 | 202 | break; 203 | } 204 | case self::UTF32_LE: 205 | { 206 | if ($x + 3 >= $y) $x = $y; 207 | else 208 | { 209 | $val = unpack("V", substr($data, $x, 4))[1]; 210 | $x += 4; 211 | } 212 | 213 | break; 214 | } 215 | case self::UTF32_BE: 216 | { 217 | if ($x + 3 >= $y) $x = $y; 218 | else 219 | { 220 | $val = unpack("N", substr($data, $x, 4))[1]; 221 | $x += 4; 222 | } 223 | 224 | break; 225 | } 226 | case self::UTF32_ARRAY: 227 | { 228 | $val = (int)$data[$x]; 229 | $x++; 230 | 231 | break; 232 | } 233 | default: $x = $y; break; 234 | } 235 | 236 | // Make sure it is a valid Unicode value. 237 | // 0xD800-0xDFFF are for UTF-16 surrogate pairs. Invalid characters. 238 | // 0xFDD0-0xFDEF are non-characters. 239 | // 0x*FFFE and 0x*FFFF are reserved. 240 | // The largest possible character is 0x10FFFF. 241 | // First character can't be a combining code point. 242 | if ($val !== false && !($val < 0 || ($val >= 0xD800 && $val <= 0xDFFF) || ($val >= 0xFDD0 && $val <= 0xFDEF) || ($val & 0xFFFE) == 0xFFFE || $val > 0x10FFFF || ($first && self::IsCombiningCodePoint($val)))) 243 | { 244 | if ($first) 245 | { 246 | if ($desttype === self::UTF8_BOM) 247 | { 248 | $result .= "\xEF\xBB\xBF"; 249 | 250 | $desttype = self::UTF8; 251 | } 252 | 253 | if ($desttype === self::UTF16_BOM) 254 | { 255 | $result .= "\xFF\xFE"; 256 | 257 | $desttype = self::UTF16_LE; 258 | } 259 | 260 | if ($srctype === self::UTF32_BOM) 261 | { 262 | $result .= "\xFF\xFE\x00\x00"; 263 | 264 | $desttype = self::UTF32_LE; 265 | } 266 | 267 | $first = false; 268 | } 269 | 270 | switch ($desttype) 271 | { 272 | case self::UTF8: 273 | { 274 | if ($val <= 0x7F) $result .= chr($val); 275 | else if ($val <= 0x7FF) $result .= chr(0xC0 | ($val >> 6)) . chr(0x80 | ($val & 0x3F)); 276 | else if ($val <= 0xFFFF) $result .= chr(0xE0 | ($val >> 12)) . chr(0x80 | (($val >> 6) & 0x3F)) . chr(0x80 | ($val & 0x3F)); 277 | else if ($val <= 0x10FFFF) $result .= chr(0xF0 | ($val >> 18)) . chr(0x80 | (($val >> 12) & 0x3F)) . chr(0x80 | (($val >> 6) & 0x3F)) . chr(0x80 | ($val & 0x3F)); 278 | 279 | break; 280 | } 281 | case self::UTF16_LE: 282 | { 283 | if ($val <= 0xFFFF) $result .= pack("v", $val); 284 | else 285 | { 286 | $val -= 0x10000; 287 | $result .= pack("v", ((($val >> 10) & 0x3FF) + 0xD800)); 288 | $result .= pack("v", (($val & 0x3FF) + 0xDC00)); 289 | } 290 | 291 | break; 292 | } 293 | case self::UTF16_BE: 294 | { 295 | if ($val <= 0xFFFF) $result .= pack("n", $val); 296 | else 297 | { 298 | $val -= 0x10000; 299 | $result .= pack("n", ((($val >> 10) & 0x3FF) + 0xD800)); 300 | $result .= pack("n", (($val & 0x3FF) + 0xDC00)); 301 | } 302 | 303 | break; 304 | } 305 | case self::UTF32_LE: 306 | { 307 | $result .= pack("V", $val); 308 | 309 | break; 310 | } 311 | case self::UTF32_BE: 312 | { 313 | $result .= pack("N", $val); 314 | 315 | break; 316 | } 317 | case self::UTF32_ARRAY: 318 | { 319 | $result[] = $val; 320 | 321 | break; 322 | } 323 | default: $x = $y; break; 324 | } 325 | } 326 | } 327 | 328 | return $result; 329 | } 330 | 331 | 332 | protected const PUNYCODE_BASE = 36; 333 | protected const PUNYCODE_TMIN = 1; 334 | protected const PUNYCODE_TMAX = 26; 335 | protected const PUNYCODE_SKEW = 38; 336 | protected const PUNYCODE_DAMP = 700; 337 | protected const PUNYCODE_INITIAL_BIAS = 72; 338 | protected const PUNYCODE_INITIAL_N = 0x80; 339 | protected const PUNYCODE_DIGIT_MAP = "abcdefghijklmnopqrstuvwxyz0123456789"; 340 | 341 | public static function ConvertToPunycode($domain) 342 | { 343 | // Reject invalid domain name lengths. 344 | if (strlen($domain) > 255) return false; 345 | 346 | $parts = explode(".", $domain); 347 | 348 | foreach ($parts as $num => $part) 349 | { 350 | // Reject invalid label lengths. 351 | $y = strlen($part); 352 | if ($y > 63) return false; 353 | 354 | // Skip already encoded portions. 355 | if (substr($part, 0, 4) === "xn--") continue; 356 | 357 | // Convert UTF-8 to UTF-32 code points. 358 | $data = self::Convert($part, self::UTF8, self::UTF32_ARRAY); 359 | 360 | // Handle ASCII code points. 361 | $part2 = ""; 362 | foreach ($data as $cp) 363 | { 364 | if ($cp <= 0x7F) $part2 .= strtolower(chr($cp)); 365 | } 366 | 367 | $numhandled = strlen($part2); 368 | $y = count($data); 369 | 370 | if ($numhandled >= $y) 371 | { 372 | $parts[$num] = $part2; 373 | 374 | continue; 375 | } 376 | 377 | if ($numhandled) $part2 .= "-"; 378 | 379 | $part2 = "xn--" . $part2; 380 | 381 | if (strlen($part2) > 63) return false; 382 | 383 | $bias = self::PUNYCODE_INITIAL_BIAS; 384 | $n = self::PUNYCODE_INITIAL_N; 385 | $delta = 0; 386 | $first = true; 387 | 388 | while ($numhandled < $y) 389 | { 390 | // Find the next largest unhandled code point. 391 | $cp2 = 0x01000000; 392 | foreach ($data as $cp) 393 | { 394 | if ($cp >= $n && $cp2 > $cp) $cp2 = $cp; 395 | } 396 | 397 | // Increase delta but prevent overflow. 398 | $delta += ($cp2 - $n) * ($numhandled + 1); 399 | if ($delta < 0) return false; 400 | $n = $cp2; 401 | 402 | foreach ($data as $cp) 403 | { 404 | if ($cp < $n) 405 | { 406 | $delta++; 407 | 408 | if ($delta < 0) return false; 409 | } 410 | else if ($cp === $n) 411 | { 412 | // Calculate and encode a variable length integer from the delta. 413 | $q = $delta; 414 | $x = 0; 415 | do 416 | { 417 | $x += self::PUNYCODE_BASE; 418 | 419 | if ($x <= $bias) $t = self::PUNYCODE_TMIN; 420 | else if ($x >= $bias + self::PUNYCODE_TMAX) $t = self::PUNYCODE_TMAX; 421 | else $t = $x - $bias; 422 | 423 | if ($q < $t) break; 424 | 425 | $part2 .= self::PUNYCODE_DIGIT_MAP[$t + (($q - $t) % (self::PUNYCODE_BASE - $t))]; 426 | 427 | $q = (int)(($q - $t) / (self::PUNYCODE_BASE - $t)); 428 | 429 | if (strlen($part2) > 63) return false; 430 | } while (1); 431 | 432 | $part2 .= self::PUNYCODE_DIGIT_MAP[$q]; 433 | if (strlen($part2) > 63) return false; 434 | 435 | // Adapt bias. 436 | $numhandled++; 437 | $bias = self::InternalPunycodeAdapt($delta, $numhandled, $first); 438 | $delta = 0; 439 | $first = false; 440 | } 441 | } 442 | 443 | $delta++; 444 | $n++; 445 | } 446 | 447 | $parts[$num] = $part2; 448 | } 449 | 450 | return implode(".", $parts); 451 | } 452 | 453 | public static function ConvertFromPunycode($domain) 454 | { 455 | // Reject invalid domain name lengths. 456 | if (strlen($domain) > 255) return false; 457 | 458 | $parts = explode(".", $domain); 459 | 460 | foreach ($parts as $num => $part) 461 | { 462 | // Reject invalid label lengths. 463 | $y = strlen($part); 464 | if ($y > 63) return false; 465 | 466 | // Skip unencoded portions. 467 | if (substr($part, 0, 4) !== "xn--") continue; 468 | 469 | $part = substr($part, 4); 470 | 471 | // Convert UTF-8 to UTF-32 code points. 472 | $data = self::Convert($part, self::UTF8, self::UTF32_ARRAY); 473 | 474 | // Handle ASCII code points. 475 | $hyphen = ord("-"); 476 | for ($x = count($data); $x && $data[$x - 1] !== $hyphen; $x--); 477 | if (!$x) $data2 = array(); 478 | else 479 | { 480 | $data2 = array_splice($data, 0, $x - 1); 481 | 482 | array_shift($data); 483 | } 484 | 485 | $numhandled = count($data2); 486 | 487 | $bias = self::PUNYCODE_INITIAL_BIAS; 488 | $n = self::PUNYCODE_INITIAL_N; 489 | $delta = 0; 490 | $first = true; 491 | 492 | $pos = 0; 493 | $y = count($data); 494 | while ($pos < $y) 495 | { 496 | // Calculate and decode a delta from the variable length integer. 497 | $olddelta = $delta; 498 | $w = 1; 499 | $x = 0; 500 | do 501 | { 502 | $x += self::PUNYCODE_BASE; 503 | 504 | $cp = $data[$pos]; 505 | $pos++; 506 | 507 | if ($cp >= ord("a") && $cp <= ord("z")) $digit = $cp - ord("a"); 508 | else if ($cp >= ord("A") && $cp <= ord("Z")) $digit = $cp - ord("A"); 509 | else if ($cp >= ord("0") && $cp <= ord("9")) $digit = $cp - ord("0") + 26; 510 | else return false; 511 | 512 | $delta += $digit * $w; 513 | if ($delta < 0) return false; 514 | 515 | if ($x <= $bias) $t = self::PUNYCODE_TMIN; 516 | else if ($x >= $bias + self::PUNYCODE_TMAX) $t = self::PUNYCODE_TMAX; 517 | else $t = $x - $bias; 518 | 519 | if ($digit < $t) break; 520 | 521 | $w *= (self::PUNYCODE_BASE - $t); 522 | if ($w < 0) return false; 523 | } while (1); 524 | 525 | // Adapt bias. 526 | $numhandled++; 527 | $bias = self::InternalPunycodeAdapt($delta - $olddelta, $numhandled, $first); 528 | $first = false; 529 | 530 | // Delta was supposed to wrap around from $numhandled to 0, incrementing $n each time, so fix that now. 531 | $n += (int)($delta / $numhandled); 532 | $delta %= $numhandled; 533 | 534 | // Insert $n (the code point) at the delta position. 535 | array_splice($data2, $delta, 0, array($n)); 536 | $delta++; 537 | } 538 | 539 | $parts[$num] = self::Convert($data2, self::UTF32_ARRAY, self::UTF8); 540 | } 541 | 542 | return implode(".", $parts); 543 | } 544 | 545 | // RFC3492 adapt() function. 546 | protected static function InternalPunycodeAdapt($delta, $numpoints, $first) 547 | { 548 | $delta = ($first ? (int)($delta / self::PUNYCODE_DAMP) : $delta >> 1); 549 | $delta += (int)($delta / $numpoints); 550 | 551 | $y = self::PUNYCODE_BASE - self::PUNYCODE_TMIN; 552 | 553 | $condval = (int)(($y * self::PUNYCODE_TMAX) / 2); 554 | for ($x = 0; $delta > $condval; $x += self::PUNYCODE_BASE) $delta = (int)($delta / $y); 555 | 556 | return (int)($x + ((($y + 1) * $delta) / ($delta + self::PUNYCODE_SKEW))); 557 | } 558 | } 559 | ?> -------------------------------------------------------------------------------- /support/smtp.php: -------------------------------------------------------------------------------- 1 | = 37 && $currchr <= 45) || ($currchr >= 47 && $currchr <= 60) || $currchr == 62 || $currchr == 63 || ($currchr >= 65 && $currchr <= 90) || $currchr == 95 || ($currchr >= 97 && $currchr <= 122)) 44 | { 45 | if (!$restrictmore) $data2 .= $data[$x]; 46 | else if (($currchr >= 48 && $currchr <= 57) || ($currchr >= 65 && $currchr <= 90) || ($currchr >= 97 && $currchr <= 122)) $data2 .= sprintf("=%02X", $currchr); 47 | else $data2 .= $data[$x]; 48 | } 49 | else if ($currchr == 13 && $x + 1 < $y && ord($data[$x + 1]) == 10) 50 | { 51 | $data2 .= "\r\n"; 52 | $x++; 53 | } 54 | else 55 | { 56 | $data2 .= sprintf("=%02X", $currchr); 57 | } 58 | } 59 | 60 | // Break the string on 75 character boundaries and add '=' character. 61 | $data2 = explode("\r\n", $data2); 62 | $result = ""; 63 | foreach ($data2 as $currline) 64 | { 65 | $x2 = 0; 66 | $y2 = strlen($currline); 67 | while ($x2 + 75 < $y2) 68 | { 69 | if ($currline[$x2 + 74] == '=') 70 | { 71 | $result .= substr($currline, $x2, 74); 72 | $x2 += 74; 73 | } 74 | else if ($currline[$x2 + 73] == '=') 75 | { 76 | $result .= substr($currline, $x2, 73); 77 | $x2 += 73; 78 | } 79 | else 80 | { 81 | $result .= substr($currline, $x2, 75); 82 | $x2 += 75; 83 | } 84 | $result .= "=\r\n"; 85 | } 86 | 87 | if ($x2 < $y2) $result .= substr($currline, $x2, $y2 - $x2); 88 | $result .= "\r\n"; 89 | } 90 | 91 | return $result; 92 | } 93 | 94 | public static function ConvertEmailMessageToRFC1341($data, $restrictmore = false) 95 | { 96 | $data = self::ReplaceNewlines("\r\n", $data); 97 | 98 | return self::ConvertToRFC1341($data, $restrictmore); 99 | } 100 | 101 | // RFC1342 is a hacky workaround to encode headers in e-mails. 102 | public static function ConvertToRFC1342($data, $lang = "UTF-8", $encodeb64 = true) 103 | { 104 | $result = ""; 105 | 106 | // An individual RFC1342-compliant string can only be 75 characters long, 6 must be markers, 107 | // one must be the encoding method, and at least one must be data (adjusted to 4 required 108 | // spaces to simplify processing). 109 | if (strlen($lang) > 75 - 6 - 1 - 4) return $result; 110 | 111 | $lang = strtoupper($lang); 112 | if ($lang != "ISO-8859-1" && $lang != "US-ASCII") $encodeb64 = true; 113 | 114 | $maxdatalength = 75 - 6 - strlen($lang) - 1; 115 | if ($encodeb64) 116 | { 117 | $maxdatalength = $maxdatalength * 3 / 4; 118 | $y = strlen($data); 119 | if ($lang == "UTF-8") 120 | { 121 | $x = 0; 122 | $pos = 0; 123 | $size = 0; 124 | while (UTF8::NextChrPos($data, $y, $pos, $size)) 125 | { 126 | if ($pos + $size - $x > $maxdatalength) 127 | { 128 | if ($x) $result .= " "; 129 | $result .= "=?" . $lang . "?B?" . base64_encode(substr($data, $x, $pos - $x)) . "?="; 130 | $x = $pos; 131 | } 132 | } 133 | } 134 | else 135 | { 136 | for ($x = 0; $x + $maxdatalength < $y; $x += $maxdatalength) 137 | { 138 | if ($x) $result .= " "; 139 | $result .= "=?" . $lang . "?B?" . base64_encode(substr($data, $x, $maxdatalength)) . "?="; 140 | } 141 | } 142 | 143 | if ($x < $y) 144 | { 145 | if ($x) $result .= " "; 146 | $result .= "=?" . $lang . "?B?" . base64_encode(substr($data, $x, $y - $x)) . "?="; 147 | } 148 | } 149 | else 150 | { 151 | // Quoted printable. 152 | $maxdatalength = $maxdatalength / 3; 153 | $y = strlen($data); 154 | for ($x = 0; $x + $maxdatalength < $y; $x += $maxdatalength) 155 | { 156 | if ($x) $result .= " "; 157 | $result .= "=?" . $lang . "?Q?" . str_replace(" ", "_", self::ConvertToRFC1341(substr($data, $x, $maxdatalength), true)) . "?="; 158 | } 159 | if ($x < $y) 160 | { 161 | if ($x) $result .= " "; 162 | $result .= "=?" . $lang . "?Q?" . str_replace(" ", "_", self::ConvertToRFC1341(substr($data, $x, $y - $x), true)) . "?="; 163 | } 164 | } 165 | 166 | return $result; 167 | } 168 | 169 | private static function SMTP_Translate() 170 | { 171 | $args = func_get_args(); 172 | if (!count($args)) return ""; 173 | 174 | return call_user_func_array((defined("CS_TRANSLATE_FUNC") && function_exists(CS_TRANSLATE_FUNC) ? CS_TRANSLATE_FUNC : "sprintf"), $args); 175 | } 176 | 177 | // Takes a potentially invalid e-mail address and attempts to make it valid. 178 | public static function MakeValidEmailAddress($email, $options = array()) 179 | { 180 | $email = str_replace("\t", " ", $email); 181 | $email = str_replace("\r", " ", $email); 182 | $email = str_replace("\n", " ", $email); 183 | $email = trim($email); 184 | 185 | // Reverse parse out the initial domain/IP address part of the e-mail address. 186 | $domain = ""; 187 | $state = "domend"; 188 | $cfwsdepth = 0; 189 | while ($email != "" && $state != "") 190 | { 191 | $prevchr = substr($email, -2, 1); 192 | $lastchr = substr($email, -1); 193 | 194 | switch ($state) 195 | { 196 | case "domend": 197 | { 198 | if ($lastchr == ")") 199 | { 200 | $laststate = "domain"; 201 | $state = "cfws"; 202 | } 203 | else if ($lastchr == "]" || $lastchr == "}") 204 | { 205 | $domain .= "]"; 206 | $email = trim(substr($email, 0, -1)); 207 | $state = "ipaddr"; 208 | } 209 | else 210 | { 211 | $state = "domain"; 212 | } 213 | 214 | break; 215 | } 216 | case "cfws": 217 | { 218 | if ($prevchr == "\\") $email = trim(substr($email, 0, -2)); 219 | else if ($lastchr == ")") 220 | { 221 | $email = trim(substr($email, 0, -1)); 222 | $cfwsdepth++; 223 | } 224 | else if ($lastchr == "(") 225 | { 226 | $email = trim(substr($email, 0, -1)); 227 | $cfwsdepth--; 228 | if (!$cfwsdepth && substr($email, -1) != ")") $state = $laststate; 229 | } 230 | else $email = trim(substr($email, 0, -1)); 231 | 232 | break; 233 | } 234 | case "ipaddr": 235 | { 236 | if ($lastchr == "[" || $lastchr == "{" || $lastchr == "@") 237 | { 238 | $domain .= "["; 239 | $state = "@"; 240 | 241 | if ($lastchr == "@") break; 242 | } 243 | else if ($lastchr == "," || $lastchr == ".") $domain .= "."; 244 | else if ($lastchr == ";" || $lastchr == ":") $domain .= ":"; 245 | else if (preg_match('/[A-Fa-f0-9]/', $lastchr)) $domain .= $lastchr; 246 | 247 | $email = trim(substr($email, 0, -1)); 248 | 249 | break; 250 | } 251 | case "domain": 252 | { 253 | if ($lastchr == "@") 254 | { 255 | $state = "@"; 256 | 257 | break; 258 | } 259 | else if ($lastchr == ")") 260 | { 261 | $state = "cfws"; 262 | $laststate = "@"; 263 | 264 | break; 265 | } 266 | else if ($lastchr == "," || $lastchr == ".") $domain .= "."; 267 | else if (preg_match('/[A-Za-z0-9-]/', $lastchr) || ord($lastchr) > 0x7F) $domain .= $lastchr; 268 | 269 | $email = trim(substr($email, 0, -1)); 270 | 271 | break; 272 | } 273 | case "@": 274 | { 275 | if ($lastchr == "@") $state = ""; 276 | 277 | $email = trim(substr($email, 0, -1)); 278 | 279 | break; 280 | } 281 | } 282 | } 283 | $domain = strrev($domain); 284 | $parts = explode(".", $domain); 285 | foreach ($parts as $num => $part) $parts[$num] = str_replace(" ", "-", trim(str_replace("-", " ", $part))); 286 | $domain = implode(".", $parts); 287 | $domain = self::GetIDNAHost($domain); 288 | 289 | // Forward parse out the local part of the e-mail address. 290 | // Remove CFWS (comments, folding whitespace). 291 | while (substr($email, 0, 1) == "(") 292 | { 293 | while ($email != "") 294 | { 295 | $currchr = substr($email, 0, 1); 296 | if ($currchr == "\\") $email = trim(substr($email, 2)); 297 | else if ($currchr == "(") 298 | { 299 | $cfwsdepth++; 300 | $email = trim(substr($email, 1)); 301 | } 302 | else if ($currchr == ")") 303 | { 304 | $email = trim(substr($email, 1)); 305 | $cfwsdepth--; 306 | if (!$cfwsdepth && substr($email, 0, 1) != "(") break; 307 | } 308 | } 309 | } 310 | 311 | // Process quoted/unquoted string. 312 | if (substr($email, 0, 1) == "\"") 313 | { 314 | $local = "\""; 315 | $email = substr($email, 1); 316 | while ($email != "") 317 | { 318 | $currchr = substr($email, 0, 1); 319 | $nextchr = substr($email, 1, 1); 320 | 321 | if ($currchr == "\\") 322 | { 323 | if ($nextchr == "\\" || $nextchr == "\"") 324 | { 325 | $local .= substr($email, 0, 2); 326 | $email = substr($email, 2); 327 | } 328 | else if (ord($nextchr) >= 32) 329 | { 330 | $local .= substr($email, 1, 1); 331 | $email = substr($email, 2); 332 | } 333 | } 334 | else if ($currchr == "\"") break; 335 | else if (ord($currchr) >= 32) 336 | { 337 | $local .= substr($email, 0, 1); 338 | $email = substr($email, 1); 339 | } 340 | else $email = substr($email, 1); 341 | } 342 | 343 | if (substr($local, -1) != "\"") $local .= "\""; 344 | } 345 | else 346 | { 347 | $local = ""; 348 | while ($email != "") 349 | { 350 | $currchr = substr($email, 0, 1); 351 | 352 | if (preg_match('/[A-Za-z0-9]/', $currchr) || ord($lastchr) > 0x7F || $currchr == "!" || $currchr == "#" || $currchr == "\$" || $currchr == "%" || $currchr == "&" || $currchr == "'" || $currchr == "*" || $currchr == "+" || $currchr == "-" || $currchr == "/" || $currchr == "=" || $currchr == "?" || $currchr == "^" || $currchr == "_" || $currchr == "`" || $currchr == "{" || $currchr == "|" || $currchr == "}" || $currchr == "~" || $currchr == ".") 353 | { 354 | $local .= $currchr; 355 | $email = substr($email, 1); 356 | } 357 | else break; 358 | } 359 | 360 | $local = preg_replace('/[.]+/', ".", $local); 361 | if (substr($local, 0, 1) == ".") $local = substr($local, 1); 362 | if (substr($local, -1) == ".") $local = substr($local, 0, -1); 363 | } 364 | while (substr($local, -2) == "\\\"") $local = substr($local, 0, -2) . "\""; 365 | if ($local == "\"" || $local == "\"\"") $local = ""; 366 | $local = UTF8::MakeValid($local); 367 | 368 | // Analyze the domain/IP part and fix any issues. 369 | $domain = preg_replace('/[.]+/', ".", $domain); 370 | if (substr($domain, -1) == "]") 371 | { 372 | if (substr($domain, 0, 1) != "[") $domain = "[" . $domain; 373 | 374 | // Process the IP address. 375 | if (strtolower(substr($domain, 0, 6)) == "[ipv6:") $ipaddr = IPAddr::NormalizeIP(substr($domain, 6, -1)); 376 | else $ipaddr = IPAddr::NormalizeIP(substr($domain, 1, -1)); 377 | 378 | if ($ipaddr["ipv4"] != "") $domain = "[" . $ipaddr["ipv4"] . "]"; 379 | else $domain = "[IPv6:" . $ipaddr["ipv6"] . "]"; 380 | } 381 | else 382 | { 383 | // Process the domain. 384 | if (substr($domain, 0, 1) == ".") $domain = substr($domain, 1); 385 | if (substr($domain, -1) == ".") $domain = substr($domain, 0, -1); 386 | $domain = explode(".", $domain); 387 | foreach ($domain as $num => $part) 388 | { 389 | if (substr($part, 0, 1) == "-") $part = substr($part, 1); 390 | if (substr($part, -1) == "-") $part = substr($part, 0, -1); 391 | if (strlen($part) > 63) $part = substr($part, 0, 63); 392 | 393 | $domain[$num] = $part; 394 | } 395 | 396 | $domain = implode(".", $domain); 397 | } 398 | 399 | // Validate the final lengths. 400 | $y = strlen($local); 401 | $y2 = strlen($domain); 402 | $email = $local . "@" . $domain; 403 | if (!$y) return array("success" => false, "error" => self::SMTP_Translate("Missing local part of e-mail address."), "errorcode" => "missing_local_part", "info" => $email); 404 | if (!$y2) return array("success" => false, "error" => self::SMTP_Translate("Missing domain part of e-mail address."), "errorcode" => "missing_domain_part", "info" => $email); 405 | if ($y > 64 || $y2 > 253 || $y + $y2 + 1 > 253) return array("success" => false, "error" => self::SMTP_Translate("E-mail address is too long."), "errorcode" => "email_too_long", "info" => $email); 406 | 407 | // Process results. 408 | if (substr($domain, 0, 1) == "[" && substr($domain, -1) == "]") $result = array("success" => true, "email" => $email, "lookup" => false, "type" => "IP"); 409 | else if (isset($options["usedns"]) && $options["usedns"] === false) $result = array("success" => true, "email" => $email, "lookup" => false, "type" => "Domain", "domain" => $domain); 410 | else if ((!isset($options["usednsttlcache"]) || $options["usednsttlcache"] === true) && isset(self::$dnsttlcache[$domain]) && self::$dnsttlcache[$domain]["ts"] >= time()) 411 | { 412 | if (self::$dnsttlcache[$domain]["success"]) $result = array("success" => true, "email" => $email, "lookup" => false, "type" => self::$dnsttlcache[$domain]["type"], "domain" => $domain); 413 | else $result = array("success" => false, "error" => self::SMTP_Translate("Invalid domain name or missing DNS record."), "errorcode" => "invalid_domain_or_missing_record", "info" => $domain); 414 | } 415 | else 416 | { 417 | // Check for a mail server based on a DNS lookup. 418 | $result = self::GetDNSRecord($domain, array("MX", "A"), (isset($options["nameservers"]) ? $options["nameservers"] : true), (!isset($options["usednsttlcache"]) || $options["usednsttlcache"] === true)); 419 | if ($result["success"]) $result = array("success" => true, "email" => $email, "lookup" => true, "type" => $result["type"], "domain" => $domain, "records" => $result["records"]); 420 | } 421 | 422 | return $result; 423 | } 424 | 425 | public static function UpdateDNSTTLCache() 426 | { 427 | $ts = time(); 428 | foreach (self::$dnsttlcache as $domain => $dinfo) 429 | { 430 | if ($dinfo["ts"] <= $ts) unset(self::$dnsttlcache[$domain]); 431 | } 432 | } 433 | 434 | public static function GetDNSRecord($domain, $types = array("MX", "A"), $nameservers = true, $cache = true) 435 | { 436 | // Process local system or preferred DNS nameservers. 437 | if (!is_array($nameservers)) 438 | { 439 | if (is_array(self::$dnsnameservers)) $nameservers = self::$dnsnameservers; 440 | else 441 | { 442 | $data = @file_get_contents("/etc/resolv.conf"); 443 | if ($data === false) $nameservers = array(); 444 | else 445 | { 446 | $nameservers = array(); 447 | $lines = explode("\n", $data); 448 | foreach ($lines as $line) 449 | { 450 | $line = preg_replace('/\s+/', " ", trim($line)); 451 | if (substr($line, 0, 11) === "nameserver ") $nameservers[trim(substr($line, 11))] = true; 452 | } 453 | 454 | $nameservers = array_keys($nameservers); 455 | } 456 | } 457 | } 458 | 459 | if (!count($nameservers)) $nameservers = array("1.1.1.1", "8.8.8.8", "8.8.4.4"); 460 | 461 | self::$dnsnameservers = $nameservers; 462 | 463 | // Check for a mail server based on a DNS lookup. 464 | if (!class_exists("Net_DNS2_Resolver", false)) require_once str_replace("\\", "/", dirname(__FILE__)) . "/Net/DNS2.php"; 465 | 466 | $resolver = new Net_DNS2_Resolver(array("nameservers" => $nameservers)); 467 | try 468 | { 469 | foreach ($types as $type) 470 | { 471 | $response = $resolver->query($domain, $type); 472 | if ($response && count($response->answer)) 473 | { 474 | if ($cache) 475 | { 476 | $minttl = -1; 477 | foreach ($response->answer as $answer) 478 | { 479 | if ($minttl < 0 || ($answer->ttl > 0 && $answer->ttl < $minttl)) $minttl = $answer->ttl; 480 | } 481 | 482 | self::$dnsttlcache[$domain] = array("success" => true, "type" => $type, "ts" => time() + 31 * 24 * 60 * 60); 483 | } 484 | 485 | return array("success" => true, "type" => $type, "records" => $response); 486 | } 487 | } 488 | 489 | if ($cache) self::$dnsttlcache[$domain] = array("success" => false, "ts" => time() + 3600); 490 | 491 | return array("success" => false, "error" => self::SMTP_Translate("Invalid domain name or missing DNS record."), "errorcode" => "invalid_domain_or_missing_record", "info" => $domain); 492 | } 493 | catch (Exception $e) 494 | { 495 | if ($cache) self::$dnsttlcache[$domain] = array("success" => false, "ts" => time() + 3600); 496 | 497 | return array("success" => false, "error" => self::SMTP_Translate("Invalid domain name. Internal exception occurred."), "errorcode" => "dns_library_exception", "info" => self::SMTP_Translate("%s (%s).", $e->getMessage(), $domain)); 498 | } 499 | } 500 | 501 | public static function EmailAddressesToNamesAndEmail(&$destnames, &$destaddrs, $emailaddrs, $removenames = false, $options = array()) 502 | { 503 | $destnames = array(); 504 | $destaddrs = array(); 505 | 506 | $data = str_replace("\t", " ", $emailaddrs); 507 | $data = str_replace("\r", " ", $data); 508 | $data = str_replace("\n", " ", $data); 509 | $data = trim($data); 510 | 511 | // Parse e-mail addresses out of the string with a state engine. 512 | // Parsed in reverse because that is easier than trying to figure out if each address 513 | // starts with a name OR a quoted string for the local part of the e-mail address. 514 | // The e-mail address parsing in this state engine is intentionally incomplete. 515 | // The goal is to identify '"name" , name , emailaddr' variations. 516 | $found = false; 517 | while ($data != "") 518 | { 519 | $name = ""; 520 | $email = ""; 521 | $state = "addrend"; 522 | $cfwsdepth = 0; 523 | $inbracket = false; 524 | 525 | while ($data != "" && $state != "") 526 | { 527 | $prevchr = substr($data, -2, 1); 528 | $lastchr = substr($data, -1); 529 | 530 | switch ($state) 531 | { 532 | case "addrend": 533 | { 534 | if ($lastchr == ">") 535 | { 536 | $data = trim(substr($data, 0, -1)); 537 | $inbracket = true; 538 | $state = "domend"; 539 | } 540 | else if ($lastchr == "," || $lastchr == ";") 541 | { 542 | $data = trim(substr($data, 0, -1)); 543 | } 544 | else $state = "domend"; 545 | 546 | break; 547 | } 548 | case "domend": 549 | { 550 | if ($lastchr == ")") 551 | { 552 | $laststate = "domain"; 553 | $state = "cfws"; 554 | } 555 | else if ($lastchr == "]" || $lastchr == "}") 556 | { 557 | $email .= "]"; 558 | $data = trim(substr($data, 0, -1)); 559 | $state = "ipaddr"; 560 | } 561 | else 562 | { 563 | $state = "domain"; 564 | } 565 | 566 | break; 567 | } 568 | case "cfws": 569 | { 570 | if ($prevchr == "\\") $data = trim(substr($data, 0, -2)); 571 | else if ($lastchr == ")") 572 | { 573 | $data = trim(substr($data, 0, -1)); 574 | $cfwsdepth++; 575 | } 576 | else if ($lastchr == "(") 577 | { 578 | $data = trim(substr($data, 0, -1)); 579 | $cfwsdepth--; 580 | if (!$cfwsdepth && substr($data, -1) != ")") $state = $laststate; 581 | } 582 | else $data = trim(substr($data, 0, -1)); 583 | 584 | break; 585 | } 586 | case "ipaddr": 587 | { 588 | if ($lastchr == "[" || $lastchr == "{" || $lastchr == "@") 589 | { 590 | $email .= "["; 591 | $state = "@"; 592 | 593 | if ($lastchr == "@") break; 594 | } 595 | else if ($lastchr == "," || $lastchr == ".") $email .= "."; 596 | else if ($lastchr == ";" || $lastchr == ":") $email .= ":"; 597 | else if (preg_match('/[A-Fa-f0-9]/', $lastchr)) $email .= $lastchr; 598 | 599 | $data = trim(substr($data, 0, -1)); 600 | 601 | break; 602 | } 603 | case "domain": 604 | { 605 | if ($lastchr == "@") 606 | { 607 | $state = "@"; 608 | 609 | break; 610 | } 611 | else if ($lastchr == ")") 612 | { 613 | $state = "cfws"; 614 | $laststate = "@"; 615 | 616 | break; 617 | } 618 | else if ($lastchr == "," || $lastchr == ".") $email .= "."; 619 | else if (preg_match('/[A-Za-z0-9-]/', $lastchr) || ord($lastchr) > 0x7F) $email .= $lastchr; 620 | 621 | $data = trim(substr($data, 0, -1)); 622 | 623 | break; 624 | } 625 | case "@": 626 | { 627 | if ($lastchr == "@") 628 | { 629 | $email .= "@"; 630 | $state = "localend"; 631 | } 632 | 633 | $data = trim(substr($data, 0, -1)); 634 | 635 | break; 636 | } 637 | case "localend": 638 | { 639 | if ($lastchr == ")") 640 | { 641 | $state = "cfws"; 642 | $laststate = "localend"; 643 | } 644 | else if ($lastchr == "\"") 645 | { 646 | $email .= "\""; 647 | $data = substr($data, 0, -1); 648 | $state = "quotedlocal"; 649 | } 650 | else $state = "local"; 651 | 652 | break; 653 | } 654 | case "quotedlocal": 655 | { 656 | if ($prevchr == "\\") 657 | { 658 | $email .= $lastchr . $prevchr; 659 | $data = substr($data, 0, -2); 660 | } 661 | else if ($lastchr == "\"") 662 | { 663 | $email .= $lastchr; 664 | $data = trim(substr($data, 0, -1)); 665 | $state = "localstart"; 666 | } 667 | else 668 | { 669 | $email .= $lastchr; 670 | $data = substr($data, 0, -1); 671 | } 672 | 673 | break; 674 | } 675 | case "local": 676 | { 677 | if (preg_match('/[A-Za-z0-9]/', $lastchr) || ord($lastchr) > 0x7F || $lastchr == "!" || $lastchr == "#" || $lastchr == "\$" || $lastchr == "%" || $lastchr == "&" || $lastchr == "'" || $lastchr == "*" || $lastchr == "+" || $lastchr == "-" || $lastchr == "/" || $lastchr == "=" || $lastchr == "?" || $lastchr == "^" || $lastchr == "_" || $lastchr == "`" || $lastchr == "{" || $lastchr == "|" || $lastchr == "}" || $lastchr == "~" || $lastchr == ".") 678 | { 679 | $email .= $lastchr; 680 | $data = substr($data, 0, -1); 681 | } 682 | else if ($lastchr == ")") 683 | { 684 | $state = "cfws"; 685 | $laststate = "localstart"; 686 | } 687 | else if ($inbracket) 688 | { 689 | if ($lastchr == "<") $state = "localstart"; 690 | else $data = substr($data, 0, -1); 691 | } 692 | else if ($lastchr == " " || $lastchr == "," || $lastchr == ";") $state = "localstart"; 693 | else $data = substr($data, 0, -1); 694 | 695 | break; 696 | } 697 | case "localstart": 698 | { 699 | if ($inbracket) 700 | { 701 | if ($lastchr == "<") $state = "nameend"; 702 | 703 | $data = trim(substr($data, 0, -1)); 704 | } 705 | else if ($lastchr == "," || $lastchr == ";") $state = ""; 706 | else $data = trim(substr($data, 0, -1)); 707 | 708 | break; 709 | } 710 | case "nameend": 711 | { 712 | if ($lastchr == "\"") 713 | { 714 | $data = substr($data, 0, -1); 715 | $state = "quotedname"; 716 | } 717 | else $state = "name"; 718 | 719 | break; 720 | } 721 | case "quotedname": 722 | { 723 | if ($prevchr == "\\") 724 | { 725 | $name .= $lastchr . $prevchr; 726 | $data = substr($data, 0, -2); 727 | } 728 | else if ($lastchr == "\"") 729 | { 730 | $data = trim(substr($data, 0, -1)); 731 | $state = ""; 732 | } 733 | else 734 | { 735 | $name .= $lastchr; 736 | $data = substr($data, 0, -1); 737 | } 738 | 739 | break; 740 | } 741 | case "name": 742 | { 743 | if ($lastchr == "," || $lastchr == ";") $state = ""; 744 | else 745 | { 746 | $name .= $lastchr; 747 | $data = substr($data, 0, -1); 748 | } 749 | 750 | break; 751 | } 752 | } 753 | } 754 | 755 | $email = self::MakeValidEmailAddress(strrev($email), $options); 756 | if ($email["success"]) 757 | { 758 | if ($removenames) $name = ""; 759 | $name = trim(strrev($name)); 760 | if (substr($name, 0, 1) == "\"") $name = trim(substr($name, 1)); 761 | $name = str_replace("\\\\", "\\", $name); 762 | $name = str_replace("\\\"", "\"", $name); 763 | 764 | $destnames[] = $name; 765 | $destaddrs[] = $email["email"]; 766 | 767 | $found = true; 768 | } 769 | 770 | $data = trim($data); 771 | } 772 | 773 | $destnames = array_reverse($destnames); 774 | $destaddrs = array_reverse($destaddrs); 775 | 776 | return $found; 777 | } 778 | 779 | // Takes in a comma-separated list of e-mail addresses and returns appropriate e-mail headers. 780 | public static function EmailAddressesToEmailHeaders($emailaddrs, $headername, $multiple = true, $removenames = false, $options = array()) 781 | { 782 | $result = ""; 783 | 784 | $tempnames = array(); 785 | $tempaddrs = array(); 786 | self::EmailAddressesToNamesAndEmail($tempnames, $tempaddrs, $emailaddrs, $removenames, $options); 787 | 788 | $y = count($tempnames); 789 | for ($x = 0; $x < $y && ($multiple || $result == ""); $x++) 790 | { 791 | $name = $tempnames[$x]; 792 | $emailaddr = $tempaddrs[$x]; 793 | 794 | if ($name != "" && !UTF8::IsASCII($name)) $name = self::ConvertToRFC1342($name) . " "; 795 | else if ($name != "") $name = '"' . $name . '" '; 796 | if ($result != "") $result .= ",\r\n "; 797 | if ($name != "") $result .= $name . '<' . $emailaddr . '>'; 798 | else $result .= $emailaddr; 799 | } 800 | 801 | if ($result != "" && $headername != "") $result = $headername . ": " . $result . "\r\n"; 802 | 803 | return $result; 804 | } 805 | 806 | public static function GetUserAgent($type) 807 | { 808 | if ($type == "Thunderbird") return "User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:24.0) Gecko/20100101 Thunderbird/24.0\r\n"; 809 | else if ($type == "Thunderbird2") return "X-Mailer: Thunderbird 2.0.0.16 (Windows/20080708)\r\n"; 810 | else if ($type == "OutlookExpress") return "X-Mailer: Microsoft Outlook Express 6.00.2900.3198\r\nX-MimeOLE: Produced By Microsoft MimeOLE V6.00.2900.3198\r\n"; 811 | else if ($type == "Exchange") return "X-Mailer: Produced By Microsoft Exchange V6.0.6619.12\r\n"; 812 | else if ($type == "OfficeOutlook") return "X-Mailer: Microsoft Office Outlook 12.0\r\n"; 813 | 814 | return ""; 815 | } 816 | 817 | public static function GetTimeLeft($start, $limit) 818 | { 819 | if ($limit === false) return false; 820 | 821 | $difftime = microtime(true) - $start; 822 | if ($difftime >= $limit) return 0; 823 | 824 | return $limit - $difftime; 825 | } 826 | 827 | private static function ProcessRateLimit($size, $start, $limit, $async) 828 | { 829 | $difftime = microtime(true) - $start; 830 | if ($difftime > 0.0) 831 | { 832 | if ($size / $difftime > $limit) 833 | { 834 | // Sleeping for some amount of time will equalize the rate. 835 | // So, solve this for $x: $size / ($x + $difftime) = $limit 836 | $amount = ($size - ($limit * $difftime)) / $limit; 837 | $amount += 0.001; 838 | 839 | if ($async) return microtime(true) + $amount; 840 | else usleep($amount * 1000000); 841 | } 842 | } 843 | 844 | return -1.0; 845 | } 846 | 847 | private static function StreamTimedOut($fp) 848 | { 849 | if (!function_exists("stream_get_meta_data")) return false; 850 | 851 | $info = stream_get_meta_data($fp); 852 | 853 | return $info["timed_out"]; 854 | } 855 | 856 | // Handles partially read input. Also deals with the hacky workaround to the second bugfix in ProcessState__WriteData(). 857 | private static function ProcessState__InternalRead(&$state, $size, $endchar = false) 858 | { 859 | $y = strlen($state["nextread"]); 860 | 861 | do 862 | { 863 | if ($size <= $y) 864 | { 865 | if ($endchar === false) $pos = $size; 866 | else 867 | { 868 | $pos = strpos($state["nextread"], $endchar); 869 | if ($pos === false || $pos > $size) $pos = $size; 870 | else $pos++; 871 | } 872 | 873 | $data = substr($state["nextread"], 0, $pos); 874 | $state["nextread"] = (string)substr($state["nextread"], $pos); 875 | 876 | return $data; 877 | } 878 | 879 | if ($endchar !== false) 880 | { 881 | $pos = strpos($state["nextread"], $endchar); 882 | if ($pos !== false) 883 | { 884 | $data = substr($state["nextread"], 0, $pos + 1); 885 | $state["nextread"] = (string)substr($state["nextread"], $pos + 1); 886 | 887 | return $data; 888 | } 889 | } 890 | 891 | if ($state["debug"]) $data2 = fread($state["fp"], $size); 892 | else $data2 = @fread($state["fp"], $size); 893 | 894 | if ($data2 === false || $data2 === "") 895 | { 896 | if ($state["nextread"] === "") return $data2; 897 | 898 | if ($state["async"] && $endchar !== false && $data2 === "") return ""; 899 | 900 | $data = $state["nextread"]; 901 | $state["nextread"] = ""; 902 | 903 | return $data; 904 | } 905 | 906 | $state["nextread"] .= $data2; 907 | 908 | $y = strlen($state["nextread"]); 909 | } while (!$state["async"] || ($size <= $y) || ($endchar !== false && strpos($state["nextread"], $endchar) !== false)); 910 | 911 | if ($endchar !== false) return ""; 912 | 913 | $data = $state["nextread"]; 914 | $state["nextread"] = ""; 915 | 916 | return $data; 917 | } 918 | 919 | // Reads one or more lines in. 920 | private static function ProcessState__ReadLine(&$state) 921 | { 922 | while (strpos($state["data"], "\n") === false) 923 | { 924 | $data2 = self::ProcessState__InternalRead($state, 116000, "\n"); 925 | 926 | if ($data2 === false || $data2 === "") 927 | { 928 | if (feof($state["fp"])) return array("success" => false, "error" => self::SMTP_Translate("Remote peer disconnected."), "errorcode" => "peer_disconnected"); 929 | else if ($state["async"]) return array("success" => false, "error" => self::SMTP_Translate("Non-blocking read returned no data."), "errorcode" => "no_data"); 930 | else if ($data2 === false) return array("success" => false, "error" => self::SMTP_Translate("Underlying stream encountered a read error."), "errorcode" => "stream_read_error"); 931 | } 932 | $pos = strpos($data2, "\n"); 933 | if ($pos === false) 934 | { 935 | if (feof($state["fp"])) return array("success" => false, "error" => self::SMTP_Translate("Remote peer disconnected."), "errorcode" => "peer_disconnected"); 936 | if (self::StreamTimedOut($state["fp"])) return array("success" => false, "error" => self::SMTP_Translate("Underlying stream timed out."), "errorcode" => "stream_timeout_exceeded"); 937 | 938 | $pos = strlen($data2); 939 | } 940 | if ($state["timeout"] !== false && self::GetTimeLeft($state["startts"], $state["timeout"]) == 0) return array("success" => false, "error" => self::SMTP_Translate("SMTP timeout exceeded."), "errorcode" => "timeout_exceeded"); 941 | if (isset($state["options"]["readlinelimit"]) && strlen($state["data"]) + $pos > $state["options"]["readlinelimit"]) return array("success" => false, "error" => self::SMTP_Translate("Read line exceeded limit."), "errorcode" => "read_line_limit_exceeded"); 942 | 943 | $state["result"]["rawrecvsize"] += strlen($data2); 944 | $state["data"] .= $data2; 945 | 946 | if (isset($state["options"]["recvlimit"]) && $state["options"]["recvlimit"] < $state["rawsize"]) return array("success" => false, "error" => self::SMTP_Translate("Received data exceeded limit."), "errorcode" => "receive_limit_exceeded"); 947 | if (isset($state["options"]["recvratelimit"])) $state["waituntil"] = self::ProcessRateLimit($state["rawsize"], $state["recvstart"], $state["options"]["recvratelimit"], $state["async"]); 948 | 949 | if (isset($state["options"]["debug_callback"]) && is_callable($state["options"]["debug_callback"])) call_user_func_array($state["options"]["debug_callback"], array("rawrecv", $data2, &$state["options"]["debug_callback_opts"])); 950 | else if ($state["debug"]) $state["result"]["rawrecv"] .= $data2; 951 | } 952 | 953 | return array("success" => true); 954 | } 955 | 956 | // Writes data out. 957 | private static function ProcessState__WriteData(&$state) 958 | { 959 | if ($state["data"] !== "") 960 | { 961 | // Serious bug in PHP core for non-blocking SSL sockets: https://bugs.php.net/bug.php?id=72333 962 | if ($state["secure"] && $state["async"]) 963 | { 964 | // This is a huge hack that has a pretty good chance of blocking on the socket. 965 | // Peeling off up to just 4KB at a time helps to minimize that possibility. It's better than guaranteed failure of the socket though. 966 | @stream_set_blocking($state["fp"], 1); 967 | if ($state["debug"]) $result = fwrite($state["fp"], (strlen($state["data"]) > 4096 ? substr($state["data"], 0, 4096) : $state["data"])); 968 | else $result = @fwrite($state["fp"], (strlen($state["data"]) > 4096 ? substr($state["data"], 0, 4096) : $state["data"])); 969 | @stream_set_blocking($state["fp"], 0); 970 | } 971 | else 972 | { 973 | if ($state["debug"]) $result = fwrite($state["fp"], $state["data"]); 974 | else $result = @fwrite($state["fp"], $state["data"]); 975 | } 976 | 977 | if ($result === false || feof($state["fp"])) return array("success" => false, "error" => self::SMTP_Translate("A fwrite() failure occurred. Most likely cause: Connection failure."), "errorcode" => "fwrite_failed"); 978 | 979 | // Serious bug in PHP core for all socket types: https://bugs.php.net/bug.php?id=73535 980 | if ($result === 0) 981 | { 982 | // Temporarily switch to non-blocking sockets and test a one byte read (doesn't matter if data is available or not). 983 | // This is a bigger hack than the first hack above. 984 | if (!$state["async"]) @stream_set_blocking($state["fp"], 0); 985 | 986 | if ($state["debug"]) $data2 = fread($state["fp"], 1); 987 | else $data2 = @fread($state["fp"], 1); 988 | 989 | if ($data2 === false) return array("success" => false, "error" => self::SMTP_Translate("Underlying stream encountered a read error."), "errorcode" => "stream_read_error"); 990 | if ($data2 === "" && feof($state["fp"])) return array("success" => false, "error" => self::SMTP_Translate("Remote peer disconnected."), "errorcode" => "peer_disconnected"); 991 | 992 | if ($data2 !== "") $state["nextread"] .= $data2; 993 | 994 | if (!$state["async"]) @stream_set_blocking($state["fp"], 1); 995 | } 996 | 997 | if ($state["timeout"] !== false && self::GetTimeLeft($state["startts"], $state["timeout"]) == 0) return array("success" => false, "error" => self::SMTP_Translate("SMTP timeout exceeded."), "errorcode" => "timeout_exceeded"); 998 | 999 | $data2 = (string)substr($state["data"], 0, $result); 1000 | $state["data"] = (string)substr($state["data"], $result); 1001 | 1002 | $state["result"]["rawsendsize"] += $result; 1003 | 1004 | if (isset($state["options"]["sendratelimit"])) 1005 | { 1006 | $state["waituntil"] = self::ProcessRateLimit($state["result"]["rawsendsize"], $state["result"]["connected"], $state["options"]["sendratelimit"], $state["async"]); 1007 | if (microtime(true) < $state["waituntil"]) return array("success" => false, "error" => self::SMTP_Translate("Rate limit for non-blocking connection has not been reached."), "errorcode" => "no_data"); 1008 | } 1009 | 1010 | if (isset($state["options"]["debug_callback"]) && is_callable($state["options"]["debug_callback"])) call_user_func_array($state["options"]["debug_callback"], array("rawsend", $data2, &$state["options"]["debug_callback_opts"])); 1011 | else if ($state["debug"]) $state["result"]["rawsend"] .= $data2; 1012 | 1013 | if ($state["async"] && strlen($state["data"])) return array("success" => false, "error" => self::SMTP_Translate("Non-blocking write did not send all data."), "errorcode" => "no_data"); 1014 | } 1015 | 1016 | return array("success" => true); 1017 | } 1018 | 1019 | public static function ForceClose(&$state) 1020 | { 1021 | if ($state["fp"] !== false) 1022 | { 1023 | @fclose($state["fp"]); 1024 | $state["fp"] = false; 1025 | } 1026 | 1027 | if (isset($state["currentfile"]) && $state["currentfile"] !== false) 1028 | { 1029 | if ($state["currentfile"]["fp"] !== false) @fclose($state["currentfile"]["fp"]); 1030 | $state["currentfile"] = false; 1031 | } 1032 | } 1033 | 1034 | private static function CleanupErrorState(&$state, $result) 1035 | { 1036 | if (!$result["success"] && $result["errorcode"] !== "no_data") 1037 | { 1038 | self::ForceClose($state); 1039 | 1040 | $state["error"] = $result; 1041 | } 1042 | 1043 | return $result; 1044 | } 1045 | 1046 | private static function InitSMTPRequest(&$state, $command, $expectedcode, $nextstate, $expectederror) 1047 | { 1048 | $state["data"] = $command . "\r\n"; 1049 | $state["state"] = "send_request"; 1050 | $state["expectedcode"] = $expectedcode; 1051 | $state["nextstate"] = $nextstate; 1052 | $state["expectederror"] = $expectederror; 1053 | } 1054 | 1055 | public static function WantRead(&$state) 1056 | { 1057 | return ($state["state"] === "get_response" || $state["state"] === "connecting_enable_crypto"); 1058 | } 1059 | 1060 | public static function WantWrite(&$state) 1061 | { 1062 | return (!self::WantRead($state) || $state["state"] === "connecting_enable_crypto"); 1063 | } 1064 | 1065 | public static function ProcessState(&$state) 1066 | { 1067 | if (isset($state["error"])) return $state["error"]; 1068 | 1069 | if ($state["timeout"] !== false && self::GetTimeLeft($state["startts"], $state["timeout"]) == 0) return self::CleanupErrorState($state, array("success" => false, "error" => self::SMTP_Translate("SMTP timeout exceeded."), "errorcode" => "timeout_exceeded")); 1070 | if (microtime(true) < $state["waituntil"]) return array("success" => false, "error" => self::SMTP_Translate("Rate limit for non-blocking connection has not been reached."), "errorcode" => "no_data"); 1071 | 1072 | while ($state["state"] !== "done") 1073 | { 1074 | switch ($state["state"]) 1075 | { 1076 | case "connecting": 1077 | { 1078 | if (function_exists("stream_select") && $state["async"]) 1079 | { 1080 | $readfp = NULL; 1081 | $writefp = array($state["fp"]); 1082 | $exceptfp = NULL; 1083 | $result = @stream_select($readfp, $writefp, $exceptfp, 0); 1084 | if ($result === false) return self::CleanupErrorState($state, array("success" => false, "error" => self::SMTP_Translate("A stream_select() failure occurred. Most likely cause: Connection failure."), "errorcode" => "stream_select_failed")); 1085 | 1086 | if (!count($writefp)) return array("success" => false, "error" => self::SMTP_Translate("Connection not established yet."), "errorcode" => "no_data"); 1087 | } 1088 | 1089 | // Deal with failed connections that hang applications. 1090 | if (isset($state["options"]["streamtimeout"]) && $state["options"]["streamtimeout"] !== false && function_exists("stream_set_timeout")) @stream_set_timeout($state["fp"], $state["options"]["streamtimeout"]); 1091 | 1092 | // Switch to the next state. 1093 | if ($state["async"] && function_exists("stream_socket_client") && $state["secure"]) $state["state"] = "connecting_enable_crypto"; 1094 | else $state["state"] = "connection_ready"; 1095 | 1096 | break; 1097 | } 1098 | case "connecting_enable_crypto": 1099 | { 1100 | // This is only used by clients that connect asynchronously via SSL. 1101 | if ($state["debug"]) $result = stream_socket_enable_crypto($state["fp"], true, STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT | STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT); 1102 | else $result = @stream_socket_enable_crypto($state["fp"], true, STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT | STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT); 1103 | 1104 | if ($result === false) return self::CleanupErrorState($state, array("success" => false, "error" => self::SMTP_Translate("A stream_socket_enable_crypto() failure occurred. Most likely cause: Connection failure or incompatible crypto setup."), "errorcode" => "stream_socket_enable_crypto_failed")); 1105 | else if ($result === true) $state["state"] = "connection_ready"; 1106 | 1107 | break; 1108 | } 1109 | case "connection_ready": 1110 | { 1111 | // Handle peer certificate retrieval. 1112 | if (function_exists("stream_context_get_options")) 1113 | { 1114 | $contextopts = stream_context_get_options($state["fp"]); 1115 | 1116 | if ($state["secure"] && isset($state["options"]["sslopts"]) && is_array($state["options"]["sslopts"])) 1117 | { 1118 | if (isset($state["options"]["peer_cert_callback"]) && is_callable($state["options"]["peer_cert_callback"])) 1119 | { 1120 | if (isset($contextopts["ssl"]["peer_certificate"]) && !call_user_func_array($state["options"]["peer_cert_callback"], array("peercert", $contextopts["ssl"]["peer_certificate"], &$state["options"]["peer_cert_callback_opts"]))) return array("success" => false, "error" => self::SMTP_Translate("Peer certificate callback returned with a failure condition."), "errorcode" => "peer_cert_callback"); 1121 | if (isset($contextopts["ssl"]["peer_certificate_chain"]) && !call_user_func_array($state["options"]["peer_cert_callback"], array("peercertchain", $contextopts["ssl"]["peer_certificate_chain"], &$state["options"]["peer_cert_callback_opts"]))) return array("success" => false, "error" => self::SMTP_Translate("Peer certificate callback returned with a failure condition."), "errorcode" => "peer_cert_callback"); 1122 | } 1123 | } 1124 | } 1125 | 1126 | $state["result"]["connected"] = microtime(true); 1127 | 1128 | $state["data"] = ""; 1129 | $state["code"] = 0; 1130 | $state["expectedcode"] = 220; 1131 | $state["expectederror"] = self::SMTP_Translate("Expected a 220 response from the SMTP server upon connecting."); 1132 | $state["response"] = ""; 1133 | $state["state"] = "get_response"; 1134 | $state["nextstate"] = "helo_ehlo"; 1135 | 1136 | break; 1137 | } 1138 | case "send_request": 1139 | { 1140 | // Send the request to the server. 1141 | $result = self::ProcessState__WriteData($state); 1142 | if (!$result["success"]) return self::CleanupErrorState($state, $result); 1143 | 1144 | $state["code"] = 0; 1145 | $state["response"] = ""; 1146 | 1147 | // Handle QUIT differently. 1148 | $state["state"] = ($state["nextstate"] === "done" ? "done" : "get_response"); 1149 | 1150 | break; 1151 | } 1152 | case "get_response": 1153 | { 1154 | $result = self::ProcessState__ReadLine($state); 1155 | if (!$result["success"]) return self::CleanupErrorState($state, $result); 1156 | 1157 | $currline = $state["data"]; 1158 | $state["data"] = ""; 1159 | if (strlen($currline) >= 4) 1160 | { 1161 | $state["response"] .= substr($currline, 4); 1162 | $state["code"] = (int)substr($currline, 0, 3); 1163 | if (substr($currline, 3, 1) == " ") 1164 | { 1165 | if ($state["expectedcode"] > 0 && $state["code"] !== $state["expectedcode"]) return self::CleanupErrorState($state, array("success" => false, "error" => $state["expectederror"], "errorcode" => "invalid_response", "info" => $state["code"] . " " . $state["response"])); 1166 | 1167 | $state["response"] = self::ReplaceNewlines("\r\n", $state["response"]); 1168 | 1169 | $state["state"] = $state["nextstate"]; 1170 | } 1171 | } 1172 | 1173 | break; 1174 | } 1175 | case "helo_ehlo": 1176 | { 1177 | // Send EHLO or HELO depending on server support. 1178 | $hostname = (isset($state["options"]["hostname"]) ? $state["options"]["hostname"] : "[" . trim(isset($_SERVER["SERVER_ADDR"]) && $_SERVER["SERVER_ADDR"] != "127.0.0.1" ? $_SERVER["SERVER_ADDR"] : "192.168.0.101") . "]"); 1179 | $state["size_supported"] = 0; 1180 | if (strpos($state["response"], " ESMTP") !== false) 1181 | { 1182 | self::InitSMTPRequest($state, "EHLO " . $hostname, 250, "esmtp_extensions", self::SMTP_Translate("Expected a 250 response from the SMTP server upon EHLO.")); 1183 | } 1184 | else 1185 | { 1186 | self::InitSMTPRequest($state, "HELO " . $hostname, 250, "mail_from", self::SMTP_Translate("Expected a 250 response from the SMTP server upon HELO.")); 1187 | } 1188 | 1189 | break; 1190 | } 1191 | case "esmtp_extensions": 1192 | { 1193 | // Process supported ESMTP extensions. 1194 | $auth = ""; 1195 | $smtpdata = explode("\r\n", $state["response"]); 1196 | $y = count($smtpdata); 1197 | for ($x = 1; $x < $y; $x++) 1198 | { 1199 | if (strtoupper(substr($smtpdata[$x], 0, 4)) == "AUTH" && ($smtpdata[$x][4] == ' ' || $smtpdata[$x][4] == '=')) $auth = strtoupper(substr($smtpdata[$x], 5)); 1200 | if (strtoupper(substr($smtpdata[$x], 0, 4)) == "SIZE" && ($smtpdata[$x][4] == ' ' || $smtpdata[$x][4] == '=')) $state["size_supported"] = (int)substr($smtpdata[$x], 5); 1201 | } 1202 | 1203 | $state["state"] = "mail_from"; 1204 | 1205 | // Process login (if any and supported). 1206 | if (strpos($auth, "LOGIN") !== false) 1207 | { 1208 | $state["username"] = (isset($state["options"]["username"]) ? (string)$state["options"]["username"] : ""); 1209 | $state["password"] = (isset($state["options"]["password"]) ? (string)$state["options"]["password"] : ""); 1210 | if ($state["username"] !== "" || $state["password"] !== "") 1211 | { 1212 | self::InitSMTPRequest($state, "AUTH LOGIN", 334, "auth_login_username", self::SMTP_Translate("Expected a 334 response from the SMTP server upon AUTH LOGIN.")); 1213 | } 1214 | } 1215 | 1216 | break; 1217 | } 1218 | case "auth_login_username": 1219 | { 1220 | self::InitSMTPRequest($state, base64_encode($state["username"]), 334, "auth_login_password", self::SMTP_Translate("Expected a 334 response from the SMTP server upon AUTH LOGIN username.")); 1221 | 1222 | break; 1223 | } 1224 | case "auth_login_password": 1225 | { 1226 | self::InitSMTPRequest($state, base64_encode($state["password"]), 235, "mail_from", self::SMTP_Translate("Expected a 235 response from the SMTP server upon AUTH LOGIN password.")); 1227 | 1228 | break; 1229 | } 1230 | case "mail_from": 1231 | { 1232 | self::InitSMTPRequest($state, "MAIL FROM:<" . $state["fromaddrs"][0] . ">" . ($state["size_supported"] ? " SIZE=" . strlen($state["message"]) : ""), 250, "rcpt_to", self::SMTP_Translate("Expected a 250 response from the SMTP server upon MAIL FROM.")); 1233 | 1234 | break; 1235 | } 1236 | case "rcpt_to": 1237 | { 1238 | $addr = array_shift($state["toaddrs"]); 1239 | self::InitSMTPRequest($state, "RCPT TO:<" . $addr . ">", 250, (count($state["toaddrs"]) ? "rcpt_to" : "data"), self::SMTP_Translate("Expected a 250 response from the SMTP server upon RCPT TO.")); 1240 | 1241 | break; 1242 | } 1243 | case "data": 1244 | { 1245 | self::InitSMTPRequest($state, "DATA", 354, "send_message", self::SMTP_Translate("Expected a 354 response from the SMTP server upon DATA.")); 1246 | 1247 | break; 1248 | } 1249 | case "send_message": 1250 | { 1251 | self::InitSMTPRequest($state, $state["message"] . "\r\n.", 250, "quit", self::SMTP_Translate("Expected a 250 response from the SMTP server upon sending the e-mail.")); 1252 | 1253 | break; 1254 | } 1255 | case "quit": 1256 | { 1257 | self::InitSMTPRequest($state, "QUIT", 0, "done", ""); 1258 | 1259 | break; 1260 | } 1261 | } 1262 | } 1263 | 1264 | $state["result"]["endts"] = microtime(true); 1265 | 1266 | fclose($state["fp"]); 1267 | 1268 | return $state["result"]; 1269 | } 1270 | 1271 | private static function SMTP_RandomHexString($length) 1272 | { 1273 | $lookup = "0123456789ABCDEF"; 1274 | $result = ""; 1275 | 1276 | while ($length) 1277 | { 1278 | $result .= $lookup[mt_rand(0, 15)]; 1279 | 1280 | $length--; 1281 | } 1282 | 1283 | return $result; 1284 | } 1285 | 1286 | public static function GetSSLCiphers($type = "intermediate") 1287 | { 1288 | $type = strtolower($type); 1289 | 1290 | // Cipher list last updated May 3, 2017. 1291 | if ($type == "modern") return "ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256"; 1292 | else if ($type == "old") return "ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:ECDHE-RSA-DES-CBC3-SHA:ECDHE-ECDSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:AES:DES-CBC3-SHA:HIGH:SEED:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!RSAPSK:!aDH:!aECDH:!EDH-DSS-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA:!SRP"; 1293 | 1294 | return "ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA:ECDHE-ECDSA-DES-CBC3-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:DES-CBC3-SHA:!DSS"; 1295 | } 1296 | 1297 | public static function GetSafeSSLOpts($cafile = true, $cipherstype = "intermediate") 1298 | { 1299 | // Result array last updated May 3, 2017. 1300 | $result = array( 1301 | "ciphers" => self::GetSSLCiphers($cipherstype), 1302 | "disable_compression" => true, 1303 | "allow_self_signed" => false, 1304 | "verify_peer" => true, 1305 | "verify_depth" => 5 1306 | ); 1307 | 1308 | if ($cafile === true) $result["auto_cainfo"] = true; 1309 | else if ($cafile !== false) $result["cafile"] = $cafile; 1310 | 1311 | return $result; 1312 | } 1313 | 1314 | private static function ProcessSSLOptions(&$options, $key, $host) 1315 | { 1316 | if (isset($options[$key]["auto_cainfo"])) 1317 | { 1318 | unset($options[$key]["auto_cainfo"]); 1319 | 1320 | $cainfo = ini_get("curl.cainfo"); 1321 | if ($cainfo !== false && strlen($cainfo) > 0) $options[$key]["cafile"] = $cainfo; 1322 | else if (file_exists(str_replace("\\", "/", dirname(__FILE__)) . "/cacert.pem")) $options[$key]["cafile"] = str_replace("\\", "/", dirname(__FILE__)) . "/cacert.pem"; 1323 | } 1324 | 1325 | if (isset($options[$key]["auto_peer_name"])) 1326 | { 1327 | unset($options[$key]["auto_peer_name"]); 1328 | 1329 | $options[$key]["peer_name"] = $host; 1330 | } 1331 | 1332 | if (isset($options[$key]["auto_cn_match"])) 1333 | { 1334 | unset($options[$key]["auto_cn_match"]); 1335 | 1336 | $options[$key]["CN_match"] = $host; 1337 | } 1338 | 1339 | if (isset($options[$key]["auto_sni"])) 1340 | { 1341 | unset($options[$key]["auto_sni"]); 1342 | 1343 | $options[$key]["SNI_enabled"] = true; 1344 | $options[$key]["SNI_server_name"] = $host; 1345 | } 1346 | } 1347 | 1348 | protected static function GetIDNAHost($host) 1349 | { 1350 | $y = strlen($host); 1351 | for ($x = 0; $x < $y && ord($host[$x]) <= 0x7F; $x++); 1352 | 1353 | if ($x < $y) 1354 | { 1355 | if (!class_exists("UTFUtils", false)) require_once str_replace("\\", "/", dirname(__FILE__)) . "/utf_utils.php"; 1356 | 1357 | $host2 = UTFUtils::ConvertToPunycode($host); 1358 | if ($host2 !== false) $host = $host2; 1359 | } 1360 | 1361 | return $host; 1362 | } 1363 | 1364 | // Sends an e-mail by directly connecting to a SMTP server using PHP sockets. Much more powerful than calling mail(). 1365 | public static function SendSMTPEmail($toaddr, $fromaddr, $message, $options = array()) 1366 | { 1367 | $startts = microtime(true); 1368 | $timeout = (isset($options["timeout"]) ? $options["timeout"] : false); 1369 | 1370 | if (!function_exists("stream_socket_client") && !function_exists("fsockopen")) return array("success" => false, "error" => self::SMTP_Translate("The functions 'stream_socket_client' and 'fsockopen' do not exist."), "errorcode" => "function_check"); 1371 | 1372 | $temptonames = array(); 1373 | $temptoaddrs = array(); 1374 | $tempfromnames = array(); 1375 | $tempfromaddrs = array(); 1376 | if (!self::EmailAddressesToNamesAndEmail($temptonames, $temptoaddrs, $toaddr, true, $options)) return array("success" => false, "error" => self::SMTP_Translate("Invalid 'To' e-mail address(es)."), "errorcode" => "invalid_to_address", "info" => $toaddr); 1377 | if (!self::EmailAddressesToNamesAndEmail($tempfromnames, $tempfromaddrs, $fromaddr, true, $options)) return array("success" => false, "error" => self::SMTP_Translate("Invalid 'From' e-mail address."), "errorcode" => "invalid_from_address", "info" => $fromaddr); 1378 | 1379 | $server = (isset($options["server"]) ? self::GetIDNAHost(trim($options["server"])) : "localhost"); 1380 | if ($server == "") return array("success" => false, "error" => self::SMTP_Translate("Invalid server specified."), "errorcode" => "invalid_server"); 1381 | $secure = (isset($options["secure"]) ? $options["secure"] : false); 1382 | $async = (isset($options["async"]) ? $options["async"] : false); 1383 | $protocol = ($secure && !$async ? (isset($options["protocol"]) ? strtolower($options["protocol"]) : "ssl") : "tcp"); 1384 | if (function_exists("stream_get_transports") && !in_array($protocol, stream_get_transports())) return array("success" => false, "error" => self::SMTP_Translate("The desired transport protocol '%s' is not installed.", $protocol), "errorcode" => "transport_not_installed"); 1385 | $port = (isset($options["port"]) ? (int)$options["port"] : -1); 1386 | if ($port < 0 || $port > 65535) $port = ($secure ? 465 : 25); 1387 | $debug = (isset($options["debug"]) ? $options["debug"] : false); 1388 | 1389 | $headers = "Message-ID: <" . self::SMTP_RandomHexString(8) . "." . self::SMTP_RandomHexString(7) . "@" . substr($tempfromaddrs[0], strrpos($tempfromaddrs[0], "@") + 1) . ">\r\n"; 1390 | $headers .= "Date: " . date("D, d M Y H:i:s O") . "\r\n"; 1391 | 1392 | $message = $headers . $message; 1393 | $message = self::ReplaceNewlines("\r\n", $message); 1394 | $message = str_replace("\r\n.\r\n", "\r\n..\r\n", $message); 1395 | 1396 | // Set up the final output array. 1397 | $result = array("success" => true, "rawsendsize" => 0, "rawrecvsize" => 0, "startts" => $startts); 1398 | $debug = (isset($options["debug"]) && $options["debug"]); 1399 | if ($debug) 1400 | { 1401 | $result["rawsend"] = ""; 1402 | $result["rawrecv"] = ""; 1403 | } 1404 | 1405 | if ($timeout !== false && self::GetTimeLeft($startts, $timeout) == 0) return array("success" => false, "error" => self::SMTP_Translate("SMTP timeout exceeded."), "errorcode" => "timeout_exceeded"); 1406 | 1407 | // Connect to the target server. 1408 | $hostname = (isset($options["hostname"]) ? $options["hostname"] : "[" . trim(isset($_SERVER["SERVER_ADDR"]) && $_SERVER["SERVER_ADDR"] != "127.0.0.1" ? $_SERVER["SERVER_ADDR"] : "192.168.0.101") . "]"); 1409 | $errornum = 0; 1410 | $errorstr = ""; 1411 | if (isset($options["fp"]) && is_resource($options["fp"])) 1412 | { 1413 | $fp = $options["fp"]; 1414 | unset($options["fp"]); 1415 | } 1416 | else 1417 | { 1418 | if (!isset($options["connecttimeout"])) $options["connecttimeout"] = 10; 1419 | $timeleft = self::GetTimeLeft($startts, $timeout); 1420 | if ($timeleft !== false) $options["connecttimeout"] = min($options["connecttimeout"], $timeleft); 1421 | if (!function_exists("stream_socket_client")) 1422 | { 1423 | if ($debug) $fp = fsockopen($protocol . "://" . $server, $port, $errornum, $errorstr, $options["connecttimeout"]); 1424 | else $fp = @fsockopen($protocol . "://" . $server, $port, $errornum, $errorstr, $options["connecttimeout"]); 1425 | } 1426 | else 1427 | { 1428 | $context = @stream_context_create(); 1429 | if (isset($options["source_ip"])) $context["socket"] = array("bindto" => $options["source_ip"] . ":0"); 1430 | if ($secure) 1431 | { 1432 | if (!isset($options["sslopts"]) || !is_array($options["sslopts"])) 1433 | { 1434 | $options["sslopts"] = self::GetSafeSSLOpts(); 1435 | $options["sslopts"]["auto_peer_name"] = true; 1436 | } 1437 | self::ProcessSSLOptions($options, "sslopts", (isset($options["sslhostname"]) ? self::GetIDNAHost($options["sslhostname"]) : $server)); 1438 | foreach ($options["sslopts"] as $key => $val) @stream_context_set_option($context, "ssl", $key, $val); 1439 | } 1440 | 1441 | if ($debug) $fp = stream_socket_client($protocol . "://" . $server . ":" . $port, $errornum, $errorstr, $options["connecttimeout"], ($async ? STREAM_CLIENT_ASYNC_CONNECT : STREAM_CLIENT_CONNECT), $context); 1442 | else $fp = @stream_socket_client($protocol . "://" . $server . ":" . $port, $errornum, $errorstr, $options["connecttimeout"], ($async ? STREAM_CLIENT_ASYNC_CONNECT : STREAM_CLIENT_CONNECT), $context); 1443 | } 1444 | 1445 | if ($fp === false) return array("success" => false, "error" => self::SMTP_Translate("Unable to establish a SMTP connection to '%s'.", $protocol . "://" . $server . ":" . $port), "errorcode" => "connection_failure", "info" => $errorstr . " (" . $errornum . ")"); 1446 | } 1447 | 1448 | if (function_exists("stream_set_blocking")) @stream_set_blocking($fp, ($async ? 0 : 1)); 1449 | 1450 | // Initialize the connection request state array. 1451 | $state = array( 1452 | "fp" => $fp, 1453 | "async" => $async, 1454 | "debug" => $debug, 1455 | "startts" => $startts, 1456 | "timeout" => $timeout, 1457 | "waituntil" => -1.0, 1458 | "data" => "", 1459 | "code" => 0, 1460 | "expectedcode" => 0, 1461 | "expectederror" => "", 1462 | "response" => "", 1463 | "fromaddrs" => $tempfromaddrs, 1464 | "toaddrs" => $temptoaddrs, 1465 | "message" => $message, 1466 | "secure" => $secure, 1467 | 1468 | "state" => "connecting", 1469 | 1470 | "options" => $options, 1471 | "result" => $result, 1472 | "nextread" => "" 1473 | ); 1474 | 1475 | // Return the state for async calls. Caller must call ProcessState(). 1476 | if ($state["async"]) return array("success" => true, "state" => $state); 1477 | 1478 | // Run through all of the valid states and return the result. 1479 | return self::ProcessState($state); 1480 | } 1481 | 1482 | // Has to be public so that TagFilter can successfully call. 1483 | public static function ConvertHTMLToText_TagCallback($stack, &$content, $open, $tagname, &$attrs, $options) 1484 | { 1485 | $content = str_replace(array(" ", " ", "\xC2\xA0"), array(" ", " ", " "), $content); 1486 | $content = str_replace("&", "&", $content); 1487 | $content = str_replace(""", "\"", $content); 1488 | 1489 | if ($tagname === "head") return array("keep_tag" => false, "keep_interior" => false); 1490 | if ($tagname === "style") return array("keep_tag" => false, "keep_interior" => false); 1491 | if ($tagname === "script") return array("keep_tag" => false, "keep_interior" => false); 1492 | if ($tagname === "a" && (!isset($attrs["href"]) || trim($attrs["href"]) === "")) return array("keep_tag" => false, "keep_interior" => false); 1493 | if ($tagname === "/a" && $stack[0]["keep_interior"]) 1494 | { 1495 | if ($stack[0]["attrs"]["href"] === trim($content)) $content = " [ " . trim($content) . " ] "; 1496 | else if (trim($content) !== "") $content = " " . trim($content) . " (" . trim($stack[0]["attrs"]["href"]) . ") "; 1497 | } 1498 | if ($tagname === "img") 1499 | { 1500 | if (!isset($attrs["src"])) $attrs["src"] = ""; 1501 | 1502 | if (isset($attrs["alt"]) && trim($attrs["alt"]) !== "" && trim($attrs["alt"]) !== $attrs["src"]) $content .= trim($attrs["alt"]) . "\n\n"; 1503 | } 1504 | 1505 | if ($tagname === "table" || $tagname === "blockquote" || $tagname === "ul") self::$depths[] = $tagname; 1506 | if ($tagname === "ol") self::$depths[] = 1; 1507 | 1508 | if (trim($content) !== "") 1509 | { 1510 | if ($tagname === "/tr") $content = ltrim($content) . "\n\n"; 1511 | if ($tagname === "/th") $content = "*" . ltrim($content) . "*\n"; 1512 | if ($tagname === "/td") $content = ltrim($content) . "\n"; 1513 | if ($tagname === "/div") $content = ltrim($content) . "\n"; 1514 | if ($tagname === "/li") $content = "\n" . (count(self::$depths) && is_int(self::$depths[count(self::$depths) - 1]) ? sprintf("%d. ", self::$depths[count(self::$depths) - 1]++) : "- ") . ltrim($content) . "\n"; 1515 | if ($tagname === "br") $content .= "\n"; 1516 | if ($tagname === "/h1" || $tagname === "/h2" || $tagname === "/h3") $content = "*" . trim($content) . "*\n\n"; 1517 | if ($tagname === "/h4" || $tagname === "/h5" || $tagname === "/h6") $content = "*" . trim($content) . "*\n"; 1518 | if ($tagname === "/i" || $tagname === "/em") $content = " _" . trim($content) . "_ "; 1519 | if ($tagname === "/b" || $tagname === "/strong") $content = " *" . trim($content) . "* "; 1520 | if ($tagname === "/p") $content = "\n\n" . trim($content) . "\n\n"; 1521 | if ($tagname === "/blockquote") $content = "------------------------\n" . trim($content) . "\n------------------------\n"; 1522 | if ($tagname === "/ul" || $tagname === "/ol" || $tagname === "/table" || $tagname === "/blockquote") 1523 | { 1524 | // Indent the lines of content varying amounts depending on final depth. 1525 | $prefix = ""; 1526 | if ($tagname === "/table") $prefix .= "\xFF\xFF"; 1527 | if ($tagname === "/ul" || $tagname === "/ol") $prefix .= "\xFF\xFF" . (count(self::$depths) > 1 ? "\xFF\xFF" : ""); 1528 | if ($tagname === "/blockquote") $prefix .= "\xFF\xFF\xFF\xFF"; 1529 | 1530 | $lines = explode("\n", $content); 1531 | foreach ($lines as $num => $line) 1532 | { 1533 | if (trim($line) !== "") 1534 | { 1535 | if ($line[0] !== "\xFF" && (($tagname === "/ul" && $line[0] !== "-") || ($tagname === "/ol" && !(int)$line[0]))) $prefix2 = "\xFF\xFF"; 1536 | else $prefix2 = ""; 1537 | 1538 | $lines[$num] = $prefix . $prefix2 . trim($line); 1539 | } 1540 | } 1541 | $content = "\n\n" . implode("\n", $lines) . "\n\n"; 1542 | } 1543 | if ($tagname === "/pre") $content = "\n\n" . $content . "\n\n"; 1544 | } 1545 | 1546 | if ($tagname === "/table" || $tagname === "/blockquote" || $tagname === "/ul" || $tagname === "/ol") array_pop(self::$depths); 1547 | 1548 | return array("keep_tag" => false); 1549 | } 1550 | 1551 | // Has to be public so that TagFilter can successfully call. 1552 | public static function ConvertHTMLToText_ContentCallback($stack, $result, &$content, $options) 1553 | { 1554 | if (TagFilter::GetParentPos($stack, "pre") === false) 1555 | { 1556 | $content = preg_replace('/\s{2,}/', " ", str_replace(array("\r\n", "\n", "\r", "\t"), " ", htmlspecialchars_decode($content))); 1557 | if ($result !== "" && substr($result, -1) === "\n") $content = trim($content); 1558 | } 1559 | } 1560 | 1561 | public static function ConvertHTMLToText($data) 1562 | { 1563 | self::$depths = array(); 1564 | 1565 | // Load TagFilter. 1566 | if (!class_exists("TagFilter", false)) require_once str_replace("\\", "/", dirname(__FILE__)) . "/tag_filter.php"; 1567 | 1568 | $data = UTF8::MakeValid($data); 1569 | 1570 | $options = TagFilter::GetHTMLOptions(); 1571 | $options["tag_callback"] = __CLASS__ . "::ConvertHTMLToText_TagCallback"; 1572 | $options["content_callback"] = __CLASS__ . "::ConvertHTMLToText_ContentCallback"; 1573 | 1574 | $data = TagFilter::Run($data, $options); 1575 | 1576 | $data = str_replace("\xFF", " ", $data); 1577 | 1578 | $data = UTF8::MakeValid($data); 1579 | 1580 | return $data; 1581 | } 1582 | 1583 | private static function MIME_RandomString($length) 1584 | { 1585 | $lookup = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; 1586 | $result = ""; 1587 | 1588 | while ($length) 1589 | { 1590 | $result .= $lookup[mt_rand(0, 61)]; 1591 | 1592 | $length--; 1593 | } 1594 | 1595 | return $result; 1596 | } 1597 | 1598 | // Implements the correct MultiAsyncHelper responses for SMTP. 1599 | public static function SendEmailAsync__Handler($mode, &$data, $key, &$info) 1600 | { 1601 | switch ($mode) 1602 | { 1603 | case "init": 1604 | { 1605 | if ($info["init"]) $data = $info["keep"]; 1606 | else 1607 | { 1608 | $info["result"] = self::SendEmail($info["fromaddr"], $info["toaddr"], $info["subject"], $info["options"]); 1609 | if (!$info["result"]["success"]) 1610 | { 1611 | $info["keep"] = false; 1612 | 1613 | if (is_callable($info["callback"])) call_user_func_array($info["callback"], array($key, $info["url"], $info["result"])); 1614 | } 1615 | else 1616 | { 1617 | $info["state"] = $info["result"]["state"]; 1618 | 1619 | // Move to the live queue. 1620 | $data = true; 1621 | } 1622 | } 1623 | 1624 | break; 1625 | } 1626 | case "update": 1627 | case "read": 1628 | case "write": 1629 | { 1630 | if ($info["keep"]) 1631 | { 1632 | $info["result"] = self::ProcessState($info["state"]); 1633 | if ($info["result"]["success"] || $info["result"]["errorcode"] !== "no_data") $info["keep"] = false; 1634 | 1635 | if (is_callable($info["callback"])) call_user_func_array($info["callback"], array($key, $info["url"], $info["result"])); 1636 | 1637 | if ($mode === "update") $data = $info["keep"]; 1638 | } 1639 | 1640 | break; 1641 | } 1642 | case "readfps": 1643 | { 1644 | if ($info["state"] !== false && self::WantRead($info["state"])) $data[$key] = $info["state"]["fp"]; 1645 | 1646 | break; 1647 | } 1648 | case "writefps": 1649 | { 1650 | if ($info["state"] !== false && self::WantWrite($info["state"])) $data[$key] = $info["state"]["fp"]; 1651 | 1652 | break; 1653 | } 1654 | case "cleanup": 1655 | { 1656 | // When true, caller is removing. Otherwise, detaching from the queue. 1657 | if ($data === true) 1658 | { 1659 | if (isset($info["state"])) 1660 | { 1661 | if ($info["state"] !== false) self::ForceClose($info["state"]); 1662 | 1663 | unset($info["state"]); 1664 | } 1665 | 1666 | $info["keep"] = false; 1667 | } 1668 | 1669 | break; 1670 | } 1671 | } 1672 | } 1673 | 1674 | public static function SendEmailAsync($helper, $key, $callback, $fromaddr, $toaddr, $subject, $options = array()) 1675 | { 1676 | $options["async"] = true; 1677 | 1678 | // DNS lookups are synchronous. Disable this until it is possible to deal with them. 1679 | $options["usedns"] = false; 1680 | 1681 | $info = array( 1682 | "init" => false, 1683 | "keep" => true, 1684 | "callback" => $callback, 1685 | "fromaddr" => $fromaddr, 1686 | "toaddr" => $toaddr, 1687 | "subject" => $subject, 1688 | "options" => $options, 1689 | "result" => false 1690 | ); 1691 | 1692 | $helper->Set($key, $info, array(__CLASS__, "SendEmailAsync__Handler")); 1693 | 1694 | return array("success" => true); 1695 | } 1696 | 1697 | // Special function for generating MAILER-DAEMON style rejection emails. 1698 | public static function CreateDeliveryStatusMessage($host, $fromaddr, $toaddr, $subject, $notificationmsg, $sender, $origrecipient, $finalrecipient, $action, $status, $diagcode, $origmsg = false, $options = array()) 1699 | { 1700 | $subject = str_replace("\r", " ", $subject); 1701 | $subject = str_replace("\n", " ", $subject); 1702 | if (!UTF8::IsASCII($subject)) $subject = self::ConvertToRFC1342($subject); 1703 | 1704 | $headers = (isset($options["headers"]) ? $options["headers"] : ""); 1705 | 1706 | $messagefromaddr = self::EmailAddressesToEmailHeaders($fromaddr, "From", false, false, $options); 1707 | if ($messagefromaddr == "") return array("success" => false, "error" => self::SMTP_Translate("From address is invalid."), "errorcode" => "invalid_from_address", "info" => $fromaddr); 1708 | $messagetoaddr = self::EmailAddressesToEmailHeaders($toaddr, "To", true, false, $options); 1709 | 1710 | $mimeboundary = self::MIME_RandomString(25) . "/" . $host; 1711 | $destheaders = ""; 1712 | $destheaders .= rtrim($messagefromaddr) . " (Mail Delivery System)\r\n"; 1713 | $destheaders .= "Subject: " . $subject . "\r\n"; 1714 | $destheaders .= $messagetoaddr; 1715 | if ($headers != "") $destheaders .= $headers; 1716 | $destheaders .= "Auto-Submitted: auto-replied\r\n"; 1717 | $destheaders .= "MIME-Version: 1.0\r\n"; 1718 | $destheaders .= "Content-Type: multipart/report; report-type=delivery-status; boundary=\"" . $mimeboundary . "\"\r\n"; 1719 | $destheaders .= "Content-Transfer-Encoding: 8bit\r\n"; 1720 | 1721 | $message = "This is a MIME-encapsulated message.\r\n"; 1722 | $message .= "\r\n"; 1723 | 1724 | $message .= "--" . $mimeboundary . "\r\n"; 1725 | $message .= "Content-Description: Notification\r\n"; 1726 | $message .= "Content-Type: text/plain; charset=utf-8\r\n"; 1727 | $message .= "Content-Transfer-Encoding: 8bit\r\n"; 1728 | $message .= "\r\n"; 1729 | $message .= $notificationmsg . "\r\n"; 1730 | 1731 | $message .= "--" . $mimeboundary . "\r\n"; 1732 | $message .= "Content-Description: Delivery report\r\n"; 1733 | $message .= "Content-Type: message/delivery-status\r\n"; 1734 | $message .= "\r\n"; 1735 | $message .= "Reporting-MTA: dns; " . $host . "\r\n"; 1736 | $message .= "X-Postfix-Queue-ID: " . strtoupper(self::MIME_RandomString(10)) . "\r\n"; 1737 | $message .= "X-Postfix-Sender: rfc822; " . $sender . "\r\n"; 1738 | $message .= "Arrival-Date: " . date("D, d M Y H:i:s O") . "\r\n"; 1739 | $message .= "\r\n"; 1740 | $message .= "Final-Recipient: rfc822; " . $finalrecipient . "\r\n"; 1741 | $message .= "Original-Recipient: rfc822; " . $origrecipient . "\r\n"; 1742 | $message .= "Action: " . $action . "\r\n"; 1743 | $message .= "Status: " . $status . "\r\n"; 1744 | $message .= "Diagnostic-Code: x-unix; " . $diagcode . "\r\n"; 1745 | $message .= "\r\n"; 1746 | 1747 | if ($origmsg != "") 1748 | { 1749 | $message .= "--" . $mimeboundary . "\r\n"; 1750 | $message .= "Content-Description: Undelivered Message\r\n"; 1751 | $message .= "Content-Type: message/rfc822\r\n"; 1752 | $message .= "Content-Transfer-Encoding: 8bit\r\n"; 1753 | $message .= "\r\n"; 1754 | 1755 | $message .= $origmsg; 1756 | $message .= "\r\n"; 1757 | } 1758 | 1759 | $message .= "--" . $mimeboundary . "--\r\n"; 1760 | 1761 | return array("success" => true, "toaddr" => $toaddr, "fromaddr" => $fromaddr, "headers" => $destheaders, "subject" => $subject, "message" => $message); 1762 | } 1763 | 1764 | 1765 | public static function SendEmail($fromaddr, $toaddr, $subject, $options = array()) 1766 | { 1767 | $subject = str_replace("\r", " ", $subject); 1768 | $subject = str_replace("\n", " ", $subject); 1769 | if (!UTF8::IsASCII($subject)) $subject = self::ConvertToRFC1342($subject); 1770 | 1771 | $replytoaddr = (isset($options["replytoaddr"]) ? $options["replytoaddr"] : ""); 1772 | $ccaddr = (isset($options["ccaddr"]) ? $options["ccaddr"] : ""); 1773 | $bccaddr = (isset($options["bccaddr"]) ? $options["bccaddr"] : ""); 1774 | $headers = (isset($options["headers"]) ? $options["headers"] : ""); 1775 | $textmessage = (isset($options["textmessage"]) ? $options["textmessage"] : ""); 1776 | $htmlmessage = (isset($options["htmlmessage"]) ? $options["htmlmessage"] : ""); 1777 | $attachments = (isset($options["attachments"]) ? $options["attachments"] : array()); 1778 | 1779 | $messagetoaddr = self::EmailAddressesToEmailHeaders($toaddr, "To", true, false, $options); 1780 | $replytoaddr = self::EmailAddressesToEmailHeaders($replytoaddr, "Reply-To", true, false, $options); 1781 | if ($replytoaddr == "") $replytoaddr = self::EmailAddressesToEmailHeaders($fromaddr, "Reply-To", true, false, $options); 1782 | $messagefromaddr = self::EmailAddressesToEmailHeaders($fromaddr, "From", false, false, $options); 1783 | if ($messagefromaddr == "" && $replytoaddr == "") return array("success" => false, "error" => self::SMTP_Translate("From address is invalid."), "errorcode" => "invalid_from_address", "info" => $fromaddr); 1784 | if ($ccaddr != "") $toaddr .= ", " . $ccaddr; 1785 | $ccaddr = self::EmailAddressesToEmailHeaders($ccaddr, "Cc", true, false, $options); 1786 | if ($bccaddr != "") $toaddr .= ", " . $bccaddr; 1787 | $bccaddr = self::EmailAddressesToEmailHeaders($bccaddr, "Bcc", true, false, $options); 1788 | 1789 | if ($htmlmessage == "" && !count($attachments)) 1790 | { 1791 | // Plain-text e-mail. 1792 | $destheaders = ""; 1793 | $destheaders .= $messagefromaddr; 1794 | if ($headers != "") $destheaders .= $headers; 1795 | $destheaders .= "MIME-Version: 1.0\r\n"; 1796 | if (!isset($options["usemail"]) || !$options["usemail"]) $destheaders .= $messagetoaddr; 1797 | if ($replytoaddr != "") $destheaders .= $replytoaddr; 1798 | if ($ccaddr != "") $destheaders .= $ccaddr; 1799 | if ($bccaddr != "") $destheaders .= $bccaddr; 1800 | if (!isset($options["usemail"]) || !$options["usemail"]) $destheaders .= "Subject: " . $subject . "\r\n"; 1801 | $destheaders .= "Content-Type: text/plain; charset=UTF-8\r\n"; 1802 | $destheaders .= "Content-Transfer-Encoding: quoted-printable\r\n"; 1803 | 1804 | $message = self::ConvertEmailMessageToRFC1341($textmessage); 1805 | } 1806 | else 1807 | { 1808 | // MIME e-mail (HTML, text, attachments). 1809 | $mimeboundary = "--------" . self::MIME_RandomString(25); 1810 | $destheaders = ""; 1811 | $destheaders .= $messagefromaddr; 1812 | if ($headers != "") $destheaders .= $headers; 1813 | $destheaders .= "MIME-Version: 1.0\r\n"; 1814 | if (!isset($options["usemail"]) || !$options["usemail"]) $destheaders .= $messagetoaddr; 1815 | if ($replytoaddr != "") $destheaders .= $replytoaddr; 1816 | if ($ccaddr != "") $destheaders .= $ccaddr; 1817 | if ($bccaddr != "") $destheaders .= $bccaddr; 1818 | if (!isset($options["usemail"]) || !$options["usemail"]) $destheaders .= "Subject: " . $subject . "\r\n"; 1819 | if (count($attachments) && isset($options["inlineattachments"]) && $options["inlineattachments"]) $destheaders .= "Content-Type: multipart/related; boundary=\"" . $mimeboundary . "\"; type=\"multipart/alternative\"\r\n"; 1820 | else if (count($attachments)) $destheaders .= "Content-Type: multipart/mixed; boundary=\"" . $mimeboundary . "\"\r\n"; 1821 | else if ($textmessage != "" && $htmlmessage != "") $destheaders .= "Content-Type: multipart/alternative; boundary=\"" . $mimeboundary . "\"\r\n"; 1822 | else $mimeboundary = ""; 1823 | 1824 | if ($mimeboundary != "") $mimecontent = "This is a multi-part message in MIME format.\r\n"; 1825 | else $mimecontent = ""; 1826 | 1827 | if ($textmessage == "" || $htmlmessage == "" || !count($attachments)) $mimeboundary2 = $mimeboundary; 1828 | else 1829 | { 1830 | $mimeboundary2 = "--------" . self::MIME_RandomString(25); 1831 | $mimecontent .= "--" . $mimeboundary . "\r\n"; 1832 | $mimecontent .= "Content-Type: multipart/alternative; boundary=\"" . $mimeboundary2 . "\"\r\n"; 1833 | $mimecontent .= "\r\n"; 1834 | } 1835 | 1836 | if ($textmessage != "") 1837 | { 1838 | if ($mimeboundary2 != "") 1839 | { 1840 | $mimecontent .= "--" . $mimeboundary2 . "\r\n"; 1841 | $mimecontent .= "Content-Type: text/plain; charset=UTF-8\r\n"; 1842 | $mimecontent .= "Content-Transfer-Encoding: quoted-printable\r\n"; 1843 | $mimecontent .= "\r\n"; 1844 | } 1845 | else 1846 | { 1847 | $destheaders .= "Content-Type: text/plain; charset=UTF-8\r\n"; 1848 | $destheaders .= "Content-Transfer-Encoding: quoted-printable\r\n"; 1849 | } 1850 | $message = self::ConvertEmailMessageToRFC1341($textmessage); 1851 | $mimecontent .= $message; 1852 | $mimecontent .= "\r\n"; 1853 | } 1854 | 1855 | if ($htmlmessage != "") 1856 | { 1857 | if ($mimeboundary2 != "") 1858 | { 1859 | $mimecontent .= "--" . $mimeboundary2 . "\r\n"; 1860 | $mimecontent .= "Content-Type: text/html; charset=UTF-8\r\n"; 1861 | $mimecontent .= "Content-Transfer-Encoding: quoted-printable\r\n"; 1862 | $mimecontent .= "\r\n"; 1863 | } 1864 | else 1865 | { 1866 | $destheaders .= "Content-Type: text/html; charset=UTF-8\r\n"; 1867 | $destheaders .= "Content-Transfer-Encoding: quoted-printable\r\n"; 1868 | } 1869 | $message = self::ConvertEmailMessageToRFC1341($htmlmessage); 1870 | $mimecontent .= $message; 1871 | $mimecontent .= "\r\n"; 1872 | } 1873 | 1874 | if ($mimeboundary2 != "" && $mimeboundary != $mimeboundary2) $mimecontent .= "--" . $mimeboundary2 . "--\r\n"; 1875 | 1876 | // Process the attachments. 1877 | $y = count($attachments); 1878 | for ($x = 0; $x < $y; $x++) 1879 | { 1880 | $mimecontent .= "--" . $mimeboundary . "\r\n"; 1881 | $type = str_replace("\r", "", $attachments[$x]["type"]); 1882 | $type = str_replace("\n", "", $type); 1883 | $type = UTF8::ConvertToASCII($type); 1884 | if (!isset($attachments[$x]["name"])) $name = ""; 1885 | else 1886 | { 1887 | $name = str_replace("\r", "", $attachments[$x]["name"]); 1888 | $name = str_replace("\n", "", $name); 1889 | $name = self::FilenameSafe($name); 1890 | } 1891 | 1892 | if (!isset($attachments[$x]["location"])) $location = ""; 1893 | else 1894 | { 1895 | $location = str_replace("\r", "", $attachments[$x]["location"]); 1896 | $location = str_replace("\n", "", $location); 1897 | $location = UTF8::ConvertToASCII($location); 1898 | } 1899 | 1900 | if (!isset($attachments[$x]["cid"])) $cid = ""; 1901 | else 1902 | { 1903 | $cid = str_replace("\r", "", $attachments[$x]["cid"]); 1904 | $cid = str_replace("\n", "", $cid); 1905 | $cid = UTF8::ConvertToASCII($cid); 1906 | } 1907 | $mimecontent .= "Content-Type: " . $type . ($name != "" ? "; name=\"" . $name . "\"" : "") . "\r\n"; 1908 | if ($cid != "") $mimecontent .= "Content-ID: <" . $cid . ">\r\n"; 1909 | if ($location != "") $mimecontent .= "Content-Location: " . $location . "\r\n"; 1910 | $mimecontent .= "Content-Transfer-Encoding: base64\r\n"; 1911 | if ($name != "") $mimecontent .= "Content-Disposition: inline; filename=\"" . $name . "\"\r\n"; 1912 | $mimecontent .= "\r\n"; 1913 | $mimecontent .= chunk_split(base64_encode($attachments[$x]["data"])); 1914 | $mimecontent .= "\r\n"; 1915 | } 1916 | 1917 | if ($mimeboundary != "") $mimecontent .= "--" . $mimeboundary . "--\r\n"; 1918 | $message = $mimecontent; 1919 | } 1920 | 1921 | if (isset($options["returnresults"]) && $options["returnresults"]) return array("success" => true, "toaddr" => $toaddr, "fromaddr" => $fromaddr, "headers" => $destheaders, "subject" => $subject, "message" => $message); 1922 | else if (isset($options["usemail"]) && $options["usemail"]) 1923 | { 1924 | $result = mail($toaddr, $subject, self::ReplaceNewlines("\n", $message), $destheaders); 1925 | if (!$result) return array("success" => false, "error" => self::SMTP_Translate("PHP mail() call failed."), "errorcode" => "mail_call_failed"); 1926 | 1927 | return array("success" => true); 1928 | } 1929 | else 1930 | { 1931 | return self::SendSMTPEmail($toaddr, $fromaddr, $destheaders . "\r\n" . $message, $options); 1932 | } 1933 | } 1934 | } 1935 | ?> --------------------------------------------------------------------------------