NETSPOOL: enable sending to mailinglists
[citadel.git] / citadel / modules / network / serv_netmail.c
1 /*
2  * This module handles shared rooms, inter-Citadel mail, and outbound
3  * mailing list processing.
4  *
5  * Copyright (c) 2000-2012 by the citadel.org team
6  *
7  *  This program is open source software; you can redistribute it and/or modify
8  *  it under the terms of the GNU General Public License, version 3.
9  *
10  *  This program is distributed in the hope that it will be useful,
11  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
12  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  *  GNU General Public License for more details.
14  *
15  * ** NOTE **   A word on the S_NETCONFIGS semaphore:
16  * This is a fairly high-level type of critical section.  It ensures that no
17  * two threads work on the netconfigs files at the same time.  Since we do
18  * so many things inside these, here are the rules:
19  *  1. begin_critical_section(S_NETCONFIGS) *before* begin_ any others.
20  *  2. Do *not* perform any I/O with the client during these sections.
21  *
22  */
23
24 /*
25  * Duration of time (in seconds) after which pending list subscribe/unsubscribe
26  * requests that have not been confirmed will be deleted.
27  */
28 #define EXP     259200  /* three days */
29
30 #include "sysdep.h"
31 #include <stdlib.h>
32 #include <unistd.h>
33 #include <stdio.h>
34 #include <fcntl.h>
35 #include <ctype.h>
36 #include <signal.h>
37 #include <pwd.h>
38 #include <errno.h>
39 #include <sys/stat.h>
40 #include <sys/types.h>
41 #include <dirent.h>
42 #if TIME_WITH_SYS_TIME
43 # include <sys/time.h>
44 # include <time.h>
45 #else
46 # if HAVE_SYS_TIME_H
47 #  include <sys/time.h>
48 # else
49 #  include <time.h>
50 # endif
51 #endif
52 #ifdef HAVE_SYSCALL_H
53 # include <syscall.h>
54 #else
55 # if HAVE_SYS_SYSCALL_H
56 #  include <sys/syscall.h>
57 # endif
58 #endif
59
60 #include <sys/wait.h>
61 #include <string.h>
62 #include <limits.h>
63 #include <libcitadel.h>
64 #include "citadel.h"
65 #include "server.h"
66 #include "citserver.h"
67 #include "support.h"
68 #include "config.h"
69 #include "user_ops.h"
70 #include "database.h"
71 #include "msgbase.h"
72 #include "internet_addressing.h"
73 #include "serv_network.h"
74 #include "clientsocket.h"
75 #include "file_ops.h"
76 #include "citadel_dirs.h"
77 #include "threads.h"
78 #include "context.h"
79 #include "ctdl_module.h"
80 #include "netspool.h"
81 #include "netmail.h"
82
83 void network_deliver_list(struct CtdlMessage *msg, SpoolControl *sc, const char *RoomName);
84
85 /*
86  * Deliver digest messages
87  */
88 void network_deliver_digest(SpoolControl *sc)
89 {
90         struct CitContext *CCC = CC;
91         char buf[SIZ];
92         int i;
93         struct CtdlMessage *msg = NULL;
94         long msglen;
95         StrBuf *recps = NULL;
96         char *precps;
97         size_t recps_len = SIZ;
98         struct recptypes *valid;
99         RoomNetCfgLine *nptr;
100         char bounce_to[256];
101
102         if (sc->num_msgs_spooled < 1) {
103                 fclose(sc->digestfp);
104                 sc->digestfp = NULL;
105                 return;
106         }
107
108         msg = malloc(sizeof(struct CtdlMessage));
109         memset(msg, 0, sizeof(struct CtdlMessage));
110         msg->cm_magic = CTDLMESSAGE_MAGIC;
111         msg->cm_format_type = FMT_RFC822;
112         msg->cm_anon_type = MES_NORMAL;
113
114         sprintf(buf, "%ld", time(NULL));
115         msg->cm_fields['T'] = strdup(buf);
116         msg->cm_fields['A'] = strdup(CC->room.QRname);
117         snprintf(buf, sizeof buf, "[%s]", CC->room.QRname);
118         msg->cm_fields['U'] = strdup(buf);
119         sprintf(buf, "room_%s@%s", CC->room.QRname, config.c_fqdn);
120         for (i=0; buf[i]; ++i) {
121                 if (isspace(buf[i])) buf[i]='_';
122                 buf[i] = tolower(buf[i]);
123         }
124         msg->cm_fields['F'] = strdup(buf);
125         msg->cm_fields['R'] = strdup(buf);
126
127         /* Set the 'List-ID' header */
128         msg->cm_fields['L'] = malloc(1024);
129         snprintf(msg->cm_fields['L'], 1024,
130                  "%s <%ld.list-id.%s>",
131                  CC->room.QRname,
132                  CC->room.QRnumber,
133                  config.c_fqdn
134                 );
135
136         /*
137          * Go fetch the contents of the digest
138          */
139         fseek(sc->digestfp, 0L, SEEK_END);
140         msglen = ftell(sc->digestfp);
141
142         msg->cm_fields['M'] = malloc(msglen + 1);
143         fseek(sc->digestfp, 0L, SEEK_SET);
144         fread(msg->cm_fields['M'], (size_t)msglen, 1, sc->digestfp);
145         msg->cm_fields['M'][msglen] = '\0';
146
147         fclose(sc->digestfp);
148         sc->digestfp = NULL;
149
150         /* Now generate the delivery instructions */
151
152         /*
153          * Figure out how big a buffer we need to allocate
154          */
155         for (nptr = sc->RNCfg->NetConfigs[digestrecp]; nptr != NULL; nptr = nptr->next) {
156                 recps_len = recps_len + StrLength(nptr->Value[0]) + 2;
157         }
158
159         recps = NewStrBufPlain(NULL, recps_len);
160
161         if (recps == NULL) {
162                 QN_syslog(LOG_EMERG,
163                           "Cannot allocate %ld bytes for recps...\n",
164                           (long)recps_len);
165                 abort();
166         }
167
168         /* Each recipient */
169         for (nptr = sc->RNCfg->NetConfigs[digestrecp]; nptr != NULL; nptr = nptr->next) {
170                 if (nptr != sc->RNCfg->NetConfigs[digestrecp]) {
171                         StrBufAppendBufPlain(recps, HKEY(","), 0);
172                 }
173                 StrBufAppendBuf(recps, nptr->Value[0], 0);
174         }
175
176         /* Where do we want bounces and other noise to be heard?
177          *Surely not the list members! */
178         snprintf(bounce_to, sizeof bounce_to, "room_aide@%s", config.c_fqdn);
179
180         /* Now submit the message */
181         precps = SmashStrBuf(&recps);
182         valid = validate_recipients(precps, NULL, 0);
183         free(precps);
184         if (valid != NULL) {
185                 valid->bounce_to = strdup(bounce_to);
186                 valid->envelope_from = strdup(bounce_to);
187                 CtdlSubmitMsg(msg, valid, NULL, 0);
188         }
189         CtdlFreeMessage(msg);
190         free_recipients(valid);
191 }
192
193
194 void network_process_digest(SpoolControl *sc, struct CtdlMessage *omsg, long *delete_after_send)
195 {
196
197         struct CtdlMessage *msg = NULL;
198
199         /*
200          * Process digest recipients
201          */
202         if ((sc->RNCfg->NetConfigs[digestrecp] == NULL) || 
203             (sc->digestfp == NULL))
204                 return;
205
206         msg = CtdlDuplicateMessage(omsg);
207         if (msg != NULL) {
208                 fprintf(sc->digestfp,
209                         " -----------------------------------"
210                         "------------------------------------"
211                         "-------\n");
212                 fprintf(sc->digestfp, "From: ");
213                 if (msg->cm_fields['A'] != NULL) {
214                         fprintf(sc->digestfp,
215                                 "%s ",
216                                 msg->cm_fields['A']);
217                 }
218                 if (msg->cm_fields['F'] != NULL) {
219                         fprintf(sc->digestfp,
220                                 "<%s> ",
221                                 msg->cm_fields['F']);
222                 }
223                 else if (msg->cm_fields['N'] != NULL) {
224                         fprintf(sc->digestfp,
225                                 "@%s ",
226                                 msg->cm_fields['N']);
227                 }
228                 fprintf(sc->digestfp, "\n");
229                 if (msg->cm_fields['U'] != NULL) {
230                         fprintf(sc->digestfp,
231                                 "Subject: %s\n",
232                                 msg->cm_fields['U']);
233                 }
234
235                 CC->redirect_buffer = NewStrBufPlain(NULL, SIZ);
236
237                 safestrncpy(CC->preferred_formats,
238                             "text/plain",
239                             sizeof CC->preferred_formats);
240
241                 CtdlOutputPreLoadedMsg(msg,
242                                        MT_CITADEL,
243                                        HEADERS_NONE,
244                                        0, 0, 0);
245
246                 StrBufTrim(CC->redirect_buffer);
247                 fwrite(HKEY("\n"), 1, sc->digestfp);
248                 fwrite(SKEY(CC->redirect_buffer), 1, sc->digestfp);
249                 fwrite(HKEY("\n"), 1, sc->digestfp);
250
251                 FreeStrBuf(&CC->redirect_buffer);
252
253                 sc->num_msgs_spooled += 1;
254                 CtdlFreeMessage(msg);
255         }
256 }
257
258
259 void network_process_list(SpoolControl *sc, struct CtdlMessage *omsg, long *delete_after_send)
260 {
261         int rlen;
262         char *pCh;
263         StrBuf *Subject, *FlatSubject;
264         struct CtdlMessage *msg = NULL;
265         int i;
266
267         /*
268          * Process mailing list recipients
269          */
270         if (sc->RNCfg->NetConfigs[listrecp] == NULL) 
271                 return;
272
273         /* create our own copy of the message.
274          *  We're going to need to modify it
275          * in order to insert the [list name] in it, etc.
276          */
277
278         msg = CtdlDuplicateMessage(omsg);
279
280         if (msg->cm_fields['K'] != NULL)
281                 free(msg->cm_fields['K']);
282         if (msg->cm_fields['V'] == NULL){
283                 /* local message, no enVelope */
284                 StrBuf *Buf;
285                 Buf = NewStrBuf();
286                 StrBufAppendBufPlain(Buf,
287                                      msg->cm_fields['O']
288                                      , -1, 0);
289                 StrBufAppendBufPlain(Buf, HKEY("@"), 0);
290                 StrBufAppendBufPlain(Buf, config.c_fqdn, -1, 0);
291
292                 msg->cm_fields['K'] = SmashStrBuf(&Buf);
293         }
294         else {
295                 msg->cm_fields['K'] =
296                         strdup (msg->cm_fields['V']);
297         }
298         /* Set the 'List-ID' header */
299         if (msg->cm_fields['L'] != NULL) {
300                 free(msg->cm_fields['L']);
301         }
302         msg->cm_fields['L'] = malloc(1024);
303         snprintf(msg->cm_fields['L'], 1024,
304                  "%s <%ld.list-id.%s>",
305                  CC->room.QRname,
306                  CC->room.QRnumber,
307                  config.c_fqdn
308                 );
309
310         /* Prepend "[List name]" to the subject */
311         if (msg->cm_fields['U'] == NULL) {
312                 Subject = NewStrBufPlain(HKEY("(no subject)"));
313         }
314         else {
315                 Subject = NewStrBufPlain(
316                         msg->cm_fields['U'], -1);
317         }
318         FlatSubject = NewStrBufPlain(NULL, StrLength(Subject));
319         StrBuf_RFC822_to_Utf8(FlatSubject, Subject, NULL, NULL);
320
321         rlen = strlen(CC->room.QRname);
322         pCh  = strstr(ChrPtr(FlatSubject), CC->room.QRname);
323         if ((pCh == NULL) ||
324             (*(pCh + rlen) != ']') ||
325             (pCh == ChrPtr(FlatSubject)) ||
326             (*(pCh - 1) != '[')
327                 )
328         {
329                 StrBuf *tmp;
330                 StrBufPlain(Subject, HKEY("["));
331                 StrBufAppendBufPlain(Subject,
332                                      CC->room.QRname,
333                                      rlen, 0);
334                 StrBufAppendBufPlain(Subject, HKEY("] "), 0);
335                 StrBufAppendBuf(Subject, FlatSubject, 0);
336                 /* so we can free the right one swap them */
337                 tmp = Subject;
338                 Subject = FlatSubject;
339                 FlatSubject = tmp;
340                 StrBufRFC2047encode(&Subject, FlatSubject);
341         }
342
343         if (msg->cm_fields['U'] != NULL)
344                 free (msg->cm_fields['U']);
345         msg->cm_fields['U'] = SmashStrBuf(&Subject);
346
347         FreeStrBuf(&FlatSubject);
348
349         /* else we won't modify the buffer, since the
350          * roomname is already here.
351          */
352
353         /* if there is no other recipient, Set the recipient
354          * of the list message to the email address of the
355          * room itself.
356          */
357         if ((msg->cm_fields['R'] == NULL) ||
358             IsEmptyStr(msg->cm_fields['R']))
359         {
360                 if (msg->cm_fields['R'] != NULL)
361                         free(msg->cm_fields['R']);
362
363                 msg->cm_fields['R'] = malloc(256);
364                 snprintf(msg->cm_fields['R'], 256,
365                          "room_%s@%s", CC->room.QRname,
366                          config.c_fqdn);
367                 for (i=0; msg->cm_fields['R'][i]; ++i) {
368                         if (isspace(msg->cm_fields['R'][i])) {
369                                 msg->cm_fields['R'][i] = '_';
370                         }
371                 }
372         }
373
374         /* Handle delivery */
375         network_deliver_list(msg, sc, CC->room.QRname);
376         CtdlFreeMessage(msg);
377 }
378
379 /*
380  * Deliver list messages to everyone on the list ... efficiently
381  */
382 void network_deliver_list(struct CtdlMessage *msg, SpoolControl *sc, const char *RoomName)
383 {
384         struct CitContext *CCC = CC;
385         StrBuf *recps = NULL;
386         char *precps = NULL;
387         size_t recps_len = SIZ;
388         struct recptypes *valid;
389         RoomNetCfgLine *nptr;
390         char bounce_to[256];
391
392         /* Don't do this if there were no recipients! */
393         if (sc->RNCfg->NetConfigs[listrecp] == NULL) return;
394
395         /* Now generate the delivery instructions */
396
397         /*
398          * Figure out how big a buffer we need to allocate
399          */
400         for (nptr = sc->RNCfg->NetConfigs[listrecp]; nptr != NULL; nptr = nptr->next) {
401                 recps_len = recps_len + StrLength(nptr->Value[0]) + 2;
402         }
403
404         recps = NewStrBufPlain(NULL, recps_len);
405
406         if (recps == NULL) {
407                 QN_syslog(LOG_EMERG,
408                           "Cannot allocate %ld bytes for recps...\n",
409                           (long)recps_len);
410                 abort();
411         }
412
413         /* Each recipient */
414         for (nptr = sc->RNCfg->NetConfigs[listrecp]; nptr != NULL; nptr = nptr->next) {
415                 if (nptr != sc->RNCfg->NetConfigs[listrecp]) {
416                         StrBufAppendBufPlain(recps, HKEY(","), 0);
417                 }
418                 StrBufAppendBuf(recps, nptr->Value[0], 0);
419         }
420
421         /* Where do we want bounces and other noise to be heard?
422          *  Surely not the list members! */
423         snprintf(bounce_to, sizeof bounce_to, "room_aide@%s", config.c_fqdn);
424
425         /* Now submit the message */
426         precps = SmashStrBuf(&recps);
427         valid = validate_recipients(precps, NULL, 0);
428         free(precps);
429         if (valid != NULL) {
430                 valid->bounce_to = strdup(bounce_to);
431                 valid->envelope_from = strdup(bounce_to);
432                 valid->sending_room = strdup(RoomName);
433                 CtdlSubmitMsg(msg, valid, NULL, 0);
434                 free_recipients(valid);
435         }
436         /* Do not call CtdlFreeMessage(msg) here; the caller will free it. */
437 }
438
439
440 void network_process_participate(SpoolControl *sc, struct CtdlMessage *omsg, long *delete_after_send)
441 {
442         struct CtdlMessage *msg = NULL;
443         int i;
444         int ok_to_participate = 0;
445         StrBuf *Buf = NULL;
446         RoomNetCfgLine *nptr;
447         struct recptypes *valid;
448
449         /*
450          * Process client-side list participations for this room
451          */
452         if (sc->RNCfg->NetConfigs[participate] == NULL)
453                 return;
454
455         msg = CtdlDuplicateMessage(omsg);
456
457         /* Only send messages which originated on our own
458          * Citadel network, otherwise we'll end up sending the
459          * remote mailing list's messages back to it, which
460          * is rude...
461          */
462         ok_to_participate = 0;
463         if (msg->cm_fields['N'] != NULL) {
464                 if (!strcasecmp(msg->cm_fields['N'],
465                                 config.c_nodename)) {
466                         ok_to_participate = 1;
467                 }
468                 
469                 Buf = NewStrBufPlain(msg->cm_fields['N'], -1);
470                 if (CtdlIsValidNode(NULL,
471                                     NULL,
472                                     Buf,
473                                     sc->working_ignetcfg,
474                                     sc->the_netmap) == 0)
475                 {
476                         ok_to_participate = 1;
477                 }
478         }
479         if (ok_to_participate) {
480                 if (msg->cm_fields['F'] != NULL) {
481                         free(msg->cm_fields['F']);
482                 }
483                 msg->cm_fields['F'] = malloc(SIZ);
484                 /* Replace the Internet email address of the
485                  * actual author with the email address of the
486                  * room itself, so the remote listserv doesn't
487                  * reject us.
488                  * FIXME  I want to be able to pick any address
489                  */
490                 snprintf(msg->cm_fields['F'], SIZ,
491                          "room_%s@%s", CC->room.QRname,
492                          config.c_fqdn);
493                 for (i=0; msg->cm_fields['F'][i]; ++i) {
494                         if (isspace(msg->cm_fields['F'][i])) {
495                                 msg->cm_fields['F'][i] = '_';
496                         }
497                 }
498                 
499                 /*
500                  * Figure out how big a buffer we need to alloc
501                  */
502                 for (nptr = sc->RNCfg->NetConfigs[participate];
503                      nptr != NULL;
504                      nptr = nptr->next)
505                 {
506                         if (msg->cm_fields['R'] != NULL) {
507                                 free(msg->cm_fields['R']);
508                         }
509                         msg->cm_fields['R'] =
510                                 strdup(ChrPtr(nptr->Value[0]));
511                         
512                         valid = validate_recipients(msg->cm_fields['R'],
513                                                     NULL, 0);
514                         
515                         CtdlSubmitMsg(msg, valid, "", 0);
516                         free_recipients(valid);
517                 }
518         }
519         FreeStrBuf(&Buf);
520         CtdlFreeMessage(msg);
521 }
522
523 void network_process_ignetpush(SpoolControl *sc, struct CtdlMessage *omsg, long *delete_after_send)
524 {
525         struct CtdlMessage *msg = NULL;
526         struct CitContext *CCC = CC;
527         struct ser_ret sermsg;
528         char buf[SIZ];
529         char filename[PATH_MAX];
530         FILE *fp;
531         size_t newpath_len;
532         char *newpath = NULL;
533         RoomNetCfgLine* mptr;
534         StrBuf *Buf = NULL;
535         int i;
536         int bang = 0;
537         int send = 1;
538
539         if (sc->RNCfg->NetConfigs[ignet_push_share] == NULL)
540                 return;
541         /*
542          * Process IGnet push shares
543          */
544         msg = CtdlDuplicateMessage(omsg);
545
546         /* Prepend our node name to the Path field whenever
547          * sending a message to another IGnet node
548          */
549         if (msg->cm_fields['P'] == NULL)
550         {
551                 msg->cm_fields['P'] = strdup("username");
552         }
553         newpath_len = strlen(msg->cm_fields['P']) +
554                 strlen(config.c_nodename) + 2;
555         newpath = malloc(newpath_len);
556         snprintf(newpath, newpath_len, "%s!%s",
557                  config.c_nodename, msg->cm_fields['P']);
558         free(msg->cm_fields['P']);
559         msg->cm_fields['P'] = newpath;
560         
561         /*
562          * Determine if this message is set to be deleted
563          * after sending out on the network
564          */
565         if (msg->cm_fields['S'] != NULL) {
566                 if (!strcasecmp(msg->cm_fields['S'], "CANCEL")) {
567                         *delete_after_send = 1;
568                 }
569         }
570
571         /* Now send it to every node */
572         for (mptr = sc->RNCfg->NetConfigs[ignet_push_share];
573              mptr != NULL;
574              mptr = mptr->next)
575         {
576                 send = 1;
577                 NewStrBufDupAppendFlush(&Buf, mptr->Value[0], NULL, 1);
578                         
579                 /* Check for valid node name */
580                 if (CtdlIsValidNode(NULL,
581                                     NULL,
582                                     Buf,
583                                     sc->working_ignetcfg,
584                                     sc->the_netmap) != 0)
585                 {
586                         QN_syslog(LOG_ERR,
587                                   "Invalid node <%s>\n",
588                                   ChrPtr(mptr->Value[0]));
589                         
590                         send = 0;
591                 }
592                 
593                 /* Check for split horizon */
594                 QN_syslog(LOG_DEBUG, "Path is %s\n", msg->cm_fields['P']);
595                 bang = num_tokens(msg->cm_fields['P'], '!');
596                 if (bang > 1) {
597                         for (i=0; i<(bang-1); ++i) {
598                                 extract_token(buf,
599                                               msg->cm_fields['P'],
600                                               i, '!',
601                                               sizeof buf);
602
603                                 QN_syslog(LOG_DEBUG, "Compare <%s> to <%s>\n",
604                                           buf, ChrPtr(mptr->Value[0])) ;
605                                 if (!strcasecmp(buf, ChrPtr(mptr->Value[0]))) {
606                                         send = 0;
607                                         break;
608                                 }
609                         }
610                         
611                         QN_syslog(LOG_INFO,
612                                   "%sSending to %s\n",
613                                   (send)?"":"Not ",
614                                   ChrPtr(mptr->Value[0]));
615                 }
616                 
617                 /* Send the message */
618                 if (send == 1)
619                 {
620                         /*
621                          * Force the message to appear in the correct
622                          * room on the far end by setting the C field
623                          * correctly
624                          */
625                         if (msg->cm_fields['C'] != NULL) {
626                                 free(msg->cm_fields['C']);
627                         }
628                         if (StrLength(mptr->Value[0]) > 0) {
629                                 msg->cm_fields['C'] =
630                                         strdup(ChrPtr(mptr->Value[0]));
631                         }
632                         else {
633                                 msg->cm_fields['C'] =
634                                         strdup(CC->room.QRname);
635                         }
636                         
637                         /* serialize it for transmission */
638                         serialize_message(&sermsg, msg);
639                         if (sermsg.len > 0) {
640                                 
641                                 /* write it to a spool file */
642                                 snprintf(filename,
643                                          sizeof(filename),
644                                          "%s/%s@%lx%x",
645                                          ctdl_netout_dir,
646                                          ChrPtr(mptr->Value[0]),
647                                          time(NULL),
648                                          rand()
649                                         );
650                                         
651                                 QN_syslog(LOG_DEBUG,
652                                           "Appending to %s\n",
653                                           filename);
654                                 
655                                 fp = fopen(filename, "ab");
656                                 if (fp != NULL) {
657                                         fwrite(sermsg.ser,
658                                                sermsg.len, 1, fp);
659                                         fclose(fp);
660                                 }
661                                 else {
662                                         QN_syslog(LOG_ERR,
663                                                   "%s: %s\n",
664                                                   filename,
665                                                   strerror(errno));
666                                 }
667
668                                 /* free the serialized version */
669                                 free(sermsg.ser);
670                         }
671                 }
672         }
673         FreeStrBuf(&Buf);
674         CtdlFreeMessage(msg);
675 }
676
677
678 /*
679  * Spools out one message from the list.
680  */
681 void network_spool_msg(long msgnum,
682                        void *userdata)
683 {
684         struct CtdlMessage *msg = NULL;
685         long delete_after_send = 0;     /* Set to 1 to delete after spooling */
686         SpoolControl *sc;
687
688         sc = (SpoolControl *)userdata;
689
690         msg = CtdlFetchMessage(msgnum, 1);
691
692         network_process_list(sc, msg, &delete_after_send);
693         network_process_digest(sc, msg, &delete_after_send);
694         network_process_participate(sc, msg, &delete_after_send);
695         network_process_ignetpush(sc, msg, &delete_after_send);
696         
697         CtdlFreeMessage(msg);
698
699         /* update lastsent */
700         sc->lastsent = msgnum;
701
702         /* Delete this message if delete-after-send is set */
703         if (delete_after_send) {
704                 CtdlDeleteMessages(CC->room.QRname, &msgnum, 1, "");
705         }
706 }