9ed132c4a360bb4b2e8a7262c9e8fe4d372bd91a
[citadel.git] / citadel / imap_misc.c
1 /*
2  * $Id$
3  *
4  *
5  */
6
7
8 #include "sysdep.h"
9 #include <stdlib.h>
10 #include <unistd.h>
11 #include <stdio.h>
12 #include <fcntl.h>
13 #include <signal.h>
14 #include <pwd.h>
15 #include <errno.h>
16 #include <sys/types.h>
17
18 #if TIME_WITH_SYS_TIME
19 # include <sys/time.h>
20 # include <time.h>
21 #else
22 # if HAVE_SYS_TIME_H
23 #  include <sys/time.h>
24 # else
25 #  include <time.h>
26 # endif
27 #endif
28
29 #include <sys/wait.h>
30 #include <ctype.h>
31 #include <string.h>
32 #include <limits.h>
33 #include "citadel.h"
34 #include "server.h"
35 #include "sysdep_decls.h"
36 #include "citserver.h"
37 #include "support.h"
38 #include "config.h"
39 #include "serv_extensions.h"
40 #include "room_ops.h"
41 #include "user_ops.h"
42 #include "policy.h"
43 #include "database.h"
44 #include "msgbase.h"
45 #include "tools.h"
46 #include "internet_addressing.h"
47 #include "serv_imap.h"
48 #include "imap_tools.h"
49 #include "imap_fetch.h"
50 #include "imap_misc.h"
51 #include "genstamp.h"
52
53
54
55
56
57
58 /*
59  * imap_copy() calls imap_do_copy() to do its actual work, once it's
60  * validated and boiled down the request a bit.  (returns 0 on success)
61  */
62 int imap_do_copy(char *destination_folder) {
63         int i;
64         char roomname[ROOMNAMELEN];
65         struct ctdlroom qrbuf;
66
67         if (IMAP->num_msgs < 1) {
68                 return(0);
69         }
70
71         i = imap_grabroom(roomname, destination_folder, 0);
72         if (i != 0) return(i);
73
74         /*
75          * Optimization note ... ajc 2005oct09
76          * 
77          * As we can see below, we're going to end up making lots of
78          * repeated calls to CtdlCopyMsgToRoom() if the user selects
79          * multiple messages and then does a copy or move operation.
80          *
81          * The plan (documented in this comment so I don't forget what I was
82          * going to do) is to refactor CtdlCopyMsgToRoom() so that it accepts
83          * a list of message ID's instead of a single one.  Then we can alter
84          * the code which appears below, to copy all the message pointers in
85          * one shot.
86          * 
87          * But the REAL resource waster is further down, where we call
88          * the CtdlSetSeen() API for each message moved.  That's a HUGE
89          * performance drag.  Fix that too.
90          *
91          */
92         for (i = 0; i < IMAP->num_msgs; ++i) {
93                 if (IMAP->flags[i] & IMAP_SELECTED) {
94                         CtdlCopyMsgToRoom(IMAP->msgids[i], roomname);
95                 }
96         }
97
98         /* Set the flags... */
99         i = getroom(&qrbuf, roomname);
100         if (i != 0) return(i);
101
102         for (i = 0; i < IMAP->num_msgs; ++i) {
103                 if (IMAP->flags[i] & IMAP_SELECTED) {
104                         CtdlSetSeen(&IMAP->msgids[i], 1,
105                                 ((IMAP->flags[i] & IMAP_SEEN) ? 1 : 0),
106                                 ctdlsetseen_seen,
107                                 NULL, &qrbuf
108                         );
109                         CtdlSetSeen(&IMAP->msgids[i], 1,
110                                 ((IMAP->flags[i] & IMAP_ANSWERED) ? 1 : 0),
111                                 ctdlsetseen_answered,
112                                 NULL, &qrbuf
113                         );
114                 }
115         }
116
117         return(0);
118 }
119
120
121
122 /*
123  * This function is called by the main command loop.
124  */
125 void imap_copy(int num_parms, char *parms[]) {
126         int ret;
127
128         if (num_parms != 4) {
129                 cprintf("%s BAD invalid parameters\r\n", parms[0]);
130                 return;
131         }
132
133         if (imap_is_message_set(parms[2])) {
134                 imap_pick_range(parms[2], 0);
135         }
136         else {
137                 cprintf("%s BAD invalid parameters\r\n", parms[0]);
138                 return;
139         }
140
141         ret = imap_do_copy(parms[3]);
142         if (!ret) {
143                 cprintf("%s OK COPY completed\r\n", parms[0]);
144         }
145         else {
146                 cprintf("%s NO COPY failed (error %d)\r\n", parms[0], ret);
147         }
148 }
149
150 /*
151  * This function is called by the main command loop.
152  */
153 void imap_uidcopy(int num_parms, char *parms[]) {
154
155         if (num_parms != 5) {
156                 cprintf("%s BAD invalid parameters\r\n", parms[0]);
157                 return;
158         }
159
160         if (imap_is_message_set(parms[3])) {
161                 imap_pick_range(parms[3], 1);
162         }
163         else {
164                 cprintf("%s BAD invalid parameters\r\n", parms[0]);
165                 return;
166         }
167
168         if (imap_do_copy(parms[4]) == 0) {
169                 cprintf("%s OK UID COPY completed\r\n", parms[0]);
170         }
171         else {
172                 cprintf("%s NO UID COPY failed\r\n", parms[0]);
173         }
174 }
175
176
177 /*
178  * Poll for instant messages (yeah, we can do this in IMAP ... I think)
179  */
180 void imap_print_instant_messages(void) {
181         struct ExpressMessage *ptr, *holdptr;
182         char *dumpomatic = NULL;
183         char tmp[SIZ];
184         int i;
185         size_t size, size2;
186         struct tm stamp;
187
188         if (CC->FirstExpressMessage == NULL) {
189                 return;
190         }
191         begin_critical_section(S_SESSION_TABLE);
192         ptr = CC->FirstExpressMessage;
193         CC->FirstExpressMessage = NULL;
194         end_critical_section(S_SESSION_TABLE);
195
196         while (ptr != NULL) {
197                 localtime_r(&(ptr->timestamp), &stamp);
198                 size = strlen(ptr->text) + SIZ;
199                 dumpomatic = malloc(size);
200                 strcpy(dumpomatic, "");
201                 if (ptr->flags && EM_BROADCAST)
202                         strcat(dumpomatic, "Broadcast message ");
203                 else if (ptr->flags && EM_CHAT)
204                         strcat(dumpomatic, "Chat request ");
205                 else if (ptr->flags && EM_GO_AWAY)
206                         strcat(dumpomatic, "Please logoff now, as requested ");
207                 else
208                         strcat(dumpomatic, "Message ");
209
210                 /* Timestamp.  Can this be improved? */
211                 if (stamp.tm_hour == 0 || stamp.tm_hour == 12)
212                         sprintf(tmp, "at 12:%02d%cm",
213                                 stamp.tm_min, 
214                                 stamp.tm_hour ? 'p' : 'a');
215                 else if (stamp.tm_hour > 12)            /* pm */
216                         sprintf(tmp, "at %d:%02dpm",
217                                 stamp.tm_hour - 12,
218                                 stamp.tm_min);
219                 else                                    /* am */
220                         sprintf(tmp, "at %d:%02dam",
221                                 stamp.tm_hour, stamp.tm_min);
222                 strcat(dumpomatic, tmp);
223
224                 size2 = strlen(dumpomatic);
225                 snprintf(&dumpomatic[size2], size - size2,
226                         " from %s:\n", ptr->sender);
227                 if (ptr->text != NULL)
228                         strcat(dumpomatic, ptr->text);
229
230                 holdptr = ptr->next;
231                 if (ptr->text != NULL) free(ptr->text);
232                 free(ptr);
233                 ptr = holdptr;
234
235                 for (i=0; i<strlen(dumpomatic); ++i) {
236                         if (!isprint(dumpomatic[i])) dumpomatic[i] = ' ';
237                         if (dumpomatic[i]=='\\') dumpomatic[i]='/';
238                         if (dumpomatic[i]=='\"') dumpomatic[i]='\'';
239                 }
240
241                 cprintf("* OK [ALERT] %s\r\n", dumpomatic);
242                 free(dumpomatic);
243         }
244         cprintf("000\n");
245 }
246
247
248 /*
249  * imap_do_append_flags() is called by imap_append() to set any flags that
250  * the client specified at append time.
251  *
252  * FIXME find a way to do these in bulk so we don't max out our db journal
253  */
254 void imap_do_append_flags(long new_msgnum, char *new_message_flags) {
255         char flags[32];
256         char this_flag[sizeof flags];
257         int i;
258
259         if (new_message_flags == NULL) return;
260         if (strlen(new_message_flags) == 0) return;
261
262         safestrncpy(flags, new_message_flags, sizeof flags);
263
264         for (i=0; i<num_tokens(flags, ' '); ++i) {
265                 extract_token(this_flag, flags, i, ' ', sizeof this_flag);
266                 if (this_flag[0] == '\\') strcpy(this_flag, &this_flag[1]);
267                 if (!strcasecmp(this_flag, "Seen")) {
268                         CtdlSetSeen(&new_msgnum, 1, 1, ctdlsetseen_seen,
269                                 NULL, NULL);
270                 }
271                 if (!strcasecmp(this_flag, "Answered")) {
272                         CtdlSetSeen(&new_msgnum, 1, 1, ctdlsetseen_answered,
273                                 NULL, NULL);
274                 }
275         }
276 }
277
278
279 /*
280  * This function is called by the main command loop.
281  */
282 void imap_append(int num_parms, char *parms[]) {
283         long literal_length;
284         long bytes_transferred;
285         long stripped_length = 0;
286         struct CtdlMessage *msg;
287         long new_msgnum = (-1L);
288         int ret = 0;
289         char roomname[ROOMNAMELEN];
290         char buf[SIZ];
291         char savedroom[ROOMNAMELEN];
292         int msgs, new;
293         int i;
294         char new_message_flags[SIZ];
295
296         if (num_parms < 4) {
297                 cprintf("%s BAD usage error\r\n", parms[0]);
298                 return;
299         }
300
301         if ( (parms[num_parms-1][0] != '{')
302            || (parms[num_parms-1][strlen(parms[num_parms-1])-1] != '}') )  {
303                 cprintf("%s BAD no message literal supplied\r\n", parms[0]);
304                 return;
305         }
306
307         strcpy(new_message_flags, "");
308         if (num_parms >= 5) {
309                 for (i=3; i<num_parms; ++i) {
310                         strcat(new_message_flags, parms[i]);
311                         strcat(new_message_flags, " ");
312                 }
313                 stripallbut(new_message_flags, '(', ')');
314         }
315
316         /* This is how we'd do this if it were relevant in our data store.
317          * if (num_parms >= 6) {
318          *  new_message_internaldate = parms[4];
319          * }
320          */
321
322         literal_length = atol(&parms[num_parms-1][1]);
323         if (literal_length < 1) {
324                 cprintf("%s BAD Message length must be at least 1.\r\n",
325                         parms[0]);
326                 return;
327         }
328
329         imap_free_transmitted_message();        /* just in case. */
330         IMAP->transmitted_message = malloc(literal_length + 1);
331         if (IMAP->transmitted_message == NULL) {
332                 cprintf("%s NO Cannot allocate memory.\r\n", parms[0]);
333                 return;
334         }
335         IMAP->transmitted_length = literal_length;
336
337         cprintf("+ Transmit message now.\r\n");
338
339         bytes_transferred = 0;
340
341         ret = client_read(IMAP->transmitted_message, literal_length);
342         IMAP->transmitted_message[literal_length] = 0;
343
344         if (ret != 1) {
345                 cprintf("%s NO Read failed.\r\n", parms[0]);
346                 return;
347         }
348
349         /* Client will transmit a trailing CRLF after the literal (the message
350          * text) is received.  This call to client_getln() absorbs it.
351          */
352         flush_output();
353         client_getln(buf, sizeof buf);
354
355         /* Convert RFC822 newlines (CRLF) to Unix newlines (LF) */
356         lprintf(CTDL_DEBUG, "Converting CRLF to LF\n");
357         stripped_length = 0;
358         for (i=0; i<literal_length; ++i) {
359                 if (strncmp(&IMAP->transmitted_message[i], "\r\n", 2)) {
360                         IMAP->transmitted_message[stripped_length++] =
361                                 IMAP->transmitted_message[i];
362                 }
363         }
364         literal_length = stripped_length;
365         IMAP->transmitted_message[literal_length] = 0;  /* reterminate it */
366
367         lprintf(CTDL_DEBUG, "Converting message format\n");
368         msg = convert_internet_message(IMAP->transmitted_message);
369         IMAP->transmitted_message = NULL;
370         IMAP->transmitted_length = 0;
371
372         ret = imap_grabroom(roomname, parms[2], 0);
373         if (ret != 0) {
374                 cprintf("%s NO Invalid mailbox name or access denied\r\n",
375                         parms[0]);
376                 return;
377         }
378
379         /*
380          * usergoto() formally takes us to the desired room.  (If another
381          * folder is selected, save its name so we can return there!!!!!)
382          */
383         if (IMAP->selected) {
384                 strcpy(savedroom, CC->room.QRname);
385         }
386         usergoto(roomname, 0, 0, &msgs, &new);
387
388         /* If the user is locally authenticated, FORCE the From: header to
389          * show up as the real sender.  FIXME do we really want to do this?
390          * Probably should make it site-definable or even room-definable.
391          *
392          * For now, we allow "forgeries" if the room is one of the user's
393          * private mailboxes.
394          */
395         if (CC->logged_in) {
396            if ( (CC->room.QRflags & QR_MAILBOX) == 0) {
397                 if (msg->cm_fields['A'] != NULL) free(msg->cm_fields['A']);
398                 if (msg->cm_fields['N'] != NULL) free(msg->cm_fields['N']);
399                 if (msg->cm_fields['H'] != NULL) free(msg->cm_fields['H']);
400                 msg->cm_fields['A'] = strdup(CC->user.fullname);
401                 msg->cm_fields['N'] = strdup(config.c_nodename);
402                 msg->cm_fields['H'] = strdup(config.c_humannode);
403             }
404         }
405
406         /* 
407          * Can we post here?
408          */
409         ret = CtdlDoIHavePermissionToPostInThisRoom(buf, sizeof buf);
410
411         if (ret) {
412                 /* Nope ... print an error message */
413                 cprintf("%s NO %s\r\n", parms[0], buf);
414         }
415
416         else {
417                 /* Yes ... go ahead and post! */
418                 if (msg != NULL) {
419                         new_msgnum = CtdlSubmitMsg(msg, NULL, "");
420                 }
421                 if (new_msgnum >= 0L) {
422                         cprintf("%s OK APPEND completed\r\n", parms[0]);
423                 }
424                 else {
425                         cprintf("%s BAD Error %ld saving message to disk.\r\n",
426                                 parms[0], new_msgnum);
427                 }
428         }
429
430         /*
431          * IMAP protocol response to client has already been sent by now.
432          *
433          * If another folder is selected, go back to that room so we can resume
434          * our happy day without violent explosions.
435          */
436         if (IMAP->selected) {
437                 usergoto(savedroom, 0, 0, &msgs, &new);
438         }
439
440         /* We don't need this buffer anymore */
441         CtdlFreeMessage(msg);
442
443         if (new_message_flags != NULL) {
444                 imap_do_append_flags(new_msgnum, new_message_flags);
445         }
446 }