4b2c9ed850cba46ba7e790739fec025329bd3c70
[citadel.git] / citadel / msgbase.c
1 /* $Id$ */
2 #include "sysdep.h"
3 #include <stdlib.h>
4 #include <unistd.h>
5 #include <stdio.h>
6 #include <fcntl.h>
7 #include <time.h>
8 #include <ctype.h>
9 #include <string.h>
10 #include <syslog.h>
11 #ifdef HAVE_PTHREAD_H
12 #include <pthread.h>
13 #endif
14 #include <limits.h>
15 #include "citadel.h"
16 #include "server.h"
17 #include <errno.h>
18 #include <sys/stat.h>
19 #include "database.h"
20 #include "msgbase.h"
21 #include "support.h"
22 #include "sysdep_decls.h"
23 #include "room_ops.h"
24 #include "user_ops.h"
25 #include "file_ops.h"
26 #include "control.h"
27 #include "dynloader.h"
28 #include "tools.h"
29 #include "mime_parser.h"
30
31 #define MSGS_ALL        0
32 #define MSGS_OLD        1
33 #define MSGS_NEW        2
34 #define MSGS_FIRST      3
35 #define MSGS_LAST       4
36 #define MSGS_GT         5
37
38 extern struct config config;
39
40
41 /*
42  * This function is self explanatory.
43  * (What can I say, I'm in a weird mood today...)
44  */
45 void remove_any_whitespace_to_the_left_or_right_of_at_symbol(char *name) {
46         int i;
47
48         for (i=0; i<strlen(name); ++i) if (name[i]=='@') {
49                 if (i>0) if (isspace(name[i-1])) {
50                         strcpy(&name[i-1], &name[i]);
51                         i = 0;
52                         }
53                 while (isspace(name[i+1])) {
54                         strcpy(&name[i+1], &name[i+2]);
55                         }
56                 }
57         }
58
59
60 /*
61  * Aliasing for network mail.
62  * (Error messages have been commented out, because this is a server.)
63  */
64 int alias(char *name)           /* process alias and routing info for mail */
65              {
66         FILE *fp;
67         int a,b;
68         char aaa[300],bbb[300];
69
70         lprintf(9, "alias() called for <%s>\n", name);
71
72         remove_any_whitespace_to_the_left_or_right_of_at_symbol(name);
73
74         fp=fopen("network/mail.aliases","r");
75         if (fp==NULL) fp=fopen("/dev/null","r");
76         if (fp==NULL) return(MES_ERROR);
77         strcpy(aaa,""); strcpy(bbb,"");
78         while (fgets(aaa, sizeof aaa, fp)!=NULL) {
79                 while (isspace(name[0])) strcpy(name, &name[1]);
80                 aaa[strlen(aaa)-1] = 0;
81                 strcpy(bbb, "");
82                 for (a=0; a<strlen(aaa); ++a) {
83                         if (aaa[a] == ',') {
84                                 strcpy(bbb, &aaa[a+1]);
85                                 aaa[a] = 0;
86                                 }
87                         }
88                 if (!strcasecmp(name, aaa)) strcpy(name, bbb);
89                 }
90         fclose(fp);
91         lprintf(7, "Mail is being forwarded to %s\n", name);
92
93         /* determine local or remote type, see citadel.h */
94         for (a=0; a<strlen(name); ++a) if (name[a]=='!') return(MES_INTERNET);
95         for (a=0; a<strlen(name); ++a)
96                 if (name[a]=='@')
97                         for (b=a; b<strlen(name); ++b)
98                                 if (name[b]=='.') return(MES_INTERNET);
99         b=0; for (a=0; a<strlen(name); ++a) if (name[a]=='@') ++b;
100         if (b>1) {
101                 lprintf(7, "Too many @'s in address\n");
102                 return(MES_ERROR);
103                 }
104         if (b==1) {
105                 for (a=0; a<strlen(name); ++a)
106                         if (name[a]=='@') strcpy(bbb,&name[a+1]);
107                 while (bbb[0]==32) strcpy(bbb,&bbb[1]);
108                 fp = fopen("network/mail.sysinfo","r");
109                 if (fp==NULL) return(MES_ERROR);
110 GETSN:          do {
111                         a=getstring(fp,aaa);
112                         } while ((a>=0)&&(strcasecmp(aaa,bbb)));
113                 a=getstring(fp,aaa);
114                 if (!strncmp(aaa,"use ",4)) {
115                         strcpy(bbb,&aaa[4]);
116                         fseek(fp,0L,0);
117                         goto GETSN;
118                         }
119                 fclose(fp);
120                 if (!strncmp(aaa,"uum",3)) {
121                         strcpy(bbb,name);
122                         for (a=0; a<strlen(bbb); ++a) {
123                                 if (bbb[a]=='@') bbb[a]=0;
124                                 if (bbb[a]==' ') bbb[a]='_';
125                                 }
126                         while(bbb[strlen(bbb)-1]=='_') bbb[strlen(bbb)-1]=0;
127                         sprintf(name,&aaa[4],bbb);
128                         return(MES_INTERNET);
129                         }
130                 if (!strncmp(aaa,"bin",3)) {
131                         strcpy(aaa,name); strcpy(bbb,name);
132                         while (aaa[strlen(aaa)-1]!='@') aaa[strlen(aaa)-1]=0;
133                         aaa[strlen(aaa)-1]=0;
134                         while (aaa[strlen(aaa)-1]==' ') aaa[strlen(aaa)-1]=0;
135                         while (bbb[0]!='@') strcpy(bbb,&bbb[1]);
136                         strcpy(bbb,&bbb[1]);
137                         while (bbb[0]==' ') strcpy(bbb,&bbb[1]);
138                         sprintf(name,"%s @%s",aaa,bbb);
139                         return(MES_BINARY);
140                         }
141                 return(MES_ERROR);
142                 }
143         return(MES_LOCAL);
144         }
145
146
147 void get_mm(void) {
148         FILE *fp;
149
150         fp=fopen("citadel.control","r");
151         fread((char *)&CitControl,sizeof(struct CitControl),1,fp);
152         fclose(fp);
153         }
154
155 /*
156  * cmd_msgs()  -  get list of message #'s in this room
157  */
158 void cmd_msgs(char *cmdbuf)
159 {
160         int a = 0;
161         int mode = 0;
162         char which[256];
163         int cm_howmany = 0;
164         long cm_gt = 0L;
165         struct visit vbuf;
166
167         extract(which,cmdbuf,0);
168
169         mode = MSGS_ALL;
170         strcat(which,"   ");
171         if (!strncasecmp(which,"OLD",3))        mode = MSGS_OLD;
172         if (!strncasecmp(which,"NEW",3))        mode = MSGS_NEW;
173         if (!strncasecmp(which,"FIRST",5))      {
174                 mode = MSGS_FIRST;
175                 cm_howmany = extract_int(cmdbuf,1);
176                 }
177         if (!strncasecmp(which,"LAST",4))       {
178                 mode = MSGS_LAST;
179                 cm_howmany = extract_int(cmdbuf,1);
180                 }
181         if (!strncasecmp(which,"GT",2)) {
182                 mode = MSGS_GT;
183                 cm_gt = extract_long(cmdbuf,1);
184                 }
185
186         if ((!(CC->logged_in))&&(!(CC->internal_pgm))) {
187                 cprintf("%d not logged in\n",ERROR+NOT_LOGGED_IN);
188                 return;
189                 }
190         get_mm();
191         get_msglist(&CC->quickroom);
192         getuser(&CC->usersupp,CC->curr_user);
193         CtdlGetRelationship(&vbuf, &CC->usersupp, &CC->quickroom);
194
195         cprintf("%d Message list...\n",LISTING_FOLLOWS);
196         if (CC->num_msgs != 0) {
197            for (a=0; a<(CC->num_msgs); ++a) 
198                if ((MessageFromList(a) >=0)
199                && ( 
200
201 (mode==MSGS_ALL)
202 || ((mode==MSGS_OLD) && (MessageFromList(a) <= vbuf.v_lastseen))
203 || ((mode==MSGS_NEW) && (MessageFromList(a) > vbuf.v_lastseen))
204 || ((mode==MSGS_NEW) && (MessageFromList(a) >= vbuf.v_lastseen)
205                      && (CC->usersupp.flags & US_LASTOLD))
206 || ((mode==MSGS_LAST)&& (a>=(CC->num_msgs-cm_howmany)))
207 || ((mode==MSGS_FIRST)&&(a<cm_howmany))
208 || ((mode==MSGS_GT) && (MessageFromList(a) > cm_gt))
209
210                         )
211                 ) {
212                         cprintf("%ld\n", MessageFromList(a));
213                         }
214            }
215         cprintf("000\n");
216         }
217
218
219
220 /* 
221  * help_subst()  -  support routine for help file viewer
222  */
223 void help_subst(char *strbuf, char *source, char *dest)
224 {
225         char workbuf[256];
226         int p;
227
228         while (p=pattern2(strbuf,source), (p>=0)) {
229                 strcpy(workbuf,&strbuf[p+strlen(source)]);
230                 strcpy(&strbuf[p],dest);
231                 strcat(strbuf,workbuf);
232                 }
233         }
234
235
236 void do_help_subst(char *buffer)
237 {
238         char buf2[16];
239
240         help_subst(buffer,"^nodename",config.c_nodename);
241         help_subst(buffer,"^humannode",config.c_humannode);
242         help_subst(buffer,"^fqdn",config.c_fqdn);
243         help_subst(buffer,"^username",CC->usersupp.fullname);
244         sprintf(buf2,"%ld",CC->usersupp.usernum);
245         help_subst(buffer,"^usernum",buf2);
246         help_subst(buffer,"^sysadm",config.c_sysadm);
247         help_subst(buffer,"^variantname",CITADEL);
248         sprintf(buf2,"%d",config.c_maxsessions);
249         help_subst(buffer,"^maxsessions",buf2);
250         }
251
252
253
254 /*
255  * memfmout()  -  Citadel text formatter and paginator.
256  *             Although the original purpose of this routine was to format
257  *             text to the reader's screen width, all we're really using it
258  *             for here is to format text out to 80 columns before sending it
259  *             to the client.  The client software may reformat it again.
260  */
261 void memfmout(int width, char *mptr, char subst)
262                         /* screen width to use */
263                         /* where are we going to get our text from? */
264                         /* nonzero if we should use hypertext mode */
265         {
266         int a,b,c;
267         int real = 0;
268         int old = 0;
269         CIT_UBYTE ch;
270         char aaa[140];
271         char buffer[256];
272         
273         strcpy(aaa,""); old=255;
274         strcpy(buffer,"");
275         c=1; /* c is the current pos */
276
277 FMTA:   if (subst) {
278                 while (ch=*mptr, ((ch!=0) && (strlen(buffer)<126) )) {
279                         ch=*mptr++;
280                         buffer[strlen(buffer)+1] = 0;
281                         buffer[strlen(buffer)] = ch;
282                         }
283
284                 if (buffer[0]=='^') do_help_subst(buffer);
285
286                 buffer[strlen(buffer)+1] = 0;
287                 a=buffer[0];
288                 strcpy(buffer,&buffer[1]);
289                 }
290         
291         else ch=*mptr++;
292
293         old=real;
294         real=ch;
295         if (ch<=0) goto FMTEND;
296         
297         if ( ((ch==13)||(ch==10)) && (old!=13) && (old!=10) ) ch=32;
298         if ( ((old==13)||(old==10)) && (isspace(real)) ) {
299                 cprintf("\n");
300                 c=1;
301                 }
302         if (ch>126) goto FMTA;
303
304         if (ch>32) {
305         if ( ((strlen(aaa)+c)>(width-5)) && (strlen(aaa)>(width-5)) )
306                 { cprintf("\n%s",aaa); c=strlen(aaa); aaa[0]=0;
307                 }
308          b=strlen(aaa); aaa[b]=ch; aaa[b+1]=0; }
309         if (ch==32) {
310                 if ((strlen(aaa)+c)>(width-5)) { 
311                         cprintf("\n");
312                         c=1;
313                         }
314                 cprintf("%s ",aaa); ++c; c=c+strlen(aaa);
315                 strcpy(aaa,"");
316                 goto FMTA;
317                 }
318         if ((ch==13)||(ch==10)) {
319                 cprintf("%s\n",aaa);
320                 c=1;
321                 strcpy(aaa,"");
322                 goto FMTA;
323                 }
324         goto FMTA;
325
326 FMTEND: cprintf("%s\n", aaa);
327         }
328
329
330
331 /*
332  * Callback function for mime parser that simply lists the part
333  */
334 void list_this_part(char *name, char *filename, char *partnum, char *disp,
335                         void *content, char *cbtype, size_t length) {
336
337         cprintf("part=%s|%s|%s|%s|%s|%d\n",
338                 name, filename, partnum, disp, cbtype, length);
339         }
340
341
342 /*
343  * Callback function for mime parser that wants to display text
344  */
345 void fixed_output(char *name, char *filename, char *partnum, char *disp,
346                         void *content, char *cbtype, size_t length) {
347
348         if (!strcasecmp(cbtype, "text/plain")) {
349                 client_write(content, length);
350                 }
351         else {
352                 cprintf("Part %s: %s (%s) (%d bytes)\n",
353                         partnum, filename, cbtype, length);
354                 }
355         }
356
357
358 /*
359  * Callback function for mime parser that opens a section for downloading
360  */
361 void mime_download(char *name, char *filename, char *partnum, char *disp,
362                         void *content, char *cbtype, size_t length) {
363
364         char tmpname[PATH_MAX];
365         static int seq = 0;
366
367         /* Silently go away if there's already a download open... */
368         if (CC->download_fp != NULL) return;
369
370         /* ...or if this is not the desired section */
371         if (strcasecmp(CC->desired_section, partnum)) return;
372
373         snprintf(tmpname, sizeof tmpname,
374                 "/tmp/CitServer.download.%4x.%4x", getpid(), ++seq);
375
376         CC->download_fp = fopen(tmpname, "wb+");
377         if (CC->download_fp == NULL) return;
378
379         /* Unlink the file while it's open, to guarantee that the
380          * temp file will always be deleted.
381          */
382         unlink(tmpname);
383
384         fwrite(content, length, 1, CC->download_fp);
385         fflush(CC->download_fp);
386         rewind(CC->download_fp);
387
388         OpenCmdResult(filename, cbtype);
389         }
390
391
392
393 /*
394  * Get a message off disk.  (return value is the message's timestamp)
395  * 
396  */
397 time_t output_message(char *msgid, int mode, int headers_only) {
398         long msg_num;
399         int a;
400         CIT_UBYTE ch, rch;
401         CIT_UBYTE format_type,anon_flag;
402         char buf[1024];
403         long msg_len;
404         int msg_ok = 0;
405
406         struct cdbdata *dmsgtext;
407         char *mptr;
408
409         /* buffers needed for RFC822 translation */
410         char suser[256];
411         char luser[256];
412         char snode[256];
413         char lnode[256];
414         char mid[256];
415         time_t xtime = 0L;
416         /*                                       */
417
418         msg_num = atol(msgid);
419
420
421         if ((!(CC->logged_in))&&(!(CC->internal_pgm))&&(mode!=MT_DATE)) {
422                 cprintf("%d Not logged in.\n",ERROR+NOT_LOGGED_IN);
423                 return(xtime);
424                 }
425
426         /* We used to need to check in the current room's message list
427          * to determine where the message's disk position.  We no longer need
428          * to do this, but we do it anyway as a security measure, in order to
429          * prevent rogue clients from reading messages not in the current room.
430          */
431
432         msg_ok = 0;
433         if (CC->num_msgs > 0) {
434                 for (a=0; a<CC->num_msgs; ++a) {
435                         if (MessageFromList(a) == msg_num) {
436                                 msg_ok = 1;
437                                 }
438                         }
439                 }
440
441         if (!msg_ok) {
442                 if (mode != MT_DATE)
443                         cprintf("%d Message %ld is not in this room.\n",
444                                 ERROR, msg_num);
445                 return(xtime);
446                 }
447         
448
449         dmsgtext = cdb_fetch(CDB_MSGMAIN, &msg_num, sizeof(long));
450         
451         if (dmsgtext == NULL) {
452                 if (mode != MT_DATE)
453                         cprintf("%d Can't find message %ld\n",
454                                 ERROR+INTERNAL_ERROR);
455                 return(xtime);
456                 }
457
458         msg_len = (long) dmsgtext->len;
459         mptr = dmsgtext->ptr;
460
461         /* this loop spews out the whole message if we're doing raw format */
462         if (mode == MT_RAW) {
463                 cprintf("%d %ld\n", BINARY_FOLLOWS, msg_len);
464                 client_write(dmsgtext->ptr, (int) msg_len);
465                 cdb_free(dmsgtext);
466                 return(xtime);
467                 }
468
469         /* Otherwise, we'll start parsing it field by field... */
470         ch = *mptr++;
471         if (ch != 255) {
472                 cprintf("%d Illegal message format on disk\n",
473                         ERROR+INTERNAL_ERROR);
474                 cdb_free(dmsgtext);
475                 return(xtime);
476                 }
477
478         anon_flag = *mptr++;
479         format_type = *mptr++;
480
481         /* Are we downloading a MIME component? */
482         if (mode == MT_DOWNLOAD) {
483                 if (format_type != 4) {
484                         cprintf("%d This is not a MIME message.\n",
485                                 ERROR);
486                         }
487                 else if (CC->download_fp != NULL) {
488                         cprintf("%d You already have a download open.\n",
489                                 ERROR);
490                         }
491                 else {
492                         /* Skip to the message body */
493                         while(ch = *mptr++, (ch!='M' && ch!=0)) {
494                                 buf[0] = 0;
495                                 do {
496                                         buf[strlen(buf)+1] = 0;
497                                         rch = *mptr++;
498                                         buf[strlen(buf)] = rch;
499                                         } while (rch > 0);
500                                 }
501                         /* Now parse it */
502                         mime_parser(mptr, NULL, *mime_download);
503                         /* If there's no file open by this time, the requested
504                          * section wasn't found, so print an error
505                          */
506                         if (CC->download_fp == NULL) {
507                                 cprintf("%d Section %s not found.\n",
508                                         ERROR+FILE_NOT_FOUND,
509                                         CC->desired_section);
510                                 }
511                         }
512                 cdb_free(dmsgtext);
513                 return(xtime);
514                 }
515
516         /* Are we just looking for the message date? */
517         if (mode == MT_DATE) while(ch = *mptr++, (ch!='M' && ch!=0)) {
518                 buf[0] = 0;
519                 do {
520                         buf[strlen(buf)+1] = 0;
521                         rch = *mptr++;
522                         buf[strlen(buf)] = rch;
523                         } while (rch > 0);
524
525                 if (ch=='T') {
526                         xtime = atol(buf);
527                         cdb_free(dmsgtext);
528                         return(xtime);
529                         }
530                 }
531
532
533         /* now for the user-mode message reading loops */
534         cprintf("%d Message %ld:\n",LISTING_FOLLOWS,msg_num);
535
536         if (mode == MT_CITADEL) cprintf("type=%d\n", format_type);
537
538         if ( (anon_flag == MES_ANON) && (mode == MT_CITADEL) ) {
539                 cprintf("nhdr=yes\n");
540                 }
541
542         /* begin header processing loop for Citadel message format */
543
544         if ((mode == MT_CITADEL)||(mode == MT_MIME))
545            while(ch = *mptr++, (ch!='M' && ch!=0)) {
546                 buf[0] = 0;
547                 do {
548                         buf[strlen(buf)+1] = 0;
549                         rch = *mptr++;
550                         buf[strlen(buf)] = rch;
551                         } while (rch > 0);
552
553                 if (ch=='A') {
554                         PerformUserHooks(buf, (-1L), EVT_OUTPUTMSG);
555                         if (anon_flag==MES_ANON) cprintf("from=****");
556                         else if (anon_flag==MES_AN2) cprintf("from=anonymous");
557                         else cprintf("from=%s",buf);
558                         if ((is_room_aide()) && ((anon_flag == MES_ANON)
559                            || (anon_flag == MES_AN2)))
560                                 cprintf(" [%s]",buf);
561                         cprintf("\n");
562                         }
563                 else if (ch=='P') cprintf("path=%s\n",buf);
564                 else if (ch=='U') cprintf("subj=%s\n",buf);
565                 else if (ch=='I') cprintf("msgn=%s\n",buf);
566                 else if (ch=='H') cprintf("hnod=%s\n",buf);
567                 else if (ch=='O') cprintf("room=%s\n",buf);
568                 else if (ch=='N') cprintf("node=%s\n",buf);
569                 else if (ch=='R') cprintf("rcpt=%s\n",buf);
570                 else if (ch=='T') cprintf("time=%s\n",buf);
571                 /* else cprintf("fld%c=%s\n",ch,buf); */
572                 }
573
574         /* begin header processing loop for RFC822 transfer format */
575
576         strcpy(suser, "");
577         strcpy(luser, "");
578         strcpy(snode, NODENAME);
579         strcpy(lnode, HUMANNODE);
580         if (mode == MT_RFC822) while(ch = *mptr++, (ch!='M' && ch!=0)) {
581                 buf[0] = 0;
582                 do {
583                         buf[strlen(buf)+1] = 0;
584                         rch = *mptr++;
585                         buf[strlen(buf)] = rch;
586                         } while (rch > 0);
587
588                 if (ch=='A') strcpy(luser, buf);
589                 else if (ch=='P') {
590                         cprintf("Path: %s\n",buf);
591                         for (a=0; a<strlen(buf); ++a) {
592                                 if (buf[a] == '!') {
593                                         strcpy(buf,&buf[a+1]);
594                                         a=0;
595                                         }
596                                 }
597                         strcpy(suser, buf);
598                         }
599                 else if (ch=='U') cprintf("Subject: %s\n",buf);
600                 else if (ch=='I') strcpy(mid, buf);
601                 else if (ch=='H') strcpy(lnode, buf);
602                 else if (ch=='O') cprintf("X-Citadel-Room: %s\n",buf);
603                 else if (ch=='N') strcpy(snode, buf);
604                 else if (ch=='R') cprintf("To: %s\n",buf);
605                 else if (ch=='T')  {
606                         xtime = atol(buf);
607                         cprintf("Date: %s", asctime(localtime(&xtime)));
608                         }
609                 }
610
611         if (mode == MT_RFC822) {
612                 if (!strcasecmp(snode, NODENAME)) {
613                         strcpy(snode, FQDN);
614                         }
615                 cprintf("Message-ID: <%s@%s>\n", mid, snode);
616                 PerformUserHooks(luser, (-1L), EVT_OUTPUTMSG);
617                 cprintf("From: %s@%s (%s)\n",
618                         suser, snode, luser);
619                 cprintf("Organization: %s\n", lnode);
620                 }
621
622         /* end header processing loop ... at this point, we're in the text */
623
624         if (ch==0) {
625                 cprintf("text\n*** ?Message truncated\n000\n");
626                 cdb_free(dmsgtext);
627                 return(xtime);
628                 }
629
630         /* do some sort of MIME output */
631         if (format_type == 4) {
632                 if ((mode == MT_CITADEL)||(mode == MT_MIME)) {
633                         mime_parser(mptr, NULL, *list_this_part);
634                         }
635                 if (mode == MT_MIME) { /* If MT_MIME then it's parts only */
636                         cprintf("000\n");
637                         cdb_free(dmsgtext);
638                         return(xtime);
639                         }
640                 }
641
642         if (headers_only) {
643                 /* give 'em a length */
644                 msg_len = 0L;
645                 while(ch = *mptr++, ch>0) {
646                         ++msg_len;
647                         }
648                 cprintf("mlen=%ld\n", msg_len);
649                 cprintf("000\n");
650                 cdb_free(dmsgtext);
651                 return(xtime);
652                 }
653
654         /* signify start of msg text */
655         if (mode == MT_CITADEL) cprintf("text\n");
656         if ( (mode == MT_RFC822) && (format_type != 4) ) cprintf("\n");
657
658         /* If the format type on disk is 1 (fixed-format), then we want
659          * everything to be output completely literally ... regardless of
660          * what message transfer format is in use.
661          */
662         if (format_type == 1) {
663                 strcpy(buf, "");
664                 while(ch = *mptr++, ch>0) {
665                         if (ch == 13) ch = 10;
666                         if ( (ch == 10) || (strlen(buf)>250) ) {
667                                 cprintf("%s\n", buf);
668                                 strcpy(buf, "");
669                                 }
670                         else {
671                                 buf[strlen(buf)+1] = 0;
672                                 buf[strlen(buf)] = ch;
673                                 }
674                         }
675                 if (strlen(buf)>0) cprintf("%s\n", buf);
676                 }
677         /* If the message on disk is format 0 (Citadel vari-format), we
678          * output using the formatter at 80 columns.  This is the final output
679          * form if the transfer format is RFC822, but if the transfer format
680          * is Citadel proprietary, it'll still work, because the indentation
681          * for new paragraphs is correct and the client will reformat the
682          * message to the reader's screen width.
683          */
684         if (format_type == 0) {
685                 memfmout(80,mptr,0);
686                 }
687         /* If the message on disk is format 4 (MIME), we've gotta hand it
688          * off to the MIME parser.  The client has already been told that
689          * this message is format 1 (fixed format), so the callback function
690          * we use will display those parts as-is.
691          */
692         if (format_type == 4) {
693                 mime_parser(mptr, NULL, *fixed_output);
694                 }
695
696         /* now we're done */
697         cprintf("000\n");
698         cdb_free(dmsgtext);
699         return(xtime);
700         }
701
702
703 /*
704  * display a message (mode 0 - Citadel proprietary)
705  */
706 void cmd_msg0(char *cmdbuf)
707 {
708         char msgid[256];
709         int headers_only = 0;
710
711         extract(msgid,cmdbuf,0);
712         headers_only = extract_int(cmdbuf, 1);
713
714         output_message(msgid, MT_CITADEL, headers_only);
715         return;
716         }
717
718
719 /*
720  * display a message (mode 2 - RFC822)
721  */
722 void cmd_msg2(char *cmdbuf)
723 {
724         char msgid[256];
725         int headers_only = 0;
726
727         extract(msgid,cmdbuf,0);
728         headers_only = extract_int(cmdbuf,1);
729
730         output_message(msgid,MT_RFC822,headers_only);
731         }
732
733 /* 
734  * display a message (mode 3 - IGnet raw format - internal programs only)
735  */
736 void cmd_msg3(char *cmdbuf)
737 {
738         char msgid[256];
739         int headers_only = 0;
740
741         if (CC->internal_pgm == 0) {
742                 cprintf("%d This command is for internal programs only.\n",
743                         ERROR);
744                 return;
745                 }
746
747         extract(msgid,cmdbuf,0);
748         headers_only = extract_int(cmdbuf,1);
749
750         output_message(msgid,MT_RAW,headers_only);
751         }
752
753 /* 
754  * display a message (mode 4 - MIME) (FIX ... still evolving, not complete)
755  */
756 void cmd_msg4(char *cmdbuf)
757 {
758         char msgid[256];
759
760         extract(msgid, cmdbuf, 0);
761
762         output_message(msgid, MT_MIME, 0);
763         }
764
765
766
767 /*
768  * Open a component of a MIME message as a download file 
769  */
770 void cmd_opna(char *cmdbuf)
771 {
772         char msgid[256];
773
774         extract(msgid, cmdbuf, 0);
775         extract(CC->desired_section, cmdbuf, 1);
776
777         output_message(msgid, MT_DOWNLOAD, 0);
778         }
779
780
781
782 /*
783  * Message base operation to send a message to the master file
784  * (returns new message number)
785  */
786 long send_message(char *message_in_memory,      /* pointer to buffer */
787                 size_t message_length,          /* length of buffer */
788                 int generate_id) {              /* 1 to generate an I field */
789
790         long newmsgid;
791         char *actual_message;
792         size_t actual_length;
793         long retval;
794         char msgidbuf[32];
795
796         /* Get a new message number */
797         newmsgid = get_new_message_number();
798
799         if (generate_id) {
800                 sprintf(msgidbuf, "I%ld", newmsgid);
801                 actual_length = message_length + strlen(msgidbuf) + 1;
802                 actual_message = mallok(actual_length);
803                 memcpy(actual_message, message_in_memory, 3);
804                 memcpy(&actual_message[3], msgidbuf, (strlen(msgidbuf)+1) );
805                 memcpy(&actual_message[strlen(msgidbuf)+4],
806                         &message_in_memory[3], message_length - 3);
807                 }
808         
809         else {
810                 actual_message = message_in_memory;
811                 actual_length = message_length;
812                 }
813
814         /* Write our little bundle of joy into the message base */
815         begin_critical_section(S_MSGMAIN);
816         if ( cdb_store(CDB_MSGMAIN, &newmsgid, sizeof(long),
817                         actual_message, actual_length) < 0 ) {
818                 lprintf(2, "Can't store message\n");
819                 retval = 0L;
820                 }
821         else {
822                 retval = newmsgid;
823                 }
824         end_critical_section(S_MSGMAIN);
825
826         if (generate_id) {
827                 phree(actual_message);
828                 }
829
830         /* Finally, return the pointers */
831         return(retval);
832         }
833
834
835
836 /*
837  * this is a simple file copy routine.
838  */
839 void copy_file(char *from, char *to)
840 {
841         FILE *ffp,*tfp;
842         int a;
843
844         ffp=fopen(from,"r");
845         if (ffp==NULL) return;
846         tfp=fopen(to,"w");
847         if (tfp==NULL) {
848                 fclose(ffp);
849                 return;
850                 }
851         while (a=getc(ffp), a>=0) {
852                 putc(a,tfp);
853                 }
854         fclose(ffp);
855         fclose(tfp);
856         return;
857         }
858
859
860
861 /*
862  * message base operation to save a message and install its pointers
863  */
864 void save_message(char *mtmp,   /* file containing proper message */
865                 char *rec,      /* Recipient (if mail) */
866                 char *force,    /* if non-zero length, force a room */
867                 int mailtype,   /* local or remote type, see citadel.h */
868                 int generate_id) /* set to 1 to generate an 'I' field */
869 {
870         char aaa[100];
871         char hold_rm[ROOMNAMELEN];
872         char actual_rm[ROOMNAMELEN];
873         char force_room[ROOMNAMELEN];
874         char recipient[256];
875         long newmsgid;
876         char *message_in_memory;
877         struct stat statbuf;
878         size_t templen;
879         FILE *fp;
880         struct usersupp userbuf;
881         int a;
882         static int seqnum = 0;
883
884         lprintf(9, "save_message(%s,%s,%s,%d,%d)\n",
885                 mtmp, rec, force, mailtype, generate_id);
886
887         strcpy(force_room, force);
888
889         /* Strip non-printable characters out of the recipient name */
890         strcpy(recipient, rec);
891         for (a=0; a<strlen(recipient); ++a)
892                 if (!isprint(recipient[a]))
893                         strcpy(&recipient[a], &recipient[a+1]);
894
895         /* Measure the message */
896         stat(mtmp, &statbuf);
897         templen = statbuf.st_size;
898
899         /* Now read it into memory */
900         message_in_memory = (char *) mallok(templen);
901         if (message_in_memory == NULL) {
902                 lprintf(2, "Can't allocate memory to save message!\n");
903                 return;
904                 }
905
906         fp = fopen(mtmp, "rb");
907         fread(message_in_memory, templen, 1, fp);
908         fclose(fp);
909
910         newmsgid = send_message(message_in_memory, templen, generate_id);
911         phree(message_in_memory);
912         if (newmsgid <= 0L) return;
913
914         strcpy(actual_rm, CC->quickroom.QRname);
915         strcpy(hold_rm, "");
916
917         /* If the user is a twit, move to the twit room for posting... */
918         if (TWITDETECT) if (CC->usersupp.axlevel==2) {
919                 strcpy(hold_rm, actual_rm);
920                 strcpy(actual_rm, config.c_twitroom);
921                 }
922
923         /* ...or if this is a private message, go to the target mailbox. */
924         lprintf(9, "mailbox aliasing loop\n");
925         if (strlen(recipient) > 0) {
926                 /* mailtype = alias(recipient); */
927                 if (mailtype == MES_LOCAL) {
928                         if (getuser(&userbuf, recipient)!=0) {
929                                 /* User not found, goto Aide */
930                                 strcpy(force_room, AIDEROOM);
931                                 }
932                         else {
933                                 strcpy(hold_rm, actual_rm);
934                                 MailboxName(actual_rm, &userbuf, MAILROOM);
935                                 }
936                         }
937                 }
938
939         /* ...or if this message is destined for Aide> then go there. */
940         lprintf(9, "actual room forcing loop\n");
941         if (strlen(force_room) > 0) {
942                 strcpy(hold_rm, actual_rm);
943                 strcpy(actual_rm, force_room);
944                 }
945
946         /* This call to usergoto() changes rooms if necessary.  It also
947          * causes the latest message list to be read into memory.
948          */
949         usergoto(actual_rm, 0);
950
951         /* read in the quickroom record, obtaining a lock... */
952         lgetroom(&CC->quickroom, actual_rm);
953
954         /* Fix an obscure bug */
955         if (!strcasecmp(CC->quickroom.QRname, AIDEROOM)) {
956                 CC->quickroom.QRflags = CC->quickroom.QRflags & ~QR_MAILBOX;
957                 }
958
959         /* Add the message pointer to the room */
960         CC->quickroom.QRhighest = AddMessageToRoom(&CC->quickroom, newmsgid);
961
962         /* update quickroom */
963         lputroom(&CC->quickroom, actual_rm);
964
965         /* Network mail - send a copy to the network program. */
966         if ( (strlen(recipient)>0) && (mailtype != MES_LOCAL) ) {
967                 sprintf(aaa,"./network/spoolin/netmail.%04lx.%04x.%04x",
968                         (long)getpid(), CC->cs_pid, ++seqnum);
969                 copy_file(mtmp,aaa);
970                 system("exec nohup ./netproc -i >/dev/null 2>&1 &");
971                 }
972
973         /* Bump this user's messages posted counter. */
974         lgetuser(&CC->usersupp, CC->curr_user);
975         CC->usersupp.posted = CC->usersupp.posted + 1;
976         lputuser(&CC->usersupp, CC->curr_user);
977
978         /* If we've posted in a room other than the current room, then we
979          * have to now go back to the current room...
980          */
981         if (strlen(hold_rm) > 0) {
982                 usergoto(hold_rm, 0);
983                 }
984         unlink(mtmp);           /* delete the temporary file */
985         }
986
987
988 /*
989  * Generate an administrative message and post it in the Aide> room.
990  */
991 void aide_message(char *text)
992 {
993         FILE *fp;
994
995         fp=fopen(CC->temp,"wb");
996         fprintf(fp,"%c%c%c",255,MES_NORMAL,0);
997         fprintf(fp,"Psysop%c",0);
998         fprintf(fp,"T%ld%c", (long)time(NULL), 0);
999         fprintf(fp,"ACitadel%c",0);
1000         fprintf(fp,"OAide%c",0);
1001         fprintf(fp,"N%s%c",NODENAME,0);
1002         fprintf(fp,"M%s\n%c",text,0);
1003         fclose(fp);
1004         save_message(CC->temp,"",AIDEROOM,MES_LOCAL,1);
1005         syslog(LOG_NOTICE,text);
1006         }
1007
1008
1009
1010 /*
1011  * Build a binary message to be saved on disk.
1012  */
1013 void make_message(
1014         char *filename,                 /* temporary file name */
1015         struct usersupp *author,        /* author's usersupp structure */
1016         char *recipient,                /* NULL if it's not mail */
1017         char *room,                     /* room where it's going */
1018         int type,                       /* see MES_ types in header file */
1019         int net_type,                   /* see MES_ types in header file */
1020         int format_type,                /* local or remote (see citadel.h) */
1021         char *fake_name) {              /* who we're masquerading as */
1022
1023         FILE *fp;
1024         int a;
1025         time_t now;
1026         char dest_node[32];
1027         char buf[256];
1028
1029         /* Don't confuse the poor folks if it's not routed mail. */
1030         strcpy(dest_node, "");
1031
1032
1033         /* If net_type is MES_BINARY, split out the destination node. */
1034         if (net_type == MES_BINARY) {
1035                 strcpy(dest_node,NODENAME);
1036                 for (a=0; a<strlen(recipient); ++a) {
1037                         if (recipient[a]=='@') {
1038                                 recipient[a]=0;
1039                                 strcpy(dest_node,&recipient[a+1]);
1040                                 }
1041                         }
1042                 }
1043
1044         /* if net_type is MES_INTERNET, set the dest node to 'internet' */
1045         if (net_type == MES_INTERNET) {
1046                 strcpy(dest_node,"internet");
1047                 }
1048
1049         while (isspace(recipient[strlen(recipient)-1]))
1050                 recipient[strlen(recipient)-1] = 0;
1051
1052         time(&now);
1053         fp=fopen(filename,"w");
1054         putc(255,fp);
1055         putc(type,fp);  /* Normal or anonymous, see MES_ flags */
1056         putc(format_type,fp);   /* Formatted or unformatted */
1057         fprintf(fp,"Pcit%ld%c",author->usernum,0);      /* path */
1058         fprintf(fp,"T%ld%c",(long)now,0);               /* date/time */
1059         if (fake_name[0])
1060            fprintf(fp,"A%s%c",fake_name,0);
1061         else
1062            fprintf(fp,"A%s%c",author->fullname,0);      /* author */
1063
1064         if (CC->quickroom.QRflags & QR_MAILBOX) {       /* room */
1065                 fprintf(fp,"O%s%c", &CC->quickroom.QRname[11], 0);
1066                 }
1067         else {
1068                 fprintf(fp,"O%s%c",CC->quickroom.QRname,0);
1069                 }
1070
1071         fprintf(fp,"N%s%c",NODENAME,0);                 /* nodename */
1072         fprintf(fp,"H%s%c",HUMANNODE,0);                /* human nodename */
1073
1074         if (recipient[0]!=0) fprintf(fp, "R%s%c", recipient, 0);
1075         if (dest_node[0]!=0) fprintf(fp, "D%s%c", dest_node, 0);
1076
1077         putc('M',fp);
1078
1079         while (client_gets(buf), strcmp(buf,"000"))
1080         {
1081            fprintf(fp,"%s\n",buf);
1082         }
1083         syslog(LOG_INFO, "Closing message");
1084         putc(0,fp);
1085         fclose(fp);
1086         }
1087
1088
1089
1090
1091
1092 /*
1093  * message entry  -  mode 0 (normal) <bc>
1094  */
1095 void cmd_ent0(char *entargs)
1096 {
1097         int post = 0;
1098         char recipient[256];
1099         int anon_flag = 0;
1100         int format_type = 0;
1101         char newusername[256];          /* <bc> */
1102
1103         int a,b;
1104         int e = 0;
1105         int mtsflag = 0;
1106         struct usersupp tempUS;
1107         char buf[256];
1108
1109         post = extract_int(entargs,0);
1110         extract(recipient,entargs,1);
1111         anon_flag = extract_int(entargs,2);
1112         format_type = extract_int(entargs,3);
1113
1114         /* first check to make sure the request is valid. */
1115
1116         if (!(CC->logged_in)) {
1117                 cprintf("%d Not logged in.\n",ERROR+NOT_LOGGED_IN);
1118                 return;
1119                 }
1120         if ((CC->usersupp.axlevel<2)&&((CC->quickroom.QRflags&QR_MAILBOX)==0)) {
1121                 cprintf("%d Need to be validated to enter ",
1122                         ERROR+HIGHER_ACCESS_REQUIRED);
1123                 cprintf("(except in %s> to sysop)\n", MAILROOM);
1124                 return;
1125                 }
1126         if ((CC->usersupp.axlevel<4)&&(CC->quickroom.QRflags&QR_NETWORK)) {
1127                 cprintf("%d Need net privileges to enter here.\n",
1128                         ERROR+HIGHER_ACCESS_REQUIRED);
1129                 return;
1130                 }
1131         if ((CC->usersupp.axlevel<6)&&(CC->quickroom.QRflags&QR_READONLY)) {
1132                 cprintf("%d Sorry, this is a read-only room.\n",
1133                         ERROR+HIGHER_ACCESS_REQUIRED);
1134                 return;
1135                 }
1136
1137         mtsflag=0;
1138         
1139                 
1140         if (post==2) {                  /* <bc> */
1141            if (CC->usersupp.axlevel<6)
1142            {
1143               cprintf("%d You don't have permission to do an aide post.\n",
1144                 ERROR+HIGHER_ACCESS_REQUIRED);
1145               return;
1146            }
1147            extract(newusername,entargs,4);
1148            memset(CC->fake_postname, 0, 32);
1149            strcpy(CC->fake_postname, newusername);
1150            cprintf("%d Ok\n",OK);
1151            return;
1152         }
1153         
1154         CC->cs_flags |= CS_POSTING;
1155         
1156         buf[0]=0;
1157         if (CC->quickroom.QRflags & QR_MAILBOX) {
1158                 if (CC->usersupp.axlevel>=2) {
1159                         strcpy(buf,recipient);
1160                         }
1161                 else strcpy(buf,"sysop");
1162                 lprintf(9, "calling alias()\n");
1163                 e=alias(buf);                   /* alias and mail type */
1164                 lprintf(9, "alias() returned %d\n", e);
1165                 if ((buf[0]==0) || (e==MES_ERROR)) {
1166                         cprintf("%d Unknown address - cannot send message.\n",
1167                                 ERROR+NO_SUCH_USER);
1168                         return;
1169                         }
1170                 if ((e!=MES_LOCAL)&&(CC->usersupp.axlevel<4)) {
1171                         cprintf("%d Net privileges required for network mail.\n",
1172                                 ERROR+HIGHER_ACCESS_REQUIRED);
1173                         return;
1174                         }
1175                 if ((RESTRICT_INTERNET==1)&&(e==MES_INTERNET)
1176                    &&((CC->usersupp.flags&US_INTERNET)==0)
1177                    &&(!CC->internal_pgm) ) {
1178                         cprintf("%d You don't have access to Internet mail.\n",
1179                                 ERROR+HIGHER_ACCESS_REQUIRED);
1180                         return;
1181                         }
1182                 if (!strcasecmp(buf,"sysop")) {
1183                         mtsflag=1;
1184                         goto SKFALL;
1185                         }
1186                 if (e!=MES_LOCAL) goto SKFALL;  /* don't search local file  */
1187                 if (!strcasecmp(buf,CC->usersupp.fullname)) {
1188                         cprintf("%d Can't send mail to yourself!\n",
1189                                 ERROR+NO_SUCH_USER);
1190                         return;
1191                         }
1192
1193                 /* Check to make sure the user exists; also get the correct
1194                 * upper/lower casing of the name. 
1195                 */
1196                 a = getuser(&tempUS, buf);
1197                 if (a != 0) {
1198                         cprintf("%d No such user.\n",ERROR+NO_SUCH_USER);
1199                         return;
1200                         }
1201                 strcpy(buf,tempUS.fullname);
1202                 }
1203         
1204 SKFALL: b=MES_NORMAL;
1205         if (CC->quickroom.QRflags&QR_ANONONLY) b=MES_ANON;
1206         if (CC->quickroom.QRflags&QR_ANONOPT) {
1207                 if (anon_flag==1) b=MES_AN2;
1208                 }
1209         if ((CC->quickroom.QRflags & QR_MAILBOX) == 0) buf[0]=0;
1210
1211         /* If we're only checking the validity of the request, return
1212          * success without creating the message.
1213          */
1214         if (post==0) {
1215                 cprintf("%d %s\n",OK,buf);
1216                 return;
1217                 }
1218         
1219         cprintf("%d send message\n",SEND_LISTING);
1220         if (CC->fake_postname[0])
1221            make_message(CC->temp,&CC->usersupp,buf,CC->quickroom.QRname,b,e,format_type, CC->fake_postname);
1222         else
1223            if (CC->fake_username[0])
1224               make_message(CC->temp,&CC->usersupp,buf,CC->quickroom.QRname,b,e,format_type, CC->fake_username);
1225            else
1226               make_message(CC->temp,&CC->usersupp,buf,CC->quickroom.QRname,b,e,format_type, "");
1227         save_message(CC->temp,buf, (mtsflag ? AIDEROOM : ""), e,1);
1228         CC->fake_postname[0]='\0';
1229         return;
1230         }
1231
1232
1233
1234 /* 
1235  * message entry - mode 3 (raw)
1236  */
1237 void cmd_ent3(char *entargs)
1238 {
1239         char recp[256];
1240         char buf[256];
1241         int a;
1242         int e = 0;
1243         struct usersupp tempUS;
1244         long msglen;
1245         long bloklen;
1246         FILE *fp;
1247
1248         if (CC->internal_pgm == 0) {
1249                 cprintf("%d This command is for internal programs only.\n",
1250                         ERROR);
1251                 return;
1252                 }
1253
1254         /* See if there's a recipient, but make sure it's a real one */
1255         extract(recp, entargs, 1);
1256         for (a=0; a<strlen(recp); ++a)
1257                 if (!isprint(recp[a]))
1258                         strcpy(&recp[a], &recp[a+1]);
1259         while (isspace(recp[0])) strcpy(recp, &recp[1]);
1260         while (isspace(recp[strlen(recp)-1])) recp[strlen(recp)-1] = 0;
1261
1262         /* If we're in Mail, check the recipient */
1263         if (strlen(recp) > 0) {
1264                 e=alias(recp);                  /* alias and mail type */
1265                 if ((recp[0]==0) || (e==MES_ERROR)) {
1266                         cprintf("%d Unknown address - cannot send message.\n",
1267                                 ERROR+NO_SUCH_USER);
1268                         return;
1269                         }
1270                 if (e == MES_LOCAL) {
1271                         a = getuser(&tempUS,recp);
1272                         if (a!=0) {
1273                                 cprintf("%d No such user.\n",
1274                                         ERROR+NO_SUCH_USER);
1275                                 return;
1276                                 }
1277                         }
1278                 }
1279
1280         /* At this point, message has been approved. */
1281         if (extract_int(entargs, 0) == 0) {
1282                 cprintf("%d OK to send\n", OK);
1283                 return;
1284                 }
1285
1286         /* open a temp file to hold the message */
1287         fp = fopen(CC->temp, "wb");
1288         if (fp == NULL) {
1289                 cprintf("%d Cannot open %s: %s\n", 
1290                         ERROR + INTERNAL_ERROR,
1291                         CC->temp, strerror(errno) );
1292                 return;
1293                 }
1294
1295         msglen = extract_long(entargs, 2);
1296         cprintf("%d %ld\n", SEND_BINARY, msglen);
1297         while(msglen > 0L) {
1298                 bloklen = ((msglen >= 255L) ? 255 : msglen);
1299                 client_read(buf, (int)bloklen );
1300                 fwrite(buf, (int)bloklen, 1, fp);
1301                 msglen = msglen - bloklen;
1302                 }
1303         fclose(fp);
1304
1305         save_message(CC->temp, recp, "", e, 0);
1306         }
1307
1308
1309 /*
1310  * Delete message from current room
1311  */
1312 void cmd_dele(char *delstr)
1313 {
1314         long delnum;
1315         int a,ok;
1316
1317         getuser(&CC->usersupp,CC->curr_user);
1318         if ((CC->usersupp.axlevel < 6)
1319            && (CC->usersupp.usernum != CC->quickroom.QRroomaide)
1320            && ((CC->quickroom.QRflags & QR_MAILBOX) == 0)) {
1321                 cprintf("%d Higher access required.\n",
1322                         ERROR+HIGHER_ACCESS_REQUIRED);
1323                 return;
1324                 }
1325
1326         delnum = extract_long(delstr, 0);
1327         
1328         /* get room records, obtaining a lock... */
1329         lgetroom(&CC->quickroom,CC->quickroom.QRname);
1330         get_msglist(&CC->quickroom);
1331
1332         ok = 0;
1333         if (CC->num_msgs > 0) for (a=0; a<(CC->num_msgs); ++a) {
1334                 if (MessageFromList(a) == delnum) {
1335                         SetMessageInList(a, 0L);
1336                         ok = 1;
1337                         }
1338                 }
1339
1340         CC->num_msgs = sort_msglist(CC->msglist, CC->num_msgs);
1341         CC->quickroom.QRhighest = MessageFromList(CC->num_msgs - 1);
1342
1343         put_msglist(&CC->quickroom);
1344         lputroom(&CC->quickroom,CC->quickroom.QRname);
1345         if (ok==1) {
1346                 cdb_delete(CDB_MSGMAIN, &delnum, sizeof(long));
1347                 cprintf("%d Message deleted.\n",OK);
1348                 }
1349         else cprintf("%d No message %ld.\n",ERROR,delnum);
1350         } 
1351
1352
1353 /*
1354  * move a message to another room
1355  */
1356 void cmd_move(char *args)
1357 {
1358         long num;
1359         char targ[32];
1360         int a;
1361         struct quickroom qtemp;
1362         int foundit;
1363
1364         num = extract_long(args,0);
1365         extract(targ,args,1);
1366         
1367         getuser(&CC->usersupp,CC->curr_user);
1368         if ((CC->usersupp.axlevel < 6)
1369            && (CC->usersupp.usernum != CC->quickroom.QRroomaide)) {
1370                 cprintf("%d Higher access required.\n",
1371                         ERROR+HIGHER_ACCESS_REQUIRED);
1372                 return;
1373                 }
1374
1375         if (getroom(&qtemp, targ) != 0) {
1376                 cprintf("%d '%s' does not exist.\n",ERROR,targ);
1377                 return;
1378                 }
1379
1380         /* yank the message out of the current room... */
1381         lgetroom(&CC->quickroom, CC->quickroom.QRname);
1382         get_msglist(&CC->quickroom);
1383
1384         foundit = 0;
1385         for (a=0; a<(CC->num_msgs); ++a) {
1386                 if (MessageFromList(a) == num) {
1387                         foundit = 1;
1388                         SetMessageInList(a, 0L);
1389                         }
1390                 }
1391         if (foundit) {
1392                 CC->num_msgs = sort_msglist(CC->msglist, CC->num_msgs);
1393                 put_msglist(&CC->quickroom);
1394                 CC->quickroom.QRhighest = MessageFromList((CC->num_msgs)-1);
1395                 }
1396         lputroom(&CC->quickroom,CC->quickroom.QRname);
1397         if (!foundit) {
1398                 cprintf("%d msg %ld does not exist.\n",ERROR,num);
1399                 return;
1400                 }
1401
1402         /* put the message into the target room */
1403         lgetroom(&qtemp, targ);
1404         qtemp.QRhighest = AddMessageToRoom(&qtemp, num);
1405         lputroom(&qtemp, targ);
1406
1407         cprintf("%d Message moved.\n", OK);
1408         }