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;
89 enum { /** Command states for login authentication */
97 #define MGSVE CC->MGSVE
99 /*****************************************************************************/
100 /* MANAGESIEVE Server */
101 /*****************************************************************************/
103 void goto_sieverules_room(void)
104 {// TODO: check if we're authenticated.
105 struct ctdlroom QRscratch;
107 char augmented_roomname[ROOMNAMELEN];
110 MailboxName(augmented_roomname, sizeof augmented_roomname,
111 &CC->user, SIEVERULES);
112 c = getroom(&QRscratch, augmented_roomname);
113 if (c != 0)/* something went wrong. hit it! */
119 /* move to the sieve room. */
120 memcpy(&CC->room, &QRscratch,
121 sizeof(struct ctdlroom));
122 usergoto(NULL, 1, transiently, NULL, NULL);
126 * Capability listing. Printed as greeting or on "CAPABILITIES"
127 * see Section 1.8 ; 2.4
129 void cmd_mgsve_caps(void)
131 cprintf("\"IMPLEMENTATION\" \"CITADEL Sieve v6.84\"\r\n" /* TODO: put citversion here. */
132 "\"SASL\" \"PLAIN\"\r\n" /*DIGEST-MD5 GSSAPI SASL sucks.*/
134 /* if TLS is already there, should we say that again? */
137 "\"SIEVE\" \"FILEINTO VACATION\"\r\n" /* TODO: print sieve extensions here. */
143 * Here's where our managesieve session begins its happy day.
145 void managesieve_greeting(void) {
147 strcpy(CC->cs_clientname, "Managesieve session");
149 CC->internal_pgm = 1;
150 CC->cs_flags |= CS_STEALTH;
151 MGSVE = malloc(sizeof(struct citmgsve));
152 memset(MGSVE, 0, sizeof(struct citmgsve));
156 /* AUTHENTICATE command; 2.1 */
157 void cmd_mgsve_auth(int num_parms, char **parms)
159 /* TODO: compare "digest-md5" or "gssapi" and answer with "NO" */
160 if ((num_parms == 3) && !strncasecmp(parms[1], "PLAIN", 5))
161 /* todo, check length*/
166 /* todo: how to do plain auth? */
168 if (parms[2][0] == '{')
173 literal_length = atol(&parms[2][1]);
174 if (literal_length < 1) {
175 cprintf("NO %s BAD Message length must be at least 1.\n",
180 MGSVE->transmitted_message = malloc(literal_length + 2);
181 if (MGSVE->transmitted_message == NULL) {
182 cprintf("NO %s Cannot allocate memory.\r\n", parms[0]);
186 MGSVE->transmitted_length = literal_length;
188 ret = client_read(MGSVE->transmitted_message, literal_length);
189 MGSVE->transmitted_message[literal_length] = 0;
192 cprintf("%s NO Read failed.\r\n", parms[0]);
196 retval = CtdlDecodeBase64(auth, MGSVE->transmitted_message, SIZ);
200 retval = CtdlDecodeBase64(auth, parms[2], SIZ);
201 if (login_ok == CtdlLoginExistingUser(auth))
204 pass = &(auth[strlen(auth)+1]);
205 /* for some reason the php script sends us the username twice. y? */
206 pass = &(pass[strlen(pass)+1]);
208 if (pass_ok == CtdlTryPassword(pass))
210 MGSVE->command_state = mgsve_password;
217 cprintf("NO\n");/* we just support auth plain. */
224 /* STARTTLS command chapter 2.2 */
225 void cmd_mgsve_starttls(void)
226 { /* answer with OK, and fire off tls session. */
228 CtdlStartTLS(NULL, NULL, NULL);
235 /* LOGOUT command, see chapter 2.3 */
236 void cmd_mgsve_logout(void)
237 {/* send "OK" and terminate the connection. */
239 lprintf(CTDL_NOTICE, "MgSve bye.");
244 /* HAVESPACE command. see chapter 2.5 */
245 void cmd_mgsve_havespace(void)
247 /* TODO answer NO in any case if auth is missing. */
248 /* as we don't have quotas in citadel we should always answer with OK;
249 * pherhaps we should have a max-scriptsize.
251 if (MGSVE->command_state != mgsve_password)
259 /* citadel doesn't have quotas. in case of change, please add code here. */
264 /* PUTSCRIPT command, see chapter 2.6 */
265 void cmd_mgsve_putscript(void)
267 /* "scriptname" {nnn+} */
268 /* TODO: answer with "NO" instant, if we're unauthorized. */
269 /* AFTER we have the whole script overwrite existing scripts */
270 /* spellcheck the script before overwrite old ones, and reply with "no" */
275 /** forward declaration for function in msgbase.c */
276 void headers_listing(long msgnum, void *userdata);
279 /* LISTSCRIPT command. see chapter 2.7 */
280 void cmd_mgsve_listscript(void)
282 goto_sieverules_room();/* TODO: do we need a template? */
283 CtdlForEachMessage(MSGS_ALL, 0, NULL, NULL, NULL,
284 headers_listing, NULL);
287 /* TODO: check auth, if not, answer with "no" */
288 /* do something like the sieve room indexlisting, one per row, in quotes. ACTIVE behind the active one.*/
290 /// ra = getroom(sieveroom, SIEVERULES);
291 /// /* Only list rooms to which the user has access!! */
292 /// CtdlRoomAccess(SIEVERULES, &CC->user, &ra, NULL);
293 /// if ((ra & UA_KNOWN)
294 /// || ((ra & UA_GOTOALLOWED) && (ra & UA_ZAPPED))) {
295 /// imap_mailboxname(buf, sizeof buf, qrbuf);
296 /// if (imap_mailbox_matches_pattern(pattern, buf)) {
297 /// cprintf("* LIST () \"/\" ");
298 /// imap_strout(buf);
307 /* SETACTIVE command. see chapter 2.8 */
308 void cmd_mgsve_setactive(void)
310 /* TODO: check auth, if not, answer with "no" */
311 /* search our room for subjects with that scriptname,
312 * if the scriptname is empty, use the default flag.
313 * if the script is not there answer "No "there is no script by that name "
321 /* GETSCRIPT command. see chapter 2.9 */
322 void cmd_mgsve_getscript(void)
324 /* TODO: check auth, if not, answer with "no" */
325 /* check first param, this is the name. look up that in the folder.
326 * answer with the size {nnn+}and spill it out, one blank line and OK
332 /* DELETESCRIPT command. see chapter 2.10 */
333 void cmd_mgsve_deletescript(void)
335 /* TODO: check auth, if not, answer with "no" */
344 void mgsve_get_user(char *argbuf) {
348 CtdlDecodeBase64(username, argbuf, SIZ);
349 / * lprintf(CTDL_DEBUG, "Trying <%s>\n", username); * /
350 if (CtdlLoginExistingUser(username) == login_ok) {
351 CtdlEncodeBase64(buf, "Password:", 9);
352 cprintf("334 %s\r\n", buf);
353 MGSVE->command_state = mgsve_password;
356 cprintf("500 5.7.0 No such user.\r\n");
357 MGSVE->command_state = mgsve_command;
365 void mgsve_get_pass(char *argbuf) {
368 CtdlDecodeBase64(password, argbuf, SIZ);
369 / * lprintf(CTDL_DEBUG, "Trying <%s>\n", password); * /
370 if (CtdlTryPassword(password) == pass_ok) {
371 mgsve_auth_greeting();
374 cprintf("535 5.7.0 Authentication failed.\r\n");
376 MGSVE->command_state = mgsve_command;
382 * Back end for PLAIN auth method (either inline or multistate)
384 void mgsve_try_plain(char *encoded_authstring) {
385 char decoded_authstring[1024];
390 CtdlDecodeBase64(decoded_authstring,
392 strlen(encoded_authstring) );
393 safestrncpy(ident, decoded_authstring, sizeof ident);
394 safestrncpy(user, &decoded_authstring[strlen(ident) + 1], sizeof user);
395 safestrncpy(pass, &decoded_authstring[strlen(ident) + strlen(user) + 2], sizeof pass);
397 // MGSVE->command_state = mgsve_command;
399 if (CtdlLoginExistingUser(user) == login_ok) {
400 if (CtdlTryPassword(pass) == pass_ok) {
401 mgsve_auth_greeting();
406 cprintf("504 5.7.4 Authentication failed.\r\n");
411 * Attempt to perform authenticated magagesieve
413 void mgsve_auth(char *argbuf) {
414 char username_prompt[64];
416 char encoded_authstring[1024];
419 cprintf("504 5.7.4 Already logged in.\r\n");
423 extract_token(method, argbuf, 0, ' ', sizeof method);
425 if (!strncasecmp(method, "login", 5) ) {
426 if (strlen(argbuf) >= 7) {
427 // mgsve_get_user(&argbuf[6]);
430 CtdlEncodeBase64(username_prompt, "Username:", 9);
431 cprintf("334 %s\r\n", username_prompt);
432 // MGSVE->command_state = mgsve_user;
437 if (!strncasecmp(method, "plain", 5) ) {
438 if (num_tokens(argbuf, ' ') < 2) {
440 // MGSVE->command_state = mgsve_plain;
444 extract_token(encoded_authstring, argbuf, 1, ' ', sizeof encoded_authstring);
446 /// mgsve_try_plain(encoded_authstring);
450 if (strncasecmp(method, "login", 5) ) {
451 cprintf("504 5.7.4 Unknown authentication method.\r\n");
460 * implements the STARTTLS command (Citadel API version)
463 void _mgsve_starttls(void)
465 char ok_response[SIZ];
466 char nosup_response[SIZ];
467 char error_response[SIZ];
470 "200 2.0.0 Begin TLS negotiation now\r\n");
471 sprintf(nosup_response,
472 "554 5.7.3 TLS not supported here\r\n");
473 sprintf(error_response,
474 "554 5.7.3 Internal error\r\n");
475 CtdlStartTLS(ok_response, nosup_response, error_response);
481 * Create the Sieve script room if it doesn't already exist
483 void mgsve_create_room(void)
485 create_room(SIEVERULES, 4, "", 0, 1, 0, VIEW_SIEVE);
490 * Main command loop for managesieve sessions.
492 void managesieve_command_loop(void) {
499 memset(cmdbuf, 0, sizeof cmdbuf); /* Clear it, just in case */
500 length = client_getln(cmdbuf, sizeof cmdbuf);
502 num_parms = imap_parameterize(parms, cmdbuf);
503 /// length = client_getln(parms[0], sizeof parms[0]);
504 length = strlen(parms[0]);
507 lprintf(CTDL_CRIT, "Client disconnected: ending session.\n");
511 lprintf(CTDL_INFO, "MANAGESIEVE: %s\n", cmdbuf);
512 //// we have different lengths while (strlen(cmdbuf) < 5) strcat(cmdbuf, " ");
513 if ((length>= 12) && (!strncasecmp(parms[0], "AUTHENTICATE", 12))){
514 cmd_mgsve_auth(num_parms, parms);
518 else if ((length>= 8) && (!strncasecmp(parms[0], "STARTTLS", 8))){
519 cmd_mgsve_starttls();
522 else if ((length>= 6) && (!strncasecmp(parms[0], "LOGOUT", 6))){
525 else if ((length>= 6) && (!strncasecmp(parms[0], "CAPABILITY", 10))){
527 } /* these commands need to be authenticated. throw it out if it tries. */
528 else if (MGSVE->command_state == mgsve_password)
530 if ((length>= 9) && (!strncasecmp(parms[0], "HAVESPACE", 9))){
531 cmd_mgsve_havespace();
533 else if ((length>= 6) && (!strncasecmp(parms[0], "PUTSCRIPT", 9))){
534 cmd_mgsve_putscript();
536 else if ((length>= 6) && (!strncasecmp(parms[0], "LISTSCRIPT", 10))){
537 cmd_mgsve_listscript();
539 else if ((length>= 6) && (!strncasecmp(parms[0], "SETACTIVE", 9))){
540 cmd_mgsve_setactive();
542 else if ((length>= 6) && (!strncasecmp(parms[0], "GETSCRIPT", 9))){
543 cmd_mgsve_getscript();
545 else if ((length>= 6) && (!strncasecmp(parms[0], "DELETESCRIPT", 11))){
546 cmd_mgsve_deletescript();
559 char *serv_managesieve_init(void)
562 CtdlRegisterServiceHook(config.c_managesieve_port, /* MGSVE */
564 managesieve_greeting,
565 managesieve_command_loop,
568 CtdlRegisterSessionHook(mgsve_create_room, EVT_LOGIN);
569 return "$Id: serv_managesieve.c 4570 2006-08-27 02:07:18Z dothebart $";
572 #else /* HAVE_LIBSIEVE */
574 char *serv_managesieve_init(void)
576 lprintf(CTDL_INFO, "This server is missing libsieve. Managesieve protocol is disabled..\n");
580 #endif /* HAVE_LIBSIEVE */