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