]> code.citadel.org Git - citadel.git/blob - citadel/serv_managesieve.c
* added room goto for listing.
[citadel.git] / citadel / serv_managesieve.c
1 /**
2  * $Id: serv_smtp.c 4570 2006-08-27 02:07:18Z ajc $
3  *
4  * This module is an Manage Sieve 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
68
69 #ifndef HAVE_SNPRINTF
70 #include "snprintf.h"
71 #endif
72
73
74
75 /**
76  * http://tools.ietf.org/html/draft-martin-managesieve-06
77  *
78  * this is the draft this code tries to implement.
79  */
80
81
82 struct citmgsve {               
83         int command_state;             /**< Information about the current session */
84         char helo_node[SIZ];
85         struct ctdluser vrfy_buffer;
86         int vrfy_count;
87         char vrfy_match[SIZ];
88         char from[SIZ];
89         char recipients[SIZ];
90         int number_of_recipients;
91         int delivery_mode;
92         int message_originated_locally;
93         char *transmitted_message;      /* for APPEND command... */
94         size_t transmitted_length;
95 };
96
97 enum {  /** Command states for login authentication */
98         mgsve_command,
99         mgsve_tls,
100         mgsve_user,
101         mgsve_password,
102         mgsve_plain
103 };
104
105
106 ////int run_queue_now = 0;      /* Set to 1 to ignore SMTP send retry times */
107
108 #define MGSVE          CC->MGSVE
109
110 /*****************************************************************************/
111 /*                      MANAGESIEVE Server                                   */
112 /*****************************************************************************/
113
114
115 void goto_sieverules_room(void)
116 {// TODO: check if we're authenticated.
117         struct ctdlroom QRscratch;
118         int c;
119         char augmented_roomname[ROOMNAMELEN];
120         int transiently = 0;
121
122         MailboxName(augmented_roomname, sizeof augmented_roomname,
123                     &CC->user, SIEVERULES);
124         c = getroom(&QRscratch, augmented_roomname);
125         if (c != 0)/* something went wrong. hit it! */
126         {
127                 cprintf("BYE\r\n");
128                 CC->kill_me = 1;
129                 return;
130         }
131         /* move to the sieve room. */
132         memcpy(&CC->room, &QRscratch,
133                sizeof(struct ctdlroom));
134         usergoto(NULL, 1, transiently, NULL, NULL);
135 }
136
137 /**
138  * Capability listing. Printed as greeting or on "CAPABILITIES" 
139  * see Section 1.8 ; 2.4
140  */
141 void cmd_mgsve_caps(void)
142
143         cprintf("\"IMPLEMENTATION\" \"CITADEL Sieve v6.84\"\r\n" /* TODO: put citversion here. */
144                 "\"SASL\" \"PLAIN\"\r\n" /*DIGEST-MD5 GSSAPI  SASL sucks.*/
145 #ifdef HAVE_OPENSSL
146 /* if TLS is already there, should we say that again? */
147                 "\"STARTTLS\"\r\n"
148 #endif
149                 "\"SIEVE\" \"FILEINTO VACATION\"\r\n" /* TODO: print sieve extensions here. */
150                 "OK\r\n");
151 }
152
153
154 /*
155  * Here's where our SMTP session begins its happy day.
156  */
157 void managesieve_greeting(void) {
158
159         strcpy(CC->cs_clientname, "Managesieve session");
160
161         CC->internal_pgm = 1;
162         CC->cs_flags |= CS_STEALTH;
163         MGSVE = malloc(sizeof(struct citmgsve));
164         memset(MGSVE, 0, sizeof(struct citmgsve));
165         cmd_mgsve_caps();
166 }
167
168 /* AUTHENTICATE command; 2.1 */
169 void cmd_mgsve_auth(int num_parms, char **parms)
170 {
171 /* TODO: compare "digest-md5" or "gssapi" and answer with "NO" */
172         if ((num_parms == 3) && !strncasecmp(parms[1], "PLAIN", 5))
173                 /* todo, check length*/
174         {
175                 char auth[SIZ];
176                 int retval;
177                 
178                 /* todo: how to do plain auth? */
179                 
180                 if (parms[2][0] == '{') 
181                 {
182                         long literal_length;
183                         long ret;
184                         
185                         literal_length = atol(&parms[2][1]);
186                         if (literal_length < 1) {
187                                 cprintf("NO %s BAD Message length must be at least 1.\n",
188                                         parms[0]);
189                                 CC->kill_me = 1;
190                                 return;
191                         }
192                         MGSVE->transmitted_message = malloc(literal_length + 2);
193                         if (MGSVE->transmitted_message == NULL) {
194                                 cprintf("NO %s Cannot allocate memory.\r\n", parms[0]);
195                                 CC->kill_me = 1;
196                                 return;
197                         }
198                         MGSVE->transmitted_length = literal_length;
199                         
200                         ret = client_read(MGSVE->transmitted_message, literal_length);
201                         MGSVE->transmitted_message[literal_length] = 0;
202                         
203                         if (ret != 1) {
204                                 cprintf("%s NO Read failed.\r\n", parms[0]);
205                                 return;
206                         } 
207                         
208                         retval = CtdlDecodeBase64(auth, MGSVE->transmitted_message, SIZ);
209                         
210                 }
211                 else 
212                         retval = CtdlDecodeBase64(auth, parms[2], SIZ);
213                 if (login_ok == CtdlLoginExistingUser(auth))
214                 {
215                         char *pass;
216                         pass = &(auth[strlen(auth)+1]);
217                         /* for some reason the php script sends us the username twice. y? */
218                         pass = &(pass[strlen(pass)+1]);
219                         
220                         if (pass_ok == CtdlTryPassword(pass))
221                         {
222                                 MGSVE->command_state = mgsve_password;
223                                 cprintf("OK\n");
224                                 return;
225                         }
226                 }
227         }
228         
229         cprintf("NO\n");/* we just support auth plain. */
230         CC->kill_me = 1;
231         
232 }
233
234
235 #ifdef HAVE_OPENSSL
236 /* STARTTLS command chapter 2.2 */
237 void cmd_mgsve_starttls(void)
238 { /* answer with OK, and fire off tls session. */
239         cprintf("OK\n");
240         CtdlStartTLS(NULL, NULL, NULL);
241         cmd_mgsve_caps();
242 }
243 #endif
244
245
246
247 /* LOGOUT command, see chapter 2.3 */
248 void cmd_mgsve_logout(void)
249 {/* send "OK" and terminate the connection. */
250         cprintf("OK\r\n");
251         lprintf(CTDL_NOTICE, "MgSve bye.");
252         CC->kill_me = 1;
253 }
254
255
256 /* HAVESPACE command. see chapter 2.5 */
257 void cmd_mgsve_havespace(void)
258 {
259 /* TODO answer NO in any case if auth is missing. */
260 /* as we don't have quotas in citadel we should always answer with OK; 
261  * pherhaps we should have a max-scriptsize. 
262  */
263         if (MGSVE->command_state != mgsve_password)
264         {
265                 cprintf("NO\n");
266                 CC->kill_me = 1;
267         }
268         else
269         {
270                 cprintf("OK"); 
271 /* citadel doesn't have quotas. in case of change, please add code here. */
272
273         }
274 }
275
276 /* PUTSCRIPT command, see chapter 2.6 */
277 void cmd_mgsve_putscript(void)
278 {
279 /* "scriptname" {nnn+} */
280 /* TODO: answer with "NO" instant, if we're unauthorized. */
281 /* AFTER we have the whole script overwrite existing scripts */
282 /* spellcheck the script before overwrite old ones, and reply with "no" */
283
284 }
285
286
287 /** forward declaration for function in msgbase.c */
288 void headers_listing(long msgnum, void *userdata);
289
290
291 /* LISTSCRIPT command. see chapter 2.7 */
292 void cmd_mgsve_listscript(void)
293 {
294         goto_sieverules_room();/* TODO: do we need a template? */
295         CtdlForEachMessage(MSGS_ALL, 0, NULL, NULL, NULL,
296                            headers_listing, NULL);
297
298         
299 /* TODO: check auth, if not, answer with "no" */
300 /* do something like the sieve room indexlisting, one per row, in quotes. ACTIVE behind the active one.*/ 
301
302 ///     ra = getroom(sieveroom, SIEVERULES);
303 ///     /* Only list rooms to which the user has access!! */
304 ///     CtdlRoomAccess(SIEVERULES, &CC->user, &ra, NULL);
305 ///     if ((ra & UA_KNOWN)
306 ///         || ((ra & UA_GOTOALLOWED) && (ra & UA_ZAPPED))) {
307 ///             imap_mailboxname(buf, sizeof buf, qrbuf);
308 ///             if (imap_mailbox_matches_pattern(pattern, buf)) {
309 ///                     cprintf("* LIST () \"/\" ");
310 ///                     imap_strout(buf);
311 ///                     cprintf("\r\n");
312 ///             }
313 ///     }
314 ///
315         cprintf("OK\r\n");
316 }
317
318
319 /* SETACTIVE command. see chapter 2.8 */
320 void cmd_mgsve_setactive(void)
321 {
322 /* TODO: check auth, if not, answer with "no" */
323 /* search our room for subjects with that scriptname, 
324  * if the scriptname is empty, use the default flag.
325  * if the script is not there answer "No "there is no script by that name "
326  */
327
328
329
330 }
331
332
333 /* GETSCRIPT command. see chapter 2.9 */
334 void cmd_mgsve_getscript(void)
335 {
336 /* TODO: check auth, if not, answer with "no" */
337 /* check first param, this is the name. look up that in the folder.
338  * answer with the size {nnn+}and spill it out, one blank line and OK
339  */
340
341 }
342
343
344 /* DELETESCRIPT command. see chapter 2.10 */
345 void cmd_mgsve_deletescript(void)
346 {
347 /* TODO: check auth, if not, answer with "no" */
348
349
350 }
351
352
353
354 /*
355  *
356 void smtp_get_user(char *argbuf) {
357         char buf[SIZ];
358         char username[SIZ];
359
360         CtdlDecodeBase64(username, argbuf, SIZ);
361         / * lprintf(CTDL_DEBUG, "Trying <%s>\n", username); * /
362         if (CtdlLoginExistingUser(username) == login_ok) {
363                 CtdlEncodeBase64(buf, "Password:", 9);
364                 cprintf("334 %s\r\n", buf);
365                 SMTP->command_state = smtp_password;
366         }
367         else {
368                 cprintf("500 5.7.0 No such user.\r\n");
369                 SMTP->command_state = smtp_command;
370         }
371 }
372  */
373
374
375 /*
376  *
377 void smtp_get_pass(char *argbuf) {
378         char password[SIZ];
379
380         CtdlDecodeBase64(password, argbuf, SIZ);
381         / * lprintf(CTDL_DEBUG, "Trying <%s>\n", password); * /
382         if (CtdlTryPassword(password) == pass_ok) {
383                 smtp_auth_greeting();
384         }
385         else {
386                 cprintf("535 5.7.0 Authentication failed.\r\n");
387         }
388         SMTP->command_state = smtp_command;
389 }
390  */
391
392
393 /*
394  * Back end for PLAIN auth method (either inline or multistate)
395  */
396 void mgsve_try_plain(char *encoded_authstring) {
397         char decoded_authstring[1024];
398         char ident[256];
399         char user[256];
400         char pass[256];
401
402         CtdlDecodeBase64(decoded_authstring,
403                         encoded_authstring,
404                         strlen(encoded_authstring) );
405         safestrncpy(ident, decoded_authstring, sizeof ident);
406         safestrncpy(user, &decoded_authstring[strlen(ident) + 1], sizeof user);
407         safestrncpy(pass, &decoded_authstring[strlen(ident) + strlen(user) + 2], sizeof pass);
408
409 //      SMTP->command_state = smtp_command;
410 /*
411         if (CtdlLoginExistingUser(user) == login_ok) {
412                 if (CtdlTryPassword(pass) == pass_ok) {
413                         smtp_auth_greeting();
414                         return;
415                 }
416         }
417 */
418         cprintf("504 5.7.4 Authentication failed.\r\n");
419 }
420
421
422 /*
423  * Attempt to perform authenticated SMTP
424  */
425 void mgsve_auth(char *argbuf) {
426         char username_prompt[64];
427         char method[64];
428         char encoded_authstring[1024];
429
430         if (CC->logged_in) {
431                 cprintf("504 5.7.4 Already logged in.\r\n");
432                 return;
433         }
434
435         extract_token(method, argbuf, 0, ' ', sizeof method);
436
437         if (!strncasecmp(method, "login", 5) ) {
438                 if (strlen(argbuf) >= 7) {
439 //                      smtp_get_user(&argbuf[6]);
440                 }
441                 else {
442                         CtdlEncodeBase64(username_prompt, "Username:", 9);
443                         cprintf("334 %s\r\n", username_prompt);
444 //                      SMTP->command_state = smtp_user;
445                 }
446                 return;
447         }
448
449         if (!strncasecmp(method, "plain", 5) ) {
450                 if (num_tokens(argbuf, ' ') < 2) {
451                         cprintf("334 \r\n");
452 //                      SMTP->command_state = smtp_plain;
453                         return;
454                 }
455
456                 extract_token(encoded_authstring, argbuf, 1, ' ', sizeof encoded_authstring);
457
458 ///             smtp_try_plain(encoded_authstring);
459                 return;
460         }
461
462         if (strncasecmp(method, "login", 5) ) {
463                 cprintf("504 5.7.4 Unknown authentication method.\r\n");
464                 return;
465         }
466
467 }
468
469
470
471 /*
472  * implements the STARTTLS command (Citadel API version)
473  */
474 #ifdef HAVE_OPENSSL
475 void _smtp_starttls(void)
476 {
477         char ok_response[SIZ];
478         char nosup_response[SIZ];
479         char error_response[SIZ];
480
481         sprintf(ok_response,
482                 "200 2.0.0 Begin TLS negotiation now\r\n");
483         sprintf(nosup_response,
484                 "554 5.7.3 TLS not supported here\r\n");
485         sprintf(error_response,
486                 "554 5.7.3 Internal error\r\n");
487         CtdlStartTLS(ok_response, nosup_response, error_response);
488 ///     smtp_rset(0);
489 }
490 #endif
491
492
493 void mgsve_create_room(void)
494 {
495
496         /* Create the tasks list room if it doesn't already exist */
497         create_room(SIEVERULES, 4, "", 0, 1, 0, VIEW_SIEVE);
498 }
499
500 /* 
501  * Main command loop for manage Sieve sessions.
502  */
503 void managesieve_command_loop(void) {
504         char cmdbuf[SIZ];
505         char *parms[SIZ];
506         int length;
507         int num_parms;
508
509         time(&CC->lastcmd);
510         memset(cmdbuf, 0, sizeof cmdbuf); /* Clear it, just in case */
511         length = client_getln(cmdbuf, sizeof cmdbuf);
512         if (length >= 1) {
513                 num_parms = imap_parameterize(parms, cmdbuf);
514                 ///             length = client_getln(parms[0], sizeof parms[0]);
515                 length = strlen(parms[0]);
516         }
517         if (length < 1) {
518                 lprintf(CTDL_CRIT, "Client disconnected: ending session.\n");
519                 CC->kill_me = 1;
520                 return;
521         }
522         lprintf(CTDL_INFO, "MANAGESIEVE: %s\n", cmdbuf);
523 //// we have different lengths  while (strlen(cmdbuf) < 5) strcat(cmdbuf, " ");
524         if ((length>= 12) && (!strncasecmp(parms[0], "AUTHENTICATE", 12))){
525                 cmd_mgsve_auth(num_parms, parms);
526         }
527
528 #ifdef HAVE_OPENSSL
529         else if ((length>= 8) && (!strncasecmp(parms[0], "STARTTLS", 8))){
530                 cmd_mgsve_starttls();
531         }
532 #endif
533         else if ((length>= 6) && (!strncasecmp(parms[0], "LOGOUT", 6))){
534                 cmd_mgsve_logout();
535         }
536         else if ((length>= 6) && (!strncasecmp(parms[0], "CAPABILITY", 10))){
537                 cmd_mgsve_caps();
538         } /* these commands need to be authenticated. throw it out if it tries. */
539         else if (MGSVE->command_state == mgsve_password)
540         {
541                 if ((length>= 9) && (!strncasecmp(parms[0], "HAVESPACE", 9))){
542                         cmd_mgsve_havespace();
543                 }
544                 else if ((length>= 6) && (!strncasecmp(parms[0], "PUTSCRIPT", 9))){
545                         cmd_mgsve_putscript();
546                 }
547                 else if ((length>= 6) && (!strncasecmp(parms[0], "LISTSCRIPT", 10))){
548                         cmd_mgsve_listscript();
549                 }
550                 else if ((length>= 6) && (!strncasecmp(parms[0], "SETACTIVE", 9))){
551                         cmd_mgsve_setactive();
552                 }
553                 else if ((length>= 6) && (!strncasecmp(parms[0], "GETSCRIPT", 9))){
554                         cmd_mgsve_getscript();
555                 }
556                 else if ((length>= 6) && (!strncasecmp(parms[0], "DELETESCRIPT", 11))){
557                         cmd_mgsve_deletescript();
558                 }
559         }
560         else {
561                 cprintf("No\r\n");
562                 CC->kill_me = 1;
563         }
564
565
566 }
567
568
569
570
571
572
573
574
575 char *serv_managesieve_init(void)
576 {
577
578         CtdlRegisterServiceHook(config.c_managesieve_port,      /* MGSVE */
579                                 NULL,
580                                 managesieve_greeting,
581                                 managesieve_command_loop,
582                                 NULL);
583
584         CtdlRegisterSessionHook(mgsve_create_room, EVT_LOGIN);
585         return "$Id: serv_managesieve.c 4570 2006-08-27 02:07:18Z dothebart $";
586 }