1 /* This file is part of Mailfromd.
2 Copyright (C) 2005-2021 Sergey Poznyakoff
3
4 This program is free software; you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation; either version 3, or (at your option)
7 any later version.
8
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
13
14 You should have received a copy of the GNU General Public License
15 along with this program. If not, see <http://www.gnu.org/licenses/>. */
16
17 #ifdef HAVE_CONFIG_H
18 # include <config.h>
19 #endif
20 #include <sys/types.h>
21 #include <sys/stat.h>
22 #include <unistd.h>
23 #include <stdlib.h>
24 #include <stdio.h>
25 #include <stdarg.h>
26 #include <syslog.h>
27 #include <signal.h>
28 #include <pwd.h>
29 #include <grp.h>
30 #include <sys/socket.h>
31 #include <netinet/in.h>
32 #include <arpa/inet.h>
33 #include <netdb.h>
34 #include <ctype.h>
35
36 #include <mailutils/mailutils.h>
37 #include <mailutils/daemon.h>
38
39 #include "libmf.h"
40 #include "srvcfg.h"
41 #include "callout.h"
42 #include "callout-dbgmod.h"
43
44 #define SMTP_MAJOR(c) ((c)/100)
45
46 #define SMTP_PARSEHLO 0x01
47 #define CAPA_VRFY 0x02
48
49 struct smtp_io_data {
50 char *id; /* I/O id */
51 char *email;
52 char *ehlo;
53 char *mailfrom;
54
55 time_t timeout[SMTP_NUM_TIMEOUT];
56 smtp_io_callback_t callback;
57 void *callback_closure;
58 mu_stream_t stream; /* I/O stream */
59 mu_opool_t pool; /* Opool for keeping commands/replies */
60 char *command; /* Last issued command */
61 char *reply; /* Last received reply */
62 char *start; /* First line of the reply, if it was multiline */
63 size_t nlines; /* Number of lines in the reply */
64 char buf[128]; /* Input buffer */
65 size_t level; /* Number of bytes in buf */
66 int code; /* Reply code */
67 int esmtp_capa;
68 };
69
70 struct smtp_io_data *
smtp_io_create(const char * id,time_t timeout[],smtp_io_callback_t callback,void * closure)71 smtp_io_create(const char *id, time_t timeout[], smtp_io_callback_t callback,
72 void *closure)
73 {
74 struct smtp_io_data *iop = mu_zalloc(sizeof(*iop));
75
76 if (!id)
77 iop->id = mu_strdup("null");
78 else {
79 size_t len;
80 iop->id = mu_strdup(id);
81 len = strlen(iop->id);
82 if (len > 2 && strcmp(iop->id + len - 2, ": ") == 0)
83 iop->id[len - 2] = 0;
84 }
85 memcpy(&iop->timeout, timeout, sizeof(iop->timeout));
86 iop->callback = callback;
87 iop->callback_closure = closure;
88 mu_opool_create(&iop->pool, MU_OPOOL_ENOMEMABRT);
89 iop->start = iop->command = iop->reply = NULL;
90 iop->nlines = 0;
91 iop->level = 0;
92 return iop;
93 }
94
95 void
smtp_io_set_timeouts(struct smtp_io_data * iop,time_t * to)96 smtp_io_set_timeouts(struct smtp_io_data *iop, time_t *to)
97 {
98 memcpy(&iop->timeout, to, sizeof(iop->timeout));
99 }
100
101 static void
replstr(char ** pdst,const char * str)102 replstr(char **pdst, const char *str)
103 {
104 if (*pdst)
105 free(*pdst);
106 *pdst = mu_strdup(str);
107 }
108
109
110 void
smtp_io_init(struct smtp_io_data * iop)111 smtp_io_init(struct smtp_io_data *iop)
112 {
113 iop->start = iop->command = iop->reply = NULL;
114 iop->level = 0;
115 }
116
117 void
smtp_io_setup_callout(struct smtp_io_data * iop,const char * email,const char * ehlo,const char * mailfrom)118 smtp_io_setup_callout(struct smtp_io_data *iop,
119 const char *email,
120 const char *ehlo,
121 const char *mailfrom)
122 {
123 replstr(&iop->email, email);
124 replstr(&iop->ehlo, ehlo);
125 if (!mailfrom || !*mailfrom)
126 mailfrom = "<>";
127 replstr(&iop->mailfrom, mailfrom);
128 }
129
130 void
smtp_io_free(struct smtp_io_data * iop)131 smtp_io_free(struct smtp_io_data *iop)
132 {
133 if (iop->stream) {
134 mu_stream_close (iop->stream);
135 mu_stream_destroy(&iop->stream);
136 }
137 mu_opool_destroy(&iop->pool);
138 free(iop->id);
139 free(iop->email);
140 free(iop->ehlo);
141 free(iop->mailfrom);
142 free(iop);
143 }
144
145
146 static int
smtp_wait(struct smtp_io_data * iop,int flags,struct timeout_ctl * tctl)147 smtp_wait(struct smtp_io_data *iop, int flags, struct timeout_ctl *tctl)
148 {
149 return mf_stream_wait(iop->stream, flags, tctl);
150 }
151
152 static int
smtp_send(struct smtp_io_data * iop,const char * command)153 smtp_send(struct smtp_io_data *iop, const char *command)
154 {
155 size_t len = strlen(command);
156 struct timeout_ctl tctl;
157
158 init_timeout_ctl (&tctl, io_timeout);
159
160 iop->reply = NULL; /* Clear reply for logging purposes */
161 do {
162 size_t nb;
163 int rc;
164
165 UPDATE_TTW(tctl);
166
167 rc = mu_stream_write(iop->stream, command, len, &nb);
168 if (rc == 0) {
169 if (nb == 0) {
170 mu_error(_("%s: stream_write: wrote 0 bytes"),
171 iop->id);
172 return -1;
173 }
174 len -= nb;
175 command += nb;
176 } else if (rc == EAGAIN) {
177 rc = smtp_wait(iop, MU_STREAM_READY_WR, &tctl);
178 if (rc) {
179 mu_error(_("%s: smtp_wait failed: %s"),
180 iop->id, mu_strerror(rc));
181 return -1;
182 }
183 continue;
184 } else {
185 mu_error("%s: mu_stream_write: %s",
186 iop->id, mu_strerror (rc));
187 return -1;
188 }
189 } while (len > 0);
190 return 0;
191 }
192
193 static int
smtp_send2(struct smtp_io_data * iop,const char * command,const char * arg)194 smtp_send2(struct smtp_io_data *iop, const char *command, const char *arg)
195 {
196 mu_opool_appendz(iop->pool, command);
197 if (arg)
198 mu_opool_appendz(iop->pool, arg);
199 mu_opool_appendz(iop->pool, "\r\n");
200 mu_opool_append_char(iop->pool, 0);
201 iop->command = mu_opool_finish(iop->pool, NULL);
202
203 return smtp_send(iop, iop->command);
204 }
205
206 static int
smtp_send3(struct smtp_io_data * iop,const char * command,const char * arg1,const char * arg2)207 smtp_send3(struct smtp_io_data *iop, const char *command,
208 const char *arg1, const char *arg2)
209 {
210 mu_opool_appendz(iop->pool, command);
211 mu_opool_appendz(iop->pool, arg1);
212 mu_opool_appendz(iop->pool, arg2);
213 mu_opool_appendz(iop->pool, "\r\n");
214 mu_opool_append_char(iop->pool, 0);
215 iop->command = mu_opool_finish(iop->pool, NULL);
216
217 return smtp_send(iop, iop->command);
218 }
219
220 static int
smtp_recvline(struct smtp_io_data * iop,enum smtp_timeout to)221 smtp_recvline(struct smtp_io_data *iop, enum smtp_timeout to)
222 {
223 struct timeout_ctl tctl;
224
225 init_timeout_ctl(&tctl, iop->timeout[to]);
226 for (;;) {
227 char *p;
228
229 UPDATE_TTW(tctl);
230
231 if (iop->level == 0) {
232 int rc = mu_stream_read(iop->stream,
233 iop->buf, sizeof iop->buf,
234 &iop->level);
235 if (rc == 0) {
236 if (iop->level == 0) {
237 mu_error(_("%s: stream_read: read 0 bytes"), iop->id);
238 return -1;
239 }
240 } else if (rc == EAGAIN) {
241 rc = smtp_wait(iop,
242 MU_STREAM_READY_RD, &tctl);
243 if (rc) {
244 mu_error(_("%s: smtp_wait failed: %s"),
245 iop->id, mu_strerror(rc));
246 return -1;
247 }
248 continue;
249 } else {
250 mu_error("%s: mu_stream_read: %s",
251 iop->id, mu_strerror (rc));
252 return -1;
253 }
254 }
255
256 p = memchr(iop->buf, '\n', iop->level);
257 if (!p) {
258 mu_opool_append(iop->pool, iop->buf, iop->level);
259 iop->level = 0;
260 continue;
261 } else {
262 size_t len = p - iop->buf + 1;
263 mu_opool_append(iop->pool, iop->buf, len);
264 mu_opool_append_char(iop->pool, 0);
265 iop->reply = mu_opool_finish(iop->pool, NULL);
266 iop->level -= len;
267 memmove(iop->buf, iop->buf + len, iop->level);
268 break;
269 }
270 }
271 return 0;
272 }
273
274 static int
smtp_recv(struct smtp_io_data * iop,enum smtp_timeout to)275 smtp_recv(struct smtp_io_data *iop, enum smtp_timeout to)
276 {
277 char *p;
278 iop->start = NULL;
279 iop->nlines = 0;
280 do {
281 int code;
282 int rc = smtp_recvline(iop, to);
283 if (rc)
284 return -1;
285 code = strtoul(iop->reply, &p, 0);
286 if (p - iop->reply != 3 || (*p != '-' && *p != ' ')) {
287 mu_error(_("%s: unexpected reply from server: %s"),
288 iop->id, iop->reply);
289 return -1;
290 } else if (!iop->start) {
291 iop->start = iop->reply;
292 iop->code = code;
293 } else if (iop->code != code) {
294 mu_error(_("%s: unexpected reply code from server: %d"),
295 iop->id, code);
296 return -1;
297 }
298 iop->nlines++;
299 if ((iop->esmtp_capa & SMTP_PARSEHLO) && iop->nlines > 1) {
300 if (strncmp(iop->reply + 4, "VRFY", 4) == 0 &&
301 (iop->reply[8] == '\r' || iop->reply[8] == '\n')) {
302 iop->esmtp_capa |= CAPA_VRFY;
303 iop->esmtp_capa &= ~SMTP_PARSEHLO;
304 }
305 }
306 } while (*p == '-');
307 iop->esmtp_capa &= ~SMTP_PARSEHLO;
308 mu_opool_clear(iop->pool);
309 return 0;
310 }
311
312 /* Return the first line (terminated by \n or \r\n) from STR,
313 or the word "nothing" if STR is NULL.
314 Desctructive version: modifies STR. */
315 static char *
first_line_of(char * str)316 first_line_of(char *str)
317 {
318 if (str) {
319 size_t len = strcspn(str, "\r\n");
320 str[len] = 0;
321 return str;
322 }
323 return "nothing";
324 }
325
326 const char *
smtp_last_sent(struct smtp_io_data * iop)327 smtp_last_sent(struct smtp_io_data *iop)
328 {
329 return first_line_of(iop->command);
330 }
331
332 const char *
smtp_last_received(struct smtp_io_data * iop)333 smtp_last_received(struct smtp_io_data *iop)
334 {
335 return first_line_of(iop->start);
336 }
337
338 const char *
smtp_io_id(struct smtp_io_data * iop)339 smtp_io_id(struct smtp_io_data *iop)
340 {
341 return iop->id;
342 }
343
344 const char *
smtp_io_email(struct smtp_io_data * iop)345 smtp_io_email(struct smtp_io_data *iop)
346 {
347 return iop->email;
348 }
349
350
351 /* Milter-specific functions */
352
353 static mf_status
reset(struct smtp_io_data * io)354 reset(struct smtp_io_data *io)
355 {
356 smtp_send2(io, "RSET", NULL);
357 if (smtp_recv(io, smtp_timeout_rset))
358 return mf_timeout;
359 else if (SMTP_MAJOR(io->code) != 2)
360 return SMTP_MAJOR(io->code) == 4 ?
361 mf_temp_failure : mf_failure;
362 return mf_success;
363 }
364
365 static mf_status
esmtp_vrfy(struct smtp_io_data * io)366 esmtp_vrfy(struct smtp_io_data *io)
367 {
368 smtp_send2(io, "VRFY ", io->email);
369 if (smtp_recv(io, smtp_timeout_rcpt))
370 /* FIXME: Need a separate timeout? */
371 return mf_timeout;
372 if (io->code / 10 == 25)
373 return mf_success;
374 else if (SMTP_MAJOR(io->code) == 5)
375 return mf_not_found;
376 return mf_failure;
377 }
378
379 static mf_status
callout_io(struct smtp_io_data * io,const char * hostname,mu_address_t addr)380 callout_io(struct smtp_io_data *io, const char *hostname, mu_address_t addr)
381 {
382 size_t i;
383 size_t mailcount;
384 mf_status status;
385
386 if (io->callback)
387 io->callback(io->callback_closure, "INIT", hostname);
388
389 mu_address_get_count(addr, &mailcount);
390
391 if (smtp_recv(io, smtp_timeout_initial))
392 return mf_timeout;
393
394 if (io->callback)
395 io->callback(io->callback_closure, "GRTNG",
396 smtp_last_received(io));
397
398 if (SMTP_MAJOR(io->code) != 2)
399 return SMTP_MAJOR(io->code) == 4 ?
400 mf_temp_failure : mf_not_found;
401
402 smtp_send2(io, "EHLO ", io->ehlo);
403 if (enable_vrfy)
404 io->esmtp_capa |= SMTP_PARSEHLO;
405 if (smtp_recv(io, smtp_timeout_helo))
406 return mf_timeout;
407
408 if (SMTP_MAJOR(io->code) == 5) {
409 /* Let's try HELO, then */
410 smtp_send2(io, "HELO ", io->ehlo);
411 if (smtp_recv(io, smtp_timeout_helo))
412 return mf_not_found;
413 }
414
415 if (io->callback)
416 io->callback(io->callback_closure,
417 "HELO", smtp_last_received(io));
418
419 if (SMTP_MAJOR(io->code) != 2)
420 return SMTP_MAJOR(io->code) == 4 ?
421 mf_temp_failure : mf_not_found;
422
423 if (io->esmtp_capa & CAPA_VRFY) {
424 status = esmtp_vrfy(io);
425 if (mf_resolved(status))
426 return status;
427 }
428
429 status = mf_success;
430 for (i = 1; i <= mailcount; i++) {
431 const char *fromaddr;
432
433 mu_address_sget_email(addr, i, &fromaddr);
434
435 smtp_send3(io, "MAIL FROM:<", fromaddr, ">");
436
437 if (smtp_recv(io, smtp_timeout_mail))
438 return mf_timeout;
439 else if (SMTP_MAJOR(io->code) != 2) {
440 if (SMTP_MAJOR(io->code) == 4) {
441 if (reset(io) != mf_success) {
442 /* RSET must always return 250
443 If it does not, there's no
444 use talking to this host
445 any more */
446 return mf_failure;
447 } else
448 status = mf_temp_failure;
449 } else
450 status = mf_not_found;
451 } else {
452 status = mf_success;
453 break;
454 }
455 }
456 if (status != mf_success)
457 return status;
458
459 smtp_send3(io, "RCPT TO:<", io->email, ">");
460 if (smtp_recv(io, smtp_timeout_rcpt))
461 return mf_timeout;
462 else if (SMTP_MAJOR(io->code) != 2)
463 return SMTP_MAJOR(io->code) == 4 ?
464 mf_temp_failure : mf_not_found;
465 return mf_success;
466 }
467
468 static int
create_transcript_stream(mu_stream_t * pstream,struct smtp_io_data * io)469 create_transcript_stream (mu_stream_t *pstream, struct smtp_io_data *io)
470 {
471 int rc;
472 mu_stream_t stream = *pstream;
473 mu_stream_t dstr, xstr;
474 char *fltargs[3] = { "INLINE-COMMENT", };
475
476 rc = mu_dbgstream_create (&dstr, MU_DIAG_DEBUG);
477 if (rc) {
478 mu_error (_("cannot create debug stream: %s; "
479 "transcript disabled"),
480 mu_strerror (rc));
481 return rc;
482 }
483
484 mu_asprintf (&fltargs[1], "%s: ", io->id);
485 fltargs[2] = NULL;
486 rc = mu_filter_create_args (&xstr, dstr,
487 "INLINE-COMMENT",
488 2, (const char**)fltargs,
489 MU_FILTER_ENCODE, MU_STREAM_WRITE);
490 free (fltargs[1]);
491
492 if (rc == 0) {
493 mu_stream_unref(dstr);
494 dstr = xstr;
495 mu_stream_set_buffer (dstr, mu_buffer_line, 0);
496 } else
497 mu_error (_("cannot create transcript filter"
498 "stream: %s"), mu_strerror (rc));
499
500 rc = mu_xscript_stream_create (&xstr, stream, dstr, NULL);
501 if (rc)
502 mu_error (_("cannot create transcript stream: %s; "
503 "transcript disabled"),
504 mu_strerror (rc));
505 else {
506 mu_stream_unref (stream);
507 *pstream = xstr;
508 }
509 return rc;
510 }
511
512
513 mf_status
smtp_io_open(struct smtp_io_data * io,const char * hostname)514 smtp_io_open(struct smtp_io_data *io, const char *hostname)
515 {
516 int rc;
517 mu_stream_t stream;
518 struct timeout_ctl tctl;
519 struct mu_sockaddr_hints hints;
520 struct mu_sockaddr *address, *srcaddr;
521
522 memset(&hints, 0, sizeof hints);
523 hints.family = AF_INET;
524 hints.socktype = SOCK_STREAM;
525 hints.protocol = IPPROTO_TCP;
526 hints.port = 25;
527 rc = mu_sockaddr_from_node(&address, hostname, NULL, &hints);
528 if (rc) {
529 mu_error(_("cannot convert %s to sockaddr: %s"),
530 hostname, mu_strerror(rc));
531 return mf_failure;
532 }
533
534 mu_sockaddr_copy (&srcaddr, source_address);
535 rc = mu_tcp_stream_create_from_sa(&stream, address, source_address,
536 MU_STREAM_NONBLOCK);
537
538 if (rc && !(rc == EAGAIN || rc == EINPROGRESS)) {
539 mu_error(_("%s: cannot connect to `%s': %s"),
540 io->id, hostname,
541 mu_strerror(rc));
542 mu_sockaddr_free(srcaddr);
543 return mf_failure;
544 }
545 mu_stream_set_buffer (stream, mu_buffer_line, 0);
546
547 init_timeout_ctl(&tctl, io->timeout[smtp_timeout_connect]);
548 while (rc) {
549 if ((rc == EAGAIN || rc == EINPROGRESS) && tctl.timeout) {
550 rc = mf_stream_wait(stream, MU_STREAM_READY_WR,
551 &tctl);
552 if (rc == 0) {
553 UPDATE_TTW(tctl);
554 rc = mu_stream_open(stream);
555 continue;
556 }
557 }
558 mu_error("%s: stream_open(%s): %s",
559 io->id, hostname, mu_strerror(rc));
560 mu_stream_destroy(&stream);
561 return mf_timeout;
562 }
563 mu_debug(MF_SOURCE_CALLOUT, MU_DEBUG_TRACE9, ("stream opened"));
564 if (smtp_transcript)
565 create_transcript_stream(&stream, io);
566
567 io->stream = stream;
568 return mf_success;
569 }
570
571 void
smtp_io_close(struct smtp_io_data * io)572 smtp_io_close(struct smtp_io_data *io)
573 {
574 if (io->stream) {
575 mu_stream_close(io->stream);
576 mu_stream_destroy(&io->stream);
577 }
578 }
579
580 mf_status
callout_host(struct smtp_io_data * io,const char * hostname)581 callout_host(struct smtp_io_data *io, const char *hostname)
582 {
583 int rc;
584 mf_status status = mf_success;
585 mu_address_t addr;
586 const char *mailfrom;
587
588 mu_debug(MF_SOURCE_CALLOUT, MU_DEBUG_TRACE5,
589 ("email = %s, hostname = %s",
590 io->email, hostname));
591
592 smtp_io_init(io);
593 status = smtp_io_open(io, hostname);
594 if (status != mf_success)
595 return status;
596
597 /* FIXME-MU: compensate for mailutils deficiency */
598 mailfrom = (io->mailfrom[0] == 0) ? "<>" : io->mailfrom;
599 rc = mu_address_create(&addr, mailfrom);
600 if (rc) {
601 mu_error(_("%s: cannot create address `%s': %s"),
602 io->id, mailfrom, mu_strerror(rc));
603 return mf_timeout;
604 }
605
606 status = callout_io(io, hostname, addr);
607
608 mu_address_destroy(&addr);
609
610 if (io->callback) {
611 io->callback(io->callback_closure,
612 "SENT", smtp_last_sent(io));
613 io->callback(io->callback_closure,
614 "RECV", smtp_last_received(io));
615 }
616
617 mu_debug(MF_SOURCE_CALLOUT, MU_DEBUG_TRACE0,
618 ("%s: verification of <%s> finished with status: %s; sent \"%s\", got \"%s\"",
619 io->id,
620 smtp_io_email(io),
621 mf_status_str(status),
622 smtp_last_sent(io),
623 smtp_last_received(io)));
624
625 smtp_send2(io, "QUIT", NULL);
626 smtp_recv(io, smtp_timeout_quit);
627
628 smtp_io_close(io);
629
630 return status;
631 }
632
633 mf_status
callout_mx(struct smtp_io_data * iop,const char * hostname,int * pcount)634 callout_mx(struct smtp_io_data *iop, const char *hostname, int *pcount)
635 {
636 int i;
637 struct dns_reply reply;
638 mf_status rc, mxstat;
639
640 mxstat = dns_to_mf_status(mx_lookup(hostname, 0, &reply));
641
642 if (pcount)
643 *pcount = 0;
644 switch (mxstat) {
645 case mf_success:
646 mu_debug(MF_SOURCE_CALLOUT, MU_DEBUG_TRACE1,
647 ("Checking MX servers for %s", iop->email));
648 rc = mf_not_found;
649 for (i = 0; i < reply.count; i++) {
650 rc = callout_host(iop, reply.data.str[i]);
651 if (mf_resolved(rc))
652 break;
653 }
654 if (pcount)
655 *pcount = reply.count;
656 dns_reply_free(&reply);
657 break;
658
659 default:
660 rc = mxstat;
661 break;
662 }
663 return rc;
664 }
665
666 /* Method "strict". Verifies whether EMAIL is understood either by
667 host CLIENT_ADDR or one of MX servers of its domain */
668 mf_status
callout_strict(struct smtp_io_data * iop,const char * hostname)669 callout_strict(struct smtp_io_data *iop, const char *hostname)
670 {
671 mf_status rc;
672
673 rc = callout_host(iop, hostname);
674 if (!mf_resolved(rc)) {
675 int mxcount;
676 mf_status mx_stat;
677 mx_stat = callout_mx(iop, hostname, &mxcount);
678 if (!(mx_stat == mf_not_found && mxcount == 0)
679 && (mf_resolved(mx_stat)
680 || mx_stat == mf_timeout
681 || mx_stat == mf_temp_failure))
682 rc = mx_stat;
683 }
684 return rc;
685 }
686
687 mf_status
callout_standard(struct smtp_io_data * iop)688 callout_standard(struct smtp_io_data *iop)
689 {
690 int rc;
691
692 char *p = strchr(iop->email, '@');
693 if (p == NULL) {
694 mu_error(_("%s: invalid address: %s"), iop->id, iop->email);
695 rc = mf_not_found;
696 } else {
697 int mxcount;
698 p++;
699 rc = callout_mx(iop, p, &mxcount);
700 if (rc != mf_success && mxcount == 0) {
701 mf_status host_stat;
702 host_stat = callout_host(iop, p);
703 if (mf_resolved(host_stat)
704 || host_stat == mf_timeout
705 || host_stat == mf_temp_failure)
706 rc = host_stat;
707 }
708 }
709 return rc;
710 }
711
712 static char *modnames[] = {
713 #define __DBGMOD_C_ARRAY
714 # include "callout-dbgmod.h"
715 #undef __DBGMOD_C_ARRAY
716 NULL
717 };
718
719 mu_debug_handle_t callout_debug_handle;
720
721 void
libcallout_init()722 libcallout_init()
723 {
724 int i;
725
726 callout_debug_handle = mu_debug_register_category (modnames[0]);
727 for (i = 1; modnames[i]; i++)
728 mu_debug_register_category (modnames[i]);
729 }
730
731