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