HUGE PATCH. This moves all of mime_parser.c and all
[citadel.git] / citadel / modules / managesieve / serv_managesieve.c
1 /**
2  * $Id$
3  *
4  * This module is an managesieve implementation for the Citadel system.
5  * It is compliant with all of the following:
6  *
7  * http://tools.ietf.org/html/draft-martin-managesieve-06
8  * as this draft expires with this writing, you might need to search for
9  * the new one.
10  */
11
12 #include "sysdep.h"
13 #include <stdlib.h>
14 #include <unistd.h>
15 #include <stdio.h>
16 #include <fcntl.h>
17 #include <signal.h>
18 #include <pwd.h>
19 #include <errno.h>
20 #include <sys/types.h>
21 #include <syslog.h>
22
23 #if TIME_WITH_SYS_TIME
24 # include <sys/time.h>
25 # include <time.h>
26 #else
27 # if HAVE_SYS_TIME_H
28 #  include <sys/time.h>
29 # else
30 #  include <time.h>
31 # endif
32 #endif
33
34 #include <sys/wait.h>
35 #include <ctype.h>
36 #include <string.h>
37 #include <limits.h>
38 #include <sys/socket.h>
39 #include <netinet/in.h>
40 #include <arpa/inet.h>
41 #include <libcitadel.h>
42 #include "citadel.h"
43 #include "server.h"
44 #include "citserver.h"
45 #include "support.h"
46 #include "config.h"
47 #include "control.h"
48 #include "room_ops.h"
49 #include "user_ops.h"
50 #include "policy.h"
51 #include "database.h"
52 #include "msgbase.h"
53 #include "internet_addressing.h"
54 #include "imap_tools.h" /* Needed for imap_parameterize */
55 #include "genstamp.h"
56 #include "domain.h"
57 #include "clientsocket.h"
58 #include "locate_host.h"
59 #include "citadel_dirs.h"
60
61 #ifndef HAVE_SNPRINTF
62 #include "snprintf.h"
63 #endif
64
65
66 #include "ctdl_module.h"
67
68
69
70 #ifdef HAVE_LIBSIEVE
71
72 #include "serv_sieve.h"
73
74
75 /**
76  * http://tools.ietf.org/html/draft-martin-managesieve-06
77  *
78  * this is the draft this code tries to implement.
79  */
80
81
82 struct citmgsve {               
83         int command_state;             /**< Information about the current session */
84         char *transmitted_message;
85         size_t transmitted_length;
86         char *imap_format_outstring;
87         int imap_outstring_length;
88 };
89
90 enum {  /** Command states for login authentication */
91         mgsve_command,
92         mgsve_tls,
93         mgsve_user,
94         mgsve_password,
95         mgsve_plain
96 };
97
98 #define MGSVE          CC->MGSVE
99
100 /*****************************************************************************/
101 /*                      MANAGESIEVE Server                                   */
102 /*****************************************************************************/
103
104 void sieve_outbuf_append(char *str)
105 {
106         size_t newlen = strlen(str)+1;
107         size_t oldlen = (MGSVE->imap_format_outstring==NULL)? 0 : strlen(MGSVE->imap_format_outstring)+2;
108         char *buf = malloc ( newlen + oldlen + 10 );
109         buf[0]='\0';
110
111         if (oldlen!=0)
112                 sprintf(buf,"%s%s",MGSVE->imap_format_outstring, str);
113         else
114                 memcpy(buf, str, newlen);
115         
116         if (oldlen != 0) free (MGSVE->imap_format_outstring);
117         MGSVE->imap_format_outstring = buf;
118 }
119
120
121 /**
122  * Capability listing. Printed as greeting or on "CAPABILITIES" 
123  * see Section 1.8 ; 2.4
124  */
125 void cmd_mgsve_caps(void)
126
127         cprintf("\"IMPLEMENTATION\" \"CITADEL Sieve v6.84\"\r\n" /* TODO: put citversion here. */
128                 "\"SASL\" \"PLAIN\"\r\n" /*DIGEST-MD5 GSSAPI  SASL sucks.*/
129 #ifdef HAVE_OPENSSL
130 /* if TLS is already there, should we say that again? */
131                 "\"STARTTLS\"\r\n"
132 #endif
133                 "\"SIEVE\" \"%s\"\r\n"
134                 "OK\r\n", msiv_extensions);
135 }
136
137
138 /*
139  * Here's where our managesieve session begins its happy day.
140  */
141 void managesieve_greeting(void) {
142
143         strcpy(CC->cs_clientname, "Managesieve session");
144
145         CC->internal_pgm = 1;
146         CC->cs_flags |= CS_STEALTH;
147         MGSVE = malloc(sizeof(struct citmgsve));
148         memset(MGSVE, 0, sizeof(struct citmgsve));
149         cmd_mgsve_caps();
150 }
151
152
153 long GetSizeToken(char * token)
154 {
155         char *cursor = token;
156         char *number;
157
158         while (!IsEmptyStr(cursor) && 
159                (*cursor != '{'))
160         {
161                 cursor++;
162         }
163         if (IsEmptyStr(cursor)) 
164                 return -1;
165         number = cursor + 1;
166         while ((*cursor != '\0') && 
167                (*cursor != '}'))
168         {
169                 cursor++;
170         }
171
172         if (cursor[-1] == '+')
173                 cursor--;
174
175         if (IsEmptyStr(cursor)) 
176                 return -1;
177         
178         return atol(number);
179 }
180
181 char *ReadString(long size, char *command)
182 {
183         long ret;
184         if (size < 1) {
185                 cprintf("NO %s: %ld BAD Message length must be at least 1.\r\n",
186                         command, size);
187                 CC->kill_me = 1;
188                 return NULL;
189         }
190         MGSVE->transmitted_message = malloc(size + 2);
191         if (MGSVE->transmitted_message == NULL) {
192                 cprintf("NO %s Cannot allocate memory.\r\n", command);
193                 CC->kill_me = 1;
194                 return NULL;
195         }
196         MGSVE->transmitted_length = size;
197         
198         ret = client_read(MGSVE->transmitted_message, size);
199         MGSVE->transmitted_message[size] = '\0';
200         
201         if (ret != 1) {
202                 cprintf("%s NO Read failed.\r\n", command);
203                 return NULL;
204         } 
205         return MGSVE->transmitted_message;
206
207 }
208 /* AUTHENTICATE command; 2.1 */
209 void cmd_mgsve_auth(int num_parms, char **parms, struct sdm_userdata *u)
210 {
211         if ((num_parms == 3) && !strncasecmp(parms[1], "PLAIN", 5))
212                 /* todo, check length*/
213         {
214                 char auth[SIZ];
215                 int retval;
216                 char *message = ReadString(GetSizeToken(parms[2]), parms[0]);
217                 
218                 if (message != NULL) {/**< do we have tokenized login? */
219                         retval = CtdlDecodeBase64(auth, MGSVE->transmitted_message, SIZ);
220                 }
221                 else 
222                         retval = CtdlDecodeBase64(auth, parms[2], SIZ);
223
224                 if (login_ok == CtdlLoginExistingUser(NULL, auth))
225                 {
226                         char *pass;
227                         pass = &(auth[strlen(auth)+1]);
228                         /* for some reason the php script sends us the username twice. y? */
229                         pass = &(pass[strlen(pass)+1]);
230                         
231                         if (pass_ok == CtdlTryPassword(pass))
232                         {
233                                 MGSVE->command_state = mgsve_password;
234                                 cprintf("OK\r\n");
235                                 return;
236                         }
237                 }
238         }
239         
240         cprintf("NO \"Authentication Failure.\"\r\n");/* we just support auth plain. */
241         CC->kill_me = 1;
242 }
243
244
245 /**
246  * STARTTLS command chapter 2.2 
247  */
248 void cmd_mgsve_starttls(void)
249 { /** answer with OK, and fire off tls session. */
250         cprintf("OK\r\n");
251         CtdlModuleStartCryptoMsgs(NULL, NULL, NULL);
252         cmd_mgsve_caps();
253 }
254
255
256
257 /**
258  *LOGOUT command, see chapter 2.3 
259  */
260 void cmd_mgsve_logout(struct sdm_userdata *u)
261 {
262         cprintf("OK\r\n");
263         lprintf(CTDL_NOTICE, "MgSve bye.");
264         CC->kill_me = 1;
265 }
266
267
268 /**
269  * HAVESPACE command. see chapter 2.5 
270  */
271 void cmd_mgsve_havespace(void)
272 {
273 /* as we don't have quotas in citadel we should always answer with OK; 
274  * pherhaps we should have a max-scriptsize. 
275  */
276         if (MGSVE->command_state != mgsve_password)
277         {
278                 cprintf("NO\r\n");
279                 CC->kill_me = 1;
280         }
281         else
282         {
283                 cprintf("OK"); 
284 /* citadel doesn't have quotas. in case of change, please add code here. */
285
286         }
287 }
288
289 /**
290  * PUTSCRIPT command, see chapter 2.6 
291  */
292 void cmd_mgsve_putscript(int num_parms, char **parms, struct sdm_userdata *u)
293 {
294 /* "scriptname" {nnn+} */
295 /* AFTER we have the whole script overwrite existing scripts */
296 /* spellcheck the script before overwrite old ones, and reply with "no" */
297         if (num_parms == 3)
298         {
299                 char *ScriptName;
300                 char *Script;
301                 long slength;
302
303                 if (parms[1][0]=='"')
304                         ScriptName = &parms[1][1];
305                 else
306                         ScriptName = parms[1];
307                 
308                 slength = strlen (ScriptName);
309                 
310                 if (ScriptName[slength] == '"')
311                         ScriptName[slength] = '\0';
312
313                 Script = ReadString(GetSizeToken(parms[2]),parms[0]);
314
315                 if (Script == NULL) return;
316                 
317                 // TODO: do we spellcheck?
318                 msiv_putscript(u, ScriptName, Script);
319                 cprintf("OK\r\n");
320         }
321         else {
322                 cprintf("%s NO Read failed.\r\n", parms[0]);
323                 CC->kill_me = 1;
324                 return;
325         } 
326
327
328
329 }
330
331
332
333
334 /**
335  * LISTSCRIPT command. see chapter 2.7 
336  */
337 void cmd_mgsve_listscript(int num_parms, char **parms, struct sdm_userdata *u)
338 {
339
340         struct sdm_script *s;
341         long nScripts = 0;
342
343         MGSVE->imap_format_outstring = NULL;
344         for (s=u->first_script; s!=NULL; s=s->next) {
345                 if (s->script_content != NULL) {
346                         cprintf("\"%s\"%s\r\n", 
347                                 s->script_name, 
348                                 (s->script_active)?" ACTIVE":"");
349                         nScripts++;
350                 }
351         }
352         cprintf("OK\r\n");
353 }
354
355
356 /**
357  * \brief SETACTIVE command. see chapter 2.8 
358  */
359 void cmd_mgsve_setactive(int num_parms, char **parms, struct sdm_userdata *u)
360 {
361         if (num_parms == 2)
362         {
363                 if (msiv_setactive(u, parms[1]) == 0) {
364                         cprintf("OK\r\n");
365                 }
366                 else
367                         cprintf("No \"there is no script by that name %s \"\r\n", parms[1]);
368         }
369         else 
370                 cprintf("NO \"unexpected parameters.\"\r\n");
371
372 }
373
374
375 /**
376  * \brief GETSCRIPT command. see chapter 2.9 
377  */
378 void cmd_mgsve_getscript(int num_parms, char **parms, struct sdm_userdata *u)
379 {
380         if (num_parms == 2){
381                 char *script_content;
382                 long  slen;
383
384                 script_content = msiv_getscript(u, parms[1]);
385                 if (script_content != NULL){
386                         char *outbuf;
387
388                         slen = strlen(script_content);
389                         outbuf = malloc (slen + 64);
390                         snprintf(outbuf, slen + 64, "{%ld+}\r\n%s\r\nOK\r\n",slen, script_content);
391                         cprintf(outbuf);
392                 }
393                 else
394                         cprintf("No \"there is no script by that name %s \"\r\n", parms[1]);
395         }
396         else 
397                 cprintf("NO \"unexpected parameters.\"\r\n");
398 }
399
400
401 /**
402  * \brief DELETESCRIPT command. see chapter 2.10 
403  */
404 void cmd_mgsve_deletescript(int num_parms, char **parms, struct sdm_userdata *u)
405 {
406         int i=-1;
407
408         if (num_parms == 2)
409                 i = msiv_deletescript(u, parms[1]);
410         switch (i){             
411         case 0:
412                 cprintf("OK\r\n");
413                 break;
414         case 1:
415                 cprintf("NO \"no script by that name: %s\"\r\n", parms[1]);
416                 break;
417         case 2:
418                 cprintf("NO \"can't delete active Script: %s\"\r\n", parms[1]);
419                 break;
420         default:
421         case -1:
422                 cprintf("NO \"unexpected parameters.\"\r\n");
423                 break;
424         }
425 }
426
427
428 /**
429  * \brief Attempt to perform authenticated managesieve
430  */
431 void mgsve_auth(char *argbuf) {
432         char username_prompt[64];
433         char method[64];
434         char encoded_authstring[1024];
435
436         if (CC->logged_in) {
437                 cprintf("NO \"Already logged in.\"\r\n");
438                 return;
439         }
440
441         extract_token(method, argbuf, 0, ' ', sizeof method);
442
443         if (!strncasecmp(method, "login", 5) ) {
444                 if (strlen(argbuf) >= 7) {
445                 }
446                 else {
447                         CtdlEncodeBase64(username_prompt, "Username:", 9, 0);
448                         cprintf("334 %s\r\n", username_prompt);
449                 }
450                 return;
451         }
452
453         if (!strncasecmp(method, "plain", 5) ) {
454                 if (num_tokens(argbuf, ' ') < 2) {
455                         cprintf("334 \r\n");
456                         return;
457                 }
458                 extract_token(encoded_authstring, argbuf, 1, ' ', sizeof encoded_authstring);
459                 return;
460         }
461
462         if (strncasecmp(method, "login", 5) ) {
463                 cprintf("NO \"Unknown authentication method.\"\r\n");
464                 return;
465         }
466
467 }
468
469
470
471 /*
472  * implements the STARTTLS command (Citadel API version)
473  */
474 void _mgsve_starttls(void)
475 {
476         char ok_response[SIZ];
477         char nosup_response[SIZ];
478         char error_response[SIZ];
479
480         sprintf(ok_response,
481                 "200 2.0.0 Begin TLS negotiation now\r\n");
482         sprintf(nosup_response,
483                 "554 5.7.3 TLS not supported here\r\n");
484         sprintf(error_response,
485                 "554 5.7.3 Internal error\r\n");
486         CtdlModuleStartCryptoMsgs(ok_response, nosup_response, error_response);
487 }
488
489
490 /* 
491  * Main command loop for managesieve sessions.
492  */
493 void managesieve_command_loop(void) {
494         char cmdbuf[SIZ];
495         char *parms[SIZ];
496         int length;
497         int num_parms;
498         struct sdm_userdata u;
499         int changes_made = 0;
500
501         memset(&u, 0, sizeof(struct sdm_userdata));
502
503         time(&CC->lastcmd);
504         memset(cmdbuf, 0, sizeof cmdbuf); /* Clear it, just in case */
505         length = client_getln(cmdbuf, sizeof cmdbuf);
506         if (length >= 1) {
507                 num_parms = imap_parameterize(parms, cmdbuf);
508                 if (num_parms == 0) return;
509                 length = strlen(parms[0]);
510         }
511         if (length < 1) {
512                 lprintf(CTDL_CRIT, "Client disconnected: ending session.\n");
513                 CC->kill_me = 1;
514                 return;
515         }
516         lprintf(CTDL_INFO, "MANAGESIEVE: %s\n", cmdbuf);
517         if ((length>= 12) && (!strncasecmp(parms[0], "AUTHENTICATE", 12))){
518                 cmd_mgsve_auth(num_parms, parms, &u);
519         }
520
521 #ifdef HAVE_OPENSSL
522         else if ((length>= 8) && (!strncasecmp(parms[0], "STARTTLS", 8))){
523                 cmd_mgsve_starttls();
524         }
525 #endif
526         else if ((length>= 6) && (!strncasecmp(parms[0], "LOGOUT", 6))){
527                 cmd_mgsve_logout(&u);
528         }
529         else if ((length>= 6) && (!strncasecmp(parms[0], "CAPABILITY", 10))){
530                 cmd_mgsve_caps();
531         } 
532         /** these commands need to be authenticated. throw it out if it tries. */
533         else if (!CtdlAccessCheck(ac_logged_in))
534         {
535                 msiv_load(&u);
536                 if ((length>= 9) && (!strncasecmp(parms[0], "HAVESPACE", 9))){
537                         cmd_mgsve_havespace();
538                 }
539                 else if ((length>= 6) && (!strncasecmp(parms[0], "PUTSCRIPT", 9))){
540                         cmd_mgsve_putscript(num_parms, parms, &u);
541                         changes_made = 1;
542                 }
543                 else if ((length>= 6) && (!strncasecmp(parms[0], "LISTSCRIPT", 10))){
544                         cmd_mgsve_listscript(num_parms, parms,&u);
545                 }
546                 else if ((length>= 6) && (!strncasecmp(parms[0], "SETACTIVE", 9))){
547                         cmd_mgsve_setactive(num_parms, parms,&u);
548                         changes_made = 1;
549                 }
550                 else if ((length>= 6) && (!strncasecmp(parms[0], "GETSCRIPT", 9))){
551                         cmd_mgsve_getscript(num_parms, parms, &u);
552                 }
553                 else if ((length>= 6) && (!strncasecmp(parms[0], "DELETESCRIPT", 11))){
554                         cmd_mgsve_deletescript(num_parms, parms, &u);
555                         changes_made = 1;
556                 }
557                 msiv_store(&u, changes_made);
558         }
559         else {
560                 cprintf("No\r\n");
561                 lprintf(CTDL_INFO, "illegal Managesieve command: %s", parms[0]);
562                 CC->kill_me = 1;
563         }
564
565
566 }
567
568
569 #endif  /* HAVE_LIBSIEVE */
570 const char* CitadelServiceManageSieve = "ManageSieve";
571 CTDL_MODULE_INIT(managesieve)
572 {
573
574 #ifdef HAVE_LIBSIEVE
575
576         CtdlRegisterServiceHook(config.c_managesieve_port,      /* MGSVE */
577                                 NULL,
578                                 managesieve_greeting,
579                                 managesieve_command_loop,
580                                 NULL, 
581                                 CitadelServiceManageSieve);
582
583 #else   /* HAVE_LIBSIEVE */
584
585         lprintf(CTDL_INFO, "This server is missing libsieve.  Managesieve protocol is disabled..\n");
586
587 #endif  /* HAVE_LIBSIEVE */
588
589         /* return our Subversion id for the Log */
590         return "$Id$";
591 }
592
593