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