4b3d1681cc65e6b565b583c31b8f3f23f4b931dc
[citadel.git] / citadel / modules / imap / imap_metadata.c
1 /*
2  * IMAP METADATA extension
3  *
4  * This is an implementation of the Bynari variant of the METADATA extension.
5  *
6  * Copyright (c) 2007-2017 by the citadel.org team
7  *
8  * This program is open source 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
24 #include "sysdep.h"
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 "database.h"
58 #include "msgbase.h"
59 #include "internet_addressing.h"
60 #include "serv_imap.h"
61 #include "imap_tools.h"
62 #include "imap_fetch.h"
63 #include "imap_misc.h"
64 #include "genstamp.h"
65
66 #include "ctdl_module.h"
67
68 /*
69  * Implements the SETMETADATA command.
70  *
71  * Again, the only thing we're interested in setting here is the folder type.
72  *
73  * Attempting to set anything else calls a stub which fools the client into
74  * thinking that there is no remaining space available to store annotations.
75  */
76 void imap_setmetadata(int num_parms, ConstStr *Params) {
77         char roomname[ROOMNAMELEN];
78         char savedroom[ROOMNAMELEN];
79         int msgs, new;
80         int ret;
81         int setting_user_value = 0;
82         char set_value[32];
83         int set_view = VIEW_BBS;
84         visit vbuf;
85
86         if (num_parms != 6) {
87                 IReply("BAD usage error");
88                 return;
89         }
90
91         /*
92          * Don't allow other types of metadata to be set
93          */
94         if (strcasecmp(Params[3].Key, "/vendor/kolab/folder-type")) {
95                 IReply("NO [METADATA TOOMANY] SETMETADATA failed");
96                 return;
97         }
98
99         if (!strcasecmp(Params[4].Key, "(value.shared")) {
100                 setting_user_value = 0;                         /* global view */
101         }
102         else if (!strcasecmp(Params[4].Key, "(value.priv")) {
103                 setting_user_value = 1;                         /* per-user view */
104         }
105         else {
106                 IReply("NO [METADATA TOOMANY] SETMETADATA failed");
107                 return;
108         }
109
110         /*
111          * Extract the folder type without any parentheses.  Then learn
112          * the Citadel view type based on the supplied folder type.
113          */
114         extract_token(set_value, Params[5].Key, 0, ')', sizeof set_value);
115         if (!strncasecmp(set_value, "mail", 4)) {
116                 set_view = VIEW_MAILBOX;
117         }
118         else if (!strncasecmp(set_value, "event", 5)) {
119                 set_view = VIEW_CALENDAR;
120         }
121         else if (!strncasecmp(set_value, "contact", 7)) {
122                 set_view = VIEW_ADDRESSBOOK;
123         }
124         else if (!strncasecmp(set_value, "journal", 7)) {
125                 set_view = VIEW_JOURNAL;
126         }
127         else if (!strncasecmp(set_value, "note", 4)) {
128                 set_view = VIEW_NOTES;
129         }
130         else if (!strncasecmp(set_value, "task", 4)) {
131                 set_view = VIEW_TASKS;
132         }
133         else {
134                 set_view = VIEW_MAILBOX;
135         }
136
137         ret = imap_grabroom(roomname, Params[2].Key, 1);
138         if (ret != 0) {
139                 IReply("NO Invalid mailbox name or access denied");
140                 return;
141         }
142
143         /*
144          * CtdlUserGoto() formally takes us to the desired room.  (If another
145          * folder is selected, save its name so we can return there!!!!!)
146          */
147         if (IMAP->selected) {
148                 strcpy(savedroom, CC->room.QRname);
149         }
150         CtdlUserGoto(roomname, 0, 0, &msgs, &new, NULL, NULL);
151
152         /*
153          * Always set the per-user view to the requested one.
154          */
155         CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
156         vbuf.v_view = set_view;
157         CtdlSetRelationship(&vbuf, &CC->user, &CC->room);
158
159         /* If this is a "value.priv" set operation, we're done. */
160
161         if (setting_user_value)
162         {
163                 IReply("OK SETANNOTATION complete");
164         }
165
166         /* If this is a "value.shared" set operation, we are allowed to perform it
167          * under certain conditions.
168          */
169         else if (       (is_room_aide())                                        /* aide or room aide */
170                 ||      (       (CC->room.QRflags & QR_MAILBOX)
171                         &&      (CC->user.usernum == atol(CC->room.QRname))     /* mailbox owner */
172                         )
173                 ||      (msgs == 0)             /* hack: if room is empty, assume we just created it */
174         ) {
175                 CtdlGetRoomLock(&CC->room, CC->room.QRname);
176                 CC->room.QRdefaultview = set_view;
177                 CtdlPutRoomLock(&CC->room);
178                 IReply("OK SETANNOTATION complete");
179         }
180
181         /* If we got to this point, we don't have permission to set the default view. */
182         else {
183                 IReply("NO [METADATA TOOMANY] SETMETADATA failed");
184         }
185
186         /*
187          * If a different folder was previously selected, return there now.
188          */
189         if ( (IMAP->selected) && (strcasecmp(roomname, savedroom)) ) {
190                 CtdlUserGoto(savedroom, 0, 0, &msgs, &new, NULL, NULL);
191         }
192         return;
193 }
194
195
196 /*
197  * Implements the GETMETADATA command.
198  *
199  * Regardless of what the client asked for, we are going to supply them with
200  * the folder type.  It's the only metadata we have anyway.
201  */
202 void imap_getmetadata(int num_parms, ConstStr *Params) {
203         char roomname[ROOMNAMELEN];
204         char savedroom[ROOMNAMELEN];
205         int msgs, new;
206         int ret;
207         int found = 0;
208
209 /* this doesn't work if you have rooms/floors with spaces. 
210    we need this for the bynari connector.
211         if (num_parms > 5) {
212                 IReply("BAD usage error");
213                 return;
214         }
215 */
216         ret = imap_grabroom(roomname, Params[2].Key, 1);
217         if (ret != 0) {
218                 IReply("NO Invalid mailbox name or access denied");
219                 return;
220         }
221
222         /*
223          * CtdlUserGoto() formally takes us to the desired room.  (If another
224          * folder is selected, save its name so we can return there!!!!!)
225          */
226         if (IMAP->selected) {
227                 strcpy(savedroom, CC->room.QRname);
228         }
229         CtdlUserGoto(roomname, 0, 0, &msgs, &new, NULL, NULL);
230
231         IAPuts("* METADATA ");
232         IPutCParamStr(2);
233         IAPuts(" \"/vendor/kolab/folder-type\" (\"value.shared\" \"");
234
235         /* If it's one of our hard-coded default rooms, we know what to do... */
236
237         if (CC->room.QRname[10] == '.')
238         {
239                 if (!strcasecmp(&CC->room.QRname[11], MAILROOM)) {
240                         found = 1;
241                         IAPuts("mail.inbox");
242                 }
243                 else if (!strcasecmp(&CC->room.QRname[11], SENTITEMS)) {
244                         found = 1;
245                         IAPuts("mail.sentitems");
246                 }
247                 else if (!strcasecmp(&CC->room.QRname[11], USERDRAFTROOM)) {
248                         found = 1;
249                         IAPuts("mail.drafts");
250                 }
251                 else if (!strcasecmp(&CC->room.QRname[11], USERCALENDARROOM)) {
252                         found = 1;
253                         IAPuts("event.default");
254                 }
255                 else if (!strcasecmp(&CC->room.QRname[11], USERCONTACTSROOM)) {
256                         found = 1;
257                         IAPuts("contact.default");
258                 }
259                 else if (!strcasecmp(&CC->room.QRname[11], USERNOTESROOM)) {
260                         found = 1;
261                         IAPuts("note.default");
262                 }
263                 else if (!strcasecmp(&CC->room.QRname[11], USERTASKSROOM)) {
264                         found = 1;
265                         IAPuts("task.default");
266                 }
267         }
268
269         /* Otherwise, use the view for this room to determine the type of data.
270          * We are going with the default view rather than the user's view, because
271          * the default view almost always defines the actual contents, while the
272          * user's view might only make changes to presentation.  It also saves us
273          * an extra database access because we don't need to load the visit record.
274          */
275         if (!found)
276         {
277                 if (CC->room.QRdefaultview == VIEW_CALENDAR) {
278                         IAPuts("event");
279                 }
280                 else if (CC->room.QRdefaultview == VIEW_ADDRESSBOOK) {
281                         IAPuts("contact");
282                 }
283                 else if (CC->room.QRdefaultview == VIEW_TASKS) {
284                         IAPuts("task");
285                 }
286                 else if (CC->room.QRdefaultview == VIEW_NOTES) {
287                         IAPuts("note");
288                 }
289                 else if (CC->room.QRdefaultview == VIEW_JOURNAL) {
290                         IAPuts("journal");
291                 }
292         }
293         /* If none of the above conditions were met, consider it an ordinary mailbox. */
294
295         if (!found) {
296                 IAPuts("mail");
297         }
298
299         /* "mail.outbox" and "junkemail" are not implemented. */
300
301         IAPuts("\")\r\n");
302
303         /*
304          * If a different folder was previously selected, return there now.
305          */
306         if ( (IMAP->selected) && (strcasecmp(roomname, savedroom)) ) {
307                 CtdlUserGoto(savedroom, 0, 0, &msgs, &new, NULL, NULL);
308         }
309
310         IReply("OK GETMETADATA complete");
311         return;
312 }
313