1 /*
2  * Copyright (c) 2014 Eric Faurot <eric@openbsd.org>
3  *
4  * Permission to use, copy, modify, and distribute this software for any
5  * purpose with or without fee is hereby granted, provided that the above
6  * copyright notice and this permission notice appear in all copies.
7  *
8  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15  */
16 
17 #include "includes.h"
18 
19 #include <sys/types.h>
20 #include <sys/socket.h>
21 
22 #include <inttypes.h>
23 #include <stdio.h>
24 #include <unistd.h>
25 
26 /* _GNU_SOURCE is not properly protected in Python.h ... */
27 #undef _GNU_SOURCE
28 #include <Python.h>
29 #ifndef _GNU_SOURCE
30 #define _GNU_SOURCE
31 #endif
32 
33 #include <smtpd-api.h>
34 
35 static PyObject *py_on_init;
36 static PyObject *py_on_insert;
37 static PyObject *py_on_commit;
38 static PyObject *py_on_rollback;
39 static PyObject *py_on_update;
40 static PyObject *py_on_delete;
41 static PyObject *py_on_hold;
42 static PyObject *py_on_release;
43 static PyObject *py_on_batch;
44 static PyObject *py_on_messages;
45 static PyObject *py_on_envelopes;
46 static PyObject *py_on_schedule;
47 static PyObject *py_on_remove;
48 static PyObject *py_on_suspend;
49 static PyObject *py_on_resume;
50 
51 static void
check_err(const char * name)52 check_err(const char *name)
53 {
54 	if (PyErr_Occurred()) {
55 		PyErr_Print();
56 		fatalx("error in %s handler", name);
57 	}
58 }
59 
60 static PyObject *
dispatch(PyObject * handler,PyObject * args)61 dispatch(PyObject *handler, PyObject *args)
62 {
63 	PyObject *ret;
64 
65 	ret = PyObject_CallObject(handler, args);
66 	Py_DECREF(args);
67 
68 	if (PyErr_Occurred()) {
69 		PyErr_Print();
70 		fatalx("exception");
71 	}
72 	return ret;
73 }
74 
75 
76 
77 static int
get_int(PyObject * o)78 get_int(PyObject *o)
79 {
80 	if (PyLong_Check(o))
81 		return PyLong_AsLong(o);
82 	if (PyInt_Check(o))
83 		return PyInt_AsLong(o);
84 
85 	PyErr_SetString(PyExc_TypeError, "int type expected");
86 	return 0;
87 }
88 
89 static size_t
get_size_t(PyObject * o)90 get_size_t(PyObject *o)
91 {
92 	if (PyLong_Check(o))
93 		return PyLong_AsUnsignedLongLong(o);
94 	if (PyInt_Check(o))
95 		return PyInt_AsUnsignedLongLongMask(o);
96 
97 	PyErr_SetString(PyExc_TypeError, "int type expected");
98 	return 0;
99 }
100 
101 static size_t
get_uint32_t(PyObject * o)102 get_uint32_t(PyObject *o)
103 {
104 	if (PyLong_Check(o))
105 		return PyLong_AsUnsignedLong(o);
106 	if (PyInt_Check(o))
107 		return PyInt_AsUnsignedLongMask(o);
108 
109 	PyErr_SetString(PyExc_TypeError, "int type expected");
110 	return 0;
111 }
112 
113 static time_t
get_time_t(PyObject * o)114 get_time_t(PyObject *o)
115 {
116 	if (PyLong_Check(o))
117 		return PyLong_AsUnsignedLongLong(o);
118 	if (PyInt_Check(o))
119 		return PyInt_AsUnsignedLongLongMask(o);
120 
121 	PyErr_SetString(PyExc_TypeError, "int type expected");
122 	return 0;
123 }
124 
125 static int
scheduler_python_init(void)126 scheduler_python_init(void)
127 {
128 	PyObject *py_ret;
129 	int r;
130 
131 	py_ret = dispatch(py_on_init, PyTuple_New(0));
132 
133 	r = get_int(py_ret);
134 	Py_DECREF(py_ret);
135 
136 	check_err("init");
137 	return r;
138 }
139 
140 static int
scheduler_python_insert(struct scheduler_info * info)141 scheduler_python_insert(struct scheduler_info *info)
142 {
143 	PyObject *py_ret;
144 	int r;
145 
146 	py_ret = dispatch(py_on_insert, Py_BuildValue("KllLLLLL",
147 	    (unsigned long long)info->evpid,
148 	    (long)info->type,
149 	    (long)info->retry,
150 	    (long long)info->creation,
151 	    (long long)info->expire,
152 	    (long long)info->lasttry,
153 	    (long long)info->lastbounce,
154 	    (long long)info->nexttry));
155 
156 	r = get_int(py_ret);
157 	Py_DECREF(py_ret);
158 
159 	check_err("insert");
160 	return r;
161 }
162 
163 static size_t
scheduler_python_commit(uint32_t msgid)164 scheduler_python_commit(uint32_t msgid)
165 {
166 	PyObject *py_ret;
167 	size_t r;
168 
169 	py_ret = dispatch(py_on_commit, Py_BuildValue("(k)",
170 	    (unsigned long)msgid));
171 
172 	r = get_size_t(py_ret);
173 	Py_DECREF(py_ret);
174 
175 	check_err("commit");
176 	return r;
177 }
178 
179 static size_t
scheduler_python_rollback(uint32_t msgid)180 scheduler_python_rollback(uint32_t msgid)
181 {
182 	PyObject *py_ret;
183 	size_t r;
184 
185 	py_ret = dispatch(py_on_rollback, Py_BuildValue("(k)",
186 	    (unsigned long)msgid));
187 
188 	r = get_size_t(py_ret);
189 	Py_DECREF(py_ret);
190 
191 	check_err("rollback");
192 	return r;
193 }
194 
195 static int
scheduler_python_update(struct scheduler_info * info)196 scheduler_python_update(struct scheduler_info *info)
197 {
198 	PyObject *py_ret;
199 	time_t nexttry;
200 
201 	py_ret = dispatch(py_on_update, Py_BuildValue("KllLLLLL",
202 	    (unsigned long long)info->evpid,
203 	    (long)info->type,
204 	    (long)info->retry,
205 	    (long long)info->creation,
206 	    (long long)info->expire,
207 	    (long long)info->lasttry,
208 	    (long long)info->lastbounce,
209 	    (long long)info->nexttry));
210 
211 	nexttry = get_time_t(py_ret);
212 	Py_DECREF(py_ret);
213 	check_err("update");
214 
215 	if (nexttry == -1)
216 		return -1;
217 	if (nexttry == 0)
218 		return 0;
219 
220 	info->nexttry = nexttry;
221 	return 1;
222 }
223 
224 static int
scheduler_python_delete(uint64_t evpid)225 scheduler_python_delete(uint64_t evpid)
226 {
227 	PyObject *py_ret;
228 	int r;
229 
230 	py_ret = dispatch(py_on_delete, Py_BuildValue("(K)",
231 	    (unsigned long long)evpid));
232 
233 	r = get_int(py_ret);
234 	Py_DECREF(py_ret);
235 
236 	check_err("delete");
237 	return r;
238 }
239 
240 static int
scheduler_python_hold(uint64_t evpid,uint64_t holdq)241 scheduler_python_hold(uint64_t evpid, uint64_t holdq)
242 {
243 	PyObject *py_ret;
244 	int r;
245 
246 	py_ret = dispatch(py_on_hold, Py_BuildValue("KK",
247 	    (unsigned long long)evpid,
248 	    (unsigned long long)holdq));
249 
250 	r = get_int(py_ret);
251 	Py_DECREF(py_ret);
252 
253 	check_err("hold");
254 	return r;
255 }
256 
257 static int
scheduler_python_release(int type,uint64_t holdq,int count)258 scheduler_python_release(int type, uint64_t holdq, int count)
259 {
260 	PyObject *py_ret;
261 	int r;
262 
263 	py_ret = dispatch(py_on_release, Py_BuildValue("lKl",
264 	    (long)type,
265 	    (unsigned long long)holdq,
266 	    (long)count));
267 
268 	r = get_int(py_ret);
269 	Py_DECREF(py_ret);
270 
271 	check_err("release");
272 	return r;
273 }
274 
275 static int
scheduler_python_batch(int mask,int * delay,size_t * count,uint64_t * evpids,int * types)276 scheduler_python_batch(int mask, int *delay, size_t *count, uint64_t *evpids, int *types)
277 {
278 	PyObject *py_ret, *o;
279 	int type;
280 	unsigned long long evpid;
281 	size_t n, i;
282 	ssize_t r;
283 
284 	n = *count;
285 
286 	py_ret = dispatch(py_on_batch,  Py_BuildValue("lK",
287 	    (long)mask,
288 	    (unsigned long long)n));
289 
290 	if (PyInt_Check(py_ret)) {
291 		*delay = PyInt_AsLong(py_ret);
292 		*count = 0;
293 		Py_DECREF(py_ret);
294 		return 0;
295 	}
296 	if (PyLong_Check(py_ret)) {
297 		*delay = PyLong_AsLong(py_ret);
298 		*count = 0;
299 		Py_DECREF(py_ret);
300 		return 0;
301 	}
302 
303 	*delay = 0;
304 	r = PySequence_Length(py_ret);
305 
306 	check_err("batch1");
307 
308 	if (r <= 0 || (size_t)r > n)
309 		fatalx("bad length");
310 
311 	for (i = 0; i < (size_t)r; i++) {
312 		o = PySequence_GetItem(py_ret, i);
313 		PyArg_ParseTuple(o, "Ki", &evpid, &type);
314 		evpids[i] = evpid;
315 		types[i] = type;
316 		Py_DECREF(o);
317 	}
318 
319 	Py_DECREF(py_ret);
320 
321 	check_err("batch");
322 
323 	return 1;
324 }
325 
326 static size_t
scheduler_python_messages(uint32_t msgid,uint32_t * dst,size_t sz)327 scheduler_python_messages(uint32_t msgid, uint32_t *dst, size_t sz)
328 {
329 	PyObject *py_ret, *o;
330 	ssize_t r;
331 	size_t i;
332 
333 	py_ret = dispatch(py_on_messages, Py_BuildValue("kK",
334 	    (unsigned long)msgid,
335 	    (unsigned long long)sz));
336 
337 	r = PySequence_Length(py_ret);
338 
339 	if (r < 0 || (size_t)r > sz)
340 		fatalx("bad length");
341 
342 	for (i = 0; i < (size_t)r; i++) {
343 		o = PySequence_ITEM(py_ret, i);
344 		dst[i] = get_uint32_t(o);
345 		Py_DECREF(o);
346 	}
347 
348 	Py_DECREF(py_ret);
349 
350 	check_err("messages");
351 
352 	return r;
353 }
354 
355 static size_t
scheduler_python_envelopes(uint64_t evpid,struct evpstate * dst,size_t sz)356 scheduler_python_envelopes(uint64_t evpid, struct evpstate *dst, size_t sz)
357 {
358 	PyObject *py_ret, *o;
359 	unsigned int flags, retry;
360 	unsigned long long tevpid;
361 	long long timestamp;
362 	ssize_t r;
363 	size_t i;
364 
365 	py_ret = dispatch(py_on_envelopes, Py_BuildValue("KK",
366 	    (unsigned long long)evpid,
367 	    (unsigned long long)sz));
368 
369 	check_err("envelopes");
370 
371 	r = PySequence_Length(py_ret);
372 
373 	if (r < 0 || (size_t)r > sz)
374 		fatalx("bad length");
375 
376 	check_err("envelopes");
377 
378 	for (i = 0; i < (size_t)r; i++) {
379 		o = PySequence_ITEM(py_ret, i);
380 
381 		check_err("envelopes");
382 
383 		PyArg_ParseTuple(o, "KIIL", &tevpid, &flags, &retry, &timestamp);
384 		check_err("envelopes");
385 
386 		dst[i].evpid = tevpid;
387 		dst[i].flags = flags;
388 		dst[i].retry = retry;
389 		dst[i].time = timestamp;
390 
391 		check_err("envelopes");
392 
393 		Py_DECREF(o);
394 	}
395 
396 	Py_DECREF(py_ret);
397 
398 	check_err("envelopes");
399 
400 	return r;
401 }
402 
403 static int
scheduler_python_schedule(uint64_t evpid)404 scheduler_python_schedule(uint64_t evpid)
405 {
406 	PyObject *py_ret;
407 	int r;
408 
409 	py_ret = dispatch(py_on_schedule, Py_BuildValue("(K)",
410 	    (unsigned long long)evpid));
411 
412 	r = get_int(py_ret);
413 	Py_DECREF(py_ret);
414 
415 	check_err("schedule");
416 	return r;
417 }
418 
419 static int
scheduler_python_remove(uint64_t evpid)420 scheduler_python_remove(uint64_t evpid)
421 {
422 	PyObject *py_ret;
423 	int r;
424 
425 	py_ret = dispatch(py_on_remove, Py_BuildValue("(K)",
426 	    (unsigned long long)evpid));
427 
428 	r = get_int(py_ret);
429 	Py_DECREF(py_ret);
430 
431 	check_err("remove");
432 	return r;
433 }
434 
435 static int
scheduler_python_suspend(uint64_t evpid)436 scheduler_python_suspend(uint64_t evpid)
437 {
438 	PyObject *py_ret;
439 	int r;
440 
441 	py_ret = dispatch(py_on_suspend, Py_BuildValue("(K)",
442 	    (unsigned long long)evpid));
443 
444 	r = get_int(py_ret);
445 	Py_DECREF(py_ret);
446 
447 	check_err("suspend");
448 	return r;
449 }
450 
451 static int
scheduler_python_resume(uint64_t evpid)452 scheduler_python_resume(uint64_t evpid)
453 {
454 	PyObject *py_ret;
455 	int r;
456 
457 	py_ret = dispatch(py_on_resume, Py_BuildValue("(K)",
458 	    (unsigned long long)evpid));
459 
460 	r = get_int(py_ret);
461 	Py_DECREF(py_ret);
462 
463 	check_err("resume");
464 	return r;
465 }
466 
467 static char *
loadfile(const char * path)468 loadfile(const char * path)
469 {
470 	FILE	*f;
471 	off_t	 oz;
472 	size_t	 sz;
473 	char	*buf;
474 
475 	if ((f = fopen(path, "r")) == NULL)
476 		fatal("fopen");
477 
478 	if (fseek(f, 0, SEEK_END) == -1)
479 		fatal("fseek");
480 
481 	oz = ftello(f);
482 
483 	if (fseek(f, 0, SEEK_SET) == -1)
484 		fatal("fseek");
485 
486 	if ((size_t)oz >= SIZE_MAX)
487 		fatal("too big");
488 
489 	sz = oz;
490 
491 	buf = xmalloc(sz + 1, "loadfile");
492 
493 	if (fread(buf, 1, sz, f) != sz)
494 		fatal("fread");
495 
496 	buf[sz] = '\0';
497 
498 	fclose(f);
499 
500 	return buf;
501 }
502 
503 static PyMethodDef py_methods[] = {
504 	{ NULL, NULL, 0, NULL }
505 };
506 
507 int
main(int argc,char ** argv)508 main(int argc, char **argv)
509 {
510 	int ch;
511 	char *path, *buf;
512 	PyObject *self, *code, *module;
513 
514 	log_init(1);
515 
516 	while ((ch = getopt(argc, argv, "")) != -1) {
517 		switch (ch) {
518 		default:
519 			fatalx("bad option");
520 			/* NOTREACHED */
521 		}
522 	}
523 	argc -= optind;
524 	argv += optind;
525 
526 	if (argc == 0)
527 		fatalx("missing path");
528 	path = argv[0];
529 
530 	Py_Initialize();
531 	self = Py_InitModule("scheduler", py_methods);
532 
533 	PyModule_AddIntConstant(self, "SCHED_REMOVE", SCHED_REMOVE);
534 	PyModule_AddIntConstant(self, "SCHED_EXPIRE", SCHED_EXPIRE);
535 	PyModule_AddIntConstant(self, "SCHED_UPDATE", SCHED_UPDATE);
536 	PyModule_AddIntConstant(self, "SCHED_BOUNCE", SCHED_BOUNCE);
537 	PyModule_AddIntConstant(self, "SCHED_MDA", SCHED_MDA);
538 	PyModule_AddIntConstant(self, "SCHED_MTA", SCHED_MTA);
539 
540 	PyModule_AddIntConstant(self, "D_BOUNCE", D_BOUNCE);
541 	PyModule_AddIntConstant(self, "D_MDA", D_MDA);
542 	PyModule_AddIntConstant(self, "D_MTA", D_MTA);
543 
544 	PyModule_AddIntConstant(self, "EF_PENDING", EF_PENDING);
545 	PyModule_AddIntConstant(self, "EF_INFLIGHT", EF_INFLIGHT);
546 	PyModule_AddIntConstant(self, "EF_SUSPEND", EF_SUSPEND);
547 	PyModule_AddIntConstant(self, "EF_HOLD", EF_HOLD);
548 
549 	buf = loadfile(path);
550 	code = Py_CompileString(buf, path, Py_file_input);
551 	free(buf);
552 
553 	if (code == NULL) {
554 		PyErr_Print();
555 		fatalx("failed to compile %s", path);
556 	}
557 
558 	module = PyImport_ExecCodeModuleEx("myscheduler", code, path);
559 	if (module == NULL) {
560 		PyErr_Print();
561 		fatalx("failed to install module %s", path);
562 	}
563 
564 	log_debug("debug: starting...");
565 
566 	py_on_init = PyObject_GetAttrString(module, "scheduler_init");
567 	py_on_insert = PyObject_GetAttrString(module, "scheduler_insert");
568 	py_on_commit = PyObject_GetAttrString(module, "scheduler_commit");
569 	py_on_rollback = PyObject_GetAttrString(module, "scheduler_rollback");
570 	py_on_update = PyObject_GetAttrString(module, "scheduler_update");
571 	py_on_delete = PyObject_GetAttrString(module, "scheduler_delete");
572 	py_on_hold = PyObject_GetAttrString(module, "scheduler_hold");
573 	py_on_release = PyObject_GetAttrString(module, "scheduler_release");
574 	py_on_batch = PyObject_GetAttrString(module, "scheduler_batch");
575 	py_on_messages = PyObject_GetAttrString(module, "scheduler_messages");
576 	py_on_envelopes = PyObject_GetAttrString(module, "scheduler_envelopes");
577 	py_on_schedule = PyObject_GetAttrString(module, "scheduler_schedule");
578 	py_on_remove = PyObject_GetAttrString(module, "scheduler_remove");
579 	py_on_suspend = PyObject_GetAttrString(module, "scheduler_suspend");
580 	py_on_resume = PyObject_GetAttrString(module, "scheduler_resume");
581 
582 	scheduler_api_on_init(scheduler_python_init);
583 	scheduler_api_on_insert(scheduler_python_insert);
584 	scheduler_api_on_commit(scheduler_python_commit);
585 	scheduler_api_on_rollback(scheduler_python_rollback);
586 	scheduler_api_on_update(scheduler_python_update);
587 	scheduler_api_on_delete(scheduler_python_delete);
588 	scheduler_api_on_hold(scheduler_python_hold);
589 	scheduler_api_on_release(scheduler_python_release);
590 	scheduler_api_on_batch(scheduler_python_batch);
591 	scheduler_api_on_messages(scheduler_python_messages);
592 	scheduler_api_on_envelopes(scheduler_python_envelopes);
593 	scheduler_api_on_schedule(scheduler_python_schedule);
594 	scheduler_api_on_remove(scheduler_python_remove);
595 	scheduler_api_on_suspend(scheduler_python_suspend);
596 	scheduler_api_on_resume(scheduler_python_resume);
597 
598 	scheduler_api_no_chroot();
599 	scheduler_api_dispatch();
600 
601 	log_debug("debug: exiting");
602 	Py_Finalize();
603 
604 	return 1;
605 }
606