#!/usr/bin/php -q depends now on php_mbstring * * Changed copyright stuff, unizh now in front. * Version: 1.2 - * PHP5 compatible * * added password character supression (works only where stty available) * * fixed selecting all files * * fixed error message * Version: 1.3 - * fixed mbstring for console messages * * Added sort order * * Added duplicate file and fromaddress detector * Version: 1.4 - * Added support for MAC mails (RFC 1740, 1741) * * Added 7bit conversion * * Made recursive functinction call -> added support for attachment in attachment (1.2.2, 1.2.2.1) * * Fixed duplicate file and email detection * Version: 1.5 - * Fixed bug with attachment only! * Version: 1.6 - * Added feature that duplicates will be only detected when the file regexp matches * Version: 1.7 - * Added support for RFC 2231, where a parameter can be split over multiple lines (beta!! does not work with current php_imap!!) * Version: 1.7.1 - * Fixed bug when NAME as an attachement was set but was empty, now we are also looking for FILENAME; fixed also further filename fixes * Version: 1.8 - * Added Flag -Hf, a regular expression for the frmo address, fixed BUG when attachment is not explicitly marked as ATTACHEMENT in the disposition * Version: 1.8.1 - * RFC 2231 seems to work! Did not da a lot of testing * * This script depends on php_imap and php_mbstring, without it, it won't work very well! */ define("ENCODING", "UTF-8"); if ($argc < 5 || in_array($argv[1], array ('--help', '-help', '-h', '-?'))) { print "This is a command line PHP script with a lot of options.\n\n"; print "Usage:\n\n"; print "{$argv[0]} mail-server mail-directory mail-username options\n\n"; print "(Mail-server my contain additional options: \"mail-server:993/ssl/readonly/novalidate-cert\")\n"; print "options are:\n\n"; print "-f file-regexp\n"; print "Filter mails, handle only mails with the correct file attachement. If not supplied, .* will be used. If you want to accept onyl files with the ending pdf or ps use: '/.*[.](ps)|(pdf)$/i' \n\n"; print "-Hf header-regexp\n"; print "Filter header, handle only mails with the correct header. If not supplied, .* will be used. If you want to accept onyl files with the ending pdf or ps use: '/.*[.](ps)|(pdf)$/i' \n\n"; print "-p command\n"; print "Prints the files with the given command, usually lpr. Options vary from printer to printer, to print duplex, use lpr -o sides=two-sided-long-edge 15.ps. %f will be replaced by the actual filename\n\n"; print "-t text\n"; print "Reply to the mail with text\n\n"; print "-r address\n"; print "Reply to the mail with a specific address\n\n"; print "-v all\n"; print "Print all warnings\n\n"; print "-d file|email|all\n"; print "Do not proceed duplicate Mails: File means that any duplicate attachement won't be processed. Email means that any duplicate Email address won't be processed. All means file and email duplicated wonm't be processed\n\n"; print "-s SORTDATE|SORTARRIVAL|SORTFROM|SORTSUBJECT|SORTTO|SORTCC|SORTSIZE\n"; print "Sort by (according to the php manual), default is SORTARRIVAL:\n"; print " * SORTDATE - message Date\n"; print " * SORTARRIVAL - arrival date\n"; print " * SORTFROM - mailbox in first From address\n"; print " * SORTSUBJECT - message subject\n"; print " * SORTTO - mailbox in first To address\n"; print " * SORTCC - mailbox in first cc address\n"; print " * SORTSIZE - size of message in octets\n\n"; print "-sorder asc|desc\n"; print "Only valid with -s. Sort descending or ascending.\n\n"; exit; } $options = array_slice($argv, 4, count($argv)); $mp = new MailFetcher($argv[1], $argv[2], $argv[3], $options); class MailFetcher { private $mbox; private $verbose = false; /* check for duplicate mail from addresses */ private $dup_email = false; /* check for duplicate files */ private $dup_file = false; /* sort mails */ private $sort_reverse = 0; /* keep track of duplicate email adresses */ private $mail_array = array (); /* keep track of duplicate files*/ private $file_array = array (); /* */ private $option = array (); private $current_user; /* standard encoding */ /** * Open a connection to the imap server and handle options */ function MailFetcher($server, $directory, $username, $options) { /* work with UTF-8 */ mb_internal_encoding(ENCODING); $this->options = $this->reorderOptions($options); echo "Password: "; /* PHP5 -> uses filters!! with PHP4 not possible */ exec("stty -echo"); $fp = fopen('php://stdin', 'r'); /* password can be up to 32 characters */ $password = fread($fp, 32); /* works only on linux!!!!! */ exec("stty echo"); echo "\nConnecting...\n"; $password = trim($password); /* open mailbox */ $this->mbox = @ imap_open('{'.$server.'}'.$directory, $username, $password); $this->check_errors(true); /* handle sorting */ $msg_arr = imap_sort($this->mbox, $this->options['-s'], $this->sort_reverse); $this->check_errors(true); /* get number of messages */ //$num_msg = imap_num_msg($this->mbox); $num_msg = count($msg_arr); /* go through all messages, for a reason I don't understand, messages start with 1 */ for ($i = 0; $i < $num_msg; $i ++) { /* get message */ $body = imap_fetchbody($this->mbox, $msg_arr[$i], 0); $structure = imap_fetchstructure($this->mbox, $msg_arr[$i]); $this->check_errors(false); if (isset ($structure->parts)) $this->handleParts($structure->parts, $msg_arr[$i]); else { /* special treatment for no attachment, the mail is the attachment */ $this->handleOnePart($structure, $msg_arr[$i], "1"); } } imap_close($this->mbox); } /** * Loop over all possible parts */ function handleParts($parts, $msg_nr, $str = '') { $start = 1; foreach ($parts as $part) { $str_tmp = $this->appendNumber($str, $start); $start ++; $this->handleOnePart($part, $msg_nr, $str_tmp); foreach ($part as $key => $val) { if (strtolower($key) == 'parts') $this->handleParts($val, $msg_nr, $str_tmp); } } } /** * Hanle just one part, process it */ function handleOnePart($part, $msg_nr, $str = '') { /* get header info */ $header = imap_header($this->mbox, $msg_nr); $this->current_user = $header->fromaddress; if ($part->ifdparameters == "1" || $part->ifparameters == "1") { // Attachment $name = $this->searchfilename($part); if ($name != '') { // we get funny stuff like: 85_1_=?iso-8859-1?Q?=DCbung1.pdf?= //if (preg_match('/^.*=[?].*[?]=.*$/', $name)) $name_orig = mb_decode_mimeheader($name); //else // $name_orig = $name; //to stay unique, we must introduce unique numbers! $name = "{$msg_nr}_{$str}_".$name_orig; $fp = fopen($name, 'w+'); //echo "***************************************"; //print_r($part); //echo "***************************************"; $content = imap_fetchbody($this->mbox, $msg_nr, $str); //echo "str [$str_tmp]"; $content = $this->coding($content, $part->encoding); fwrite($fp, $content); /* make unique checksums */ $check_file = md5($content); $check_header = md5($header->fromaddress); fclose($fp); /* this flag idicates that we should proceed with the -f option */ $continue = true; //echo "check $name_orig from ".mb_decode_mimeheader($header->fromaddress)."\n"; //echo "header is: ".$from." Hf is ".$this->options['-Hf']."\n"; if (preg_match($this->options['-f'], $name_orig) && preg_match($this->options['-Hf'], $this->current_user)) { //echo "got $name_orig\n"; if ($this->dup_email) { if (isset ($this->mail_array[$check_header])) { $continue = false; if ($this->verbose) print "Warning: Do not handle duplicate address $check_header!\n"; } else $this->mail_array[$check_header] = 1; } if ($this->dup_file) { if (isset ($this->file_array[$check_file])) { //print_r($this->file_array); $continue = false; if ($this->verbose) print "Warning: Do not handle duplicate file $check_file!\n"; } else $this->file_array[$check_file] = 1; //print_r($this->file_array); } if (strtolower($part->subtype) == 'applefile') { if ($this->verbose) print "Warning: Do not handle apple files!\n"; $continue = false; } if (strtolower($part->subtype) == 'mac-binhex40') { print "Error: BinHex not supported!\n"; $continue = false; } /* check if we should continue */ if ($continue) { print "--Found Document from ".mb_decode_mimeheader($header->fromaddress)."--\n"; if (isset ($this->options['-p'])) $this->printout($name, $this->options['-p']); if (isset ($this->options['-t'])) $this->printmail($header->fromaddress, $header->subject, $this->options['-t'], $this->options['-r']); } else if ($this->verbose) print "Warning: ".mb_decode_mimeheader($header->fromaddress).' / '.mb_decode_mimeheader($header->subject).' not processed ('.mb_decode_mimeheader($name).")\n"; } unlink($name); } } else if ($this->verbose) print "Warning: ".mb_decode_mimeheader($header->fromaddress).' / '.mb_decode_mimeheader($header->subject)." no attachment!\n"; } /** * Append number */ function appendNumber($str, $nr) { return $str == '' ? "".$nr : $str.'.'.$nr; } /** * Check if an imap error occured, exit if the error is critical */ function check_errors($critical = false) { if ($err = imap_errors()) { print ("Error in IMAP: ".print_r($err, true)."\n"); if ($critical) exit (); } return; } /** * Options that are in the array will get reordered. We receive the following format: * 0 -> -p * 1 -> lpr * 2 -> -f * 3 -> .* * etc. * * we return the following array * -p -> lpr * -f -> .* */ function reorderOptions($options) { $ret = array (); $c = count($options); if ($c % 2 == 1) { echo "options count mismatch!"; exit; } for ($i = 0; $i < $c; $i += 2) $ret[trim($options[$i])] = trim($options[$i +1]); /* set defaults */ if (!isset ($ret['-f'])) $ret['-f'] = '/.*/'; if (!isset ($ret['-Hf'])) $ret['-Hf'] = '/.*/'; if (isset ($ret['-v'])) $this->verbose = true; if (isset ($ret['-d'])) { if (strtolower($ret['-d']) == 'email' || $ret['-d'] == 'all') $this->dup_email = true; if (strtolower($ret['-d']) == 'file' || $ret['-d'] == 'all') $this->dup_file = true; } if (!isset ($ret['-s'])) $ret['-s'] = 'SORTARRIVAL'; if (isset ($ret['-sorder'])) if (strtolower($ret['-sorder']) == 'desc') $this->sort_reverse = 1; return $ret; } /** * Print with a command */ function printout($filename, $command) { $q = str_replace('%f', $filename, $command); exec($q); if ($this->verbose) print "$q\n"; } function searchname($params, $paramname) { $encoding = ""; $name = array (); $matches = array (); $tmp = ""; foreach ($params as $val) { //echo "attribute: {$val->attribute}\n"; //print_r($params); if (preg_match('/^'.$paramname.'$/', strtoupper($val->attribute))) { //echo "found simple filename\n"; // no special treatement return $val->value; } elseif (preg_match('/^'.$paramname.'[*]$/', strtoupper($val->attribute))) { //just encode and return $encoding = $this->getEncoding($val->value); return $this->encoding($this->getNameAfterEncoding($val->value), $encoding); } elseif (preg_match('/^'.$paramname.'[*]([0-9])+$/', strtoupper($val->attribute), $matches)) { echo "(name*1) Warning, this does not fully tested! This is RFC 2231. User ".$this->current_user."\n"; $name[$matches[1]] = $val->value; } elseif (preg_match('/^'.$paramname.'[*]([0-9])+[*]$/', strtoupper($val->attribute), $matches)) { //echo "(name*1*)Warning, this does not work with the latest PHP implementiation! This is RFC 2231. User ".$this->current_user."\n"; if ($encoding == "") { $encoding = $this->getEncoding($val->value); $name[$matches[1]] = $this->encoding($this->getNameAfterEncoding($val->value), $encoding); //echo "!! ".$name[$matches[1]]."\n"; } else { $name[$matches[1]] = $this->encoding($val->value, $encoding); //echo "?? ".$name[$matches[1]]."\n"; } } } ksort($name); foreach ($name as $value) $tmp .= $value; return $tmp; } /** * Search a filename in the parameters and dparameters fields. Added support for RFC 2184 */ function searchfilename($part) { $name = ''; if (isset ($part->parameters)) $name = $this->searchname($part->parameters, 'NAME'); if (isset ($part->dparameters)) { $test = $this->searchname($part->dparameters, 'FILENAME'); //echo "XXX:$test\n"; $name = $test == '' ? $name : $test; } return $name; } function getEncoding($text) { //get the first position $pos = strpos($text, '\''); if (!($pos === false)) return substr($text, 0, $pos); else return null; } function getNameAfterEncoding($text) { //get the second position $pos = strpos($text, '\''); $pos = strpos($text, '\'', $pos); if (!($pos === false)) return substr($text, $pos +1, strlen($text) - ($pos +1)); } function encoding($text, $encoding) { return mb_encode_mimeheader(mb_convert_encoding(urldecode($text), ENCODING, $encoding)); } /** * Decode the message */ function coding($message, $encoding) { if ($encoding == 1) $message = imap_8bit($message); elseif ($encoding == 2) $message = imap_binary($message); elseif ($encoding == 3) $message = imap_base64($message); elseif ($encoding == 4) $message = imap_qprint($message); elseif ($encoding == 0) $message = mb_convert_encoding($message, ENCODING, "7bit"); return $message; } /** * Mail a message, append X-Loop, to prevent looping forever. * Use this wisly with your procmail configuration... */ function printmail($email, $subject, $text, $from = '') { /* I don't have to use mbox?? strange...*/ //$this->mbox $headers = 'X-Loop: '."$from\r\n"; $ret = true; if ($from != '') $headers .= "From: $from\r\n"; echo $headers; /* not necessary if Re: has not Umlauts, required otherwise! */ $ret = mail($email, mb_encode_mimeheader('Re: '.mb_decode_mimeheader($subject)), $text, $headers); if ($this->verbose && $ret) print "Info: Mail sent to ".mb_decode_mimeheader($email)."\n"; if ($this->verbose && !$ret) print "Info: Mail NOT sent to ".mb_decode_mimeheader($email)."\n"; } } ?>