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