4b67bfc4ea324569a86e117b01e6aefafbbf84c9
[citadel.git] / citadel / server / modules / imap / imap_store.c
1 // Implements the STORE command in IMAP.
2 //
3 // Copyright (c) 1987-2022 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 "../../ctdl_module.h"
9
10 #include <stdlib.h>
11 #include <unistd.h>
12 #include <stdio.h>
13 #include <fcntl.h>
14 #include <signal.h>
15 #include <pwd.h>
16 #include <errno.h>
17 #include <sys/types.h>
18 #include <time.h>
19 #include <sys/wait.h>
20 #include <ctype.h>
21 #include <string.h>
22 #include <limits.h>
23 #include <libcitadel.h>
24 #include "../../citadel.h"
25 #include "../../server.h"
26 #include "../../sysdep_decls.h"
27 #include "../../citserver.h"
28 #include "../../support.h"
29 #include "../../config.h"
30 #include "../../user_ops.h"
31 #include "../../database.h"
32 #include "../../room_ops.h"
33 #include "../../msgbase.h"
34 #include "../../internet_addressing.h"
35 #include "serv_imap.h"
36 #include "imap_tools.h"
37 #include "imap_fetch.h"
38 #include "imap_store.h"
39 #include "../../genstamp.h"
40
41
42 // imap_do_store() calls imap_do_store_msg() to tweak the settings of
43 // an individual message.
44 //
45 // We also implement the ".SILENT" protocol option here.  :(
46 void imap_do_store_msg(int seq, const char *oper, unsigned int bits_to_twiddle) {
47         citimap *Imap = IMAP;
48
49         if (!strncasecmp(oper, "FLAGS", 5)) {
50                 Imap->flags[seq] &= IMAP_MASK_SYSTEM;
51                 Imap->flags[seq] |= bits_to_twiddle;
52         }
53         else if (!strncasecmp(oper, "+FLAGS", 6)) {
54                 Imap->flags[seq] |= bits_to_twiddle;
55         }
56         else if (!strncasecmp(oper, "-FLAGS", 6)) {
57                 Imap->flags[seq] &= (~bits_to_twiddle);
58         }
59 }
60
61
62 // imap_store() calls imap_do_store() to perform the actual bit twiddling
63 // on the flags.
64 void imap_do_store(citimap_command *Cmd) {
65         int i, j;
66         unsigned int bits_to_twiddle = 0;
67         const char *oper;
68         char flag[32];
69         char whichflags[256];
70         char num_flags;
71         int silent = 0;
72         long *ss_msglist;
73         int num_ss = 0;
74         int last_item_twiddled = (-1);
75         citimap *Imap = IMAP;
76
77         if (Cmd->num_parms < 2) return;
78         oper = Cmd->Params[0].Key;
79         if (cbmstrcasestr(oper, ".SILENT")) {
80                 silent = 1;
81         }
82
83         // ss_msglist is an array of message numbers to manipulate.  We are going to supply this array to CtdlSetSeen() later.
84         ss_msglist = malloc(Imap->num_msgs * sizeof(long));
85         if (ss_msglist == NULL) return;
86
87         // Ok, go ahead and parse the flags.
88         for (i=1; i<Cmd->num_parms; ++i) {
89                 strcpy(whichflags, Cmd->Params[i].Key);
90                 if (whichflags[0]=='(') {
91                         safestrncpy(whichflags, &whichflags[1], 
92                                 sizeof whichflags);
93                 }
94                 if (whichflags[strlen(whichflags)-1]==')') {
95                         whichflags[strlen(whichflags)-1]=0;
96                 }
97                 string_trim(whichflags);
98
99                 // A client might twiddle more than one bit at a time.
100                 // Note that we check for the flag names without the leading
101                 // backslash because imap_parameterize() strips them out.
102                 num_flags = num_tokens(whichflags, ' ');
103                 for (j=0; j<num_flags; ++j) {
104                         extract_token(flag, whichflags, j, ' ', sizeof flag);
105
106                         if ((!strcasecmp(flag, "\\Deleted"))
107                            || (!strcasecmp(flag, "Deleted"))) {
108                                 if (CtdlDoIHavePermissionToDeleteMessagesFromThisRoom()) {
109                                         bits_to_twiddle |= IMAP_DELETED;
110                                 }
111                         }
112                         if ((!strcasecmp(flag, "\\Seen"))
113                            || (!strcasecmp(flag, "Seen"))) {
114                                 bits_to_twiddle |= IMAP_SEEN;
115                         }
116                         if ((!strcasecmp(flag, "\\Answered")) 
117                            || (!strcasecmp(flag, "Answered"))) {
118                                 bits_to_twiddle |= IMAP_ANSWERED;
119                         }
120                 }
121         }
122
123         if (Imap->num_msgs > 0) {
124                 for (i = 0; i < Imap->num_msgs; ++i) {
125                         if (Imap->flags[i] & IMAP_SELECTED) {
126                                 last_item_twiddled = i;
127
128                                 ss_msglist[num_ss++] = Imap->msgids[i];
129                                 imap_do_store_msg(i, oper, bits_to_twiddle);
130
131                                 if (!silent) {
132                                         IAPrintf("* %d FETCH (", i+1);
133                                         imap_fetch_flags(i);
134                                         IAPuts(")\r\n");
135                                 }
136
137                         }
138                 }
139         }
140
141         // Now manipulate the database -- all in one shot.
142         if ( (last_item_twiddled >= 0) && (num_ss > 0) ) {
143
144                 if (bits_to_twiddle & IMAP_SEEN) {
145                         CtdlSetSeen(ss_msglist, num_ss,
146                                 ((Imap->flags[last_item_twiddled] & IMAP_SEEN) ? 1 : 0),
147                                 ctdlsetseen_seen,
148                                 NULL, NULL
149                         );
150                 }
151
152                 if (bits_to_twiddle & IMAP_ANSWERED) {
153                         CtdlSetSeen(ss_msglist, num_ss,
154                                 ((Imap->flags[last_item_twiddled] & IMAP_ANSWERED) ? 1 : 0),
155                                 ctdlsetseen_answered,
156                                 NULL, NULL
157                         );
158                 }
159
160         }
161
162         free(ss_msglist);
163         imap_do_expunge();              // Citadel always expunges immediately.
164         imap_rescan_msgids();
165 }
166
167
168 // This function is called by the main command loop.
169 void imap_store(int num_parms, ConstStr *Params) {
170         citimap_command Cmd;
171         int num_items;
172
173         if (num_parms < 3) {
174                 IReply("BAD invalid parameters");
175                 return;
176         }
177
178         if (imap_is_message_set(Params[2].Key)) {
179                 imap_pick_range(Params[2].Key, 0);
180         }
181         else {
182                 IReply("BAD invalid parameters");
183                 return;
184         }
185
186         memset(&Cmd, 0, sizeof(citimap_command));
187         Cmd.CmdBuf = NewStrBufPlain(NULL, StrLength(IMAP->Cmd.CmdBuf));
188         MakeStringOf(Cmd.CmdBuf, 3);
189
190         num_items = imap_extract_data_items(&Cmd);
191         if (num_items < 1) {
192                 IReply("BAD invalid data item list");
193                 FreeStrBuf(&Cmd.CmdBuf);
194                 free(Cmd.Params);
195                 return;
196         }
197
198         imap_do_store(&Cmd);
199         IReply("OK STORE completed");
200         FreeStrBuf(&Cmd.CmdBuf);
201         free(Cmd.Params);
202 }
203
204 // This function is called by the main command loop.
205 void imap_uidstore(int num_parms, ConstStr *Params) {
206         citimap_command Cmd;
207         int num_items;
208
209         if (num_parms < 4) {
210                 IReply("BAD invalid parameters");
211                 return;
212         }
213
214         if (imap_is_message_set(Params[3].Key)) {
215                 imap_pick_range(Params[3].Key, 1);
216         }
217         else {
218                 IReply("BAD invalid parameters");
219                 return;
220         }
221
222         memset(&Cmd, 0, sizeof(citimap_command));
223         Cmd.CmdBuf = NewStrBufPlain(NULL, StrLength(IMAP->Cmd.CmdBuf));
224         MakeStringOf(Cmd.CmdBuf, 4);
225
226         num_items = imap_extract_data_items(&Cmd);
227         if (num_items < 1) {
228                 IReply("BAD invalid data item list");
229                 FreeStrBuf(&Cmd.CmdBuf);
230                 free(Cmd.Params);
231                 return;
232         }
233
234         imap_do_store(&Cmd);
235         IReply("OK UID STORE completed");
236         FreeStrBuf(&Cmd.CmdBuf);
237         free(Cmd.Params);
238 }