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