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