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, ×tamp);
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