Overview

Packages

  • Horde
    • Icalendar
      • UnitTests
  • Ingo
    • UnitTests
  • None

Classes

  • Horde_Core_Ui_VarRenderer_Ingo
  • Ingo
  • Ingo_Api
  • Ingo_Exception
  • Ingo_Exception_Pear
  • Ingo_LoginTasks_SystemTask_Upgrade
  • Ingo_Script
  • Ingo_Script_Imap
  • Ingo_Script_Imap_Api
  • Ingo_Script_Imap_Live
  • Ingo_Script_Maildrop
  • Ingo_Script_Maildrop_Comment
  • Ingo_Script_Maildrop_Recipe
  • Ingo_Script_Maildrop_Variable
  • Ingo_Script_Procmail
  • Ingo_Script_Procmail_Comment
  • Ingo_Script_Procmail_Recipe
  • Ingo_Script_Procmail_Variable
  • Ingo_Script_Sieve
  • Ingo_Script_Sieve_Action
  • Ingo_Script_Sieve_Action_Addflag
  • Ingo_Script_Sieve_Action_Discard
  • Ingo_Script_Sieve_Action_Fileinto
  • Ingo_Script_Sieve_Action_Flag
  • Ingo_Script_Sieve_Action_Keep
  • Ingo_Script_Sieve_Action_Notify
  • Ingo_Script_Sieve_Action_Redirect
  • Ingo_Script_Sieve_Action_Reject
  • Ingo_Script_Sieve_Action_Removeflag
  • Ingo_Script_Sieve_Action_Stop
  • Ingo_Script_Sieve_Action_Vacation
  • Ingo_Script_Sieve_Comment
  • Ingo_Script_Sieve_Else
  • Ingo_Script_Sieve_Elsif
  • Ingo_Script_Sieve_If
  • Ingo_Script_Sieve_Test
  • Ingo_Script_Sieve_Test_Address
  • Ingo_Script_Sieve_Test_Allof
  • Ingo_Script_Sieve_Test_Anyof
  • Ingo_Script_Sieve_Test_Body
  • Ingo_Script_Sieve_Test_Exists
  • Ingo_Script_Sieve_Test_False
  • Ingo_Script_Sieve_Test_Header
  • Ingo_Script_Sieve_Test_Not
  • Ingo_Script_Sieve_Test_Relational
  • Ingo_Script_Sieve_Test_Size
  • Ingo_Script_Sieve_Test_True
  • Ingo_Storage
  • Ingo_Storage_Blacklist
  • Ingo_Storage_Filters
  • Ingo_Storage_Filters_Sql
  • Ingo_Storage_Forward
  • Ingo_Storage_Mock
  • Ingo_Storage_Prefs
  • Ingo_Storage_Rule
  • Ingo_Storage_Spam
  • Ingo_Storage_Sql
  • Ingo_Storage_Vacation
  • Ingo_Storage_VacationTest
  • Ingo_Storage_Whitelist
  • Ingo_Test
  • Ingo_Transport
  • Ingo_Transport_Ldap
  • Ingo_Transport_Null
  • Ingo_Transport_Sivtest
  • Ingo_Transport_Timsieved
  • Ingo_Transport_Vfs
  • Overview
  • Package
  • Class
  • Tree
  1: <?php
  2: /**
  3:  * The Ingo_Script_Procmail_Recipe:: class represents a Procmail recipe.
  4:  *
  5:  * Copyright 2003-2012 Horde LLC (http://www.horde.org/)
  6:  *
  7:  * See the enclosed file LICENSE for license information (ASL).  If you
  8:  * did not receive this file, see http://www.horde.org/licenses/apache.
  9:  *
 10:  * @author  Ben Chavet <ben@horde.org>
 11:  * @package Ingo
 12:  */
 13: class Ingo_Script_Procmail_Recipe
 14: {
 15:     /**
 16:      */
 17:     protected $_action = array();
 18: 
 19:     /**
 20:      */
 21:     protected $_conditions = array();
 22: 
 23:     /**
 24:      */
 25:     protected $_disable = '';
 26: 
 27:     /**
 28:      */
 29:     protected $_flags = '';
 30: 
 31:     /**
 32:      */
 33:     protected $_params = array(
 34:         'date' => 'date',
 35:         'echo' => 'echo',
 36:         'ls'   => 'ls'
 37:     );
 38: 
 39:     /**
 40:      */
 41:     protected $_valid = true;
 42: 
 43:     /**
 44:      * Constructs a new procmail recipe.
 45:      *
 46:      * @param array $params        Array of parameters.
 47:      *                               REQUIRED FIELDS:
 48:      *                                'action'
 49:      *                               OPTIONAL FIELDS:
 50:      *                                'action-value' (only used if the
 51:      *                                'action' requires it)
 52:      * @param array $scriptparams  Array of parameters passed to
 53:      *                             Ingo_Script_Procmail::.
 54:      */
 55:     public function __construct($params = array(), $scriptparams = array())
 56:     {
 57:         $this->_disable = !empty($params['disable']);
 58:         $this->_params = array_merge($this->_params, $scriptparams);
 59: 
 60:         switch ($params['action']) {
 61:         case Ingo_Storage::ACTION_KEEP:
 62:             // Note: you may have to set the DEFAULT variable in your
 63:             // backend configuration.
 64:             if (isset($this->_params['delivery_agent']) && isset($this->_params['delivery_mailbox_prefix'])) {
 65:                 $this->_action[] = '| ' . $this->_params['delivery_agent'] . ' ' . $this->_params['delivery_mailbox_prefix'] . '$DEFAULT';
 66:             } elseif (isset($this->_params['delivery_agent'])) {
 67:                 $this->_action[] = '| ' . $this->_params['delivery_agent'] . ' $DEFAULT';
 68:             } else {
 69:                 $this->_action[] = '$DEFAULT';
 70:             }
 71:             break;
 72: 
 73:         case Ingo_Storage::ACTION_MOVE:
 74:             if (isset($this->_params['delivery_agent']) && isset($this->_params['delivery_mailbox_prefix'])) {
 75:                 $this->_action[] = '| ' . $this->_params['delivery_agent'] . ' ' . $this->_params['delivery_mailbox_prefix'] . $this->procmailPath($params['action-value']);
 76:             } elseif (isset($this->_params['delivery_agent'])) {
 77:                 $this->_action[] = '| ' . $this->_params['delivery_agent'] . ' ' . $this->procmailPath($params['action-value']);
 78:             } else {
 79:                 $this->_action[] = $this->procmailPath($params['action-value']);
 80:             }
 81:             break;
 82: 
 83:         case Ingo_Storage::ACTION_DISCARD:
 84:             $this->_action[] = '/dev/null';
 85:             break;
 86: 
 87:         case Ingo_Storage::ACTION_REDIRECT:
 88:             $this->_action[] = '! ' . $params['action-value'];
 89:             break;
 90: 
 91:         case Ingo_Storage::ACTION_REDIRECTKEEP:
 92:             $this->_action[] = '{';
 93:             $this->_action[] = '  :0 c';
 94:             $this->_action[] = '  ! ' . $params['action-value'];
 95:             $this->_action[] = '';
 96:             $this->_action[] = '  :0' . (isset($this->_params['delivery_agent']) ? ' w' : '');
 97:             if (isset($this->_params['delivery_agent']) && isset($this->_params['delivery_mailbox_prefix'])) {
 98:                 $this->_action[] = '  | ' . $this->_params['delivery_agent'] . ' ' . $this->_params['delivery_mailbox_prefix'] . '$DEFAULT';
 99:             } elseif (isset($this->_params['delivery_agent'])) {
100:                 $this->_action[] = '  | ' . $this->_params['delivery_agent'] . ' $DEFAULT';
101:             } else {
102:                 $this->_action[] = '  $DEFAULT';
103:             }
104:             $this->_action[] = '}';
105:             break;
106: 
107:         case Ingo_Storage::ACTION_REJECT:
108:             $this->_action[] = '{';
109:             $this->_action[] = '  EXITCODE=' . $params['action-value'];
110:             $this->_action[] = '  HOST="no.address.here"';
111:             $this->_action[] = '}';
112:             break;
113: 
114:         case Ingo_Storage::ACTION_VACATION:
115:             $days = $params['action-value']['days'];
116:             $timed = !empty($params['action-value']['start']) &&
117:                 !empty($params['action-value']['end']);
118:             $this->_action[] = '{';
119:             foreach ($params['action-value']['addresses'] as $address) {
120:                 if (!empty($address)) {
121:                     $this->_action[] = '  :0';
122:                     $this->_action[] = '  * ^TO_' . $address;
123:                     $this->_action[] = '  {';
124:                     $this->_action[] = '    FILEDATE=`test -f ${VACATION_DIR:-.}/\'.vacation.' . $address . '\' && '
125:                         . $this->_params['ls'] . ' -lcn --time-style=+%s ${VACATION_DIR:-.}/\'.vacation.' . $address . '\' | '
126:                         . 'awk \'{ print $6 + (' . $days * 86400 . ') }\'`';
127:                     $this->_action[] = '    DATE=`' . $this->_params['date'] . ' +%s`';
128:                     $this->_action[] = '    DUMMY=`test -f ${VACATION_DIR:-.}/\'.vacation.' . $address . '\' && '
129:                         . 'test $FILEDATE -le $DATE && '
130:                         . 'rm ${VACATION_DIR:-.}/\'.vacation.' . $address . '\'`';
131:                     if ($timed) {
132:                         $this->_action[] = '    START=' . $params['action-value']['start'];
133:                         $this->_action[] = '    END=' . $params['action-value']['end'];
134:                     }
135:                     $this->_action[] = '';
136:                     $this->_action[] = '    :0 h';
137:                     $this->_action[] = '    SUBJECT=| formail -xSubject:';
138:                     $this->_action[] = '';
139:                     $this->_action[] = '    :0 Whc: ${VACATION_DIR:-.}/vacation.lock';
140:                     if ($timed) {
141:                         $this->_action[] = '    * ? test $DATE -gt $START && test $END -gt $DATE';
142:                     }
143:             $this->_action[] = '    {';
144:                     $this->_action[] = '      :0 Wh';
145:                     $this->_action[] = '      * ^TO_' . $address;
146:                     $this->_action[] = '      * !^X-Loop: ' . $address;
147:                     $this->_action[] = '      * !^X-Spam-Flag: YES';
148:                     if (count($params['action-value']['excludes']) > 0) {
149:                         foreach ($params['action-value']['excludes'] as $exclude) {
150:                             if (!empty($exclude)) {
151:                                 $this->_action[] = '      * !^From.*' . $exclude;
152:                             }
153:                         }
154:                     }
155:                     if ($params['action-value']['ignorelist']) {
156:                         $this->_action[] = '      * !^FROM_DAEMON';
157:                     }
158:                     $this->_action[] = '      | formail -rD 8192 ${VACATION_DIR:-.}/.vacation.' . $address;
159:                     $this->_action[] = '      :0 eh';
160:                     $this->_action[] = '      | (formail -rI"Precedence: junk" \\';
161:                     $this->_action[] = '       -a"From: <' . $address . '>" \\';
162:                     $this->_action[] = '       -A"X-Loop: ' . $address . '" \\';
163:                     if (Horde_Mime::is8bit($params['action-value']['reason'])) {
164:                         $this->_action[] = '       -i"Subject: ' . Horde_Mime::encode($params['action-value']['subject'] . ' (Re: $SUBJECT)', 'UTF-8') . '" \\';
165:                         $this->_action[] = '       -i"Content-Transfer-Encoding: quoted-printable" \\';
166:                         $this->_action[] = '       -i"Content-Type: text/plain; charset=UTF-8" ; \\';
167:                         $reason = Horde_Mime::quotedPrintableEncode($params['action-value']['reason'], "\n");
168:                     } else {
169:                         $this->_action[] = '       -i"Subject: ' . Horde_Mime::encode($params['action-value']['subject'] . ' (Re: $SUBJECT)', 'UTF-8') . '" ; \\';
170:                         $reason = $params['action-value']['reason'];
171:                     }
172:                     $reason = addcslashes($reason, "\\\n\r\t\"`");
173:                     $this->_action[] = '       ' . $this->_params['echo'] . ' -e "' . $reason . '" \\';
174:                     $this->_action[] = '      ) | $SENDMAIL -f' . $address . ' -oi -t';
175:                     $this->_action[] = '    }';
176:                     $this->_action[] = '  }';
177:                 }
178:             }
179:             $this->_action[] = '}';
180:             break;
181: 
182:         case Ingo_Storage::ACTION_FORWARD:
183:             /* Make sure that we prevent mail loops using 3 methods.
184:              *
185:              * First, we call sendmail -f to set the envelope sender to be the
186:              * same as the original sender, so bounces will go to the original
187:              * sender rather than to us.  This unfortunately triggers lots of
188:              * Authentication-Warning: messages in sendmail's logs.
189:              *
190:              * Second, add an X-Loop header, to handle the case where the
191:              * address we forward to forwards back to us.
192:              *
193:              * Third, don't forward mailer daemon messages (i.e., bounces).
194:              * Method 1 above should make this redundant, unless we're sending
195:              * mail from this account and have a bad forward-to account.
196:              *
197:              * Get the from address, saving a call to formail if possible.
198:              * The procmail code for doing this is borrowed from the
199:              * Procmail Library Project, http://pm-lib.sourceforge.net/.
200:              * The Ingo project has the permission to use Procmail Library code
201:              * under Apache licence v 1.x or any later version.
202:              * Permission obtained 2006-04-04 from Author Jari Aalto. */
203:             $this->_action[] = '{';
204:             $this->_action[] = '  :0 ';
205:             $this->_action[] = '  *$ ! ^From *\/[^  ]+';
206:             $this->_action[] = '  *$ ! ^Sender: *\/[^   ]+';
207:             $this->_action[] = '  *$ ! ^From: *\/[^     ]+';
208:             $this->_action[] = '  *$ ! ^Reply-to: *\/[^     ]+';
209:             $this->_action[] = '  {';
210:             $this->_action[] = '    OUTPUT = `formail -zxFrom:`';
211:             $this->_action[] = '  }';
212:             $this->_action[] = '  :0 E';
213:             $this->_action[] = '  {';
214:             $this->_action[] = '    OUTPUT = $MATCH';
215:             $this->_action[] = '  }';
216:             $this->_action[] = '';
217: 
218:             /* Forward to each address on our list. */
219:             foreach ($params['action-value'] as $address) {
220:                 if (!empty($address)) {
221:                     $this->_action[] = '  :0 c';
222:                     $this->_action[] = '  * !^FROM_MAILER';
223:                     $this->_action[] = '  * !^X-Loop: to-' . $address;
224:                     $this->_action[] = '  | formail -A"X-Loop: to-' . $address . '" | $SENDMAIL -oi -f $OUTPUT ' . $address;
225:                 }
226:             }
227: 
228:             /* In case of mail loop or bounce, store a copy locally.  Note
229:              * that if we forward to more than one address, only a mail loop
230:              * on the last address will cause a local copy to be saved.  TODO:
231:              * The next two lines are redundant (and create an extra copy of
232:              * the message) if "Keep a copy of messages in this account" is
233:              * checked. */
234:             $this->_action[] = '  :0 E' . (isset($this->_params['delivery_agent']) ? 'w' : '');
235:             if (isset($this->_params['delivery_agent'])) {
236:                 $this->_action[] = isset($this->_params['delivery_mailbox_prefix']) ?
237:                     ' | ' . $this->_params['delivery_agent'] . ' ' . $this->_params['delivery_mailbox_prefix'] . '$DEFAULT' :
238:                     ' | ' . $this->_params['delivery_agent'] . ' $DEFAULT';
239:             } else {
240:                 $this->_action[] = '  $DEFAULT';
241:             }
242:             $this->_action[] = '  :0 ';
243:             $this->_action[] = '  /dev/null';
244:             $this->_action[] = '}';
245:             break;
246: 
247:         default:
248:             $this->_valid = false;
249:             break;
250:         }
251:     }
252: 
253:     /**
254:      * Adds a flag to the recipe.
255:      *
256:      * @param string $flag  String of flags to append to the current flags.
257:      */
258:     public function addFlag($flag)
259:     {
260:         $this->_flags .= $flag;
261:     }
262: 
263:     /**
264:      * Adds a condition to the recipe.
265:      *
266:      * @param array $condition  Array of parameters. Required keys are 'field'
267:      *                          and 'value'. 'case' is an optional key.
268:      */
269:     public function addCondition($condition = array())
270:     {
271:         $flag = !empty($condition['case']) ? 'D' : '';
272:         $match = isset($condition['match']) ? $condition['match'] : null;
273:         $string = '';
274:         $prefix = '';
275: 
276:         switch ($condition['field']) {
277:         case 'Destination':
278:             $string = '^TO_';
279:             break;
280: 
281:         case 'Body':
282:             $flag .= 'B';
283:             break;
284: 
285:         default:
286:             // convert 'field' to PCRE pattern matching
287:             if (!strpos($condition['field'], ',')) {
288:                 $string = '^' . $condition['field'] . ':';
289:             } else {
290:                 $string .= '^(' . str_replace(',', '|', $condition['field']) . '):';
291:             }
292:             $prefix = ' ';
293:         }
294: 
295:         $reverseCondition = false;
296:         switch ($match) {
297:         case 'regex':
298:             $string .= $prefix . $condition['value'];
299:             break;
300: 
301:         case 'address':
302:             $string .= '(.*\<)?' . preg_quote($condition['value']);
303:             break;
304: 
305:         case 'not begins with':
306:             $reverseCondition = true;
307:             // fall through
308:         case 'begins with':
309:             $string .= $prefix . preg_quote($condition['value']);
310:             break;
311: 
312:         case 'not ends with':
313:             $reverseCondition = true;
314:             // fall through
315:         case 'ends with':
316:             $string .= '.*' . preg_quote($condition['value']) . '$';
317:             break;
318: 
319:         case 'not contain':
320:             $reverseCondition = true;
321:             // fall through
322:         case 'contains':
323:         default:
324:             $string .= '.*' . preg_quote($condition['value']);
325:             break;
326:         }
327: 
328:         $this->_conditions[] = array('condition' => ($reverseCondition ? '* !' : '* ') . $string,
329:                                      'flags' => $flag);
330:     }
331: 
332:     /**
333:      * Generates procmail code to represent the recipe.
334:      *
335:      * @return string  Procmail code to represent the recipe.
336:      */
337:     public function generate()
338:     {
339:         $nest = 0;
340:         $prefix = '';
341:         $text = array();
342: 
343:         if (!$this->_valid) {
344:             return '';
345:         }
346: 
347:         // Set the global flags for the whole rule, each condition
348:         // will add its own (such as Body or Case Sensitive)
349:         $global = $this->_flags;
350:         if (isset($this->_conditions[0])) {
351:             $global .= $this->_conditions[0]['flags'];
352:         }
353:         $text[] = ':0 ' . $global . (isset($this->_params['delivery_agent']) ? 'w' : '');
354:         foreach ($this->_conditions as $condition) {
355:             if ($nest > 0) {
356:                 $text[] = str_repeat('  ', $nest - 1) . '{';
357:                 $text[] = str_repeat('  ', $nest) . ':0 ' . $condition['flags'];
358:                 $text[] = str_repeat('  ', $nest) . $condition['condition'];
359:             } else {
360:                 $text[] = $condition['condition'];
361:             }
362:             $nest++;
363:         }
364: 
365:         if (--$nest > 0) {
366:             $prefix = str_repeat('  ', $nest);
367:         }
368:         foreach ($this->_action as $val) {
369:             $text[] = $prefix . $val;
370:         }
371: 
372:         for ($i = $nest; $i > 0; $i--) {
373:             $text[] = str_repeat('  ', $i - 1) . '}';
374:         }
375: 
376:         if ($this->_disable) {
377:             $code = '';
378:             foreach ($text as $val) {
379:                 $comment = new Ingo_Script_Procmail_Comment($val);
380:                 $code .= $comment->generate() . "\n";
381:             }
382:             return $code . "\n";
383:         } else {
384:             return implode("\n", $text) . "\n";
385:         }
386:     }
387: 
388:     /**
389:      * Returns a procmail-ready mailbox path, converting IMAP folder
390:      * pathname conventions as necessary.
391:      *
392:      * @param string $folder  The IMAP folder name.
393:      *
394:      * @return string  The procmail mailbox path.
395:      */
396:     public function procmailPath($folder)
397:     {
398:         /* NOTE: '$DEFAULT' here is a literal, not a PHP variable. */
399:         if (empty($folder) || ($folder == 'INBOX')) {
400:             return '$DEFAULT';
401:         }
402:         if (isset($this->_params) &&
403:             ($this->_params['path_style'] == 'maildir')) {
404:             if (substr($folder, 0, 6) == 'INBOX.') {
405:                 $folder = substr($folder, 6);
406:             }
407:             return '".' . escapeshellcmd($folder) . '/"';
408:         }
409:         return str_replace(' ', '\ ', escapeshellcmd($folder));
410:     }
411: }
412: 
API documentation generated by ApiGen