1: <?php
2: /**
3: * Sendmail interface.
4: *
5: * LICENSE:
6: *
7: * Copyright (c) 2010 Chuck Hagenbuch
8: * All rights reserved.
9: *
10: * Redistribution and use in source and binary forms, with or without
11: * modification, are permitted provided that the following conditions
12: * are met:
13: *
14: * o Redistributions of source code must retain the above copyright
15: * notice, this list of conditions and the following disclaimer.
16: * o Redistributions in binary form must reproduce the above copyright
17: * notice, this list of conditions and the following disclaimer in the
18: * documentation and/or other materials provided with the distribution.
19: * o The names of the authors may not be used to endorse or promote
20: * products derived from this software without specific prior written
21: * permission.
22: *
23: * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
24: * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
25: * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
26: * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
27: * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
28: * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
29: * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
30: * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
31: * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
32: * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
33: * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
34: *
35: * @category Horde
36: * @package Mail
37: * @author Chuck Hagenbuch <chuck@horde.org>
38: * @author Michael Slusarz <slusarz@horde.org>
39: * @copyright 2010 Chuck Hagenbuch
40: * @copyright 2010 Michael Slusarz
41: * @license http://www.horde.org/licenses/bsd New BSD License
42: */
43:
44: /**
45: * Sendmail interface.
46: *
47: * @category Horde
48: * @package Mail
49: */
50: class Horde_Mail_Transport_Sendmail extends Horde_Mail_Transport
51: {
52: /**
53: * Any extra command-line parameters to pass to the sendmail or
54: * sendmail wrapper binary.
55: *
56: * @var string
57: */
58: protected $_sendmailArgs = '-i';
59:
60: /**
61: * The location of the sendmail or sendmail wrapper binary on the
62: * filesystem.
63: *
64: * @var string
65: */
66: protected $_sendmailPath = '/usr/sbin/sendmail';
67:
68: /**
69: * Constructor.
70: *
71: * @param array $params Additional parameters:
72: * - sendmail_args: (string) Any extra parameters to pass to the sendmail
73: * or sendmail wrapper binary.
74: * DEFAULT: -i
75: * - sendmail_path: (string) The location of the sendmail binary on the
76: * filesystem.
77: * DEFAULT: /usr/sbin/sendmail
78: */
79: public function __construct(array $params = array())
80: {
81: if (isset($params['sendmail_args'])) {
82: $this->_sendmailArgs = $params['sendmail_args'];
83: }
84:
85: if (isset($params['sendmail_path'])) {
86: $this->_sendmailPath = $params['sendmail_path'];
87: }
88: }
89:
90: /**
91: * Send a message.
92: *
93: * @param mixed $recipients Either a comma-seperated list of recipients
94: * (RFC822 compliant), or an array of
95: * recipients, each RFC822 valid. This may
96: * contain recipients not specified in the
97: * headers, for Bcc:, resending messages, etc.
98: * @param array $headers The headers to send with the mail, in an
99: * associative array, where the array key is the
100: * header name (ie, 'Subject'), and the array
101: * value is the header value (ie, 'test'). The
102: * header produced from those values would be
103: * 'Subject: test'.
104: * If the '_raw' key exists, the value of this
105: * key will be used as the exact text for
106: * sending the message.
107: * @param mixed $body The full text of the message body, including
108: * any Mime parts, etc. Either a string or a
109: * stream resource.
110: *
111: * @throws Horde_Mail_Exception
112: */
113: public function send($recipients, array $headers, $body)
114: {
115: $recipients = implode(' ', array_map('escapeshellarg', $this->parseRecipients($recipients)));
116:
117: $headers = $this->_sanitizeHeaders($headers);
118: list($from, $text_headers) = $this->prepareHeaders($headers);
119:
120: /* Since few MTAs are going to allow this header to be forged
121: * unless it's in the MAIL FROM: exchange, we'll use Return-Path
122: * instead of From: if it's set. */
123: foreach (array_keys($headers) as $hdr) {
124: if (strcasecmp($hdr, 'Return-Path') === 0) {
125: $from = $headers[$hdr];
126: break;
127: }
128: }
129:
130: if (!strlen($from)) {
131: throw new Horde_Mail_Exception('No From address given.');
132: } elseif ((strpos($from, ' ') !== false) ||
133: (strpos($from, ';') !== false) ||
134: (strpos($from, '&') !== false) ||
135: (strpos($from, '`') !== false)) {
136: throw new Horde_Mail_Exception('From address specified with dangerous characters.');
137: }
138:
139: $mail = @popen($this->_sendmailPath . (empty($this->_sendmailArgs) ? '' : ' ' . $this->_sendmailArgs) . ' -f ' . escapeshellarg($from) . ' -- ' . $recipients, 'w');
140: if (!$mail) {
141: throw new Horde_Mail_Exception('Failed to open sendmail [' . $this->_sendmailPath . '] for execution.');
142: }
143:
144: // Write the headers following by two newlines: one to end the headers
145: // section and a second to separate the headers block from the body.
146: fputs($mail, $text_headers . $this->sep . $this->sep);
147:
148: if (is_resource($body)) {
149: stream_filter_register('horde_eol', 'Horde_Stream_Filter_Eol');
150: stream_filter_append($body, 'horde_eol', STREAM_FILTER_READ, array('eol' => $this->sep));
151:
152: rewind($body);
153: while (!feof($body)) {
154: fputs($mail, fread($body, 8192));
155: }
156: } else {
157: fputs($mail, $this->_normalizeEOL($body));
158: }
159: $result = pclose($mail);
160:
161: if (!$result) {
162: return;
163: }
164:
165: switch ($result) {
166: case 64: // EX_USAGE
167: $msg = 'command line usage error';
168: break;
169:
170: case 65: // EX_DATAERR
171: $msg = 'data format error';
172: break;
173:
174: case 66: // EX_NOINPUT
175: $msg = 'cannot open input';
176: break;
177:
178: case 67: // EX_NOUSER
179: $msg = 'addressee unknown';
180: break;
181:
182: case 68: // EX_NOHOST
183: $msg = 'host name unknown';
184: break;
185:
186: case 69: // EX_UNAVAILABLE
187: $msg = 'service unavailable';
188: break;
189:
190: case 70: // EX_SOFTWARE
191: $msg = 'internal software error';
192: break;
193:
194: case 71: // EX_OSERR
195: $msg = 'system error';
196: break;
197:
198: case 72: // EX_OSFILE
199: $msg = 'critical system file missing';
200: break;
201:
202: case 73: // EX_CANTCREAT
203: $msg = 'cannot create output file';
204: break;
205:
206: case 74: // EX_IOERR
207: $msg = 'input/output error';
208:
209: case 75: // EX_TEMPFAIL
210: $msg = 'temporary failure';
211: break;
212:
213: case 76: // EX_PROTOCOL
214: $msg = 'remote error in protocol';
215: break;
216:
217: case 77: // EX_NOPERM
218: $msg = 'permission denied';
219: break;
220:
221: case 77: // EX_NOPERM
222: $msg = 'permission denied';
223: break;
224:
225: case 78: // EX_CONFIG
226: $msg = 'configuration error';
227: break;
228:
229: case 79: // EX_NOTFOUND
230: $msg = 'entry not found';
231: break;
232:
233: default:
234: $msg = 'unknown error';
235: break;
236: }
237:
238: throw new Horde_Mail_Exception('sendmail: ' . $msg . ' (' . $result . ')', $result);
239: }
240: }
241: