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