validate_recipients() - completed removal of unused param
[citadel.git] / citadel / server / modules / ctdlproto / serv_messages.c
1 // Message-related protocol commands for Citadel clients
2 //
3 // Copyright (c) 1987-2024 by the citadel.org team
4 //
5 // This program is open source software.  Use, duplication, or disclosure
6 // is subject to the terms of the GNU General Public License version 3.
7
8 #include <stdio.h>
9 #include <libcitadel.h>
10 #include "../../citserver.h"
11 #include "../../ctdl_module.h"
12 #include "../../internet_addressing.h"
13 #include "../../user_ops.h"
14 #include "../../room_ops.h"
15 #include "../../config.h"
16
17 extern char *msgkeys[];
18
19
20 // Back end for the MSGS command: output message number only.
21 void simple_listing(long msgnum, void *userdata) {
22         cprintf("%ld\n", msgnum);
23 }
24
25
26 // Back end for the MSGS command: output header summary.
27 void headers_listing(long msgnum, void *userdata) {
28         struct CtdlMessage *msg;
29         int output_mode = *(int *)userdata;
30
31         msg = CtdlFetchMessage(msgnum, 0);
32         if (msg == NULL) {
33                 cprintf("%ld|0|||||||\n", msgnum);
34                 return;
35         }
36
37         // change all vertical bars in the subject to hyphens so it doesn't screw up the protocol
38         if (!CM_IsEmpty(msg, eMsgSubject)) {
39                 char *p;
40                 for (p=msg->cm_fields[eMsgSubject]; *p; p++) {
41                         if (*p == '|') {
42                                 *p = '-';
43                         }
44                 }
45         }
46
47         // output all fields except the references hash
48         cprintf("%ld|%s|%s|%s|%s|%s",
49                 msgnum,
50                 (!CM_IsEmpty(msg, eTimestamp) ? msg->cm_fields[eTimestamp] : "0"),
51                 (!CM_IsEmpty(msg, eAuthor) ? msg->cm_fields[eAuthor] : ""),
52                 CtdlGetConfigStr("c_nodename"),                                         // no more nodenames anymore
53                 (!CM_IsEmpty(msg, erFc822Addr) ? msg->cm_fields[erFc822Addr] : ""),
54                 (!CM_IsEmpty(msg, eMsgSubject) ? msg->cm_fields[eMsgSubject] : "")
55         );
56
57         if (output_mode == MSG_HDRS_THREADS) {          // field view with thread hashes
58
59                 // output the references hash
60                 cprintf ("|%d|", 
61                         (!CM_IsEmpty(msg, emessageId) ? HashLittle(msg->cm_fields[emessageId],strlen(msg->cm_fields[emessageId])) : 0)
62                 );
63         
64                 // output the references hash (yes it's ok that we're trashing the source buffer by doing this)
65                 if (!CM_IsEmpty(msg, eWeferences)) {
66                         char *token;
67                         char *rest = msg->cm_fields[eWeferences];
68                         char *prev = rest;
69                         while((token = strtok_r(rest, "|", &rest))) {
70                                 cprintf("%d%s", HashLittle(token,rest-prev-(*rest==0?0:1)), (*rest==0?"":","));
71                                 prev = rest;
72                         }
73                 }
74         
75                 cprintf("|\n");
76         }
77
78         else {                                          // field view with no threads, subject extends out forever
79                 cprintf("\n");
80         }
81
82         CM_Free(msg);
83 }
84
85 typedef struct _msg_filter {
86         HashList *Filter;
87         HashPos *p;
88         StrBuf *buffer;
89 } msg_filter;
90
91
92 void headers_brief_filter(long msgnum, void *userdata) {
93         long i, l;
94         struct CtdlMessage *msg;
95         msg_filter *flt = (msg_filter*) userdata;
96
97         l = GetCount(flt->Filter);
98         msg = CtdlFetchMessage(msgnum, 0);
99         StrBufPrintf(flt->buffer, "%ld", msgnum);
100         if (msg == NULL) {
101                 for (i = 0; i < l; i++) {
102                         StrBufAppendBufPlain(flt->buffer, HKEY("|"), 0);
103                 }
104         }
105         else {
106                 const char *k;
107                 long len;
108                 void *v;
109                 RewindHashPos(flt->Filter, flt->p, 0);
110                 while (GetNextHashPos(flt->Filter, flt->p, &len, &k, &v)) {
111                         eMsgField f = (eMsgField) v;
112                         
113                         StrBufAppendBufPlain(flt->buffer, HKEY("|"), 0);
114                         if (!CM_IsEmpty(msg, f)) {
115                                 StrBufAppendBufPlain(flt->buffer, CM_KEY(msg, f), 0);
116                         }
117                 }
118         }
119         StrBufAppendBufPlain(flt->buffer, HKEY("\n"), 0);
120         cputbuf(flt->buffer);
121 }
122
123 // Back end for the MSGS command: output EUID header.
124 void headers_euid(long msgnum, void *userdata) {
125         struct CtdlMessage *msg;
126
127         msg = CtdlFetchMessage(msgnum, 0);
128         if (msg == NULL) {
129                 cprintf("%ld||\n", msgnum);
130                 return;
131         }
132
133         cprintf("%ld|%s|%s\n", 
134                 msgnum, 
135                 (!CM_IsEmpty(msg, eExclusiveID) ? msg->cm_fields[eExclusiveID] : ""),
136                 (!CM_IsEmpty(msg, eTimestamp) ? msg->cm_fields[eTimestamp] : "0"));
137         CM_Free(msg);
138 }
139
140
141 // cmd_msgs()  -  get list of message #'s in this room
142 //              implements the MSGS server command using CtdlForEachMessage()
143 void cmd_msgs(char *cmdbuf) {
144         int mode = 0;
145         char which[16];
146         char buf[256];
147         char tfield[256];
148         char tvalue[256];
149         int cm_ref = 0;
150         int with_template = 0;
151         struct CtdlMessage *template = NULL;
152         msg_filter filt;
153         char search_string[1024];
154         ForEachMsgCallback CallBack;
155
156         if (CtdlAccessCheck(ac_logged_in_or_guest)) return;
157
158         extract_token(which, cmdbuf, 0, '|', sizeof which);
159         cm_ref = extract_int(cmdbuf, 1);
160         extract_token(search_string, cmdbuf, 1, '|', sizeof search_string);
161         with_template = extract_int(cmdbuf, 2);
162         int output_mode = extract_int(cmdbuf, 3);
163         switch (output_mode) {
164                 default:
165                 case MSG_HDRS_BRIEF:
166                         CallBack = simple_listing;
167                         break;
168                 case MSG_HDRS_ALL:
169                 case MSG_HDRS_THREADS:
170                         CallBack = headers_listing;
171                         break;
172                 case MSG_HDRS_EUID:
173                         CallBack = headers_euid;
174                         break;
175                 case MSG_HDRS_BRIEFFILTER:
176                         with_template = 2;
177                         CallBack = headers_brief_filter;
178                         break;
179         }
180
181         strcat(which, "   ");
182         if (!strncasecmp(which, "OLD", 3))
183                 mode = MSGS_OLD;
184         else if (!strncasecmp(which, "NEW", 3))
185                 mode = MSGS_NEW;
186         else if (!strncasecmp(which, "FIRST", 5))
187                 mode = MSGS_FIRST;
188         else if (!strncasecmp(which, "LAST", 4))
189                 mode = MSGS_LAST;
190         else if (!strncasecmp(which, "GT", 2))
191                 mode = MSGS_GT;
192         else if (!strncasecmp(which, "LT", 2))
193                 mode = MSGS_LT;
194         else if (!strncasecmp(which, "SEARCH", 6))
195                 mode = MSGS_SEARCH;
196         else
197                 mode = MSGS_ALL;
198
199         if ( (mode == MSGS_SEARCH) && (!CtdlGetConfigInt("c_enable_fulltext")) ) {
200                 cprintf("%d Full text index is not enabled on this server.\n",
201                         ERROR + CMD_NOT_SUPPORTED);
202                 return;
203         }
204
205         if (with_template == 1) {
206                 memset(buf, 0, 5);
207                 unbuffer_output();
208                 cprintf("%d Send template then receive message list\n", SEND_THEN_RECV);
209                 template = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
210                 memset(template, 0, sizeof(struct CtdlMessage));
211                 template->cm_magic = CTDLMESSAGE_MAGIC;
212                 template->cm_anon_type = MES_NORMAL;
213
214                 while(client_getln(buf, sizeof buf) >= 0 && strcmp(buf,"000")) {
215                         eMsgField f;
216                         long tValueLen;
217
218                         tValueLen = extract_token(tfield, buf, 0, '|', sizeof tfield);
219                         if ((tValueLen == 4) && GetFieldFromMnemonic(&f, tfield)) {
220                                 tValueLen = extract_token(tvalue, buf, 1, '|', sizeof tvalue);
221                                 if (tValueLen >= 0) {
222                                         CM_SetField(template, f, tvalue);
223                                 }
224                         }
225                 }
226                 buffer_output();
227         }
228         else if (with_template == 2) {
229                 long i = 0;
230                 memset(buf, 0, 5);
231                 cprintf("%d Send list of headers\n", SEND_THEN_RECV);
232                 filt.Filter = NewHash(1, lFlathash);
233                 filt.buffer = NewStrBufPlain(NULL, 1024);
234                 while(client_getln(buf, sizeof buf) >= 0 && strcmp(buf,"000")) {
235                         eMsgField f;
236         
237                         if (GetFieldFromMnemonic(&f, buf)) {
238                                 Put(filt.Filter, LKEY(i), (void*)f, reference_free_handler);
239                                 i++;
240                         }
241                 }
242                 filt.p = GetNewHashPos(filt.Filter, 0);
243                 buffer_output();
244         }
245         else {
246                 cprintf("%d  \n", LISTING_FOLLOWS);
247         }
248
249         if (with_template < 2) {
250                 CtdlForEachMessage(mode,
251                         ( (mode == MSGS_SEARCH) ? 0 : cm_ref ),
252                         ( (mode == MSGS_SEARCH) ? search_string : NULL ),
253                         NULL,
254                         template,
255                         CallBack,
256                         &output_mode
257                 );
258                 if (template != NULL) CM_Free(template);
259         }
260         else {
261                 CtdlForEachMessage(mode,
262                         ( (mode == MSGS_SEARCH) ? 0 : cm_ref ),
263                         ( (mode == MSGS_SEARCH) ? search_string : NULL ),
264                         NULL,
265                         NULL,
266                         CallBack,
267                         &filt
268                 );
269                 DeleteHashPos(&filt.p);
270                 DeleteHash(&filt.Filter);
271                 FreeStrBuf(&filt.buffer);
272                 
273         }
274         cprintf("000\n");
275 }
276
277
278 // display a message (mode 0 - Citadel proprietary)
279 void cmd_msg0(char *cmdbuf) {
280         long msgid;
281         int headers_only = HEADERS_ALL;
282
283         msgid = extract_long(cmdbuf, 0);
284         headers_only = extract_int(cmdbuf, 1);
285
286         CtdlOutputMsg(msgid, MT_CITADEL, headers_only, 1, 0, NULL, 0, NULL, NULL, NULL);
287         return;
288 }
289
290
291 // display a message (mode 2 - RFC822)
292 void cmd_msg2(char *cmdbuf) {
293         long msgid;
294         int headers_only = HEADERS_ALL;
295
296         msgid = extract_long(cmdbuf, 0);
297         headers_only = extract_int(cmdbuf, 1);
298
299         CtdlOutputMsg(msgid, MT_RFC822, headers_only, 1, 1, NULL, 0, NULL, NULL, NULL);
300 }
301
302
303 // Display a message using MIME content types
304 void cmd_msg4(char *cmdbuf) {
305         long msgid;
306         char section[64];
307
308         msgid = extract_long(cmdbuf, 0);
309         extract_token(section, cmdbuf, 1, '|', sizeof section);
310         CtdlOutputMsg(msgid, MT_MIME, 0, 1, 0, (section[0] ? section : NULL) , 0, NULL, NULL, NULL);
311 }
312
313
314 // Client tells us its preferred message format(s)
315 void cmd_msgp(char *cmdbuf) {
316         if (!strcasecmp(cmdbuf, "dont_decode")) {
317                 CC->msg4_dont_decode = 1;
318                 cprintf("%d MSG4 will not pre-decode messages.\n", CIT_OK);
319         }
320         else {
321                 safestrncpy(CC->preferred_formats, cmdbuf, sizeof(CC->preferred_formats));
322                 cprintf("%d Preferred MIME formats have been set.\n", CIT_OK);
323         }
324 }
325
326
327 // Open a component of a MIME message as a download file 
328 void cmd_opna(char *cmdbuf) {
329         long msgid;
330         char desired_section[128];
331
332         msgid = extract_long(cmdbuf, 0);
333         extract_token(desired_section, cmdbuf, 1, '|', sizeof desired_section);
334         safestrncpy(CC->download_desired_section, desired_section, sizeof CC->download_desired_section);
335         CtdlOutputMsg(msgid, MT_DOWNLOAD, 0, 1, 1, NULL, 0, NULL, NULL, NULL);
336 }                       
337
338
339 // Open a component of a MIME message and transmit it all at once
340 void cmd_dlat(char *cmdbuf) {
341         long msgid;
342         char desired_section[128];
343
344         msgid = extract_long(cmdbuf, 0);
345         extract_token(desired_section, cmdbuf, 1, '|', sizeof desired_section);
346         safestrncpy(CC->download_desired_section, desired_section, sizeof CC->download_desired_section);
347         CtdlOutputMsg(msgid, MT_SPEW_SECTION, 0, 1, 1, NULL, 0, NULL, NULL, NULL);
348 }
349
350
351 // message entry  -  mode 0 (normal)
352 void cmd_ent0(char *entargs) {
353         int post = 0;
354         char recp[SIZ];
355         char cc[SIZ];
356         char bcc[SIZ];
357         char supplied_euid[128];
358         int anon_flag = 0;
359         int format_type = 0;
360         char newusername[256];
361         char newuseremail[256];
362         struct CtdlMessage *msg;
363         int anonymous = 0;
364         char errmsg[SIZ];
365         int err = 0;
366         struct recptypes *valid = NULL;
367         struct recptypes *valid_to = NULL;
368         struct recptypes *valid_cc = NULL;
369         struct recptypes *valid_bcc = NULL;
370         char subject[SIZ];
371         int subject_required = 0;
372         int do_confirm = 0;
373         long msgnum;
374         int i, j;
375         char buf[256];
376         int newuseremail_ok = 0;
377         char references[SIZ];
378         char *ptr;
379
380         unbuffer_output();
381
382         post = extract_int(entargs, 0);
383         extract_token(recp, entargs, 1, '|', sizeof recp);
384         anon_flag = extract_int(entargs, 2);
385         format_type = extract_int(entargs, 3);
386         extract_token(subject, entargs, 4, '|', sizeof subject);
387         extract_token(newusername, entargs, 5, '|', sizeof newusername);
388         do_confirm = extract_int(entargs, 6);
389         extract_token(cc, entargs, 7, '|', sizeof cc);
390         extract_token(bcc, entargs, 8, '|', sizeof bcc);
391         switch(CC->room.QRdefaultview) {
392         case VIEW_NOTES:
393         case VIEW_WIKI:
394                 extract_token(supplied_euid, entargs, 9, '|', sizeof supplied_euid);
395                 break;
396         default:
397                 supplied_euid[0] = 0;
398                 break;
399         }
400         extract_token(newuseremail, entargs, 10, '|', sizeof newuseremail);
401         extract_token(references, entargs, 11, '|', sizeof references);
402         for (ptr=references; *ptr != 0; ++ptr) {
403                 if (*ptr == '!') *ptr = '|';
404         }
405
406         // first check to make sure the request is valid.
407
408         err = CtdlDoIHavePermissionToPostInThisRoom(
409                 errmsg,
410                 sizeof errmsg,
411                 POST_LOGGED_IN,
412                 (!IsEmptyStr(references))               // is this a reply?  or a top-level post?
413         );
414         if (err) {
415                 cprintf("%d %s\n", err, errmsg);
416                 return;
417         }
418
419         // Check some other permission type things.
420
421         if (IsEmptyStr(newusername)) {
422                 strcpy(newusername, CC->user.fullname);
423         }
424         if (    (CC->user.axlevel < AxAideU)
425                 && (strcasecmp(newusername, CC->user.fullname))
426                 && (strcasecmp(newusername, CC->cs_inet_fn))
427         ) {     
428                 cprintf("%d You don't have permission to author messages as '%s'.\n",
429                         ERROR + HIGHER_ACCESS_REQUIRED,
430                         newusername
431                 );
432                 return;
433         }
434
435         if (IsEmptyStr(newuseremail)) {
436                 newuseremail_ok = 1;
437         }
438
439         if (!IsEmptyStr(newuseremail)) {
440                 if (!strcasecmp(newuseremail, CC->cs_inet_email)) {
441                         newuseremail_ok = 1;
442                 }
443                 else if (!IsEmptyStr(CC->cs_inet_other_emails)) {
444                         j = num_tokens(CC->cs_inet_other_emails, '|');
445                         for (i=0; i<j; ++i) {
446                                 extract_token(buf, CC->cs_inet_other_emails, i, '|', sizeof buf);
447                                 if (!strcasecmp(newuseremail, buf)) {
448                                         newuseremail_ok = 1;
449                                 }
450                         }
451                 }
452         }
453
454         if (!newuseremail_ok) {
455                 cprintf("%d You don't have permission to author messages as '%s'.\n",
456                         ERROR + HIGHER_ACCESS_REQUIRED,
457                         newuseremail
458                 );
459                 return;
460         }
461
462         CC->cs_flags |= CS_POSTING;
463
464         // In mailbox rooms we have to behave a little differently --
465         // make sure the user has specified at least one recipient.  Then
466         // validate the recipient(s).  We do this for the Mail> room, as
467         // well as any room which has the "Mailbox" view set - unless it
468         // is the DRAFTS room which does not require recipients.
469
470         if ( (  ( (CC->room.QRflags & QR_MAILBOX) && (!strcasecmp(&CC->room.QRname[11], MAILROOM)) )
471                 || ( (CC->room.QRflags & QR_MAILBOX) && (CC->curr_view == VIEW_MAILBOX) )
472                      ) && (strcasecmp(&CC->room.QRname[11], USERDRAFTROOM)) !=0 ) {
473                 if (CC->user.axlevel < AxProbU) {
474                         strcpy(recp, "sysop");
475                         strcpy(cc, "");
476                         strcpy(bcc, "");
477                 }
478
479                 valid_to = validate_recipients(recp, 0);
480                 if (valid_to->num_error > 0) {
481                         cprintf("%d %s\n", ERROR + NO_SUCH_USER, valid_to->errormsg);
482                         free_recipients(valid_to);
483                         return;
484                 }
485
486                 valid_cc = validate_recipients(cc, 0);
487                 if (valid_cc->num_error > 0) {
488                         cprintf("%d %s\n", ERROR + NO_SUCH_USER, valid_cc->errormsg);
489                         free_recipients(valid_to);
490                         free_recipients(valid_cc);
491                         return;
492                 }
493
494                 valid_bcc = validate_recipients(bcc, 0);
495                 if (valid_bcc->num_error > 0) {
496                         cprintf("%d %s\n", ERROR + NO_SUCH_USER, valid_bcc->errormsg);
497                         free_recipients(valid_to);
498                         free_recipients(valid_cc);
499                         free_recipients(valid_bcc);
500                         return;
501                 }
502
503                 // Recipient required, but none were specified
504                 if ( (valid_to->num_error < 0) && (valid_cc->num_error < 0) && (valid_bcc->num_error < 0) ) {
505                         free_recipients(valid_to);
506                         free_recipients(valid_cc);
507                         free_recipients(valid_bcc);
508                         cprintf("%d At least one recipient is required.\n", ERROR + NO_SUCH_USER);
509                         return;
510                 }
511
512                 if (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet > 0) {
513                         if (CtdlCheckInternetMailPermission(&CC->user)==0) {
514                                 cprintf("%d You do not have permission to send Internet mail.\n",
515                                         ERROR + HIGHER_ACCESS_REQUIRED);
516                                 free_recipients(valid_to);
517                                 free_recipients(valid_cc);
518                                 free_recipients(valid_bcc);
519                                 return;
520                         }
521                 }
522
523                 if ( ( (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet) > 0) && (CC->user.axlevel < AxNetU) ) {
524                         cprintf("%d Higher access required for network mail.\n", ERROR + HIGHER_ACCESS_REQUIRED);
525                         free_recipients(valid_to);
526                         free_recipients(valid_cc);
527                         free_recipients(valid_bcc);
528                         return;
529                 }
530         
531                 if (    (RESTRICT_INTERNET == 1)
532                         && (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet > 0)
533                         && ((CC->user.flags & US_INTERNET) == 0)
534                         && (!CC->internal_pgm)
535                 ) {
536                         cprintf("%d You don't have access to Internet mail.\n", ERROR + HIGHER_ACCESS_REQUIRED);
537                         free_recipients(valid_to);
538                         free_recipients(valid_cc);
539                         free_recipients(valid_bcc);
540                         return;
541                 }
542
543         }
544
545         // Is this a room which has anonymous-only or anonymous-option?
546         anonymous = MES_NORMAL;
547         if (CC->room.QRflags & QR_ANONONLY) {
548                 anonymous = MES_ANONONLY;
549         }
550         if (CC->room.QRflags & QR_ANONOPT) {
551                 if (anon_flag == 1) {   // only if the user requested it
552                         anonymous = MES_ANONOPT;
553                 }
554         }
555
556         if ((CC->room.QRflags & QR_MAILBOX) == 0) {
557                 recp[0] = 0;
558         }
559
560         // Recommend to the client that the use of a message subject is
561         // strongly recommended in this room, if either the SUBJECTREQ flag
562         // is set, or if there is one or more Internet email recipients.
563
564         if (CC->room.QRflags2 & QR2_SUBJECTREQ) subject_required = 1;
565         if ((valid_to)  && (valid_to->num_internet > 0))        subject_required = 1;
566         if ((valid_cc)  && (valid_cc->num_internet > 0))        subject_required = 1;
567         if ((valid_bcc) && (valid_bcc->num_internet > 0))       subject_required = 1;
568
569         // If we're only checking the validity of the request, return success without creating the message.
570         if (post == 0) {
571                 cprintf("%d %s|%d\n", CIT_OK,
572                         ((valid_to != NULL) ? valid_to->display_recp : ""), 
573                         subject_required);
574                 free_recipients(valid_to);
575                 free_recipients(valid_cc);
576                 free_recipients(valid_bcc);
577                 return;
578         }
579
580         // We don't need these anymore because we'll do it differently below
581         free_recipients(valid_to);
582         free_recipients(valid_cc);
583         free_recipients(valid_bcc);
584
585         // Read in the message from the client.
586         if (do_confirm) {
587                 cprintf("%d send message\n", SEND_THEN_RECV);
588         }
589         else {
590                 cprintf("%d send message\n", SEND_LISTING);
591         }
592
593         msg = CtdlMakeMessage(
594                 &CC->user, recp, cc,
595                 CC->room.QRname, anonymous, format_type,
596                 newusername, newuseremail, subject,
597                 ((!IsEmptyStr(supplied_euid)) ? supplied_euid : NULL),
598                 NULL, references
599         );
600
601         // Put together one big recipients struct containing to/cc/bcc all in one.  This is for the envelope.
602         char *all_recps = malloc(SIZ * 3);
603         strcpy(all_recps, recp);
604         if (!IsEmptyStr(cc)) {
605                 if (!IsEmptyStr(all_recps)) {
606                         strcat(all_recps, ",");
607                 }
608                 strcat(all_recps, cc);
609         }
610         if (!IsEmptyStr(bcc)) {
611                 if (!IsEmptyStr(all_recps)) {
612                         strcat(all_recps, ",");
613                 }
614                 strcat(all_recps, bcc);
615         }
616         if (!IsEmptyStr(all_recps)) {
617                 valid = validate_recipients(all_recps, 0);
618         }
619         else {
620                 valid = NULL;
621         }
622         free(all_recps);
623
624         // posting into a mailing list room? set the envelope from 
625         // to the actual mail address so others get a valid reply-to-header.
626         if ((valid != NULL) && (valid->num_room == 1) && !IsEmptyStr(valid->recp_orgroom)) {
627                 CM_SetField(msg, eenVelopeTo, valid->recp_orgroom);
628         }
629
630         if (msg != NULL) {
631                 msgnum = CtdlSubmitMsg(msg, valid, "");
632                 if (do_confirm) {
633                         cprintf("%ld\n", msgnum);
634
635                         if (StrLength(CC->StatusMessage) > 0) {
636                                 cprintf("%s\n", ChrPtr(CC->StatusMessage));
637                         }
638                         else if (msgnum >= 0L) {
639                                 client_write(HKEY("Message accepted.\n"));
640                         }
641                         else {
642                                 client_write(HKEY("Internal error.\n"));
643                         }
644
645                         if (!CM_IsEmpty(msg, eExclusiveID)) {
646                                 cprintf("%s\n", msg->cm_fields[eExclusiveID]);
647                         }
648                         else {
649                                 cprintf("\n");
650                         }
651                         cprintf("000\n");
652                 }
653
654                 CM_Free(msg);
655         }
656         if (valid != NULL) {
657                 free_recipients(valid);
658         }
659         return;
660 }
661
662
663 // Delete message from current room
664 void cmd_dele(char *args) {
665         int num_deleted;
666         int i;
667         char msgset[SIZ];
668         char msgtok[32];
669         long *msgs;
670         int num_msgs = 0;
671
672         extract_token(msgset, args, 0, '|', sizeof msgset);
673         num_msgs = num_tokens(msgset, ',');
674         if (num_msgs < 1) {
675                 cprintf("%d Nothing to do.\n", CIT_OK);
676                 return;
677         }
678
679         if (CtdlDoIHavePermissionToDeleteMessagesFromThisRoom() == 0) {
680                 cprintf("%d Higher access required.\n", ERROR + HIGHER_ACCESS_REQUIRED);
681                 return;
682         }
683
684         // Build our message set to be moved/copied
685         msgs = malloc(num_msgs * sizeof(long));
686         for (i=0; i<num_msgs; ++i) {
687                 extract_token(msgtok, msgset, i, ',', sizeof msgtok);
688                 msgs[i] = atol(msgtok);
689         }
690
691         num_deleted = CtdlDeleteMessages(CC->room.QRname, msgs, num_msgs, "");
692         free(msgs);
693
694         if (num_deleted) {
695                 cprintf("%d %d message%s deleted.\n", CIT_OK, num_deleted, ((num_deleted != 1) ? "s" : ""));
696         }
697         else {
698                 cprintf("%d Message not found.\n", ERROR + MESSAGE_NOT_FOUND);
699         }
700 }
701
702
703 // move or copy a message to another room
704 void cmd_move(char *args) {
705         char msgset[SIZ];
706         char msgtok[32];
707         long *msgs;
708         int num_msgs = 0;
709
710         char targ[ROOMNAMELEN];
711         struct ctdlroom qtemp;
712         int err;
713         int is_copy = 0;
714         int ra;
715         int permit = 0;
716         int i;
717
718         extract_token(msgset, args, 0, '|', sizeof msgset);
719         num_msgs = num_tokens(msgset, ',');
720         if (num_msgs < 1) {
721                 cprintf("%d Nothing to do.\n", CIT_OK);
722                 return;
723         }
724
725         extract_token(targ, args, 1, '|', sizeof targ);
726         convert_room_name_macros(targ, sizeof targ);
727         targ[ROOMNAMELEN - 1] = 0;
728         is_copy = extract_int(args, 2);
729
730         if (CtdlGetRoom(&qtemp, targ) != 0) {
731                 cprintf("%d '%s' does not exist.\n", ERROR + ROOM_NOT_FOUND, targ);
732                 return;
733         }
734
735         if (!strcasecmp(qtemp.QRname, CC->room.QRname)) {
736                 cprintf("%d Source and target rooms are the same.\n", ERROR + ALREADY_EXISTS);
737                 return;
738         }
739
740         CtdlGetUser(&CC->user, CC->curr_user);
741         CtdlRoomAccess(&qtemp, &CC->user, &ra, NULL);
742
743         // Check for permission to perform this operation.
744         // Remember: "CC->room" is source, "qtemp" is target.
745         permit = 0;
746
747         // Admins can move/copy
748         if (CC->user.axlevel >= AxAideU) permit = 1;
749
750         // Room aides can move/copy
751         if (CC->user.usernum == CC->room.QRroomaide) permit = 1;
752
753         // Permit move/copy from personal rooms
754         if (    (CC->room.QRflags & QR_MAILBOX)
755                 && (qtemp.QRflags & QR_MAILBOX)
756         ) {
757                 permit = 1;
758         }
759
760         // Permit only copy from public to personal room
761         if (    (is_copy)
762                 && (!(CC->room.QRflags & QR_MAILBOX))
763                 && (qtemp.QRflags & QR_MAILBOX)
764         ) {
765                 permit = 1;
766         }
767
768         // Permit message removal from collaborative delete rooms
769         if (CC->room.QRflags2 & QR2_COLLABDEL) permit = 1;
770
771         // Users allowed to post into the target room may move into it too.
772         if (    (CC->room.QRflags & QR_MAILBOX)
773                 && (qtemp.QRflags & UA_POSTALLOWED)
774         ) {
775                 permit = 1;
776         }
777
778         // User must have access to target room
779         if (!(ra & UA_KNOWN)) {
780                 permit = 0;
781         }
782
783         if (!permit) {
784                 cprintf("%d Higher access required.\n", ERROR + HIGHER_ACCESS_REQUIRED);
785                 return;
786         }
787
788         // Build our message set to be moved/copied
789         msgs = malloc(num_msgs * sizeof(long));
790         for (i=0; i<num_msgs; ++i) {
791                 extract_token(msgtok, msgset, i, ',', sizeof msgtok);
792                 msgs[i] = atol(msgtok);
793         }
794
795         // Do the copy
796         err = CtdlSaveMsgPointersInRoom(targ, msgs, num_msgs, 1, NULL, 0);
797         if (err != 0) {
798                 cprintf("%d Cannot store message(s) in %s: error %d\n", err, targ, err);
799                 free(msgs);
800                 return;
801         }
802
803         // Now delete the message from the source room, if this is a 'move' rather than a 'copy' operation.
804         if (is_copy == 0) {
805                 CtdlDeleteMessages(CC->room.QRname, msgs, num_msgs, "");
806         }
807         free(msgs);
808
809         cprintf("%d Message(s) %s.\n", CIT_OK, (is_copy ? "copied" : "moved") );
810 }
811
812
813 // Initialization function, called from modules_init.c
814 char *ctdl_module_init_ctdl_message(void) {
815         if (!threading) {
816                 CtdlRegisterProtoHook(cmd_msgs, "MSGS", "Output a list of messages in the current room");
817                 CtdlRegisterProtoHook(cmd_msg0, "MSG0", "Output a message in plain text format");
818                 CtdlRegisterProtoHook(cmd_msg2, "MSG2", "Output a message in RFC822 format");
819                 CtdlRegisterProtoHook(cmd_msg4, "MSG4", "Output a message in the client's preferred format");
820                 CtdlRegisterProtoHook(cmd_msgp, "MSGP", "Select preferred format for MSG4 output");
821                 CtdlRegisterProtoHook(cmd_opna, "OPNA", "Open an attachment for download");
822                 CtdlRegisterProtoHook(cmd_dlat, "DLAT", "Download an attachment");
823                 CtdlRegisterProtoHook(cmd_ent0, "ENT0", "Enter a message");
824                 CtdlRegisterProtoHook(cmd_dele, "DELE", "Delete a message");
825                 CtdlRegisterProtoHook(cmd_move, "MOVE", "Move or copy a message to another room");
826         }
827
828         // return a module name for the log
829         return "ctdl_message";
830 }