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