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