1/*
2  +----------------------------------------------------------------------+
3  | PHP Version 7                                                        |
4  +----------------------------------------------------------------------+
5  | Copyright (c) 1997-2014 The PHP Group                                |
6  +----------------------------------------------------------------------+
7   | This source file is subject to version 3.01 of the PHP license,      |
8   | that is bundled with this package in the file LICENSE, and is        |
9   | available through the world-wide-web at the following url:           |
10   | http://www.php.net/license/3_01.txt                                  |
11   | If you did not receive a copy of the PHP license and are unable to   |
12   | obtain it through the world-wide-web, please send a note to          |
13   | license@php.net so we can mail you a copy immediately.               |
14  +----------------------------------------------------------------------+
15  | Author: Sara Golemon <pollita@php.net>                               |
16  +----------------------------------------------------------------------+
17
18  $Id$
19*/
20
21#include "php.h"
22
23#include "php_mcrypt_filter.h"
24#include "php_ini.h"
25#include <mcrypt.h>
26
27typedef struct _php_mcrypt_filter_data {
28    MCRYPT module;
29    char encrypt;
30    int blocksize;
31    char *block_buffer;
32    int block_used;
33    char persistent;
34} php_mcrypt_filter_data;
35
36static php_stream_filter_status_t php_mcrypt_filter(
37    php_stream *stream,
38    php_stream_filter *thisfilter,
39    php_stream_bucket_brigade *buckets_in,
40    php_stream_bucket_brigade *buckets_out,
41    size_t *bytes_consumed,
42    int flags TSRMLS_DC)
43{
44    php_mcrypt_filter_data *data;
45    php_stream_bucket *bucket;
46    size_t consumed = 0;
47    php_stream_filter_status_t exit_status = PSFS_FEED_ME;
48
49    if (!thisfilter || !Z_PTR(thisfilter->abstract)) {
50        /* Should never happen */
51        return PSFS_ERR_FATAL;
52    }
53
54    data = (php_mcrypt_filter_data *)(Z_PTR(thisfilter->abstract));
55    while(buckets_in->head) {
56        bucket = buckets_in->head;
57
58        consumed += bucket->buflen;
59
60        if (data->blocksize) {
61            /* Blockmode cipher */
62            char *outchunk;
63            int chunklen = bucket->buflen + data->block_used, n;
64            php_stream_bucket *newbucket;
65
66            outchunk = pemalloc(chunklen, data->persistent);
67            if (data->block_used) {
68                memcpy(outchunk, data->block_buffer, data->block_used);
69            }
70            memcpy(outchunk + data->block_used, bucket->buf, bucket->buflen);
71
72            for(n=0; (n + data->blocksize) <= chunklen; n += data->blocksize) {
73
74                if (data->encrypt) {
75                    mcrypt_generic(data->module, outchunk + n, data->blocksize);
76                } else {
77                    mdecrypt_generic(data->module, outchunk + n, data->blocksize);
78                }
79            }
80            data->block_used = chunklen - n;
81            memcpy(data->block_buffer, outchunk + n, data->block_used);
82
83            newbucket = php_stream_bucket_new(stream, outchunk, n, 1, data->persistent TSRMLS_CC);
84            php_stream_bucket_append(buckets_out, newbucket TSRMLS_CC);
85
86            exit_status = PSFS_PASS_ON;
87
88            php_stream_bucket_unlink(bucket TSRMLS_CC);
89            php_stream_bucket_delref(bucket TSRMLS_CC);
90        } else {
91            /* Stream cipher */
92            php_stream_bucket_make_writeable(bucket TSRMLS_CC);
93            if (data->encrypt) {
94                mcrypt_generic(data->module, bucket->buf, bucket->buflen);
95            } else {
96                mdecrypt_generic(data->module, bucket->buf, bucket->buflen);
97            }
98            php_stream_bucket_append(buckets_out, bucket TSRMLS_CC);
99
100            exit_status = PSFS_PASS_ON;
101        }
102    }
103
104    if ((flags & PSFS_FLAG_FLUSH_CLOSE) && data->blocksize && data->block_used) {
105        php_stream_bucket *newbucket;
106
107        memset(data->block_buffer + data->block_used, 0, data->blocksize - data->block_used);
108        if (data->encrypt) {
109            mcrypt_generic(data->module, data->block_buffer, data->blocksize);
110        } else {
111            mdecrypt_generic(data->module, data->block_buffer, data->blocksize);
112        }
113
114        newbucket = php_stream_bucket_new(stream, data->block_buffer, data->blocksize, 0, data->persistent TSRMLS_CC);
115        php_stream_bucket_append(buckets_out, newbucket TSRMLS_CC);
116
117        exit_status = PSFS_PASS_ON;
118    }
119
120    if (bytes_consumed) {
121        *bytes_consumed = consumed;
122    }
123
124    return exit_status;
125}
126
127static void php_mcrypt_filter_dtor(php_stream_filter *thisfilter TSRMLS_DC)
128{
129    if (thisfilter && Z_PTR(thisfilter->abstract)) {
130        php_mcrypt_filter_data *data = (php_mcrypt_filter_data*) Z_PTR(thisfilter->abstract);
131
132        if (data->block_buffer) {
133            pefree(data->block_buffer, data->persistent);
134        }
135
136        mcrypt_generic_deinit(data->module);
137        mcrypt_module_close(data->module);
138
139        pefree(data, data->persistent);
140    }
141}
142
143static php_stream_filter_ops php_mcrypt_filter_ops = {
144    php_mcrypt_filter,
145    php_mcrypt_filter_dtor,
146    "mcrypt.*"
147};
148
149/* {{{ php_mcrypt_filter_create
150 * Instantiate mcrypt filter
151 */
152static php_stream_filter *php_mcrypt_filter_create(const char *filtername, zval *filterparams, int persistent TSRMLS_DC)
153{
154    int encrypt = 1, iv_len, key_len, keyl, result;
155    const char *cipher = filtername + sizeof("mcrypt.") - 1;
156    zval *tmpzval;
157    MCRYPT mcrypt_module;
158    char *iv = NULL, *key = NULL;
159    char *algo_dir = INI_STR("mcrypt.algorithms_dir");
160    char *mode_dir = INI_STR("mcrypt.modes_dir");
161    char *mode = "cbc";
162    php_mcrypt_filter_data *data;
163
164    if (strncasecmp(filtername, "mdecrypt.", sizeof("mdecrypt.") - 1) == 0) {
165        encrypt = 0;
166        cipher += sizeof("de") - 1;
167    } else if (strncasecmp(filtername, "mcrypt.", sizeof("mcrypt.") - 1) != 0) {
168        /* Should never happen */
169        return NULL;
170    }
171
172    if (!filterparams || Z_TYPE_P(filterparams) != IS_ARRAY) {
173        php_error_docref(NULL TSRMLS_CC, E_WARNING, "Filter parameters for %s must be an array", filtername);
174        return NULL;
175    }
176
177    if ((tmpzval = zend_hash_str_find(HASH_OF(filterparams), ZEND_STRL("mode")))) {
178        if (Z_TYPE_P(tmpzval) == IS_STRING) {
179            mode = Z_STRVAL_P(tmpzval);
180        } else {
181            php_error_docref(NULL TSRMLS_CC, E_WARNING, "mode is not a string, ignoring");
182        }
183    }
184
185    if ((tmpzval=zend_hash_str_find(HASH_OF(filterparams), ZEND_STRL("algorithms_dir")))) {
186        if (Z_TYPE_P(tmpzval) == IS_STRING) {
187            algo_dir = Z_STRVAL_P(tmpzval);
188        } else {
189            php_error_docref(NULL TSRMLS_CC, E_WARNING, "algorithms_dir is not a string, ignoring");
190        }
191    }
192
193    if ((tmpzval=zend_hash_str_find(HASH_OF(filterparams), ZEND_STRL("modes_dir")))) {
194        if (Z_TYPE_P(tmpzval) == IS_STRING) {
195            mode_dir = Z_STRVAL_P(tmpzval);
196        } else {
197            php_error_docref(NULL TSRMLS_CC, E_WARNING, "modes_dir is not a string, ignoring");
198        }
199    }
200
201    if ((tmpzval = zend_hash_str_find(HASH_OF(filterparams), ZEND_STRL("key"))) &&
202        Z_TYPE_P(tmpzval) == IS_STRING) {
203        key = Z_STRVAL_P(tmpzval);
204        key_len = Z_STRLEN_P(tmpzval);
205    } else {
206        php_error_docref(NULL TSRMLS_CC, E_WARNING, "key not specified or is not a string");
207        return NULL;
208    }
209
210    mcrypt_module = mcrypt_module_open(cipher, algo_dir, mode, mode_dir);
211    if (mcrypt_module == MCRYPT_FAILED) {
212        php_error_docref(NULL TSRMLS_CC, E_WARNING, "Could not open encryption module");
213        return NULL;
214    }
215    iv_len = mcrypt_enc_get_iv_size(mcrypt_module);
216    keyl = mcrypt_enc_get_key_size(mcrypt_module);
217    if (keyl < key_len) {
218        key_len = keyl;
219    }
220
221    if (!(tmpzval = zend_hash_str_find(HASH_OF(filterparams), ZEND_STRL("iv"))) ||
222        Z_TYPE_P(tmpzval) != IS_STRING) {
223        php_error_docref(NULL TSRMLS_CC, E_WARNING, "Filter parameter[iv] not provided or not of type: string");
224        mcrypt_module_close(mcrypt_module);
225        return NULL;
226    }
227
228    iv = emalloc(iv_len + 1);
229    if (iv_len <= Z_STRLEN_P(tmpzval)) {
230        memcpy(iv, Z_STRVAL_P(tmpzval), iv_len);
231    } else {
232        memcpy(iv, Z_STRVAL_P(tmpzval), Z_STRLEN_P(tmpzval));
233        memset(iv + Z_STRLEN_P(tmpzval), 0, iv_len - Z_STRLEN_P(tmpzval));
234    }
235
236    result = mcrypt_generic_init(mcrypt_module, key, key_len, iv);
237    efree(iv);
238    if (result < 0) {
239        switch (result) {
240            case -3:
241                php_error_docref(NULL TSRMLS_CC, E_WARNING, "Key length incorrect");
242                break;
243            case -4:
244                php_error_docref(NULL TSRMLS_CC, E_WARNING, "Memory allocation error");
245                break;
246            case -1:
247            default:
248                php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unknown error");
249                break;
250        }
251        mcrypt_module_close(mcrypt_module);
252        return NULL;
253    }
254
255    data = pemalloc(sizeof(php_mcrypt_filter_data), persistent);
256    data->module = mcrypt_module;
257    data->encrypt = encrypt;
258    if (mcrypt_enc_is_block_mode(mcrypt_module)) {
259        data->blocksize = mcrypt_enc_get_block_size(mcrypt_module);
260        data->block_buffer = pemalloc(data->blocksize, persistent);
261    } else {
262        data->blocksize = 0;
263        data->block_buffer = NULL;
264    }
265    data->block_used = 0;
266    data->persistent = persistent;
267
268    return php_stream_filter_alloc(&php_mcrypt_filter_ops, data, persistent);
269}
270/* }}} */
271
272php_stream_filter_factory php_mcrypt_filter_factory = {
273    php_mcrypt_filter_create
274};
275
276/*
277 * Local variables:
278 * tab-width: 4
279 * c-basic-offset: 4
280 * End:
281 * vim600: noet sw=4 ts=4 fdm=marker
282 * vim<600: noet sw=4 ts=4
283 */
284