4 * This module is an managesieve implementation for the Citadel system.
5 * It is compliant with all of the following:
7 * http://tools.ietf.org/html/draft-martin-managesieve-06
8 * as this draft expires with this writing, you might need to search for
20 #include <sys/types.h>
23 #if TIME_WITH_SYS_TIME
24 # include <sys/time.h>
28 # include <sys/time.h>
38 #include <sys/socket.h>
39 #include <netinet/in.h>
40 #include <arpa/inet.h>
43 #include "sysdep_decls.h"
44 #include "citserver.h"
48 #include "serv_extensions.h"
55 #include "internet_addressing.h"
56 #include "imap_tools.h"
59 #include "clientsocket.h"
60 #include "locate_host.h"
61 #include "citadel_dirs.h"
64 #include "serv_crypto.h"
73 #include "serv_sieve.h"
77 * http://tools.ietf.org/html/draft-martin-managesieve-06
79 * this is the draft this code tries to implement.
84 int command_state; /**< Information about the current session */
85 char *transmitted_message;
86 size_t transmitted_length;
87 char *imap_format_outstring;
88 int imap_outstring_length;
91 enum { /** Command states for login authentication */
99 #define MGSVE CC->MGSVE
101 /*****************************************************************************/
102 /* MANAGESIEVE Server */
103 /*****************************************************************************/
105 void sieve_outbuf_append(char *str)
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 );
113 sprintf(buf,"%s%s",MGSVE->imap_format_outstring, str);
115 memcpy(buf, str, newlen);
117 if (oldlen != 0) free (MGSVE->imap_format_outstring);
118 MGSVE->imap_format_outstring = buf;
121 void goto_sieverules_room(void)
122 {// TODO: check if we're authenticated.
123 struct ctdlroom QRscratch;
125 char augmented_roomname[ROOMNAMELEN];
128 MailboxName(augmented_roomname, sizeof augmented_roomname,
129 &CC->user, SIEVERULES);
130 c = getroom(&QRscratch, augmented_roomname);
131 if (c != 0)/* something went wrong. hit it! */
137 /* move to the sieve room. */
138 memcpy(&CC->room, &QRscratch,
139 sizeof(struct ctdlroom));
140 usergoto(NULL, 0, transiently, NULL, NULL);
144 * Capability listing. Printed as greeting or on "CAPABILITIES"
145 * see Section 1.8 ; 2.4
147 void cmd_mgsve_caps(void)
149 cprintf("\"IMPLEMENTATION\" \"CITADEL Sieve v6.84\"\r\n" /* TODO: put citversion here. */
150 "\"SASL\" \"PLAIN\"\r\n" /*DIGEST-MD5 GSSAPI SASL sucks.*/
152 /* if TLS is already there, should we say that again? */
155 "\"SIEVE\" \"FILEINTO VACATION\"\r\n" /* TODO: print sieve extensions here. */
161 * Here's where our managesieve session begins its happy day.
163 void managesieve_greeting(void) {
165 strcpy(CC->cs_clientname, "Managesieve session");
167 CC->internal_pgm = 1;
168 CC->cs_flags |= CS_STEALTH;
169 MGSVE = malloc(sizeof(struct citmgsve));
170 memset(MGSVE, 0, sizeof(struct citmgsve));
175 long GetSizeToken(char * token)
177 char *cursor = token;
180 while ((*cursor != '\0') &&
188 while ((*cursor != '\0') &&
194 if (cursor[-1] == '+')
203 char *ReadString(long size, char *command)
207 cprintf("NO %s: %ld BAD Message length must be at least 1.\r\n",
212 MGSVE->transmitted_message = malloc(size + 2);
213 if (MGSVE->transmitted_message == NULL) {
214 cprintf("NO %s Cannot allocate memory.\r\n", command);
218 MGSVE->transmitted_length = size;
220 ret = client_read(MGSVE->transmitted_message, size);
221 MGSVE->transmitted_message[size] = '\0';
224 cprintf("%s NO Read failed.\r\n", command);
227 return MGSVE->transmitted_message;
230 /* AUTHENTICATE command; 2.1 */
231 void cmd_mgsve_auth(int num_parms, char **parms, struct sdm_userdata *u)
233 /* TODO: compare "digest-md5" or "gssapi" and answer with "NO" */
234 if ((num_parms == 3) && !strncasecmp(parms[1], "PLAIN", 5))
235 /* todo, check length*/
240 /* todo: how to do plain auth? */
241 char *message = ReadString(GetSizeToken(parms[2]), parms[0]);
243 if (message != NULL) {/* do we have tokenized login? */
244 retval = CtdlDecodeBase64(auth, MGSVE->transmitted_message, SIZ);
247 retval = CtdlDecodeBase64(auth, parms[2], SIZ);
249 if (login_ok == CtdlLoginExistingUser(auth))
252 pass = &(auth[strlen(auth)+1]);
253 /* for some reason the php script sends us the username twice. y? */
254 pass = &(pass[strlen(pass)+1]);
256 if (pass_ok == CtdlTryPassword(pass))
258 MGSVE->command_state = mgsve_password;
265 cprintf("NO\r\n");/* we just support auth plain. */
271 /* STARTTLS command chapter 2.2 */
272 void cmd_mgsve_starttls(void)
273 { /* answer with OK, and fire off tls session. */
275 CtdlStartTLS(NULL, NULL, NULL);
282 /* LOGOUT command, see chapter 2.3 */
283 void cmd_mgsve_logout(struct sdm_userdata *u)
284 {/* send "OK" and terminate the connection. */
286 lprintf(CTDL_NOTICE, "MgSve bye.");
291 /* HAVESPACE command. see chapter 2.5 */
292 void cmd_mgsve_havespace(void)
294 /* TODO answer NO in any case if auth is missing. */
295 /* as we don't have quotas in citadel we should always answer with OK;
296 * pherhaps we should have a max-scriptsize.
298 if (MGSVE->command_state != mgsve_password)
306 /* citadel doesn't have quotas. in case of change, please add code here. */
311 /* PUTSCRIPT command, see chapter 2.6 */
312 void cmd_mgsve_putscript(int num_parms, char **parms, struct sdm_userdata *u)
314 /* "scriptname" {nnn+} */
315 /* TODO: answer with "NO" instant, if we're unauthorized. */
316 /* AFTER we have the whole script overwrite existing scripts */
317 /* spellcheck the script before overwrite old ones, and reply with "no" */
324 if (parms[1][0]=='"')
325 ScriptName = &parms[1][1];
327 ScriptName = parms[1];
329 slength = strlen (ScriptName);
331 if (ScriptName[slength] == '"')
332 ScriptName[slength] = '\0';
334 Script = ReadString(GetSizeToken(parms[2]),parms[0]);
336 if (Script == NULL) return;
338 // TODO: do we spellcheck?
339 msiv_putscript(u, ScriptName, Script);
343 cprintf("%s NO Read failed.\r\n", parms[0]);
355 /* LISTSCRIPT command. see chapter 2.7 */
356 void cmd_mgsve_listscript(int num_parms, char **parms, struct sdm_userdata *u)
359 struct sdm_script *s;
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",
367 (s->script_active)?" ACTIVE":"");
375 //cprintf("NO \"No scripts found.\"\r\n");
379 /* SETACTIVE command. see chapter 2.8 */
380 void cmd_mgsve_setactive(int num_parms, char **parms, struct sdm_userdata *u)
382 /* TODO: check auth, if not, answer with "no" */
383 /* search our room for subjects with that scriptname,
384 * if the scriptname is empty, use the default flag.
385 * if the script is not there answer "No "there is no script by that name "
389 if (msiv_setactive(u, parms[1]) == 0) {
393 cprintf("No \"there is no script by that name %s \"\r\n", parms[1]);
396 cprintf("NO \"unexpected parameters.\"\r\n");
401 /* GETSCRIPT command. see chapter 2.9 */
402 void cmd_mgsve_getscript(int num_parms, char **parms, struct sdm_userdata *u)
404 /* check first param, this is the name. look up that in the folder.
405 * answer with the size {nnn+}and spill it out, one blank line and OK
409 char *script_content;
412 script_content = msiv_getscript(u, parms[1]);
413 if (script_content != NULL){
416 slen = strlen(script_content);
417 outbuf = malloc (slen + 64);
418 snprintf(outbuf, slen + 64, "{%ld+}\r\n%s\r\nOK\r\n",slen, script_content);
422 cprintf("No \"there is no script by that name %s \"\r\n", parms[1]);
425 cprintf("NO \"unexpected parameters.\"\r\n");
429 /* DELETESCRIPT command. see chapter 2.10 */
430 void cmd_mgsve_deletescript(int num_parms, char **parms, struct sdm_userdata *u)
432 /* TODO: check auth, if not, answer with "no" */
436 i = msiv_deletescript(u, parms[1]);
442 cprintf("NO \"no script by that name: %s\"\r\n", parms[1]);
445 cprintf("NO \"can't delete active Script: %s\"\r\n", parms[1]);
449 cprintf("NO \"unexpected parameters.\"\r\n");
458 void mgsve_get_user(char *argbuf) {
462 CtdlDecodeBase64(username, argbuf, SIZ);
463 / * lprintf(CTDL_DEBUG, "Trying <%s>\n", username); * /
464 if (CtdlLoginExistingUser(username) == login_ok) {
465 CtdlEncodeBase64(buf, "Password:", 9);
466 cprintf("334 %s\r\n", buf);
467 MGSVE->command_state = mgsve_password;
470 cprintf("500 5.7.0 No such user.\r\n");
471 MGSVE->command_state = mgsve_command;
479 void mgsve_get_pass(char *argbuf) {
482 CtdlDecodeBase64(password, argbuf, SIZ);
483 / * lprintf(CTDL_DEBUG, "Trying <%s>\n", password); * /
484 if (CtdlTryPassword(password) == pass_ok) {
485 mgsve_auth_greeting();
488 cprintf("535 5.7.0 Authentication failed.\r\n");
490 MGSVE->command_state = mgsve_command;
496 * Back end for PLAIN auth method (either inline or multistate)
498 void mgsve_try_plain(char *encoded_authstring) {
499 char decoded_authstring[1024];
504 CtdlDecodeBase64(decoded_authstring,
506 strlen(encoded_authstring) );
507 safestrncpy(ident, decoded_authstring, sizeof ident);
508 safestrncpy(user, &decoded_authstring[strlen(ident) + 1], sizeof user);
509 safestrncpy(pass, &decoded_authstring[strlen(ident) + strlen(user) + 2], sizeof pass);
511 // MGSVE->command_state = mgsve_command;
513 if (CtdlLoginExistingUser(user) == login_ok) {
514 if (CtdlTryPassword(pass) == pass_ok) {
515 mgsve_auth_greeting();
520 cprintf("504 5.7.4 Authentication failed.\r\n");
525 * Attempt to perform authenticated managesieve
527 void mgsve_auth(char *argbuf) {
528 char username_prompt[64];
530 char encoded_authstring[1024];
533 cprintf("504 5.7.4 Already logged in.\r\n");
537 extract_token(method, argbuf, 0, ' ', sizeof method);
539 if (!strncasecmp(method, "login", 5) ) {
540 if (strlen(argbuf) >= 7) {
541 // mgsve_get_user(&argbuf[6]);
544 CtdlEncodeBase64(username_prompt, "Username:", 9);
545 cprintf("334 %s\r\n", username_prompt);
546 // MGSVE->command_state = mgsve_user;
551 if (!strncasecmp(method, "plain", 5) ) {
552 if (num_tokens(argbuf, ' ') < 2) {
554 // MGSVE->command_state = mgsve_plain;
558 extract_token(encoded_authstring, argbuf, 1, ' ', sizeof encoded_authstring);
560 /// mgsve_try_plain(encoded_authstring);
564 if (strncasecmp(method, "login", 5) ) {
565 cprintf("504 5.7.4 Unknown authentication method.\r\n");
574 * implements the STARTTLS command (Citadel API version)
577 void _mgsve_starttls(void)
579 char ok_response[SIZ];
580 char nosup_response[SIZ];
581 char error_response[SIZ];
584 "200 2.0.0 Begin TLS negotiation now\r\n");
585 sprintf(nosup_response,
586 "554 5.7.3 TLS not supported here\r\n");
587 sprintf(error_response,
588 "554 5.7.3 Internal error\r\n");
589 CtdlStartTLS(ok_response, nosup_response, error_response);
595 * Create the Sieve script room if it doesn't already exist
597 void mgsve_create_room(void)
599 create_room(SIEVERULES, 4, "", 0, 1, 0, VIEW_SIEVE);
604 * Main command loop for managesieve sessions.
606 void managesieve_command_loop(void) {
611 struct sdm_userdata u;
614 memset(&u, 0, sizeof(struct sdm_userdata));
617 memset(cmdbuf, 0, sizeof cmdbuf); /* Clear it, just in case */
618 length = client_getln(cmdbuf, sizeof cmdbuf);
620 num_parms = imap_parameterize(parms, cmdbuf);
621 /// length = client_getln(parms[0], sizeof parms[0]);
622 if (num_parms == 0) return;
623 length = strlen(parms[0]);
626 lprintf(CTDL_CRIT, "Client disconnected: ending session.\n");
630 lprintf(CTDL_INFO, "MANAGESIEVE: %s\n", cmdbuf);
631 //// we have different lengths while (strlen(cmdbuf) < 5) strcat(cmdbuf, " ");
632 if ((length>= 12) && (!strncasecmp(parms[0], "AUTHENTICATE", 12))){
633 cmd_mgsve_auth(num_parms, parms, &u);
637 else if ((length>= 8) && (!strncasecmp(parms[0], "STARTTLS", 8))){
638 cmd_mgsve_starttls();
641 else if ((length>= 6) && (!strncasecmp(parms[0], "LOGOUT", 6))){
642 cmd_mgsve_logout(&u);
644 else if ((length>= 6) && (!strncasecmp(parms[0], "CAPABILITY", 10))){
646 } /* these commands need to be authenticated. throw it out if it tries. */
647 else if (!CtdlAccessCheck(ac_logged_in))
650 if ((length>= 9) && (!strncasecmp(parms[0], "HAVESPACE", 9))){
651 cmd_mgsve_havespace();
653 else if ((length>= 6) && (!strncasecmp(parms[0], "PUTSCRIPT", 9))){
654 cmd_mgsve_putscript(num_parms, parms, &u);
656 else if ((length>= 6) && (!strncasecmp(parms[0], "LISTSCRIPT", 10))){
657 cmd_mgsve_listscript(num_parms, parms,&u);
659 else if ((length>= 6) && (!strncasecmp(parms[0], "SETACTIVE", 9))){
660 cmd_mgsve_setactive(num_parms, parms,&u);
662 else if ((length>= 6) && (!strncasecmp(parms[0], "GETSCRIPT", 9))){
663 cmd_mgsve_getscript(num_parms, parms, &u);
665 else if ((length>= 6) && (!strncasecmp(parms[0], "DELETESCRIPT", 11))){
666 cmd_mgsve_deletescript(num_parms, parms, &u);
681 char *serv_managesieve_init(void)
684 CtdlRegisterServiceHook(config.c_managesieve_port, /* MGSVE */
686 managesieve_greeting,
687 managesieve_command_loop,
690 CtdlRegisterSessionHook(mgsve_create_room, EVT_LOGIN);
691 return "$Id: serv_managesieve.c 4570 2006-08-27 02:07:18Z dothebart $";
694 #else /* HAVE_LIBSIEVE */
696 char *serv_managesieve_init(void)
698 lprintf(CTDL_INFO, "This server is missing libsieve. Managesieve protocol is disabled..\n");
702 #endif /* HAVE_LIBSIEVE */