a25908708bb57ef8456079bcd224cf946c1311da
[citadel.git] / citadel / modules / imap / imap_store.c
1 /*
2  * Implements the STORE command in IMAP.
3  *
4  * Copyright (c) 2001-2009 by the citadel.org team
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 3 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software
18  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
19  */
20
21 #include "ctdl_module.h"
22
23 #include <stdlib.h>
24 #include <unistd.h>
25 #include <stdio.h>
26 #include <fcntl.h>
27 #include <signal.h>
28 #include <pwd.h>
29 #include <errno.h>
30 #include <sys/types.h>
31
32 #if TIME_WITH_SYS_TIME
33 # include <sys/time.h>
34 # include <time.h>
35 #else
36 # if HAVE_SYS_TIME_H
37 #  include <sys/time.h>
38 # else
39 #  include <time.h>
40 # endif
41 #endif
42
43 #include <sys/wait.h>
44 #include <ctype.h>
45 #include <string.h>
46 #include <limits.h>
47 #include <libcitadel.h>
48 #include "citadel.h"
49 #include "server.h"
50 #include "sysdep_decls.h"
51 #include "citserver.h"
52 #include "support.h"
53 #include "config.h"
54 #include "user_ops.h"
55 #include "database.h"
56 #include "msgbase.h"
57 #include "internet_addressing.h"
58 #include "imap_tools.h"
59 #include "serv_imap.h"
60 #include "imap_fetch.h"
61 #include "imap_store.h"
62 #include "genstamp.h"
63
64
65 /*
66  * imap_do_store() calls imap_do_store_msg() to tweak the settings of
67  * an individual message.
68  *
69  * We also implement the ".SILENT" protocol option here.  :(
70  */
71 void imap_do_store_msg(int seq, const char *oper, unsigned int bits_to_twiddle) {
72
73
74         if (!strncasecmp(oper, "FLAGS", 5)) {
75                 IMAP->flags[seq] &= IMAP_MASK_SYSTEM;
76                 IMAP->flags[seq] |= bits_to_twiddle;
77         }
78         else if (!strncasecmp(oper, "+FLAGS", 6)) {
79                 IMAP->flags[seq] |= bits_to_twiddle;
80         }
81         else if (!strncasecmp(oper, "-FLAGS", 6)) {
82                 IMAP->flags[seq] &= (~bits_to_twiddle);
83         }
84 }
85
86
87 /*
88  * imap_store() calls imap_do_store() to perform the actual bit twiddling
89  * on the flags.
90  */
91 void imap_do_store(citimap_command *Cmd) {
92         int i, j;
93         unsigned int bits_to_twiddle = 0;
94         const char *oper;
95         char flag[32];
96         char whichflags[256];
97         char num_flags;
98         int silent = 0;
99         long *ss_msglist;
100         int num_ss = 0;
101         int last_item_twiddled = (-1);
102         citimap *Imap = IMAP;
103
104         if (Cmd->num_parms < 2) return;
105         oper = Cmd->Params[0].Key;
106         if (cbmstrcasestr(oper, ".SILENT")) {
107                 silent = 1;
108         }
109
110         /*
111          * ss_msglist is an array of message numbers to manipulate.  We
112          * are going to supply this array to CtdlSetSeen() later.
113          */
114         ss_msglist = malloc(Imap->num_msgs * sizeof(long));
115         if (ss_msglist == NULL) return;
116
117         /*
118          * Ok, go ahead and parse the flags.
119          */
120         for (i=1; i<Cmd->num_parms; ++i) {///TODO: why strcpy? 
121                 strcpy(whichflags, Cmd->Params[i].Key);
122                 if (whichflags[0]=='(') {
123                         safestrncpy(whichflags, &whichflags[1], 
124                                 sizeof whichflags);
125                 }
126                 if (whichflags[strlen(whichflags)-1]==')') {
127                         whichflags[strlen(whichflags)-1]=0;
128                 }
129                 striplt(whichflags);
130
131                 /* A client might twiddle more than one bit at a time.
132                  * Note that we check for the flag names without the leading
133                  * backslash because imap_parameterize() strips them out.
134                  */
135                 num_flags = num_tokens(whichflags, ' ');
136                 for (j=0; j<num_flags; ++j) {
137                         extract_token(flag, whichflags, j, ' ', sizeof flag);
138
139                         if ((!strcasecmp(flag, "\\Deleted"))
140                            || (!strcasecmp(flag, "Deleted"))) {
141                                 if (CtdlDoIHavePermissionToDeleteMessagesFromThisRoom()) {
142                                         bits_to_twiddle |= IMAP_DELETED;
143                                 }
144                         }
145                         if ((!strcasecmp(flag, "\\Seen"))
146                            || (!strcasecmp(flag, "Seen"))) {
147                                 bits_to_twiddle |= IMAP_SEEN;
148                         }
149                         if ((!strcasecmp(flag, "\\Answered")) 
150                            || (!strcasecmp(flag, "Answered"))) {
151                                 bits_to_twiddle |= IMAP_ANSWERED;
152                         }
153                 }
154         }
155
156         if (Imap->num_msgs > 0) {
157                 for (i = 0; i < Imap->num_msgs; ++i) {
158                         if (Imap->flags[i] & IMAP_SELECTED) {
159                                 last_item_twiddled = i;
160
161                                 ss_msglist[num_ss++] = Imap->msgids[i];
162                                 imap_do_store_msg(i, oper, bits_to_twiddle);
163
164                                 if (!silent) {
165                                         cprintf("* %d FETCH (", i+1);
166                                         imap_fetch_flags(i);
167                                         cprintf(")\r\n");
168                                 }
169
170                         }
171                 }
172         }
173
174         /*
175          * Now manipulate the database -- all in one shot.
176          */
177         if ( (last_item_twiddled >= 0) && (num_ss > 0) ) {
178
179                 if (bits_to_twiddle & IMAP_SEEN) {
180                         CtdlSetSeen(ss_msglist, num_ss,
181                                 ((Imap->flags[last_item_twiddled] & IMAP_SEEN) ? 1 : 0),
182                                 ctdlsetseen_seen,
183                                 NULL, NULL
184                         );
185                 }
186
187                 if (bits_to_twiddle & IMAP_ANSWERED) {
188                         CtdlSetSeen(ss_msglist, num_ss,
189                                 ((Imap->flags[last_item_twiddled] & IMAP_ANSWERED) ? 1 : 0),
190                                 ctdlsetseen_answered,
191                                 NULL, NULL
192                         );
193                 }
194
195         }
196
197         free(ss_msglist);
198
199         /*
200          * The following two commands implement "instant expunge" if enabled.
201          */
202         if (config.c_instant_expunge) {
203                 imap_do_expunge();
204                 imap_rescan_msgids();
205         }
206
207 }
208
209
210 /*
211  * This function is called by the main command loop.
212  */
213 void imap_store(int num_parms, ConstStr *Params) {
214         citimap_command Cmd;
215         int num_items;
216
217         if (num_parms < 3) {
218                 cprintf("%s BAD invalid parameters\r\n", Params[0].Key);
219                 return;
220         }
221
222         if (imap_is_message_set(Params[2].Key)) {
223                 imap_pick_range(Params[2].Key, 0);
224         }
225         else {
226                 cprintf("%s BAD invalid parameters\r\n", Params[0].Key);
227                 return;
228         }
229
230         memset(&Cmd, 0, sizeof(citimap_command));
231         Cmd.CmdBuf = NewStrBufPlain(NULL, StrLength(IMAP->Cmd.CmdBuf));
232         MakeStringOf(Cmd.CmdBuf, 3);
233
234         num_items = imap_extract_data_items(&Cmd);
235         if (num_items < 1) {
236                 cprintf("%s BAD invalid data item list\r\n", Params[0].Key);
237                 FreeStrBuf(&Cmd.CmdBuf);
238                 free(Cmd.Params);
239                 return;
240         }
241
242         imap_do_store(&Cmd);
243         cprintf("%s OK STORE completed\r\n", Params[0].Key);
244         FreeStrBuf(&Cmd.CmdBuf);
245         free(Cmd.Params);
246 }
247
248 /*
249  * This function is called by the main command loop.
250  */
251 void imap_uidstore(int num_parms, ConstStr *Params) {
252         citimap_command Cmd;
253         int num_items;
254
255         if (num_parms < 4) {
256                 cprintf("%s BAD invalid parameters\r\n", Params[0].Key);
257                 return;
258         }
259
260         if (imap_is_message_set(Params[3].Key)) {
261                 imap_pick_range(Params[3].Key, 1);
262         }
263         else {
264                 cprintf("%s BAD invalid parameters\r\n", Params[0].Key);
265                 return;
266         }
267
268         memset(&Cmd, 0, sizeof(citimap_command));
269         Cmd.CmdBuf = NewStrBufPlain(NULL, StrLength(IMAP->Cmd.CmdBuf));
270         MakeStringOf(Cmd.CmdBuf, 4);
271
272         num_items = imap_extract_data_items(&Cmd);
273         if (num_items < 1) {
274                 cprintf("%s BAD invalid data item list\r\n", Params[0].Key);
275                 FreeStrBuf(&Cmd.CmdBuf);
276                 free(Cmd.Params);
277                 return;
278         }
279
280         imap_do_store(&Cmd);
281         cprintf("%s OK UID STORE completed\r\n", Params[0].Key);
282         FreeStrBuf(&Cmd.CmdBuf);
283         free(Cmd.Params);
284 }
285
286