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