1 /* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
2  */
3 
4 #include "lib.h"
5 
6 #include "sieve-common.h"
7 #include "sieve-extensions.h"
8 #include "sieve-commands.h"
9 #include "sieve-stringlist.h"
10 #include "sieve-code.h"
11 #include "sieve-comparators.h"
12 #include "sieve-match-types.h"
13 #include "sieve-address-parts.h"
14 #include "sieve-validator.h"
15 #include "sieve-generator.h"
16 #include "sieve-interpreter.h"
17 #include "sieve-dump.h"
18 #include "sieve-match.h"
19 
20 #include "ext-spamvirustest-common.h"
21 
22 /*
23  * Tests
24  */
25 
26 static bool tst_spamvirustest_validate
27 	(struct sieve_validator *valdtr, struct sieve_command *tst);
28 static bool tst_spamvirustest_generate
29 	(const struct sieve_codegen_env *cgenv, struct sieve_command *ctx);
30 static bool tst_spamvirustest_registered
31 	(struct sieve_validator *valdtr, const struct sieve_extension *ext,
32 		struct sieve_command_registration *cmd_reg);
33 
34 /* Spamtest test
35  *
36  * Syntax:
37  *   spamtest [":percent"] [COMPARATOR] [MATCH-TYPE] <value: string>
38  */
39 
40 const struct sieve_command_def spamtest_test = {
41 	.identifier = "spamtest",
42 	.type = SCT_TEST,
43 	.positional_args = 1,
44 	.subtests = 0,
45 	.block_allowed = FALSE,
46 	.block_required = FALSE,
47 	.registered = tst_spamvirustest_registered,
48 	.validate = tst_spamvirustest_validate,
49 	.generate = tst_spamvirustest_generate
50 };
51 
52 /* Virustest test
53  *
54  * Syntax:
55  *   virustest [COMPARATOR] [MATCH-TYPE] <value: string>
56  */
57 
58 const struct sieve_command_def virustest_test = {
59 	.identifier = "virustest",
60 	.type = SCT_TEST,
61 	.positional_args = 1,
62 	.subtests = 0,
63 	.block_allowed = FALSE,
64 	.block_required = FALSE,
65 	.registered = tst_spamvirustest_registered,
66 	.validate = tst_spamvirustest_validate,
67 	.generate = tst_spamvirustest_generate
68 };
69 
70 /*
71  * Tagged arguments
72  */
73 
74 static bool tst_spamtest_validate_percent_tag
75 	(struct sieve_validator *valdtr, struct sieve_ast_argument **arg,
76 		struct sieve_command *tst);
77 
78 static const struct sieve_argument_def spamtest_percent_tag = {
79  	.identifier = "percent",
80 	.validate = tst_spamtest_validate_percent_tag
81 };
82 
83 /*
84  * Spamtest and virustest operations
85  */
86 
87 static bool tst_spamvirustest_operation_dump
88 	(const struct sieve_dumptime_env *denv, sieve_size_t *address);
89 static int tst_spamvirustest_operation_execute
90 	(const struct sieve_runtime_env *renv, sieve_size_t *address);
91 
92 const struct sieve_operation_def spamtest_operation = {
93 	.mnemonic = "SPAMTEST",
94 	.ext_def = &spamtest_extension,
95 	.dump = tst_spamvirustest_operation_dump,
96 	.execute = tst_spamvirustest_operation_execute
97 };
98 
99 const struct sieve_operation_def virustest_operation = {
100 	.mnemonic = "VIRUSTEST",
101 	.ext_def = &virustest_extension,
102 	.dump = tst_spamvirustest_operation_dump,
103 	.execute = tst_spamvirustest_operation_execute
104 };
105 
106 /*
107  * Optional operands
108  */
109 
110 enum tst_spamvirustest_optional {
111 	OPT_SPAMTEST_PERCENT = SIEVE_MATCH_OPT_LAST,
112 	OPT_SPAMTEST_LAST
113 };
114 
115 /*
116  * Test registration
117  */
118 
tst_spamvirustest_registered(struct sieve_validator * valdtr,const struct sieve_extension * ext,struct sieve_command_registration * cmd_reg)119 static bool tst_spamvirustest_registered
120 (struct sieve_validator *valdtr, const struct sieve_extension *ext,
121 	struct sieve_command_registration *cmd_reg)
122 {
123 	sieve_comparators_link_tag(valdtr, cmd_reg, SIEVE_MATCH_OPT_COMPARATOR);
124 	sieve_match_types_link_tags(valdtr, cmd_reg, SIEVE_MATCH_OPT_MATCH_TYPE);
125 
126 	if ( sieve_extension_is(ext, spamtestplus_extension) ||
127 		sieve_extension_is(ext, spamtest_extension) ) {
128 		sieve_validator_register_tag
129 			(valdtr, cmd_reg, ext, &spamtest_percent_tag, OPT_SPAMTEST_PERCENT);
130 	}
131 
132 	return TRUE;
133 }
134 
135 /*
136  * Validation
137  */
138 
tst_spamtest_validate_percent_tag(struct sieve_validator * valdtr,struct sieve_ast_argument ** arg,struct sieve_command * tst)139 static bool tst_spamtest_validate_percent_tag
140 (struct sieve_validator *valdtr, struct sieve_ast_argument **arg,
141 	struct sieve_command *tst)
142 {
143 	if ( !sieve_extension_is(tst->ext, spamtestplus_extension) ) {
144 		sieve_argument_validate_error(valdtr, *arg,
145 			"the spamtest test only accepts the :percent argument when "
146 			"the spamtestplus extension is active");
147 		return FALSE;
148 	}
149 
150 	/* Skip tag */
151 	*arg = sieve_ast_argument_next(*arg);
152 
153 	return TRUE;
154 }
155 
tst_spamvirustest_validate(struct sieve_validator * valdtr,struct sieve_command * tst)156 static bool tst_spamvirustest_validate
157 (struct sieve_validator *valdtr, struct sieve_command *tst)
158 {
159 	struct sieve_ast_argument *arg = tst->first_positional;
160 	const struct sieve_match_type mcht_default =
161 		SIEVE_MATCH_TYPE_DEFAULT(is_match_type);
162 	const struct sieve_comparator cmp_default =
163 		SIEVE_COMPARATOR_DEFAULT(i_ascii_casemap_comparator);
164 
165 	/* Check value */
166 
167 	if ( !sieve_validate_positional_argument
168 		(valdtr, tst, arg, "value", 1, SAAT_STRING) ) {
169 		return FALSE;
170 	}
171 
172 	if ( !sieve_validator_argument_activate(valdtr, tst, arg, FALSE) )
173 		return FALSE;
174 
175 	/* Validate the key argument to a specified match type */
176 	return sieve_match_type_validate
177 		(valdtr, tst, arg, &mcht_default, &cmp_default);
178 }
179 
180 /*
181  * Code generation
182  */
183 
tst_spamvirustest_generate(const struct sieve_codegen_env * cgenv,struct sieve_command * tst)184 static bool tst_spamvirustest_generate
185 (const struct sieve_codegen_env *cgenv, struct sieve_command *tst)
186 {
187 	if ( sieve_command_is(tst, spamtest_test) )
188 		sieve_operation_emit(cgenv->sblock, tst->ext, &spamtest_operation);
189 	else if ( sieve_command_is(tst, virustest_test) )
190 		sieve_operation_emit(cgenv->sblock, tst->ext, &virustest_operation);
191 	else
192 		i_unreached();
193 
194 	/* Generate arguments */
195 	return sieve_generate_arguments(cgenv, tst, NULL);
196 }
197 
198 /*
199  * Code dump
200  */
201 
tst_spamvirustest_operation_dump(const struct sieve_dumptime_env * denv,sieve_size_t * address)202 static bool tst_spamvirustest_operation_dump
203 (const struct sieve_dumptime_env *denv, sieve_size_t *address)
204 {
205 	int opt_code = 0;
206 	const struct sieve_operation *op = denv->oprtn;
207 
208 	sieve_code_dumpf(denv, "%s", sieve_operation_mnemonic(op));
209 	sieve_code_descend(denv);
210 
211 	/* Optional operands */
212 	for (;;) {
213 		int opt;
214 
215 		if ( (opt=sieve_match_opr_optional_dump(denv, address, &opt_code)) < 0 )
216 			return FALSE;
217 
218 		if ( opt == 0 ) break;
219 
220 		switch ( opt_code ) {
221 		case OPT_SPAMTEST_PERCENT:
222 			sieve_code_dumpf(denv, "percent");
223 			break;
224     default:
225 			return FALSE;
226 		}
227 	}
228 
229 	return
230 		sieve_opr_string_dump(denv, address, "value");
231 }
232 
233 /*
234  * Code execution
235  */
236 
tst_spamvirustest_operation_execute(const struct sieve_runtime_env * renv,sieve_size_t * address)237 static int tst_spamvirustest_operation_execute
238 (const struct sieve_runtime_env *renv, sieve_size_t *address)
239 {
240 	const struct sieve_operation *op = renv->oprtn;
241 	const struct sieve_extension *this_ext = op->ext;
242 	int opt_code = 0;
243 	struct sieve_match_type mcht =
244 		SIEVE_MATCH_TYPE_DEFAULT(is_match_type);
245 	struct sieve_comparator cmp =
246 		SIEVE_COMPARATOR_DEFAULT(i_ascii_casemap_comparator);
247 	bool percent = FALSE;
248 	struct sieve_stringlist *value_list, *key_list;
249 	const char *score_value;
250 	int match, ret;
251 
252 	/* Read optional operands */
253 	for (;;) {
254 		int opt;
255 
256 		if ( (opt=sieve_match_opr_optional_read
257 			(renv, address, &opt_code, &ret, &cmp, &mcht)) < 0 )
258 			return ret;
259 
260 		if ( opt == 0 ) break;
261 
262 		switch ( opt_code ) {
263 		case OPT_SPAMTEST_PERCENT:
264 			percent = TRUE;
265 			break;
266 		default:
267 			sieve_runtime_trace_error(renv, "unknown optional operand");
268 			return SIEVE_EXEC_BIN_CORRUPT;
269 		}
270 	}
271 
272 	/* Read value part */
273 	if ( (ret=sieve_opr_stringlist_read(renv, address, "value", &key_list)) <= 0 )
274 		return ret;
275 
276 	/* Perform test */
277 
278 	if ( sieve_operation_is(op, spamtest_operation) ) {
279 		sieve_runtime_trace
280 			(renv, SIEVE_TRLVL_TESTS, "spamtest test [percent=%s]",
281 				( percent ? "true" : "false" ));
282 	} else {
283 		sieve_runtime_trace
284 			(renv, SIEVE_TRLVL_TESTS, "virustest test");
285 	}
286 
287 	/* Get score value */
288 	sieve_runtime_trace_descend(renv);
289 	if ( (ret=ext_spamvirustest_get_value
290 		(renv, this_ext, percent, &score_value)) <= 0 )
291 		return ret;
292 	sieve_runtime_trace_ascend(renv);
293 
294 	/* Construct value list */
295 	value_list = sieve_single_stringlist_create_cstr(renv, score_value, TRUE);
296 
297 	/* Perform match */
298 	if ( (match=sieve_match(renv, &mcht, &cmp, value_list, key_list, &ret)) < 0 )
299 		return ret;
300 
301 	/* Set test result for subsequent conditional jump */
302 	sieve_interpreter_set_test_result(renv->interp, match > 0);
303 	return SIEVE_EXEC_OK;
304 }
305