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