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