Back to index

citadel  8.12
serv_netmail.c
Go to the documentation of this file.
00001 /*
00002  * This module handles shared rooms, inter-Citadel mail, and outbound
00003  * mailing list processing.
00004  *
00005  * Copyright (c) 2000-2012 by the citadel.org team
00006  *
00007  *  This program is open source software; you can redistribute it and/or modify
00008  *  it under the terms of the GNU General Public License as published by
00009  *  the Free Software Foundation; either version 3 of the License, or
00010  *  (at your option) any later version.
00011  *
00012  *  This program is distributed in the hope that it will be useful,
00013  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
00014  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00015  *  GNU General Public License for more details.
00016  *
00017  *  You should have received a copy of the GNU General Public License
00018  *  along with this program; if not, write to the Free Software
00019  *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
00020  *
00021  * ** NOTE **   A word on the S_NETCONFIGS semaphore:
00022  * This is a fairly high-level type of critical section.  It ensures that no
00023  * two threads work on the netconfigs files at the same time.  Since we do
00024  * so many things inside these, here are the rules:
00025  *  1. begin_critical_section(S_NETCONFIGS) *before* begin_ any others.
00026  *  2. Do *not* perform any I/O with the client during these sections.
00027  *
00028  */
00029 
00030 /*
00031  * Duration of time (in seconds) after which pending list subscribe/unsubscribe
00032  * requests that have not been confirmed will be deleted.
00033  */
00034 #define EXP   259200 /* three days */
00035 
00036 #include "sysdep.h"
00037 #include <stdlib.h>
00038 #include <unistd.h>
00039 #include <stdio.h>
00040 #include <fcntl.h>
00041 #include <ctype.h>
00042 #include <signal.h>
00043 #include <pwd.h>
00044 #include <errno.h>
00045 #include <sys/stat.h>
00046 #include <sys/types.h>
00047 #include <dirent.h>
00048 #if TIME_WITH_SYS_TIME
00049 # include <sys/time.h>
00050 # include <time.h>
00051 #else
00052 # if HAVE_SYS_TIME_H
00053 #  include <sys/time.h>
00054 # else
00055 #  include <time.h>
00056 # endif
00057 #endif
00058 #ifdef HAVE_SYSCALL_H
00059 # include <syscall.h>
00060 #else
00061 # if HAVE_SYS_SYSCALL_H
00062 #  include <sys/syscall.h>
00063 # endif
00064 #endif
00065 
00066 #include <sys/wait.h>
00067 #include <string.h>
00068 #include <limits.h>
00069 #include <libcitadel.h>
00070 #include "citadel.h"
00071 #include "server.h"
00072 #include "citserver.h"
00073 #include "support.h"
00074 #include "config.h"
00075 #include "user_ops.h"
00076 #include "database.h"
00077 #include "msgbase.h"
00078 #include "internet_addressing.h"
00079 #include "serv_network.h"
00080 #include "clientsocket.h"
00081 #include "file_ops.h"
00082 #include "citadel_dirs.h"
00083 #include "threads.h"
00084 
00085 #ifndef HAVE_SNPRINTF
00086 #include "snprintf.h"
00087 #endif
00088 
00089 #include "context.h"
00090 #include "netconfig.h"
00091 #include "netspool.h"
00092 #include "netmail.h"
00093 #include "ctdl_module.h"
00094 
00095 
00096 /*
00097  * Deliver digest messages
00098  */
00099 void network_deliver_digest(SpoolControl *sc) {
00100        struct CitContext *CCC = CC;
00101        char buf[SIZ];
00102        int i;
00103        struct CtdlMessage *msg = NULL;
00104        long msglen;
00105        char *recps = NULL;
00106        size_t recps_len = SIZ;
00107        struct recptypes *valid;
00108        namelist *nptr;
00109        char bounce_to[256];
00110 
00111        if (sc->num_msgs_spooled < 1) {
00112               fclose(sc->digestfp);
00113               sc->digestfp = NULL;
00114               return;
00115        }
00116 
00117        msg = malloc(sizeof(struct CtdlMessage));
00118        memset(msg, 0, sizeof(struct CtdlMessage));
00119        msg->cm_magic = CTDLMESSAGE_MAGIC;
00120        msg->cm_format_type = FMT_RFC822;
00121        msg->cm_anon_type = MES_NORMAL;
00122 
00123        sprintf(buf, "%ld", time(NULL));
00124        msg->cm_fields['T'] = strdup(buf);
00125        msg->cm_fields['A'] = strdup(CC->room.QRname);
00126        snprintf(buf, sizeof buf, "[%s]", CC->room.QRname);
00127        msg->cm_fields['U'] = strdup(buf);
00128        sprintf(buf, "room_%s@%s", CC->room.QRname, config.c_fqdn);
00129        for (i=0; buf[i]; ++i) {
00130               if (isspace(buf[i])) buf[i]='_';
00131               buf[i] = tolower(buf[i]);
00132        }
00133        msg->cm_fields['F'] = strdup(buf);
00134        msg->cm_fields['R'] = strdup(buf);
00135 
00136        /* Set the 'List-ID' header */
00137        msg->cm_fields['L'] = malloc(1024);
00138        snprintf(msg->cm_fields['L'], 1024,
00139               "%s <%ld.list-id.%s>",
00140               CC->room.QRname,
00141               CC->room.QRnumber,
00142               config.c_fqdn
00143        );
00144 
00145        /*
00146         * Go fetch the contents of the digest
00147         */
00148        fseek(sc->digestfp, 0L, SEEK_END);
00149        msglen = ftell(sc->digestfp);
00150 
00151        msg->cm_fields['M'] = malloc(msglen + 1);
00152        fseek(sc->digestfp, 0L, SEEK_SET);
00153        fread(msg->cm_fields['M'], (size_t)msglen, 1, sc->digestfp);
00154        msg->cm_fields['M'][msglen] = '\0';
00155 
00156        fclose(sc->digestfp);
00157        sc->digestfp = NULL;
00158 
00159        /* Now generate the delivery instructions */
00160 
00161        /*
00162         * Figure out how big a buffer we need to allocate
00163         */
00164        for (nptr = sc->digestrecps; nptr != NULL; nptr = nptr->next) {
00165               recps_len = recps_len + strlen(nptr->name) + 2;
00166        }
00167 
00168        recps = malloc(recps_len);
00169 
00170        if (recps == NULL) {
00171               QN_syslog(LOG_EMERG,
00172                        "Cannot allocate %ld bytes for recps...\n",
00173                        (long)recps_len);
00174               abort();
00175        }
00176 
00177        strcpy(recps, "");
00178 
00179        /* Each recipient */
00180        for (nptr = sc->digestrecps; nptr != NULL; nptr = nptr->next) {
00181               if (nptr != sc->digestrecps) {
00182                      strcat(recps, ",");
00183               }
00184               strcat(recps, nptr->name);
00185        }
00186 
00187        /* Where do we want bounces and other noise to be heard?
00188         *Surely not the list members! */
00189        snprintf(bounce_to, sizeof bounce_to, "room_aide@%s", config.c_fqdn);
00190 
00191        /* Now submit the message */
00192        valid = validate_recipients(recps, NULL, 0);
00193        free(recps);
00194        if (valid != NULL) {
00195               valid->bounce_to = strdup(bounce_to);
00196               valid->envelope_from = strdup(bounce_to);
00197               CtdlSubmitMsg(msg, valid, NULL, 0);
00198        }
00199        CtdlFreeMessage(msg);
00200        free_recipients(valid);
00201 }
00202 
00203 
00204 /*
00205  * Deliver list messages to everyone on the list ... efficiently
00206  */
00207 void network_deliver_list(struct CtdlMessage *msg, SpoolControl *sc, const char *RoomName)
00208 {
00209        struct CitContext *CCC = CC;
00210        char *recps = NULL;
00211        size_t recps_len = SIZ;
00212        struct recptypes *valid;
00213        namelist *nptr;
00214        char bounce_to[256];
00215 
00216        /* Don't do this if there were no recipients! */
00217        if (sc->listrecps == NULL) return;
00218 
00219        /* Now generate the delivery instructions */
00220 
00221        /*
00222         * Figure out how big a buffer we need to allocate
00223         */
00224        for (nptr = sc->listrecps; nptr != NULL; nptr = nptr->next) {
00225               recps_len = recps_len + strlen(nptr->name) + 2;
00226        }
00227 
00228        recps = malloc(recps_len);
00229 
00230        if (recps == NULL) {
00231               QN_syslog(LOG_EMERG,
00232                        "Cannot allocate %ld bytes for recps...\n",
00233                        (long)recps_len);
00234               abort();
00235        }
00236 
00237        strcpy(recps, "");
00238 
00239        /* Each recipient */
00240        for (nptr = sc->listrecps; nptr != NULL; nptr = nptr->next) {
00241               if (nptr != sc->listrecps) {
00242                      strcat(recps, ",");
00243               }
00244               strcat(recps, nptr->name);
00245        }
00246 
00247        /* Where do we want bounces and other noise to be heard?
00248         *  Surely not the list members! */
00249        snprintf(bounce_to, sizeof bounce_to, "room_aide@%s", config.c_fqdn);
00250 
00251        /* Now submit the message */
00252        valid = validate_recipients(recps, NULL, 0);
00253        free(recps);
00254        if (valid != NULL) {
00255               valid->bounce_to = strdup(bounce_to);
00256               valid->envelope_from = strdup(bounce_to);
00257               valid->sending_room = strdup(RoomName);
00258               CtdlSubmitMsg(msg, valid, NULL, 0);
00259               free_recipients(valid);
00260        }
00261        /* Do not call CtdlFreeMessage(msg) here; the caller will free it. */
00262 }
00263 
00264 
00265 /*
00266  * Spools out one message from the list.
00267  */
00268 void network_spool_msg(long msgnum,
00269                      void *userdata)
00270 {
00271        struct CitContext *CCC = CC;
00272        StrBuf *Buf = NULL;
00273        SpoolControl *sc;
00274        int i;
00275        char *newpath = NULL;
00276        struct CtdlMessage *msg = NULL;
00277        namelist *nptr;
00278        maplist *mptr;
00279        struct ser_ret sermsg;
00280        FILE *fp;
00281        char filename[PATH_MAX];
00282        char buf[SIZ];
00283        int bang = 0;
00284        int send = 1;
00285        int delete_after_send = 0;  /* Set to 1 to delete after spooling */
00286        int ok_to_participate = 0;
00287        struct recptypes *valid;
00288 
00289        sc = (SpoolControl *)userdata;
00290 
00291        /*
00292         * Process mailing list recipients
00293         */
00294        if (sc->listrecps != NULL) {
00295               /* Fetch the message.  We're going to need to modify it
00296                * in order to insert the [list name] in it, etc.
00297                */
00298               msg = CtdlFetchMessage(msgnum, 1);
00299               if (msg != NULL) {
00300                      int rlen;
00301                      char *pCh;
00302                      StrBuf *Subject, *FlatSubject;
00303 
00304                      if (msg->cm_fields['K'] != NULL)
00305                             free(msg->cm_fields['K']);
00306                      if (msg->cm_fields['V'] == NULL){
00307                             /* local message, no enVelope */
00308                             StrBuf *Buf;
00309                             Buf = NewStrBuf();
00310                             StrBufAppendBufPlain(Buf,
00311                                                msg->cm_fields['O']
00312                                                , -1, 0);
00313                             StrBufAppendBufPlain(Buf, HKEY("@"), 0);
00314                             StrBufAppendBufPlain(Buf, config.c_fqdn, -1, 0);
00315 
00316                             msg->cm_fields['K'] = SmashStrBuf(&Buf);
00317                      }
00318                      else {
00319                             msg->cm_fields['K'] =
00320                                    strdup (msg->cm_fields['V']);
00321                      }
00322                      /* Set the 'List-ID' header */
00323                      if (msg->cm_fields['L'] != NULL) {
00324                             free(msg->cm_fields['L']);
00325                      }
00326                      msg->cm_fields['L'] = malloc(1024);
00327                      snprintf(msg->cm_fields['L'], 1024,
00328                             "%s <%ld.list-id.%s>",
00329                             CC->room.QRname,
00330                             CC->room.QRnumber,
00331                             config.c_fqdn
00332                      );
00333 
00334                      /* Prepend "[List name]" to the subject */
00335                      if (msg->cm_fields['U'] == NULL) {
00336                             Subject = NewStrBufPlain(HKEY("(no subject)"));
00337                      }
00338                      else {
00339                             Subject = NewStrBufPlain(
00340                                    msg->cm_fields['U'], -1);
00341                      }
00342                      FlatSubject = NewStrBufPlain(NULL, StrLength(Subject));
00343                      StrBuf_RFC822_to_Utf8(FlatSubject, Subject, NULL, NULL);
00344 
00345                      rlen = strlen(CC->room.QRname);
00346                      pCh  = strstr(ChrPtr(FlatSubject), CC->room.QRname);
00347                      if ((pCh == NULL) ||
00348                          (*(pCh + rlen) != ']') ||
00349                          (pCh == ChrPtr(FlatSubject)) ||
00350                          (*(pCh - 1) != '[')
00351                             )
00352                      {
00353                             StrBuf *tmp;
00354                             StrBufPlain(Subject, HKEY("["));
00355                             StrBufAppendBufPlain(Subject,
00356                                                CC->room.QRname,
00357                                                rlen, 0);
00358                             StrBufAppendBufPlain(Subject, HKEY("] "), 0);
00359                             StrBufAppendBuf(Subject, FlatSubject, 0);
00360                              /* so we can free the right one swap them */
00361                             tmp = Subject;
00362                             Subject = FlatSubject;
00363                             FlatSubject = tmp;
00364                             StrBufRFC2047encode(&Subject, FlatSubject);
00365                      }
00366 
00367                      if (msg->cm_fields['U'] != NULL)
00368                             free (msg->cm_fields['U']);
00369                      msg->cm_fields['U'] = SmashStrBuf(&Subject);
00370 
00371                      FreeStrBuf(&FlatSubject);
00372 
00373                      /* else we won't modify the buffer, since the
00374                       * roomname is already here.
00375                       */
00376 
00377                      /* if there is no other recipient, Set the recipient
00378                       * of the list message to the email address of the
00379                       * room itself.
00380                       */
00381                      if ((msg->cm_fields['R'] == NULL) ||
00382                          IsEmptyStr(msg->cm_fields['R']))
00383                      {
00384                             if (msg->cm_fields['R'] != NULL)
00385                                    free(msg->cm_fields['R']);
00386 
00387                             msg->cm_fields['R'] = malloc(256);
00388                             snprintf(msg->cm_fields['R'], 256,
00389                                     "room_%s@%s", CC->room.QRname,
00390                                     config.c_fqdn);
00391                             for (i=0; msg->cm_fields['R'][i]; ++i) {
00392                                    if (isspace(msg->cm_fields['R'][i])) {
00393                                           msg->cm_fields['R'][i] = '_';
00394                                    }
00395                             }
00396                      }
00397 
00398                      /* Handle delivery */
00399                      network_deliver_list(msg, sc, CC->room.QRname);
00400                      CtdlFreeMessage(msg);
00401               }
00402        }
00403 
00404        /*
00405         * Process digest recipients
00406         */
00407        if ((sc->digestrecps != NULL) && (sc->digestfp != NULL)) {
00408               msg = CtdlFetchMessage(msgnum, 1);
00409               if (msg != NULL) {
00410                      fprintf(sc->digestfp,
00411                             " -----------------------------------"
00412                             "------------------------------------"
00413                             "-------\n");
00414                      fprintf(sc->digestfp, "From: ");
00415                      if (msg->cm_fields['A'] != NULL) {
00416                             fprintf(sc->digestfp,
00417                                    "%s ",
00418                                    msg->cm_fields['A']);
00419                      }
00420                      if (msg->cm_fields['F'] != NULL) {
00421                             fprintf(sc->digestfp,
00422                                    "<%s> ",
00423                                    msg->cm_fields['F']);
00424                      }
00425                      else if (msg->cm_fields['N'] != NULL) {
00426                             fprintf(sc->digestfp,
00427                                    "@%s ",
00428                                    msg->cm_fields['N']);
00429                      }
00430                      fprintf(sc->digestfp, "\n");
00431                      if (msg->cm_fields['U'] != NULL) {
00432                             fprintf(sc->digestfp,
00433                                    "Subject: %s\n",
00434                                    msg->cm_fields['U']);
00435                      }
00436 
00437                      CC->redirect_buffer = NewStrBufPlain(NULL, SIZ);
00438 
00439                      safestrncpy(CC->preferred_formats,
00440                                 "text/plain",
00441                                 sizeof CC->preferred_formats);
00442 
00443                      CtdlOutputPreLoadedMsg(msg,
00444                                           MT_CITADEL,
00445                                           HEADERS_NONE,
00446                                           0, 0, 0);
00447 
00448                      StrBufTrim(CC->redirect_buffer);
00449                      fwrite(HKEY("\n"), 1, sc->digestfp);
00450                      fwrite(SKEY(CC->redirect_buffer), 1, sc->digestfp);
00451                      fwrite(HKEY("\n"), 1, sc->digestfp);
00452 
00453                      FreeStrBuf(&CC->redirect_buffer);
00454 
00455                      sc->num_msgs_spooled += 1;
00456                      CtdlFreeMessage(msg);
00457               }
00458        }
00459 
00460        /*
00461         * Process client-side list participations for this room
00462         */
00463        if (sc->participates != NULL) {
00464               msg = CtdlFetchMessage(msgnum, 1);
00465               if (msg != NULL) {
00466 
00467                      /* Only send messages which originated on our own
00468                       * Citadel network, otherwise we'll end up sending the
00469                       * remote mailing list's messages back to it, which
00470                       * is rude...
00471                       */
00472                      ok_to_participate = 0;
00473                      if (msg->cm_fields['N'] != NULL) {
00474                             if (!strcasecmp(msg->cm_fields['N'],
00475                                           config.c_nodename)) {
00476                                    ok_to_participate = 1;
00477                             }
00478 
00479                             Buf = NewStrBufPlain(msg->cm_fields['N'], -1);
00480                             if (is_valid_node(NULL,
00481                                             NULL,
00482                                             Buf,
00483                                             sc->working_ignetcfg,
00484                                             sc->the_netmap) == 0)
00485                             {
00486                                    ok_to_participate = 1;
00487                             }
00488                      }
00489                      if (ok_to_participate) {
00490                             if (msg->cm_fields['F'] != NULL) {
00491                                    free(msg->cm_fields['F']);
00492                             }
00493                             msg->cm_fields['F'] = malloc(SIZ);
00494                             /* Replace the Internet email address of the
00495                              * actual author with the email address of the
00496                              * room itself, so the remote listserv doesn't
00497                              * reject us.
00498                              * FIXME  I want to be able to pick any address
00499                             */
00500                             snprintf(msg->cm_fields['F'], SIZ,
00501                                    "room_%s@%s", CC->room.QRname,
00502                                    config.c_fqdn);
00503                             for (i=0; msg->cm_fields['F'][i]; ++i) {
00504                                    if (isspace(msg->cm_fields['F'][i])) {
00505                                           msg->cm_fields['F'][i] = '_';
00506                                    }
00507                             }
00508 
00509                             /*
00510                              * Figure out how big a buffer we need to alloc
00511                              */
00512                             for (nptr = sc->participates;
00513                                  nptr != NULL;
00514                                  nptr = nptr->next)
00515                             {
00516                                    if (msg->cm_fields['R'] != NULL) {
00517                                           free(msg->cm_fields['R']);
00518                                    }
00519                                    msg->cm_fields['R'] =
00520                                           strdup(nptr->name);
00521 
00522                                    valid = validate_recipients(nptr->name,
00523                                                             NULL, 0);
00524 
00525                                    CtdlSubmitMsg(msg, valid, "", 0);
00526                                    free_recipients(valid);
00527                             }
00528                      }
00529                      CtdlFreeMessage(msg);
00530               }
00531        }
00532 
00533        /*
00534         * Process IGnet push shares
00535         */
00536        msg = CtdlFetchMessage(msgnum, 1);
00537        if (msg != NULL) {
00538               size_t newpath_len;
00539 
00540               /* Prepend our node name to the Path field whenever
00541                * sending a message to another IGnet node
00542                */
00543               if (msg->cm_fields['P'] == NULL) {
00544                      msg->cm_fields['P'] = strdup("username");
00545               }
00546               newpath_len = strlen(msg->cm_fields['P']) +
00547                       strlen(config.c_nodename) + 2;
00548               newpath = malloc(newpath_len);
00549               snprintf(newpath, newpath_len, "%s!%s",
00550                       config.c_nodename, msg->cm_fields['P']);
00551               free(msg->cm_fields['P']);
00552               msg->cm_fields['P'] = newpath;
00553 
00554               /*
00555                * Determine if this message is set to be deleted
00556                * after sending out on the network
00557                */
00558               if (msg->cm_fields['S'] != NULL) {
00559                      if (!strcasecmp(msg->cm_fields['S'], "CANCEL")) {
00560                             delete_after_send = 1;
00561                      }
00562               }
00563 
00564               /* Now send it to every node */
00565               if (sc->ignet_push_shares != NULL)
00566                 for (mptr = sc->ignet_push_shares; mptr != NULL;
00567                   mptr = mptr->next) {
00568 
00569                      send = 1;
00570                      if (Buf == NULL)
00571                             Buf = NewStrBufPlain(mptr->remote_nodename, -1);
00572                      else
00573                             StrBufPlain(Buf, mptr->remote_nodename, -1);
00574                      /* Check for valid node name */
00575                      if (is_valid_node(NULL,
00576                                      NULL,
00577                                      Buf,
00578                                      sc->working_ignetcfg,
00579                                      sc->the_netmap) != 0)
00580                      {
00581                             QN_syslog(LOG_ERR,
00582                                      "Invalid node <%s>\n",
00583                                      mptr->remote_nodename);
00584 
00585                             send = 0;
00586                      }
00587 
00588                      /* Check for split horizon */
00589                      QN_syslog(LOG_DEBUG, "Path is %s\n", msg->cm_fields['P']);
00590                      bang = num_tokens(msg->cm_fields['P'], '!');
00591                      if (bang > 1) {
00592                             for (i=0; i<(bang-1); ++i) {
00593                                    extract_token(buf,
00594                                                 msg->cm_fields['P'],
00595                                                 i, '!',
00596                                                 sizeof buf);
00597                                    
00598                                    QN_syslog(LOG_DEBUG, "Compare <%s> to <%s>\n",
00599                                             buf, mptr->remote_nodename) ;
00600                                    if (!strcasecmp(buf, mptr->remote_nodename)) {
00601                                           send = 0;
00602                                           break;
00603                                    }
00604                             }
00605 
00606                             QN_syslog(LOG_INFO,
00607                                      "%sSending to %s\n",
00608                                      (send)?"":"Not ",
00609                                      mptr->remote_nodename);
00610                      }
00611 
00612                      /* Send the message */
00613                      if (send == 1)
00614                      {
00615                             /*
00616                              * Force the message to appear in the correct
00617                              * room on the far end by setting the C field
00618                              * correctly
00619                              */
00620                             if (msg->cm_fields['C'] != NULL) {
00621                                    free(msg->cm_fields['C']);
00622                             }
00623                             if (!IsEmptyStr(mptr->remote_roomname)) {
00624                                    msg->cm_fields['C'] =
00625                                           strdup(mptr->remote_roomname);
00626                             }
00627                             else {
00628                                    msg->cm_fields['C'] =
00629                                           strdup(CC->room.QRname);
00630                             }
00631 
00632                             /* serialize it for transmission */
00633                             serialize_message(&sermsg, msg);
00634                             if (sermsg.len > 0) {
00635 
00636                                    /* write it to a spool file */
00637                                    snprintf(filename,
00638                                            sizeof(filename),
00639                                            "%s/%s@%lx%x",
00640                                            ctdl_netout_dir,
00641                                            mptr->remote_nodename,
00642                                            time(NULL),
00643                                            rand()
00644                                    );
00645 
00646                                    QN_syslog(LOG_DEBUG,
00647                                             "Appending to %s\n",
00648                                             filename);
00649 
00650                                    fp = fopen(filename, "ab");
00651                                    if (fp != NULL) {
00652                                           fwrite(sermsg.ser,
00653                                                  sermsg.len, 1, fp);
00654                                           fclose(fp);
00655                                    }
00656                                    else {
00657                                           QN_syslog(LOG_ERR,
00658                                                    "%s: %s\n",
00659                                                    filename,
00660                                                    strerror(errno));
00661                                    }
00662 
00663                                    /* free the serialized version */
00664                                    free(sermsg.ser);
00665                             }
00666 
00667                      }
00668               }
00669               CtdlFreeMessage(msg);
00670        }
00671 
00672        /* update lastsent */
00673        sc->lastsent = msgnum;
00674 
00675        /* Delete this message if delete-after-send is set */
00676        if (delete_after_send) {
00677               CtdlDeleteMessages(CC->room.QRname, &msgnum, 1, "");
00678        }
00679        FreeStrBuf(&Buf);
00680 }