]> code.citadel.org Git - citadel.git/blob - citadel/serv_managesieve.c
serv_managesieve.c is now only built on Citadel servers that
[citadel.git] / citadel / 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
12 #include "sysdep.h"
13 #include <stdlib.h>
14 #include <unistd.h>
15 #include <stdio.h>
16 #include <fcntl.h>
17 #include <signal.h>
18 #include <pwd.h>
19 #include <errno.h>
20 #include <sys/types.h>
21 #include <syslog.h>
22
23 #if TIME_WITH_SYS_TIME
24 # include <sys/time.h>
25 # include <time.h>
26 #else
27 # if HAVE_SYS_TIME_H
28 #  include <sys/time.h>
29 # else
30 #  include <time.h>
31 # endif
32 #endif
33
34 #include <sys/wait.h>
35 #include <ctype.h>
36 #include <string.h>
37 #include <limits.h>
38 #include <sys/socket.h>
39 #include <netinet/in.h>
40 #include <arpa/inet.h>
41 #include "citadel.h"
42 #include "server.h"
43 #include "sysdep_decls.h"
44 #include "citserver.h"
45 #include "support.h"
46 #include "config.h"
47 #include "control.h"
48 #include "serv_extensions.h"
49 #include "room_ops.h"
50 #include "user_ops.h"
51 #include "policy.h"
52 #include "database.h"
53 #include "msgbase.h"
54 #include "tools.h"
55 #include "internet_addressing.h"
56 #include "imap_tools.h"
57 #include "genstamp.h"
58 #include "domain.h"
59 #include "clientsocket.h"
60 #include "locate_host.h"
61 #include "citadel_dirs.h"
62
63 #ifdef HAVE_OPENSSL
64 #include "serv_crypto.h"
65 #endif
66
67 #ifndef HAVE_SNPRINTF
68 #include "snprintf.h"
69 #endif
70
71 #ifdef HAVE_LIBSIEVE
72
73 #include "serv_sieve.h"
74
75
76 /**
77  * http://tools.ietf.org/html/draft-martin-managesieve-06
78  *
79  * this is the draft this code tries to implement.
80  */
81
82
83 struct citmgsve {               
84         int command_state;             /**< Information about the current session */
85         char *transmitted_message;
86         size_t transmitted_length;
87 };
88
89 enum {  /** Command states for login authentication */
90         mgsve_command,
91         mgsve_tls,
92         mgsve_user,
93         mgsve_password,
94         mgsve_plain
95 };
96
97 #define MGSVE          CC->MGSVE
98
99 /*****************************************************************************/
100 /*                      MANAGESIEVE Server                                   */
101 /*****************************************************************************/
102
103 void goto_sieverules_room(void)
104 {// TODO: check if we're authenticated.
105         struct ctdlroom QRscratch;
106         int c;
107         char augmented_roomname[ROOMNAMELEN];
108         int transiently = 0;
109
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! */
114         {
115                 cprintf("BYE\r\n");
116                 CC->kill_me = 1;
117                 return;
118         }
119         /* move to the sieve room. */
120         memcpy(&CC->room, &QRscratch,
121                sizeof(struct ctdlroom));
122         usergoto(NULL, 1, transiently, NULL, NULL);
123 }
124
125 /**
126  * Capability listing. Printed as greeting or on "CAPABILITIES" 
127  * see Section 1.8 ; 2.4
128  */
129 void cmd_mgsve_caps(void)
130
131         cprintf("\"IMPLEMENTATION\" \"CITADEL Sieve v6.84\"\r\n" /* TODO: put citversion here. */
132                 "\"SASL\" \"PLAIN\"\r\n" /*DIGEST-MD5 GSSAPI  SASL sucks.*/
133 #ifdef HAVE_OPENSSL
134 /* if TLS is already there, should we say that again? */
135                 "\"STARTTLS\"\r\n"
136 #endif
137                 "\"SIEVE\" \"FILEINTO VACATION\"\r\n" /* TODO: print sieve extensions here. */
138                 "OK\r\n");
139 }
140
141
142 /*
143  * Here's where our managesieve session begins its happy day.
144  */
145 void managesieve_greeting(void) {
146
147         strcpy(CC->cs_clientname, "Managesieve session");
148
149         CC->internal_pgm = 1;
150         CC->cs_flags |= CS_STEALTH;
151         MGSVE = malloc(sizeof(struct citmgsve));
152         memset(MGSVE, 0, sizeof(struct citmgsve));
153         cmd_mgsve_caps();
154 }
155
156 /* AUTHENTICATE command; 2.1 */
157 void cmd_mgsve_auth(int num_parms, char **parms)
158 {
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*/
162         {
163                 char auth[SIZ];
164                 int retval;
165                 
166                 /* todo: how to do plain auth? */
167                 
168                 if (parms[2][0] == '{') 
169                 {
170                         long literal_length;
171                         long ret;
172                         
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",
176                                         parms[0]);
177                                 CC->kill_me = 1;
178                                 return;
179                         }
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]);
183                                 CC->kill_me = 1;
184                                 return;
185                         }
186                         MGSVE->transmitted_length = literal_length;
187                         
188                         ret = client_read(MGSVE->transmitted_message, literal_length);
189                         MGSVE->transmitted_message[literal_length] = 0;
190                         
191                         if (ret != 1) {
192                                 cprintf("%s NO Read failed.\r\n", parms[0]);
193                                 return;
194                         } 
195                         
196                         retval = CtdlDecodeBase64(auth, MGSVE->transmitted_message, SIZ);
197                         
198                 }
199                 else 
200                         retval = CtdlDecodeBase64(auth, parms[2], SIZ);
201                 if (login_ok == CtdlLoginExistingUser(auth))
202                 {
203                         char *pass;
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]);
207                         
208                         if (pass_ok == CtdlTryPassword(pass))
209                         {
210                                 MGSVE->command_state = mgsve_password;
211                                 cprintf("OK\n");
212                                 return;
213                         }
214                 }
215         }
216         
217         cprintf("NO\n");/* we just support auth plain. */
218         CC->kill_me = 1;
219         
220 }
221
222
223 #ifdef HAVE_OPENSSL
224 /* STARTTLS command chapter 2.2 */
225 void cmd_mgsve_starttls(void)
226 { /* answer with OK, and fire off tls session. */
227         cprintf("OK\n");
228         CtdlStartTLS(NULL, NULL, NULL);
229         cmd_mgsve_caps();
230 }
231 #endif
232
233
234
235 /* LOGOUT command, see chapter 2.3 */
236 void cmd_mgsve_logout(void)
237 {/* send "OK" and terminate the connection. */
238         cprintf("OK\r\n");
239         lprintf(CTDL_NOTICE, "MgSve bye.");
240         CC->kill_me = 1;
241 }
242
243
244 /* HAVESPACE command. see chapter 2.5 */
245 void cmd_mgsve_havespace(void)
246 {
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. 
250  */
251         if (MGSVE->command_state != mgsve_password)
252         {
253                 cprintf("NO\n");
254                 CC->kill_me = 1;
255         }
256         else
257         {
258                 cprintf("OK"); 
259 /* citadel doesn't have quotas. in case of change, please add code here. */
260
261         }
262 }
263
264 /* PUTSCRIPT command, see chapter 2.6 */
265 void cmd_mgsve_putscript(void)
266 {
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" */
271
272 }
273
274
275 /** forward declaration for function in msgbase.c */
276 void headers_listing(long msgnum, void *userdata);
277
278
279 /* LISTSCRIPT command. see chapter 2.7 */
280 void cmd_mgsve_listscript(void)
281 {
282         goto_sieverules_room();/* TODO: do we need a template? */
283         CtdlForEachMessage(MSGS_ALL, 0, NULL, NULL, NULL,
284                            headers_listing, NULL);
285
286         
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.*/ 
289
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);
299 ///                     cprintf("\r\n");
300 ///             }
301 ///     }
302 ///
303         cprintf("OK\r\n");
304 }
305
306
307 /* SETACTIVE command. see chapter 2.8 */
308 void cmd_mgsve_setactive(void)
309 {
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 "
314  */
315
316
317
318 }
319
320
321 /* GETSCRIPT command. see chapter 2.9 */
322 void cmd_mgsve_getscript(void)
323 {
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
327  */
328
329 }
330
331
332 /* DELETESCRIPT command. see chapter 2.10 */
333 void cmd_mgsve_deletescript(void)
334 {
335 /* TODO: check auth, if not, answer with "no" */
336
337
338 }
339
340
341
342 /*
343  *
344 void mgsve_get_user(char *argbuf) {
345         char buf[SIZ];
346         char username[SIZ];
347
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;
354         }
355         else {
356                 cprintf("500 5.7.0 No such user.\r\n");
357                 MGSVE->command_state = mgsve_command;
358         }
359 }
360  */
361
362
363 /*
364  *
365 void mgsve_get_pass(char *argbuf) {
366         char password[SIZ];
367
368         CtdlDecodeBase64(password, argbuf, SIZ);
369         / * lprintf(CTDL_DEBUG, "Trying <%s>\n", password); * /
370         if (CtdlTryPassword(password) == pass_ok) {
371                 mgsve_auth_greeting();
372         }
373         else {
374                 cprintf("535 5.7.0 Authentication failed.\r\n");
375         }
376         MGSVE->command_state = mgsve_command;
377 }
378  */
379
380
381 /*
382  * Back end for PLAIN auth method (either inline or multistate)
383  */
384 void mgsve_try_plain(char *encoded_authstring) {
385         char decoded_authstring[1024];
386         char ident[256];
387         char user[256];
388         char pass[256];
389
390         CtdlDecodeBase64(decoded_authstring,
391                         encoded_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);
396
397 //      MGSVE->command_state = mgsve_command;
398 /*
399         if (CtdlLoginExistingUser(user) == login_ok) {
400                 if (CtdlTryPassword(pass) == pass_ok) {
401                         mgsve_auth_greeting();
402                         return;
403                 }
404         }
405 */
406         cprintf("504 5.7.4 Authentication failed.\r\n");
407 }
408
409
410 /*
411  * Attempt to perform authenticated magagesieve
412  */
413 void mgsve_auth(char *argbuf) {
414         char username_prompt[64];
415         char method[64];
416         char encoded_authstring[1024];
417
418         if (CC->logged_in) {
419                 cprintf("504 5.7.4 Already logged in.\r\n");
420                 return;
421         }
422
423         extract_token(method, argbuf, 0, ' ', sizeof method);
424
425         if (!strncasecmp(method, "login", 5) ) {
426                 if (strlen(argbuf) >= 7) {
427 //                      mgsve_get_user(&argbuf[6]);
428                 }
429                 else {
430                         CtdlEncodeBase64(username_prompt, "Username:", 9);
431                         cprintf("334 %s\r\n", username_prompt);
432 //                      MGSVE->command_state = mgsve_user;
433                 }
434                 return;
435         }
436
437         if (!strncasecmp(method, "plain", 5) ) {
438                 if (num_tokens(argbuf, ' ') < 2) {
439                         cprintf("334 \r\n");
440 //                      MGSVE->command_state = mgsve_plain;
441                         return;
442                 }
443
444                 extract_token(encoded_authstring, argbuf, 1, ' ', sizeof encoded_authstring);
445
446 ///             mgsve_try_plain(encoded_authstring);
447                 return;
448         }
449
450         if (strncasecmp(method, "login", 5) ) {
451                 cprintf("504 5.7.4 Unknown authentication method.\r\n");
452                 return;
453         }
454
455 }
456
457
458
459 /*
460  * implements the STARTTLS command (Citadel API version)
461  */
462 #ifdef HAVE_OPENSSL
463 void _mgsve_starttls(void)
464 {
465         char ok_response[SIZ];
466         char nosup_response[SIZ];
467         char error_response[SIZ];
468
469         sprintf(ok_response,
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);
476 }
477 #endif
478
479
480 /*
481  * Create the Sieve script room if it doesn't already exist
482  */
483 void mgsve_create_room(void)
484 {
485         create_room(SIEVERULES, 4, "", 0, 1, 0, VIEW_SIEVE);
486 }
487
488
489 /* 
490  * Main command loop for managesieve sessions.
491  */
492 void managesieve_command_loop(void) {
493         char cmdbuf[SIZ];
494         char *parms[SIZ];
495         int length;
496         int num_parms;
497
498         time(&CC->lastcmd);
499         memset(cmdbuf, 0, sizeof cmdbuf); /* Clear it, just in case */
500         length = client_getln(cmdbuf, sizeof cmdbuf);
501         if (length >= 1) {
502                 num_parms = imap_parameterize(parms, cmdbuf);
503                 ///             length = client_getln(parms[0], sizeof parms[0]);
504                 length = strlen(parms[0]);
505         }
506         if (length < 1) {
507                 lprintf(CTDL_CRIT, "Client disconnected: ending session.\n");
508                 CC->kill_me = 1;
509                 return;
510         }
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);
515         }
516
517 #ifdef HAVE_OPENSSL
518         else if ((length>= 8) && (!strncasecmp(parms[0], "STARTTLS", 8))){
519                 cmd_mgsve_starttls();
520         }
521 #endif
522         else if ((length>= 6) && (!strncasecmp(parms[0], "LOGOUT", 6))){
523                 cmd_mgsve_logout();
524         }
525         else if ((length>= 6) && (!strncasecmp(parms[0], "CAPABILITY", 10))){
526                 cmd_mgsve_caps();
527         } /* these commands need to be authenticated. throw it out if it tries. */
528         else if (MGSVE->command_state == mgsve_password)
529         {
530                 if ((length>= 9) && (!strncasecmp(parms[0], "HAVESPACE", 9))){
531                         cmd_mgsve_havespace();
532                 }
533                 else if ((length>= 6) && (!strncasecmp(parms[0], "PUTSCRIPT", 9))){
534                         cmd_mgsve_putscript();
535                 }
536                 else if ((length>= 6) && (!strncasecmp(parms[0], "LISTSCRIPT", 10))){
537                         cmd_mgsve_listscript();
538                 }
539                 else if ((length>= 6) && (!strncasecmp(parms[0], "SETACTIVE", 9))){
540                         cmd_mgsve_setactive();
541                 }
542                 else if ((length>= 6) && (!strncasecmp(parms[0], "GETSCRIPT", 9))){
543                         cmd_mgsve_getscript();
544                 }
545                 else if ((length>= 6) && (!strncasecmp(parms[0], "DELETESCRIPT", 11))){
546                         cmd_mgsve_deletescript();
547                 }
548         }
549         else {
550                 cprintf("No\r\n");
551                 CC->kill_me = 1;
552         }
553
554
555 }
556
557
558
559 char *serv_managesieve_init(void)
560 {
561
562         CtdlRegisterServiceHook(config.c_managesieve_port,      /* MGSVE */
563                                 NULL,
564                                 managesieve_greeting,
565                                 managesieve_command_loop,
566                                 NULL);
567
568         CtdlRegisterSessionHook(mgsve_create_room, EVT_LOGIN);
569         return "$Id: serv_managesieve.c 4570 2006-08-27 02:07:18Z dothebart $";
570 }
571
572 #else   /* HAVE_LIBSIEVE */
573
574 char *serv_managesieve_init(void)
575 {
576         lprintf(CTDL_INFO, "This server is missing libsieve.  Managesieve protocol is disabled..\n");
577         return "$Id:  $";
578 }
579
580 #endif  /* HAVE_LIBSIEVE */