2 * This module is an managesieve implementation for the Citadel system.
3 * It is compliant with all of the following:
5 * http://tools.ietf.org/html/draft-martin-managesieve-06
6 * as this draft expires with this writing, you might need to search for
9 * Copyright (c) 2007-2012 by the citadel.org team
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.
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.
28 #include <sys/types.h>
31 #if TIME_WITH_SYS_TIME
32 # include <sys/time.h>
36 # include <sys/time.h>
46 #include <sys/socket.h>
47 #include <netinet/in.h>
48 #include <arpa/inet.h>
49 #include <libcitadel.h>
52 #include "citserver.h"
59 #include "internet_addressing.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"
70 * http://tools.ietf.org/html/draft-martin-managesieve-06
72 * this is the draft this code tries to implement.
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;
84 enum { /** Command states for login authentication */
92 #define MGSVE ((struct citmgsve *)CC->session_specific_data)
94 int old_imap_parameterize(char** args, char *in)
101 /* Skip whitespace. */
108 /* Found the start of a token. */
112 /* Read in the token. */
122 /* Found a quoted section. */
154 /*****************************************************************************/
155 /* MANAGESIEVE Server */
156 /*****************************************************************************/
159 void sieve_outbuf_append(char *str)
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 );
167 sprintf(buf,"%s%s",MGSVE->imap_format_outstring, str);
169 memcpy(buf, str, newlen);
171 if (oldlen != 0) free (MGSVE->imap_format_outstring);
172 MGSVE->imap_format_outstring = buf;
177 * Capability listing. Printed as greeting or on "CAPABILITIES"
178 * see Section 1.8 ; 2.4
180 void cmd_mgsve_caps(void)
182 cprintf("\"IMPLEMENTATION\" \"CITADEL Sieve " PACKAGE_VERSION "\"\r\n"
183 "\"SASL\" \"PLAIN\"\r\n" /*DIGEST-MD5 GSSAPI SASL sucks.*/
185 /* if TLS is already there, should we say that again? */
188 "\"SIEVE\" \"%s\"\r\n"
189 "OK\r\n", msiv_extensions);
194 * Here's where our managesieve session begins its happy day.
196 void managesieve_greeting(void) {
198 strcpy(CC->cs_clientname, "Managesieve session");
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));
208 long GetSizeToken(char * token)
210 char *cursor = token;
213 while (!IsEmptyStr(cursor) &&
218 if (IsEmptyStr(cursor))
221 while ((*cursor != '\0') &&
227 if (cursor[-1] == '+')
230 if (IsEmptyStr(cursor))
236 char *ReadString(long size, char *command)
240 cprintf("NO %s: %ld BAD Message length must be at least 1.\r\n",
242 CC->kill_me = KILLME_READSTRING_FAILED;
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;
251 MGSVE->transmitted_length = size;
253 ret = client_read(MGSVE->transmitted_message, size);
254 MGSVE->transmitted_message[size] = '\0';
257 cprintf("%s NO Read failed.\r\n", command);
260 return MGSVE->transmitted_message;
263 /* AUTHENTICATE command; 2.1 */
264 void cmd_mgsve_auth(int num_parms, char **parms, struct sdm_userdata *u)
266 if ((num_parms == 3) && !strncasecmp(parms[1], "PLAIN", 5))
267 /* todo, check length*/
274 memset (auth, 0, SIZ);
275 if (parms[2][0] == '{')
276 message = ReadString(GetSizeToken(parms[2]), parms[0]);
278 if (message != NULL) {/**< do we have tokenized login? */
279 CtdlDecodeBase64(auth, MGSVE->transmitted_message, strlen(MGSVE->transmitted_message));
282 CtdlDecodeBase64(auth, parms[2], strlen(parms[2]));
284 if ((*username == '\0') && (*(username + 1) != '\0'))
287 if (login_ok == CtdlLoginExistingUser(NULL, username))
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]);
295 if (pass_ok == CtdlTryPassword(pass, strlen(pass)))
297 MGSVE->command_state = mgsve_password;
303 cprintf("NO \"Authentication Failure.\"\r\n");/* we just support auth plain. */
304 CC->kill_me = KILLME_AUTHFAILED;
309 * STARTTLS command chapter 2.2
311 void cmd_mgsve_starttls(void)
312 { /** answer with OK, and fire off tls session. */
314 CtdlModuleStartCryptoMsgs(NULL, NULL, NULL);
321 * LOGOUT command, see chapter 2.3
323 void cmd_mgsve_logout(struct sdm_userdata *u)
326 syslog(LOG_NOTICE, "MgSve bye.");
327 CC->kill_me = KILLME_CLIENT_LOGGED_OUT;
332 * HAVESPACE command. see chapter 2.5
334 void cmd_mgsve_havespace(void)
336 /* as we don't have quotas in citadel we should always answer with OK;
337 * pherhaps we should have a max-scriptsize.
339 if (MGSVE->command_state != mgsve_password)
342 CC->kill_me = KILLME_QUOTA;
347 /* citadel doesn't have quotas. in case of change, please add code here. */
353 * PUTSCRIPT command, see chapter 2.6
355 void cmd_mgsve_putscript(int num_parms, char **parms, struct sdm_userdata *u)
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" */
366 if (parms[1][0]=='"')
367 ScriptName = &parms[1][1];
369 ScriptName = parms[1];
371 slength = strlen (ScriptName);
373 if (ScriptName[slength] == '"')
374 ScriptName[slength] = '\0';
376 Script = ReadString(GetSizeToken(parms[2]),parms[0]);
378 if (Script == NULL) return;
380 // TODO: do we spellcheck?
381 msiv_putscript(u, ScriptName, Script);
385 cprintf("%s NO Read failed.\r\n", parms[0]);
386 CC->kill_me = KILLME_READ_FAILED;
398 * LISTSCRIPT command. see chapter 2.7
400 void cmd_mgsve_listscript(int num_parms, char **parms, struct sdm_userdata *u)
403 struct sdm_script *s;
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",
411 (s->script_active)?" ACTIVE":"");
420 * \brief SETACTIVE command. see chapter 2.8
422 void cmd_mgsve_setactive(int num_parms, char **parms, struct sdm_userdata *u)
426 if (msiv_setactive(u, parms[1]) == 0) {
430 cprintf("No \"there is no script by that name %s \"\r\n", parms[1]);
433 cprintf("NO \"unexpected parameters.\"\r\n");
439 * \brief GETSCRIPT command. see chapter 2.9
441 void cmd_mgsve_getscript(int num_parms, char **parms, struct sdm_userdata *u)
444 char *script_content;
447 script_content = msiv_getscript(u, parms[1]);
448 if (script_content != NULL){
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);
457 cprintf("No \"there is no script by that name %s \"\r\n", parms[1]);
460 cprintf("NO \"unexpected parameters.\"\r\n");
465 * \brief DELETESCRIPT command. see chapter 2.10
467 void cmd_mgsve_deletescript(int num_parms, char **parms, struct sdm_userdata *u)
472 i = msiv_deletescript(u, parms[1]);
478 cprintf("NO \"no script by that name: %s\"\r\n", parms[1]);
481 cprintf("NO \"can't delete active Script: %s\"\r\n", parms[1]);
485 cprintf("NO \"unexpected parameters.\"\r\n");
492 * \brief Attempt to perform authenticated managesieve
494 void mgsve_auth(char *argbuf) {
495 char username_prompt[64];
497 char encoded_authstring[1024];
500 cprintf("NO \"Already logged in.\"\r\n");
504 extract_token(method, argbuf, 0, ' ', sizeof method);
506 if (!strncasecmp(method, "login", 5) ) {
507 if (strlen(argbuf) >= 7) {
510 size_t len = CtdlEncodeBase64(username_prompt, "Username:", 9, 0);
511 if (username_prompt[len - 1] == '\n') {
512 username_prompt[len - 1] = '\0';
514 cprintf("334 %s\r\n", username_prompt);
519 if (!strncasecmp(method, "plain", 5) ) {
520 if (num_tokens(argbuf, ' ') < 2) {
524 extract_token(encoded_authstring, argbuf, 1, ' ', sizeof encoded_authstring);
528 if (strncasecmp(method, "login", 5) ) {
529 cprintf("NO \"Unknown authentication method.\"\r\n");
538 * implements the STARTTLS command (Citadel API version)
540 void _mgsve_starttls(void)
542 char ok_response[SIZ];
543 char nosup_response[SIZ];
544 char error_response[SIZ];
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);
557 * Main command loop for managesieve sessions.
559 void managesieve_command_loop(void) {
564 struct sdm_userdata u;
565 int changes_made = 0;
567 memset(&u, 0, sizeof(struct sdm_userdata));
570 memset(cmdbuf, 0, sizeof cmdbuf); /* Clear it, just in case */
571 length = client_getln(cmdbuf, sizeof cmdbuf);
573 num_parms = old_imap_parameterize(parms, cmdbuf);
574 if (num_parms == 0) return;
575 length = strlen(parms[0]);
578 syslog(LOG_CRIT, "managesieve: client disconnected: ending session.\n");
579 CC->kill_me = KILLME_CLIENT_DISCONNECTED;
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);
588 else if ((length>= 8) && (!strncasecmp(parms[0], "STARTTLS", 8))){
589 cmd_mgsve_starttls();
592 else if ((length>= 6) && (!strncasecmp(parms[0], "LOGOUT", 6))){
593 cmd_mgsve_logout(&u);
595 else if ((length>= 6) && (!strncasecmp(parms[0], "CAPABILITY", 10))){
598 /** these commands need to be authenticated. throw it out if it tries. */
599 else if (CC->logged_in != 0)
602 if ((length>= 9) && (!strncasecmp(parms[0], "HAVESPACE", 9))){
603 cmd_mgsve_havespace();
605 else if ((length>= 6) && (!strncasecmp(parms[0], "PUTSCRIPT", 9))){
606 cmd_mgsve_putscript(num_parms, parms, &u);
609 else if ((length>= 6) && (!strncasecmp(parms[0], "LISTSCRIPT", 10))){
610 cmd_mgsve_listscript(num_parms, parms,&u);
612 else if ((length>= 6) && (!strncasecmp(parms[0], "SETACTIVE", 9))){
613 cmd_mgsve_setactive(num_parms, parms,&u);
616 else if ((length>= 6) && (!strncasecmp(parms[0], "GETSCRIPT", 9))){
617 cmd_mgsve_getscript(num_parms, parms, &u);
619 else if ((length>= 6) && (!strncasecmp(parms[0], "DELETESCRIPT", 11))){
620 cmd_mgsve_deletescript(num_parms, parms, &u);
623 msiv_store(&u, changes_made);
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;
635 * This cleanup function blows away the temporary memory and files used by
638 void managesieve_cleanup_function(void) {
640 /* Don't do this stuff if this is not a managesieve session! */
641 if (CC->h_command_function != managesieve_command_loop) return;
643 syslog(LOG_DEBUG, "Performing managesieve cleanup hook\n");
649 const char* CitadelServiceManageSieve = "ManageSieve";
650 CTDL_MODULE_INIT(managesieve)
654 CtdlRegisterServiceHook(config.c_managesieve_port,
656 managesieve_greeting,
657 managesieve_command_loop,
659 CitadelServiceManageSieve);
660 CtdlRegisterSessionHook(managesieve_cleanup_function, EVT_STOP, PRIO_STOP + 30);
663 /* return our module name for the log */
664 return "managesieve";