f93e20942d1d2c056d0e4888cfa0760401975c1c
[citadel.git] / citadel / 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 "citadel.h"
42 #include "server.h"
43 #include "sysdep_decls.h"
44 #include "citserver.h"
45 #include "support.h"
46 #include "config.h"
47 #include "control.h"
48 #include "serv_extensions.h"
49 #include "room_ops.h"
50 #include "user_ops.h"
51 #include "policy.h"
52 #include "database.h"
53 #include "msgbase.h"
54 #include "tools.h"
55 #include "internet_addressing.h"
56 #include "imap_tools.h"
57 #include "genstamp.h"
58 #include "domain.h"
59 #include "clientsocket.h"
60 #include "locate_host.h"
61 #include "citadel_dirs.h"
62
63 #ifdef HAVE_OPENSSL
64 #include "serv_crypto.h"
65 #endif
66
67 #ifndef HAVE_SNPRINTF
68 #include "snprintf.h"
69 #endif
70
71 #ifdef HAVE_LIBSIEVE
72
73 #include "serv_sieve.h"
74
75
76 /**
77  * http://tools.ietf.org/html/draft-martin-managesieve-06
78  *
79  * this is the draft this code tries to implement.
80  */
81
82
83 struct citmgsve {               
84         int command_state;             /**< Information about the current session */
85         char *transmitted_message;
86         size_t transmitted_length;
87         char *imap_format_outstring;
88         int imap_outstring_length;
89 };
90
91 enum {  /** Command states for login authentication */
92         mgsve_command,
93         mgsve_tls,
94         mgsve_user,
95         mgsve_password,
96         mgsve_plain
97 };
98
99 #define MGSVE          CC->MGSVE
100
101 /*****************************************************************************/
102 /*                      MANAGESIEVE Server                                   */
103 /*****************************************************************************/
104
105 void sieve_outbuf_append(char *str)
106 {
107         size_t newlen = strlen(str)+1;
108         size_t oldlen = (MGSVE->imap_format_outstring==NULL)? 0 : strlen(MGSVE->imap_format_outstring)+2;
109         char *buf = malloc ( newlen + oldlen + 10 );
110         buf[0]='\0';
111
112         if (oldlen!=0)
113                 sprintf(buf,"%s%s",MGSVE->imap_format_outstring, str);
114         else
115                 memcpy(buf, str, newlen);
116         
117         if (oldlen != 0) free (MGSVE->imap_format_outstring);
118         MGSVE->imap_format_outstring = buf;
119 }
120
121
122 /**
123  * Capability listing. Printed as greeting or on "CAPABILITIES" 
124  * see Section 1.8 ; 2.4
125  */
126 void cmd_mgsve_caps(void)
127
128         cprintf("\"IMPLEMENTATION\" \"CITADEL Sieve v6.84\"\r\n" /* TODO: put citversion here. */
129                 "\"SASL\" \"PLAIN\"\r\n" /*DIGEST-MD5 GSSAPI  SASL sucks.*/
130 #ifdef HAVE_OPENSSL
131 /* if TLS is already there, should we say that again? */
132                 "\"STARTTLS\"\r\n"
133 #endif
134                 "\"SIEVE\" \"FILEINTO VACATION\"\r\n" /* TODO: print sieve extensions here. */
135                 "OK\r\n");
136 }
137
138
139 /*
140  * Here's where our managesieve session begins its happy day.
141  */
142 void managesieve_greeting(void) {
143
144         strcpy(CC->cs_clientname, "Managesieve session");
145
146         CC->internal_pgm = 1;
147         CC->cs_flags |= CS_STEALTH;
148         MGSVE = malloc(sizeof(struct citmgsve));
149         memset(MGSVE, 0, sizeof(struct citmgsve));
150         cmd_mgsve_caps();
151 }
152
153
154 long GetSizeToken(char * token)
155 {
156         char *cursor = token;
157         char *number;
158
159         while ((*cursor != '\0') && 
160                (*cursor != '{'))
161         {
162                 cursor++;
163         }
164         if (*cursor == '\0') 
165                 return -1;
166         number = cursor + 1;
167         while ((*cursor != '\0') && 
168                (*cursor != '}'))
169         {
170                 cursor++;
171         }
172
173         if (cursor[-1] == '+')
174                 cursor--;
175
176         if (*cursor == '\0') 
177                 return -1;
178         
179         return atol(number);
180 }
181
182 char *ReadString(long size, char *command)
183 {
184         long ret;
185         if (size < 1) {
186                 cprintf("NO %s: %ld BAD Message length must be at least 1.\r\n",
187                         command, size);
188                 CC->kill_me = 1;
189                 return NULL;
190         }
191         MGSVE->transmitted_message = malloc(size + 2);
192         if (MGSVE->transmitted_message == NULL) {
193                 cprintf("NO %s Cannot allocate memory.\r\n", command);
194                 CC->kill_me = 1;
195                 return NULL;
196         }
197         MGSVE->transmitted_length = size;
198         
199         ret = client_read(MGSVE->transmitted_message, size);
200         MGSVE->transmitted_message[size] = '\0';
201         
202         if (ret != 1) {
203                 cprintf("%s NO Read failed.\r\n", command);
204                 return NULL;
205         } 
206         return MGSVE->transmitted_message;
207
208 }
209 /* AUTHENTICATE command; 2.1 */
210 void cmd_mgsve_auth(int num_parms, char **parms, struct sdm_userdata *u)
211 {
212 /* TODO: compare "digest-md5" or "gssapi" and answer with "NO" */
213         if ((num_parms == 3) && !strncasecmp(parms[1], "PLAIN", 5))
214                 /* todo, check length*/
215         {
216                 char auth[SIZ];
217                 int retval;
218                 
219                 /* todo: how to do plain auth? */
220                 char *message = ReadString(GetSizeToken(parms[2]), parms[0]);
221                 
222                 if (message != NULL) {/* do we have tokenized login? */
223                         retval = CtdlDecodeBase64(auth, MGSVE->transmitted_message, SIZ);
224                 }
225                 else 
226                         retval = CtdlDecodeBase64(auth, parms[2], SIZ);
227
228                 if (login_ok == CtdlLoginExistingUser(auth))
229                 {
230                         char *pass;
231                         pass = &(auth[strlen(auth)+1]);
232                         /* for some reason the php script sends us the username twice. y? */
233                         pass = &(pass[strlen(pass)+1]);
234                         
235                         if (pass_ok == CtdlTryPassword(pass))
236                         {
237                                 MGSVE->command_state = mgsve_password;
238                                 cprintf("OK\r\n");
239                                 return;
240                         }
241                 }
242         }
243         
244         cprintf("NO\r\n");/* we just support auth plain. */
245         CC->kill_me = 1;
246 }
247
248
249 #ifdef HAVE_OPENSSL
250 /* STARTTLS command chapter 2.2 */
251 void cmd_mgsve_starttls(void)
252 { /* answer with OK, and fire off tls session. */
253         cprintf("OK\r\n");
254         CtdlStartTLS(NULL, NULL, NULL);
255         cmd_mgsve_caps();
256 }
257 #endif
258
259
260
261 /* LOGOUT command, see chapter 2.3 */
262 void cmd_mgsve_logout(struct sdm_userdata *u)
263 {/* send "OK" and terminate the connection. */
264         cprintf("OK\r\n");
265         lprintf(CTDL_NOTICE, "MgSve bye.");
266         CC->kill_me = 1;
267 }
268
269
270 /* HAVESPACE command. see chapter 2.5 */
271 void cmd_mgsve_havespace(void)
272 {
273 /* TODO answer NO in any case if auth is missing. */
274 /* as we don't have quotas in citadel we should always answer with OK; 
275  * pherhaps we should have a max-scriptsize. 
276  */
277         if (MGSVE->command_state != mgsve_password)
278         {
279                 cprintf("NO\r\n");
280                 CC->kill_me = 1;
281         }
282         else
283         {
284                 cprintf("OK"); 
285 /* citadel doesn't have quotas. in case of change, please add code here. */
286
287         }
288 }
289
290 /* PUTSCRIPT command, see chapter 2.6 */
291 void cmd_mgsve_putscript(int num_parms, char **parms, struct sdm_userdata *u)
292 {
293 /* "scriptname" {nnn+} */
294 /* TODO: answer with "NO" instant, if we're unauthorized. */
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 /* LISTSCRIPT command. see chapter 2.7 */
335 void cmd_mgsve_listscript(int num_parms, char **parms, struct sdm_userdata *u)
336 {
337
338         struct sdm_script *s;
339         long nScripts = 0;
340
341         MGSVE->imap_format_outstring = NULL;
342         for (s=u->first_script; s!=NULL; s=s->next) {
343                 if (s->script_content != NULL) {
344                         cprintf("\"%s\"%s\r\n", 
345                                 s->script_name, 
346                                 (s->script_active)?" ACTIVE":"");
347                         nScripts++;
348                 }
349         }
350
351         //      if (nScripts > 0)
352                 cprintf("OK\r\n");
353                 //      else
354                 //cprintf("NO \"No scripts found.\"\r\n");
355 }
356
357
358 /* SETACTIVE command. see chapter 2.8 */
359 void cmd_mgsve_setactive(int num_parms, char **parms, struct sdm_userdata *u)
360 {
361 /* TODO: check auth, if not, answer with "no" */
362 /* search our room for subjects with that scriptname, 
363  * if the scriptname is empty, use the default flag.
364  * if the script is not there answer "No "there is no script by that name "
365  */
366         if (num_parms == 2)
367         {
368                 if (msiv_setactive(u, parms[1]) == 0) {
369                         cprintf("OK\r\n");
370                 }
371                 else
372                         cprintf("No \"there is no script by that name %s \"\r\n", parms[1]);
373         }
374         else 
375                 cprintf("NO \"unexpected parameters.\"\r\n");
376
377 }
378
379
380 /* GETSCRIPT command. see chapter 2.9 */
381 void cmd_mgsve_getscript(int num_parms, char **parms, struct sdm_userdata *u)
382 {
383 /* check first param, this is the name. look up that in the folder.
384  * answer with the size {nnn+}and spill it out, one blank line and OK
385  */
386
387         if (num_parms == 2){
388                 char *script_content;
389                 long  slen;
390
391                 script_content = msiv_getscript(u, parms[1]);
392                 if (script_content != NULL){
393                         char *outbuf;
394
395                         slen = strlen(script_content);
396                         outbuf = malloc (slen + 64);
397                         snprintf(outbuf, slen + 64, "{%ld+}\r\n%s\r\nOK\r\n",slen, script_content);
398                         cprintf(outbuf);
399                 }
400                 else
401                         cprintf("No \"there is no script by that name %s \"\r\n", parms[1]);
402         }
403         else 
404                 cprintf("NO \"unexpected parameters.\"\r\n");
405 }
406
407
408 /* DELETESCRIPT command. see chapter 2.10 */
409 void cmd_mgsve_deletescript(int num_parms, char **parms, struct sdm_userdata *u)
410 {
411 /* TODO: check auth, if not, answer with "no" */
412         int i=-1;
413
414         if (num_parms == 2)
415                 i = msiv_deletescript(u, parms[1]);
416         switch (i){             
417         case 0:
418                 cprintf("OK\r\n");
419                 break;
420         case 1:
421                 cprintf("NO \"no script by that name: %s\"\r\n", parms[1]);
422                 break;
423         case 2:
424                 cprintf("NO \"can't delete active Script: %s\"\r\n", parms[1]);
425                 break;
426         default:
427         case -1:
428                 cprintf("NO \"unexpected parameters.\"\r\n");
429                 break;
430         }
431 }
432
433
434
435 /*
436  *
437 void mgsve_get_user(char *argbuf) {
438         char buf[SIZ];
439         char username[SIZ];
440
441         CtdlDecodeBase64(username, argbuf, SIZ);
442         / * lprintf(CTDL_DEBUG, "Trying <%s>\n", username); * /
443         if (CtdlLoginExistingUser(username) == login_ok) {
444                 CtdlEncodeBase64(buf, "Password:", 9);
445                 cprintf("334 %s\r\n", buf);
446                 MGSVE->command_state = mgsve_password;
447         }
448         else {
449                 cprintf("500 5.7.0 No such user.\r\n");
450                 MGSVE->command_state = mgsve_command;
451         }
452 }
453  */
454
455
456 /*
457  *
458 void mgsve_get_pass(char *argbuf) {
459         char password[SIZ];
460
461         CtdlDecodeBase64(password, argbuf, SIZ);
462         / * lprintf(CTDL_DEBUG, "Trying <%s>\n", password); * /
463         if (CtdlTryPassword(password) == pass_ok) {
464                 mgsve_auth_greeting();
465         }
466         else {
467                 cprintf("535 5.7.0 Authentication failed.\r\n");
468         }
469         MGSVE->command_state = mgsve_command;
470 }
471  */
472
473
474 /*
475  * Back end for PLAIN auth method (either inline or multistate)
476  */
477 void mgsve_try_plain(char *encoded_authstring) {
478         char decoded_authstring[1024];
479         char ident[256];
480         char user[256];
481         char pass[256];
482
483         CtdlDecodeBase64(decoded_authstring,
484                         encoded_authstring,
485                         strlen(encoded_authstring) );
486         safestrncpy(ident, decoded_authstring, sizeof ident);
487         safestrncpy(user, &decoded_authstring[strlen(ident) + 1], sizeof user);
488         safestrncpy(pass, &decoded_authstring[strlen(ident) + strlen(user) + 2], sizeof pass);
489
490 //      MGSVE->command_state = mgsve_command;
491 /*
492         if (CtdlLoginExistingUser(user) == login_ok) {
493                 if (CtdlTryPassword(pass) == pass_ok) {
494                         mgsve_auth_greeting();
495                         return;
496                 }
497         }
498 */
499         cprintf("504 5.7.4 Authentication failed.\r\n");
500 }
501
502
503 /*
504  * Attempt to perform authenticated managesieve
505  */
506 void mgsve_auth(char *argbuf) {
507         char username_prompt[64];
508         char method[64];
509         char encoded_authstring[1024];
510
511         if (CC->logged_in) {
512                 cprintf("504 5.7.4 Already logged in.\r\n");
513                 return;
514         }
515
516         extract_token(method, argbuf, 0, ' ', sizeof method);
517
518         if (!strncasecmp(method, "login", 5) ) {
519                 if (strlen(argbuf) >= 7) {
520 //                      mgsve_get_user(&argbuf[6]);
521                 }
522                 else {
523                         CtdlEncodeBase64(username_prompt, "Username:", 9);
524                         cprintf("334 %s\r\n", username_prompt);
525 //                      MGSVE->command_state = mgsve_user;
526                 }
527                 return;
528         }
529
530         if (!strncasecmp(method, "plain", 5) ) {
531                 if (num_tokens(argbuf, ' ') < 2) {
532                         cprintf("334 \r\n");
533 //                      MGSVE->command_state = mgsve_plain;
534                         return;
535                 }
536
537                 extract_token(encoded_authstring, argbuf, 1, ' ', sizeof encoded_authstring);
538
539 ///             mgsve_try_plain(encoded_authstring);
540                 return;
541         }
542
543         if (strncasecmp(method, "login", 5) ) {
544                 cprintf("504 5.7.4 Unknown authentication method.\r\n");
545                 return;
546         }
547
548 }
549
550
551
552 /*
553  * implements the STARTTLS command (Citadel API version)
554  */
555 #ifdef HAVE_OPENSSL
556 void _mgsve_starttls(void)
557 {
558         char ok_response[SIZ];
559         char nosup_response[SIZ];
560         char error_response[SIZ];
561
562         sprintf(ok_response,
563                 "200 2.0.0 Begin TLS negotiation now\r\n");
564         sprintf(nosup_response,
565                 "554 5.7.3 TLS not supported here\r\n");
566         sprintf(error_response,
567                 "554 5.7.3 Internal error\r\n");
568         CtdlStartTLS(ok_response, nosup_response, error_response);
569 }
570 #endif
571
572
573 /* 
574  * Main command loop for managesieve sessions.
575  */
576 void managesieve_command_loop(void) {
577         char cmdbuf[SIZ];
578         char *parms[SIZ];
579         int length;
580         int num_parms;
581         struct sdm_userdata u;
582         int changes_made = 0;
583
584         memset(&u, 0, sizeof(struct sdm_userdata));
585
586         time(&CC->lastcmd);
587         memset(cmdbuf, 0, sizeof cmdbuf); /* Clear it, just in case */
588         length = client_getln(cmdbuf, sizeof cmdbuf);
589         if (length >= 1) {
590                 num_parms = imap_parameterize(parms, cmdbuf);
591                 ///             length = client_getln(parms[0], sizeof parms[0]);
592                 if (num_parms == 0) return;
593                 length = strlen(parms[0]);
594         }
595         if (length < 1) {
596                 lprintf(CTDL_CRIT, "Client disconnected: ending session.\n");
597                 CC->kill_me = 1;
598                 return;
599         }
600         lprintf(CTDL_INFO, "MANAGESIEVE: %s\n", cmdbuf);
601 //// we have different lengths  while (strlen(cmdbuf) < 5) strcat(cmdbuf, " ");
602         if ((length>= 12) && (!strncasecmp(parms[0], "AUTHENTICATE", 12))){
603                 cmd_mgsve_auth(num_parms, parms, &u);
604         }
605
606 #ifdef HAVE_OPENSSL
607         else if ((length>= 8) && (!strncasecmp(parms[0], "STARTTLS", 8))){
608                 cmd_mgsve_starttls();
609         }
610 #endif
611         else if ((length>= 6) && (!strncasecmp(parms[0], "LOGOUT", 6))){
612                 cmd_mgsve_logout(&u);
613         }
614         else if ((length>= 6) && (!strncasecmp(parms[0], "CAPABILITY", 10))){
615                 cmd_mgsve_caps();
616         } /* these commands need to be authenticated. throw it out if it tries. */
617         else if (!CtdlAccessCheck(ac_logged_in))
618         {
619                 msiv_load(&u);
620                 if ((length>= 9) && (!strncasecmp(parms[0], "HAVESPACE", 9))){
621                         cmd_mgsve_havespace();
622                 }
623                 else if ((length>= 6) && (!strncasecmp(parms[0], "PUTSCRIPT", 9))){
624                         cmd_mgsve_putscript(num_parms, parms, &u);
625                         changes_made = 1;
626                 }
627                 else if ((length>= 6) && (!strncasecmp(parms[0], "LISTSCRIPT", 10))){
628                         cmd_mgsve_listscript(num_parms, parms,&u);
629                 }
630                 else if ((length>= 6) && (!strncasecmp(parms[0], "SETACTIVE", 9))){
631                         cmd_mgsve_setactive(num_parms, parms,&u);
632                         changes_made = 1;
633                 }
634                 else if ((length>= 6) && (!strncasecmp(parms[0], "GETSCRIPT", 9))){
635                         cmd_mgsve_getscript(num_parms, parms, &u);
636                 }
637                 else if ((length>= 6) && (!strncasecmp(parms[0], "DELETESCRIPT", 11))){
638                         cmd_mgsve_deletescript(num_parms, parms, &u);
639                         changes_made = 1;
640                 }
641                 msiv_store(&u, changes_made);
642         }
643         else {
644                 /// todo: log this.
645                 cprintf("No\r\n");
646                 CC->kill_me = 1;
647         }
648
649
650 }
651
652
653
654 char *serv_managesieve_init(void)
655 {
656
657         CtdlRegisterServiceHook(config.c_managesieve_port,      /* MGSVE */
658                                 NULL,
659                                 managesieve_greeting,
660                                 managesieve_command_loop,
661                                 NULL);
662
663         return "$Id: serv_managesieve.c 4570 2006-08-27 02:07:18Z dothebart $";
664 }
665
666 #else   /* HAVE_LIBSIEVE */
667
668 char *serv_managesieve_init(void)
669 {
670         lprintf(CTDL_INFO, "This server is missing libsieve.  Managesieve protocol is disabled..\n");
671         return "$Id:  $";
672 }
673
674 #endif  /* HAVE_LIBSIEVE */