1<?php
2/**
3 * Copyright 2010-2017 Horde LLC (http://www.horde.org/)
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 * o Redistributions of source code must retain the above copyright
11 *   notice, this list of conditions and the following disclaimer.
12 * o Redistributions in binary form must reproduce the above copyright
13 *   notice, this list of conditions and the following disclaimer in the
14 *   documentation and/or other materials provided with the distribution.
15 * o The names of the authors may not be used to endorse or promote
16 *   products derived from this software without specific prior written
17 *   permission.
18 *
19 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 *
31 * @category  Horde
32 * @copyright 2010-2017 Horde LLC
33 * @license   http://www.horde.org/licenses/bsd New BSD License
34 * @package   Mail
35 */
36
37/**
38 * Sendmail interface.
39 *
40 * @author    Chuck Hagenbuch <chuck@horde.org>
41 * @author    Michael Slusarz <slusarz@horde.org>
42 * @category  Horde
43 * @copyright 2010-2017 Horde LLC
44 * @license   http://www.horde.org/licenses/bsd New BSD License
45 * @package   Mail
46 */
47class Horde_Mail_Transport_Sendmail extends Horde_Mail_Transport
48{
49    /**
50     * Any extra command-line parameters to pass to the sendmail or
51     * sendmail wrapper binary.
52     *
53     * @var string
54     */
55    protected $_sendmailArgs = '-i';
56
57    /**
58     * The location of the sendmail or sendmail wrapper binary on the
59     * filesystem.
60     *
61     * @var string
62     */
63    protected $_sendmailPath = '/usr/sbin/sendmail';
64
65    /**
66     * Constructor.
67     *
68     * @param array $params  Additional parameters:
69     *   - sendmail_args: (string) Any extra parameters to pass to the sendmail
70     *                    or sendmail wrapper binary.
71     *                    DEFAULT: -i
72     *   - sendmail_path: (string) The location of the sendmail binary on the
73     *                    filesystem.
74     *                    DEFAULT: /usr/sbin/sendmail
75     */
76    public function __construct(array $params = array())
77    {
78        if (isset($params['sendmail_args'])) {
79            $this->_sendmailArgs = $params['sendmail_args'];
80        }
81
82        if (isset($params['sendmail_path'])) {
83            $this->_sendmailPath = $params['sendmail_path'];
84        }
85    }
86
87    /**
88     */
89    public function send($recipients, array $headers, $body)
90    {
91        $recipients = implode(' ', array_map('escapeshellarg', $this->parseRecipients($recipients)));
92
93        $headers = $this->_sanitizeHeaders($headers);
94        list($from, $text_headers) = $this->prepareHeaders($headers);
95        $from = $this->_getFrom($from, $headers);
96
97        $mail = @popen($this->_sendmailPath . (empty($this->_sendmailArgs) ? '' : ' ' . $this->_sendmailArgs) . ' -f ' . escapeshellarg($from) . ' -- ' . $recipients, 'w');
98        if (!$mail) {
99            throw new Horde_Mail_Exception('Failed to open sendmail [' . $this->_sendmailPath . '] for execution.');
100        }
101
102        // Write the headers following by two newlines: one to end the headers
103        // section and a second to separate the headers block from the body.
104        fputs($mail, $text_headers . $this->sep . $this->sep);
105
106        if (is_resource($body)) {
107            stream_filter_register('horde_eol', 'Horde_Stream_Filter_Eol');
108            stream_filter_append($body, 'horde_eol', STREAM_FILTER_READ, array('eol' => $this->sep));
109
110            rewind($body);
111            while (!feof($body)) {
112                fputs($mail, fread($body, 8192));
113            }
114        } else {
115            fputs($mail, $this->_normalizeEOL($body));
116        }
117        $result = pclose($mail);
118
119        if (!$result) {
120            return;
121        }
122
123        switch ($result) {
124        case 64: // EX_USAGE
125            $msg = 'command line usage error';
126            break;
127
128        case 65: // EX_DATAERR
129            $msg =  'data format error';
130            break;
131
132        case 66: // EX_NOINPUT
133            $msg = 'cannot open input';
134            break;
135
136        case 67: // EX_NOUSER
137            $msg = 'addressee unknown';
138            break;
139
140        case 68: // EX_NOHOST
141            $msg = 'host name unknown';
142            break;
143
144        case 69: // EX_UNAVAILABLE
145            $msg = 'service unavailable';
146            break;
147
148        case 70: // EX_SOFTWARE
149            $msg = 'internal software error';
150            break;
151
152        case 71: // EX_OSERR
153            $msg = 'system error';
154            break;
155
156        case 72: // EX_OSFILE
157            $msg = 'critical system file missing';
158            break;
159
160        case 73: // EX_CANTCREAT
161            $msg = 'cannot create output file';
162            break;
163
164        case 74: // EX_IOERR
165            $msg = 'input/output error';
166
167        case 75: // EX_TEMPFAIL
168            $msg = 'temporary failure';
169            break;
170
171        case 76: // EX_PROTOCOL
172            $msg = 'remote error in protocol';
173            break;
174
175        case 77: // EX_NOPERM
176            $msg = 'permission denied';
177            break;
178
179        case 78: // EX_CONFIG
180            $msg = 'configuration error';
181            break;
182
183        case 79: // EX_NOTFOUND
184            $msg = 'entry not found';
185            break;
186
187        default:
188            $msg = 'unknown error';
189            break;
190        }
191
192        throw new Horde_Mail_Exception('sendmail: ' . $msg . ' (' . $result . ')', $result);
193    }
194
195}
196