1: <?php
2: 3: 4:
5:
6:
7: require_once dirname(__FILE__) . '/Base.php';
8:
9:
10: require_once dirname(__FILE__) . '/Transport.php';
11:
12: define('RM_STATE_READING_HEADER', 1 );
13: define('RM_STATE_READING_FROM', 2 );
14: define('RM_STATE_READING_SUBJECT',3 );
15: define('RM_STATE_READING_SENDER', 4 );
16: define('RM_STATE_READING_BODY', 5 );
17:
18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29:
30: class Horde_Kolab_Filter_Content extends Horde_Kolab_Filter_Base
31: {
32: 33: 34: 35: 36: 37: 38: 39:
40: function _parse($inh, $transport)
41: {
42: global $conf;
43:
44: $result = $this->init();
45: if (is_a($result, 'PEAR_Error')) {
46: return $result;
47: }
48:
49: if (isset($conf['kolab']['filter']['verify_from_header'])) {
50: $verify_from_header = $conf['kolab']['filter']['verify_from_header'];
51: } else {
52: $verify_from_header = false;
53: }
54:
55: if (isset($conf['kolab']['filter']['allow_sender_header'])) {
56: $allow_sender_header = $conf['kolab']['filter']['allow_sender_header'];
57: } else {
58: $allow_sender_header = false;
59: }
60:
61: if (isset($conf['kolab']['filter']['allow_outlook_ical_forward'])) {
62: $allow_outlook_ical_forward = $conf['kolab']['filter']['allow_outlook_ical_forward'];
63: } else {
64: $allow_outlook_ical_forward = true;
65: }
66:
67: if (empty($transport)) {
68: $transport = 'smtp';
69: }
70:
71: $ical = false;
72: $from = false;
73: $subject = false;
74: $senderok = true;
75: $rewrittenfrom = false;
76: $state = RM_STATE_READING_HEADER;
77:
78: while (!feof($inh) && $state != RM_STATE_READING_BODY) {
79:
80: $buffer = fgets($inh, 8192);
81: $line = rtrim($buffer, "\r\n");
82:
83: if ($line == '') {
84:
85: $state = RM_STATE_READING_BODY;
86: if ($from && $verify_from_header) {
87: $rc = $this->_verify_sender($this->_sasl_username, $this->_sender,
88: $from, $this->_client_address);
89: if (is_a($rc, 'PEAR_Error')) {
90: return $rc;
91: } else if ($rc === true) {
92:
93: } else if ($rc === false) {
94:
95: $senderok = false;
96: } else if (is_string($rc)) {
97:
98: if (strpos($from, $rc) === false) {
99: Horde::logMessage(sprintf("Rewriting '%s' to '%s'",
100: $from, $rc), 'DEBUG');
101: $rewrittenfrom = "From: $rc\r\n";
102: }
103: }
104: }
105: } else {
106: if ($line[0] != ' ' && $line[0] != "\t") {
107: $state = RM_STATE_READING_HEADER;
108: }
109: switch( $state ) {
110: case RM_STATE_READING_HEADER:
111: if ($allow_sender_header &&
112: preg_match('#^Sender: (.*)#i', $line, $regs)) {
113: $from = $regs[1];
114: $state = RM_STATE_READING_SENDER;
115: } else if (!$from && preg_match('#^From: (.*)#i', $line, $regs)) {
116: $from = $regs[1];
117: $state = RM_STATE_READING_FROM;
118: } else if (preg_match('#^Subject: (.*)#i', $line, $regs)) {
119: $subject = $regs[1];
120: $state = RM_STATE_READING_SUBJECT;
121: } else if (preg_match('#^Content-Type: text/calendar#i', $line)) {
122: Horde::logMessage("Found iCal data in message", 'DEBUG');
123: $ical = true;
124: } else if (preg_match('#^Message-ID: (.*)#i', $line, $regs)) {
125: $this->_id = $regs[1];
126: }
127: break;
128: case RM_STATE_READING_FROM:
129: $from .= $line;
130: break;
131: case RM_STATE_READING_SENDER:
132: $from .= $line;
133: break;
134: case RM_STATE_READING_SUBJECT:
135: $subject .= $line;
136: break;
137: }
138: }
139: if (@fwrite($this->_tmpfh, $buffer) === false) {
140: $msg = $php_errormsg;
141: return PEAR::raiseError(sprintf("Error: Could not write to %s: %s",
142: $this->_tmpfile, $msg),
143: OUT_LOG | EX_IOERR);
144: }
145: }
146: while (!feof($inh)) {
147: $buffer = fread($inh, 8192);
148: if (@fwrite($this->_tmpfh, $buffer) === false) {
149: $msg = $php_errormsg;
150: return PEAR::raiseError(sprintf("Error: Could not write to %s: %s",
151: $this->_tmpfile, $msg),
152: OUT_LOG | EX_IOERR);
153: }
154: }
155:
156: if (@fclose($this->_tmpfh) === false) {
157: $msg = $php_errormsg;
158: return PEAR::raiseError(sprintf("Error: Failed closing %s: %s",
159: $this->_tmpfile, $msg),
160: OUT_LOG | EX_IOERR);
161: }
162:
163: if (!$senderok) {
164: if ($ical && $allow_outlook_ical_forward ) {
165: require_once(dirname(__FILE__) . '/Outlook.php');
166: $rc = Kolab_Filter_Outlook::embedICal($this->_fqhostname,
167: $this->_sender,
168: $this->_recipients,
169: $from, $subject,
170: $this->_tmpfile,
171: $transport);
172: if (is_a($rc, 'PEAR_Error')) {
173: return $rc;
174: } else if ($rc === true) {
175: return;
176: }
177: } else {
178: return PEAR::raiseError(sprintf("Invalid From: header. %s looks like a forged sender",
179: $from),
180: OUT_LOG | OUT_STDOUT | EX_NOPERM);
181: }
182: }
183:
184: $result = $this->_deliver($rewrittenfrom, $transport);
185: if (is_a($result, 'PEAR_Error')) {
186: return $result;
187: }
188: }
189:
190: 191: 192: 193: 194: 195: 196:
197: function _deliver($rewrittenfrom, $transport)
198: {
199: global $conf;
200:
201: if (isset($conf['kolab']['filter']['smtp_host'])) {
202: $host = $conf['kolab']['filter']['smtp_host'];
203: } else {
204: $host = 'localhost';
205: }
206: if (isset($conf['kolab']['filter']['smtp_port'])) {
207: $port = $conf['kolab']['filter']['smtp_port'];
208: } else {
209: $port = 10025;
210: }
211:
212: $transport = &Horde_Kolab_Filter_Transport::factory($transport,
213: array('host' => $host,
214: 'port' => $port));
215:
216: $tmpf = @fopen($this->_tmpfile, 'r');
217: if (!$tmpf) {
218: $msg = $php_errormsg;
219: return PEAR::raiseError(sprintf("Error: Could not open %s for writing: %s",
220: $this->_tmpfile, $msg),
221: OUT_LOG | EX_IOERR);
222: }
223:
224: $result = $transport->start($this->_sender, $this->_recipients);
225: if (is_a($result, 'PEAR_Error')) {
226: return $result;
227: }
228:
229: $state = RM_STATE_READING_HEADER;
230: while (!feof($tmpf) && $state != RM_STATE_READING_BODY) {
231: $buffer = fgets($tmpf, 8192);
232: if ($rewrittenfrom) {
233: if (preg_match( '#^From: (.*)#i', $buffer)) {
234: $result = $transport->data($rewrittenfrom);
235: if (is_a($result, 'PEAR_Error')) {
236: return $result;
237: }
238: $state = RM_STATE_READING_FROM;
239: continue;
240: } else if ($state == RM_STATE_READING_FROM &&
241: ($buffer[0] == ' ' || $buffer[0] == "\t")) {
242:
243: continue;
244: }
245: }
246: if (rtrim($buffer, "\r\n") == '') {
247: $state = RM_STATE_READING_BODY;
248: } else if ($buffer[0] != ' ' && $buffer[0] != "\t") {
249: $state = RM_STATE_READING_HEADER;
250: }
251: $result = $transport->data($buffer);
252: if (is_a($result, 'PEAR_Error')) {
253: return $result;
254: }
255: }
256: while (!feof($tmpf)) {
257: $buffer = fread($tmpf, 8192);
258: $len = strlen($buffer);
259:
260: 261: 262: 263:
264: while ($buffer{$len-1} == "\r" && $len < 8192 + 100) {
265: $buffer .= fread($tmpf,1);
266: $len++;
267: }
268: $result = $transport->data($buffer);
269: if (is_a($result, 'PEAR_Error')) {
270: return $result;
271: }
272: }
273: return $transport->end();
274: }
275:
276: 277: 278: 279: 280: 281: 282: 283: 284: 285: 286: 287: 288: 289:
290: function _verify_sender($sasluser, $sender, $fromhdr, $client_addr) {
291:
292: global $conf;
293:
294: if (isset($conf['kolab']['filter']['email_domain'])) {
295: $domains = $conf['kolab']['filter']['email_domain'];
296: } else {
297: $domains = 'localhost';
298: }
299:
300: if (!is_array($domains)) {
301: $domains = array($domains);
302: }
303:
304: if (isset($conf['kolab']['filter']['local_addr'])) {
305: $local_addr = $conf['kolab']['filter']['local_addr'];
306: } else {
307: $local_addr = '127.0.0.1';
308: }
309:
310: if (empty($client_addr)) {
311: $client_addr = $local_addr;
312: }
313:
314: if (isset($conf['kolab']['filter']['verify_subdomains'])) {
315: $verify_subdomains = $conf['kolab']['filter']['verify_subdomains'];
316: } else {
317: $verify_subdomains = true;
318: }
319:
320: if (isset($conf['kolab']['filter']['reject_forged_from_header'])) {
321: $reject_forged_from_header = $conf['kolab']['filter']['reject_forged_from_header'];
322: } else {
323: $reject_forged_from_header = false;
324: }
325:
326: if (isset($conf['kolab']['filter']['kolabhosts'])) {
327: $kolabhosts = $conf['kolab']['filter']['kolabhosts'];
328: } else {
329: $kolabhosts = 'localhost';
330: }
331:
332: if (isset($conf['kolab']['filter']['privileged_networks'])) {
333: $privnetworks = $conf['kolab']['filter']['privileged_networks'];
334: } else {
335: $privnetworks = '127.0.0.0/8';
336: }
337:
338: 339: 340:
341: if ($client_addr == $local_addr) {
342: return true;
343: }
344:
345: $kolabhosts = explode(',', $kolabhosts);
346: $kolabhosts = array_map('gethostbyname', $kolabhosts );
347:
348: $privnetworks = explode(',', $privnetworks);
349:
350: if (array_search($client_addr, $kolabhosts) !== false) {
351: return true;
352: }
353:
354: foreach ($privnetworks as $network) {
355:
356: $iplong = ip2long($client_addr);
357: $cidr = explode("/", $network);
358: $netiplong = ip2long($cidr[0]);
359: if (count($cidr) == 2) {
360: $iplong = $iplong & (0xffffffff << 32 - $cidr[1]);
361: $netiplong = $netiplong & (0xffffffff << 32 - $cidr[1]);
362: }
363:
364: if ($iplong == $netiplong) {
365: return true;
366: }
367: }
368:
369: if ($sasluser) {
370:
371: require_once 'Horde/Kolab/Server.php';
372:
373: $server = &Horde_Kolab_Server::singleton();
374: if (is_a($server, 'PEAR_Error')) {
375: $server->code = OUT_LOG | EX_TEMPFAIL;
376: return $server;
377: }
378:
379: $allowed_addrs = $server->addrsForIdOrMail($sasluser);
380: if (is_a($allowed_addrs, 'PEAR_Error')) {
381: $allowed_addrs->code = OUT_LOG | EX_NOUSER;
382: return $allowed_addrs;
383: }
384: } else {
385: $allowed_addrs = false;
386: }
387:
388: if (isset($conf['kolab']['filter']['unauthenticated_from_insert'])) {
389: $fmt = $conf['kolab']['filter']['unauthenticated_from_insert'];
390: } else {
391: $fmt = '(UNTRUSTED, sender <%s> is not authenticated)';
392: }
393:
394: $adrs = imap_rfc822_parse_adrlist($fromhdr, $domains[0]);
395:
396: foreach ($adrs as $adr) {
397: $from = $adr->mailbox . '@' . $adr->host;
398: $fromdom = $adr->host;
399:
400: if ($sasluser) {
401: if (!in_array(strtolower($from), $allowed_addrs)) {
402: Horde::logMessage(sprintf("%s is not an allowed From address for %s", $from, $sasluser), 'DEBUG');
403: return false;
404: }
405: } else {
406: foreach ($domains as $domain) {
407: if (strtolower($fromdom) == $domain
408: || ($verify_subdomains
409: && substr($fromdom, -strlen($domain)-1) == ".$domain")) {
410: if ($reject_forged_from_header) {
411: Horde::logMessage(sprintf("%s is not an allowed From address for unauthenticated users.", $from), 'DEBUG');
412: return false;
413: } else {
414: require_once 'Horde/String.php';
415: require_once 'Horde/MIME.php';
416:
417:
418: Horde::logMessage(sprintf("%s is not an allowed From address for unauthenticated users, rewriting.", $from), 'DEBUG');
419:
420: if (property_exists($adr, 'personal')) {
421: $name = str_replace(array("\\", '"'),
422: array("\\\\",'\"'),
423: MIME::decode($adr->personal, 'utf-8'));
424: } else {
425: $name = '';
426: }
427:
428: $untrusted = sprintf($fmt, $sender, $from, $name);
429:
430:
431:
432:
433: if (strpos( $fromhdr, $untrusted )===false) {
434: $new_from = '"' . MIME::encode($untrusted) . '"';
435: return $new_from . ' <' . $from . '>';
436: } else {
437: return true;
438: }
439: }
440: }
441: }
442: }
443: }
444:
445:
446: return true;
447: }
448: }
449:
450: ?>
451: