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