1/*
2  +----------------------------------------------------------------------+
3  | PHP Version 7                                                        |
4  +----------------------------------------------------------------------+
5  | Copyright (c) 1997-2016 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)
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 = (int)(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);
84			php_stream_bucket_append(buckets_out, newbucket);
85
86			exit_status = PSFS_PASS_ON;
87
88			php_stream_bucket_unlink(bucket);
89			php_stream_bucket_delref(bucket);
90		} else {
91			/* Stream cipher */
92			php_stream_bucket_make_writeable(bucket);
93			if (data->encrypt) {
94				mcrypt_generic(data->module, bucket->buf, (int)bucket->buflen);
95			} else {
96				mdecrypt_generic(data->module, bucket->buf, (int)bucket->buflen);
97			}
98			php_stream_bucket_append(buckets_out, bucket);
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);
115		php_stream_bucket_append(buckets_out, newbucket);
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)
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)
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, E_WARNING, "Filter parameters for %s must be an array", filtername);
174		return NULL;
175	}
176
177	if ((tmpzval = zend_hash_str_find(Z_ARRVAL_P(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, E_WARNING, "mode is not a string, ignoring");
182		}
183	}
184
185	if ((tmpzval=zend_hash_str_find(Z_ARRVAL_P(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, E_WARNING, "algorithms_dir is not a string, ignoring");
190		}
191	}
192
193	if ((tmpzval=zend_hash_str_find(Z_ARRVAL_P(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, E_WARNING, "modes_dir is not a string, ignoring");
198		}
199	}
200
201	if ((tmpzval = zend_hash_str_find(Z_ARRVAL_P(filterparams), ZEND_STRL("key"))) &&
202		Z_TYPE_P(tmpzval) == IS_STRING) {
203		key = Z_STRVAL_P(tmpzval);
204		key_len = (int)Z_STRLEN_P(tmpzval);
205	} else {
206		php_error_docref(NULL, E_WARNING, "key not specified or is not a string");
207		return NULL;
208	}
209
210	mcrypt_module = mcrypt_module_open((char *)cipher, algo_dir, mode, mode_dir);
211	if (mcrypt_module == MCRYPT_FAILED) {
212		php_error_docref(NULL, 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(Z_ARRVAL_P(filterparams), ZEND_STRL("iv"))) ||
222		Z_TYPE_P(tmpzval) != IS_STRING) {
223		php_error_docref(NULL, 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, E_WARNING, "Key length incorrect");
242				break;
243			case -4:
244				php_error_docref(NULL, E_WARNING, "Memory allocation error");
245				break;
246			case -1:
247			default:
248				php_error_docref(NULL, 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