1 /* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
2  */
3 
4 #include "lib.h"
5 #include "array.h"
6 
7 #include "sieve-common.h"
8 #include "sieve-stringlist.h"
9 #include "sieve-extensions.h"
10 #include "sieve-code.h"
11 #include "sieve-commands.h"
12 #include "sieve-validator.h"
13 #include "sieve-generator.h"
14 #include "sieve-interpreter.h"
15 
16 #include "ext-ihave-common.h"
17 
18 /*
19  * Ihave test
20  *
21  * Syntax:
22  *   ihave <capabilities: string-list>
23  */
24 
25 static bool
26 tst_ihave_validate(struct sieve_validator *valdtr, struct sieve_command *tst);
27 static bool
28 tst_ihave_validate_const(struct sieve_validator *valdtr,
29 			 struct sieve_command *tst, int *const_current,
30 			 int const_next);
31 static bool
32 tst_ihave_generate(const struct sieve_codegen_env *cgenv,
33 		   struct sieve_command *tst);
34 
35 const struct sieve_command_def ihave_test = {
36 	.identifier = "ihave",
37 	.type = SCT_TEST,
38 	.positional_args = 1,
39 	.subtests = 0,
40 	.block_allowed = FALSE,
41 	.block_required = FALSE,
42 	.validate = tst_ihave_validate,
43 	.validate_const = tst_ihave_validate_const,
44 	.generate = tst_ihave_generate
45 };
46 
47 /*
48  * Ihave operation
49  */
50 
51 static bool
52 tst_ihave_operation_dump(const struct sieve_dumptime_env *denv,
53 			 sieve_size_t *address);
54 static int
55 tst_ihave_operation_execute(const struct sieve_runtime_env *renv,
56 			    sieve_size_t *address);
57 
58 const struct sieve_operation_def tst_ihave_operation = {
59 	.mnemonic = "IHAVE",
60 	.ext_def = &ihave_extension,
61 	.code = EXT_IHAVE_OPERATION_IHAVE,
62 	.dump = tst_ihave_operation_dump,
63 	.execute = tst_ihave_operation_execute
64 };
65 
66 /*
67  * Code validation
68  */
69 
70 static bool
tst_ihave_validate(struct sieve_validator * valdtr,struct sieve_command * tst)71 tst_ihave_validate(struct sieve_validator *valdtr, struct sieve_command *tst)
72 {
73 	struct _capability {
74 		const struct sieve_extension *ext;
75 		struct sieve_ast_argument *arg;
76 	};
77 	struct sieve_ast_argument *arg = tst->first_positional;
78 	struct sieve_ast_argument *stritem;
79 	enum sieve_compile_flags cpflags =
80 		sieve_validator_compile_flags(valdtr);
81 	bool no_global = ((cpflags & SIEVE_COMPILE_FLAG_NOGLOBAL) != 0);
82 	ARRAY(struct _capability) capabilities;
83 	struct _capability capability;
84 	const struct _capability *caps;
85 	unsigned int i, count;
86 	bool all_known = TRUE;
87 
88 	t_array_init(&capabilities, 64);
89 
90 	tst->data = (void *)FALSE;
91 
92 	/* Check stringlist argument */
93 	if (!sieve_validate_positional_argument(valdtr, tst, arg,
94 						"capabilities", 1,
95 						SAAT_STRING_LIST))
96 		return FALSE;
97 
98 	switch (sieve_ast_argument_type(arg)) {
99 	case SAAT_STRING:
100 		/* Single string */
101 		capability.arg = arg;
102 		capability.ext = sieve_extension_get_by_name(
103 			tst->ext->svinst, sieve_ast_argument_strc(arg));
104 
105 		if (capability.ext == NULL ||
106 		    (no_global && capability.ext->global)) {
107 			all_known = FALSE;
108 
109 			ext_ihave_ast_add_missing_extension(
110 				tst->ext, tst->ast_node->ast,
111 				sieve_ast_argument_strc(arg));
112 		} else {
113 			array_append(&capabilities, &capability, 1);
114 		}
115 
116 		break;
117 
118 	case SAAT_STRING_LIST:
119 		/* String list */
120 		stritem = sieve_ast_strlist_first(arg);
121 
122 		while (stritem != NULL) {
123 			capability.arg = stritem;
124 			capability.ext = sieve_extension_get_by_name(
125 				tst->ext->svinst,
126 				sieve_ast_argument_strc(stritem));
127 
128 			if (capability.ext == NULL ||
129 			    (no_global && capability.ext->global)) {
130 				all_known = FALSE;
131 
132 				ext_ihave_ast_add_missing_extension(
133 					tst->ext, tst->ast_node->ast,
134 					sieve_ast_argument_strc(stritem));
135 			} else {
136 				array_append(&capabilities, &capability, 1);
137 			}
138 			stritem = sieve_ast_strlist_next(stritem);
139 		}
140 
141 		break;
142 	default:
143 		i_unreached();
144 	}
145 
146 	if (!all_known)
147 		return TRUE;
148 
149 	/* RFC 5463, Section 4, page 4:
150 
151 	   The "ihave" extension is designed to be used with other extensions
152 	   that add tests, actions, comparators, or arguments.  Implementations
153 	   MUST NOT allow it to be used with extensions that change the
154 	   underlying Sieve grammar, or extensions like encoded-character
155 	   [RFC5228], or variables [RFC5229] that change how the content of
156 	   Sieve scripts are interpreted.  The test MUST fail and the extension
157 	   MUST NOT be enabled if such usage is attempted.
158 
159 	   FIXME: current implementation of this restriction is hardcoded and
160 	   therefore highly inflexible
161 	 */
162 	caps = array_get(&capabilities, &count);
163 	for (i = 0; i < count; i++) {
164 		if (sieve_extension_name_is(caps[i].ext, "variables") ||
165 		    sieve_extension_name_is(caps[i].ext, "encoded-character"))
166 			return TRUE;
167 	}
168 
169 	/* Load all extensions */
170 	caps = array_get(&capabilities, &count);
171 	for (i = 0; i < count; i++) {
172 		if (!sieve_validator_extension_load(valdtr, tst, caps[i].arg,
173 						    caps[i].ext, FALSE))
174 			return FALSE;
175 	}
176 
177 	if (!sieve_validator_argument_activate(valdtr, tst, arg, FALSE))
178 		return FALSE;
179 
180 	tst->data = (void *)TRUE;
181 	return TRUE;
182 }
183 
184 static bool
tst_ihave_validate_const(struct sieve_validator * valdtr ATTR_UNUSED,struct sieve_command * tst,int * const_current,int const_next ATTR_UNUSED)185 tst_ihave_validate_const(struct sieve_validator *valdtr ATTR_UNUSED,
186 			 struct sieve_command *tst, int *const_current,
187 			 int const_next ATTR_UNUSED)
188 {
189 	if ((bool)tst->data == TRUE)
190 		*const_current = -1;
191 	else
192 		*const_current = 0;
193 	return TRUE;
194 }
195 
196 /*
197  * Code generation
198  */
199 
tst_ihave_generate(const struct sieve_codegen_env * cgenv,struct sieve_command * tst)200 bool tst_ihave_generate(const struct sieve_codegen_env *cgenv,
201 			struct sieve_command *tst)
202 {
203 	/* Emit opcode */
204 	sieve_operation_emit(cgenv->sblock, tst->ext, &tst_ihave_operation);
205 
206 	/* Generate arguments */
207 	return sieve_generate_arguments(cgenv, tst, NULL);
208 }
209 
210 /*
211  * Code dump
212  */
213 
214 static bool
tst_ihave_operation_dump(const struct sieve_dumptime_env * denv,sieve_size_t * address)215 tst_ihave_operation_dump(const struct sieve_dumptime_env *denv,
216 			 sieve_size_t *address)
217 {
218 	sieve_code_dumpf(denv, "IHAVE");
219 	sieve_code_descend(denv);
220 
221 	return sieve_opr_stringlist_dump(denv, address, "capabilities");
222 }
223 
224 /*
225  * Code execution
226  */
227 
228 static int
tst_ihave_operation_execute(const struct sieve_runtime_env * renv,sieve_size_t * address)229 tst_ihave_operation_execute(const struct sieve_runtime_env *renv,
230 			    sieve_size_t *address)
231 {
232 	const struct sieve_execute_env *eenv = renv->exec_env;
233 	struct sieve_instance *svinst = eenv->svinst;
234 	struct sieve_stringlist *capabilities;
235 	string_t *cap_item;
236 	bool matched;
237 	int ret;
238 
239 	/*
240 	 * Read operands
241 	 */
242 
243 	/* Read capabilities */
244 	if ((ret = sieve_opr_stringlist_read(renv, address, "capabilities",
245 					     &capabilities)) <= 0)
246 		return ret;
247 
248 	/*
249 	 * Perform test
250 	 */
251 
252 	/* Perform the test */
253 	sieve_runtime_trace(renv, SIEVE_TRLVL_TESTS, "ihave test");
254 	sieve_runtime_trace_descend(renv);
255 
256 	cap_item = NULL;
257 	matched = TRUE;
258 	while (matched &&
259 	       (ret = sieve_stringlist_next_item(capabilities,
260 						 &cap_item)) > 0) {
261 		const struct sieve_extension *ext;
262 		int sret;
263 
264 		ext = sieve_extension_get_by_name(svinst, str_c(cap_item));
265 		if (ext == NULL) {
266 			sieve_runtime_trace_error(
267 				renv, "ihave: invalid extension name");
268 			return SIEVE_EXEC_BIN_CORRUPT;
269 		}
270 		sret = sieve_interpreter_extension_start(renv->interp, ext);
271 		if (sret == SIEVE_EXEC_FAILURE) {
272 			sieve_runtime_trace(
273 				renv, SIEVE_TRLVL_TESTS,
274 				"extension `%s' not available",
275 				sieve_extension_name(ext));
276 			matched = FALSE;
277 		} else if (sret == SIEVE_EXEC_OK) {
278 			sieve_runtime_trace(
279 				renv, SIEVE_TRLVL_TESTS,
280 				"extension `%s' available",
281 				sieve_extension_name(ext));
282 		} else {
283 			return sret;
284 		}
285 	}
286 	if (ret < 0) {
287 		sieve_runtime_trace_error(renv, "invalid capabilities item");
288 		return SIEVE_EXEC_BIN_CORRUPT;
289 	}
290 
291 	/* Set test result for subsequent conditional jump */
292 	sieve_interpreter_set_test_result(renv->interp, matched);
293 	return SIEVE_EXEC_OK;
294 }
295