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