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