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