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>
41 #include <libcitadel.h>
44 #include "citserver.h"
53 #include "internet_addressing.h"
54 #include "imap_tools.h" /* Needed for imap_parameterize */
57 #include "clientsocket.h"
58 #include "locate_host.h"
59 #include "citadel_dirs.h"
66 #include "ctdl_module.h"
72 #include "serv_sieve.h"
76 * http://tools.ietf.org/html/draft-martin-managesieve-06
78 * this is the draft this code tries to implement.
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;
90 enum { /** Command states for login authentication */
98 #define MGSVE CC->MGSVE
100 /*****************************************************************************/
101 /* MANAGESIEVE Server */
102 /*****************************************************************************/
104 void sieve_outbuf_append(char *str)
106 size_t newlen = strlen(str)+1;
107 size_t oldlen = (MGSVE->imap_format_outstring==NULL)? 0 : strlen(MGSVE->imap_format_outstring)+2;
108 char *buf = malloc ( newlen + oldlen + 10 );
112 sprintf(buf,"%s%s",MGSVE->imap_format_outstring, str);
114 memcpy(buf, str, newlen);
116 if (oldlen != 0) free (MGSVE->imap_format_outstring);
117 MGSVE->imap_format_outstring = buf;
122 * Capability listing. Printed as greeting or on "CAPABILITIES"
123 * see Section 1.8 ; 2.4
125 void cmd_mgsve_caps(void)
127 cprintf("\"IMPLEMENTATION\" \"CITADEL Sieve v6.84\"\r\n" /* TODO: put citversion here. */
128 "\"SASL\" \"PLAIN\"\r\n" /*DIGEST-MD5 GSSAPI SASL sucks.*/
130 /* if TLS is already there, should we say that again? */
133 "\"SIEVE\" \"%s\"\r\n"
134 "OK\r\n", msiv_extensions);
139 * Here's where our managesieve session begins its happy day.
141 void managesieve_greeting(void) {
143 strcpy(CC->cs_clientname, "Managesieve session");
145 CC->internal_pgm = 1;
146 CC->cs_flags |= CS_STEALTH;
147 MGSVE = malloc(sizeof(struct citmgsve));
148 memset(MGSVE, 0, sizeof(struct citmgsve));
153 long GetSizeToken(char * token)
155 char *cursor = token;
158 while (!IsEmptyStr(cursor) &&
163 if (IsEmptyStr(cursor))
166 while ((*cursor != '\0') &&
172 if (cursor[-1] == '+')
175 if (IsEmptyStr(cursor))
181 char *ReadString(long size, char *command)
185 cprintf("NO %s: %ld BAD Message length must be at least 1.\r\n",
190 MGSVE->transmitted_message = malloc(size + 2);
191 if (MGSVE->transmitted_message == NULL) {
192 cprintf("NO %s Cannot allocate memory.\r\n", command);
196 MGSVE->transmitted_length = size;
198 ret = client_read(MGSVE->transmitted_message, size);
199 MGSVE->transmitted_message[size] = '\0';
202 cprintf("%s NO Read failed.\r\n", command);
205 return MGSVE->transmitted_message;
208 /* AUTHENTICATE command; 2.1 */
209 void cmd_mgsve_auth(int num_parms, char **parms, struct sdm_userdata *u)
211 if ((num_parms == 3) && !strncasecmp(parms[1], "PLAIN", 5))
212 /* todo, check length*/
216 char *message = ReadString(GetSizeToken(parms[2]), parms[0]);
218 if (message != NULL) {/**< do we have tokenized login? */
219 retval = CtdlDecodeBase64(auth, MGSVE->transmitted_message, SIZ);
222 retval = CtdlDecodeBase64(auth, parms[2], SIZ);
224 if (login_ok == CtdlLoginExistingUser(NULL, auth))
227 pass = &(auth[strlen(auth)+1]);
228 /* for some reason the php script sends us the username twice. y? */
229 pass = &(pass[strlen(pass)+1]);
231 if (pass_ok == CtdlTryPassword(pass))
233 MGSVE->command_state = mgsve_password;
240 cprintf("NO \"Authentication Failure.\"\r\n");/* we just support auth plain. */
246 * STARTTLS command chapter 2.2
248 void cmd_mgsve_starttls(void)
249 { /** answer with OK, and fire off tls session. */
251 CtdlModuleStartCryptoMsgs(NULL, NULL, NULL);
258 *LOGOUT command, see chapter 2.3
260 void cmd_mgsve_logout(struct sdm_userdata *u)
263 lprintf(CTDL_NOTICE, "MgSve bye.");
269 * HAVESPACE command. see chapter 2.5
271 void cmd_mgsve_havespace(void)
273 /* as we don't have quotas in citadel we should always answer with OK;
274 * pherhaps we should have a max-scriptsize.
276 if (MGSVE->command_state != mgsve_password)
284 /* citadel doesn't have quotas. in case of change, please add code here. */
290 * PUTSCRIPT command, see chapter 2.6
292 void cmd_mgsve_putscript(int num_parms, char **parms, struct sdm_userdata *u)
294 /* "scriptname" {nnn+} */
295 /* AFTER we have the whole script overwrite existing scripts */
296 /* spellcheck the script before overwrite old ones, and reply with "no" */
303 if (parms[1][0]=='"')
304 ScriptName = &parms[1][1];
306 ScriptName = parms[1];
308 slength = strlen (ScriptName);
310 if (ScriptName[slength] == '"')
311 ScriptName[slength] = '\0';
313 Script = ReadString(GetSizeToken(parms[2]),parms[0]);
315 if (Script == NULL) return;
317 // TODO: do we spellcheck?
318 msiv_putscript(u, ScriptName, Script);
322 cprintf("%s NO Read failed.\r\n", parms[0]);
335 * LISTSCRIPT command. see chapter 2.7
337 void cmd_mgsve_listscript(int num_parms, char **parms, struct sdm_userdata *u)
340 struct sdm_script *s;
343 MGSVE->imap_format_outstring = NULL;
344 for (s=u->first_script; s!=NULL; s=s->next) {
345 if (s->script_content != NULL) {
346 cprintf("\"%s\"%s\r\n",
348 (s->script_active)?" ACTIVE":"");
357 * \brief SETACTIVE command. see chapter 2.8
359 void cmd_mgsve_setactive(int num_parms, char **parms, struct sdm_userdata *u)
363 if (msiv_setactive(u, parms[1]) == 0) {
367 cprintf("No \"there is no script by that name %s \"\r\n", parms[1]);
370 cprintf("NO \"unexpected parameters.\"\r\n");
376 * \brief GETSCRIPT command. see chapter 2.9
378 void cmd_mgsve_getscript(int num_parms, char **parms, struct sdm_userdata *u)
381 char *script_content;
384 script_content = msiv_getscript(u, parms[1]);
385 if (script_content != NULL){
388 slen = strlen(script_content);
389 outbuf = malloc (slen + 64);
390 snprintf(outbuf, slen + 64, "{%ld+}\r\n%s\r\nOK\r\n",slen, script_content);
394 cprintf("No \"there is no script by that name %s \"\r\n", parms[1]);
397 cprintf("NO \"unexpected parameters.\"\r\n");
402 * \brief DELETESCRIPT command. see chapter 2.10
404 void cmd_mgsve_deletescript(int num_parms, char **parms, struct sdm_userdata *u)
409 i = msiv_deletescript(u, parms[1]);
415 cprintf("NO \"no script by that name: %s\"\r\n", parms[1]);
418 cprintf("NO \"can't delete active Script: %s\"\r\n", parms[1]);
422 cprintf("NO \"unexpected parameters.\"\r\n");
429 * \brief Attempt to perform authenticated managesieve
431 void mgsve_auth(char *argbuf) {
432 char username_prompt[64];
434 char encoded_authstring[1024];
437 cprintf("NO \"Already logged in.\"\r\n");
441 extract_token(method, argbuf, 0, ' ', sizeof method);
443 if (!strncasecmp(method, "login", 5) ) {
444 if (strlen(argbuf) >= 7) {
447 CtdlEncodeBase64(username_prompt, "Username:", 9, 0);
448 cprintf("334 %s\r\n", username_prompt);
453 if (!strncasecmp(method, "plain", 5) ) {
454 if (num_tokens(argbuf, ' ') < 2) {
458 extract_token(encoded_authstring, argbuf, 1, ' ', sizeof encoded_authstring);
462 if (strncasecmp(method, "login", 5) ) {
463 cprintf("NO \"Unknown authentication method.\"\r\n");
472 * implements the STARTTLS command (Citadel API version)
474 void _mgsve_starttls(void)
476 char ok_response[SIZ];
477 char nosup_response[SIZ];
478 char error_response[SIZ];
481 "200 2.0.0 Begin TLS negotiation now\r\n");
482 sprintf(nosup_response,
483 "554 5.7.3 TLS not supported here\r\n");
484 sprintf(error_response,
485 "554 5.7.3 Internal error\r\n");
486 CtdlModuleStartCryptoMsgs(ok_response, nosup_response, error_response);
491 * Main command loop for managesieve sessions.
493 void managesieve_command_loop(void) {
498 struct sdm_userdata u;
499 int changes_made = 0;
501 memset(&u, 0, sizeof(struct sdm_userdata));
504 memset(cmdbuf, 0, sizeof cmdbuf); /* Clear it, just in case */
505 length = client_getln(cmdbuf, sizeof cmdbuf);
507 num_parms = imap_parameterize(parms, cmdbuf);
508 if (num_parms == 0) return;
509 length = strlen(parms[0]);
512 lprintf(CTDL_CRIT, "Client disconnected: ending session.\n");
516 lprintf(CTDL_INFO, "MANAGESIEVE: %s\n", cmdbuf);
517 if ((length>= 12) && (!strncasecmp(parms[0], "AUTHENTICATE", 12))){
518 cmd_mgsve_auth(num_parms, parms, &u);
522 else if ((length>= 8) && (!strncasecmp(parms[0], "STARTTLS", 8))){
523 cmd_mgsve_starttls();
526 else if ((length>= 6) && (!strncasecmp(parms[0], "LOGOUT", 6))){
527 cmd_mgsve_logout(&u);
529 else if ((length>= 6) && (!strncasecmp(parms[0], "CAPABILITY", 10))){
532 /** these commands need to be authenticated. throw it out if it tries. */
533 else if (!CtdlAccessCheck(ac_logged_in))
536 if ((length>= 9) && (!strncasecmp(parms[0], "HAVESPACE", 9))){
537 cmd_mgsve_havespace();
539 else if ((length>= 6) && (!strncasecmp(parms[0], "PUTSCRIPT", 9))){
540 cmd_mgsve_putscript(num_parms, parms, &u);
543 else if ((length>= 6) && (!strncasecmp(parms[0], "LISTSCRIPT", 10))){
544 cmd_mgsve_listscript(num_parms, parms,&u);
546 else if ((length>= 6) && (!strncasecmp(parms[0], "SETACTIVE", 9))){
547 cmd_mgsve_setactive(num_parms, parms,&u);
550 else if ((length>= 6) && (!strncasecmp(parms[0], "GETSCRIPT", 9))){
551 cmd_mgsve_getscript(num_parms, parms, &u);
553 else if ((length>= 6) && (!strncasecmp(parms[0], "DELETESCRIPT", 11))){
554 cmd_mgsve_deletescript(num_parms, parms, &u);
557 msiv_store(&u, changes_made);
561 lprintf(CTDL_INFO, "illegal Managesieve command: %s", parms[0]);
569 #endif /* HAVE_LIBSIEVE */
570 const char* CitadelServiceManageSieve = "ManageSieve";
571 CTDL_MODULE_INIT(managesieve)
576 CtdlRegisterServiceHook(config.c_managesieve_port, /* MGSVE */
578 managesieve_greeting,
579 managesieve_command_loop,
581 CitadelServiceManageSieve);
583 #else /* HAVE_LIBSIEVE */
585 lprintf(CTDL_INFO, "This server is missing libsieve. Managesieve protocol is disabled..\n");
587 #endif /* HAVE_LIBSIEVE */
589 /* return our Subversion id for the Log */