Did away with lprintf all together now its called CtdlLogPrintf()
[citadel.git] / citadel / modules / managesieve / 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 <libcitadel.h>
42 #include "citadel.h"
43 #include "server.h"
44 #include "citserver.h"
45 #include "support.h"
46 #include "config.h"
47 #include "control.h"
48 #include "room_ops.h"
49 #include "user_ops.h"
50 #include "policy.h"
51 #include "database.h"
52 #include "msgbase.h"
53 #include "internet_addressing.h"
54 #include "imap_tools.h" /* Needed for imap_parameterize */
55 #include "genstamp.h"
56 #include "domain.h"
57 #include "clientsocket.h"
58 #include "locate_host.h"
59 #include "citadel_dirs.h"
60
61 #ifndef HAVE_SNPRINTF
62 #include "snprintf.h"
63 #endif
64
65
66 #include "ctdl_module.h"
67 #include "serv_sieve.h"
68
69
70 /**
71  * http://tools.ietf.org/html/draft-martin-managesieve-06
72  *
73  * this is the draft this code tries to implement.
74  */
75
76
77 struct citmgsve {               
78         int command_state;             /**< Information about the current session */
79         char *transmitted_message;
80         size_t transmitted_length;
81         char *imap_format_outstring;
82         int imap_outstring_length;
83 };
84
85 enum {  /** Command states for login authentication */
86         mgsve_command,
87         mgsve_tls,
88         mgsve_user,
89         mgsve_password,
90         mgsve_plain
91 };
92
93 #define MGSVE          ((struct citmgsve *)CC->session_specific_data)
94
95 /*****************************************************************************/
96 /*                      MANAGESIEVE Server                                   */
97 /*****************************************************************************/
98
99
100 void sieve_outbuf_append(char *str)
101 {
102         size_t newlen = strlen(str)+1;
103         size_t oldlen = (MGSVE->imap_format_outstring==NULL)? 0 : strlen(MGSVE->imap_format_outstring)+2;
104         char *buf = malloc ( newlen + oldlen + 10 );
105         buf[0]='\0';
106
107         if (oldlen!=0)
108                 sprintf(buf,"%s%s",MGSVE->imap_format_outstring, str);
109         else
110                 memcpy(buf, str, newlen);
111         
112         if (oldlen != 0) free (MGSVE->imap_format_outstring);
113         MGSVE->imap_format_outstring = buf;
114 }
115
116
117 /**
118  * Capability listing. Printed as greeting or on "CAPABILITIES" 
119  * see Section 1.8 ; 2.4
120  */
121 void cmd_mgsve_caps(void)
122
123         cprintf("\"IMPLEMENTATION\" \"CITADEL Sieve " PACKAGE_VERSION "\"\r\n" 
124                 "\"SASL\" \"PLAIN\"\r\n" /*DIGEST-MD5 GSSAPI  SASL sucks.*/
125 #ifdef HAVE_OPENSSL
126 /* if TLS is already there, should we say that again? */
127                 "\"STARTTLS\"\r\n"
128 #endif
129                 "\"SIEVE\" \"%s\"\r\n"
130                 "OK\r\n", msiv_extensions);
131 }
132
133
134 /*
135  * Here's where our managesieve session begins its happy day.
136  */
137 void managesieve_greeting(void) {
138
139         strcpy(CC->cs_clientname, "Managesieve session");
140
141         CC->internal_pgm = 0;
142         CC->cs_flags |= CS_STEALTH;
143         CC->session_specific_data = malloc(sizeof(struct citmgsve));
144         memset(MGSVE, 0, sizeof(struct citmgsve));
145         cmd_mgsve_caps();
146 }
147
148
149 long GetSizeToken(char * token)
150 {
151         char *cursor = token;
152         char *number;
153
154         while (!IsEmptyStr(cursor) && 
155                (*cursor != '{'))
156         {
157                 cursor++;
158         }
159         if (IsEmptyStr(cursor)) 
160                 return -1;
161         number = cursor + 1;
162         while ((*cursor != '\0') && 
163                (*cursor != '}'))
164         {
165                 cursor++;
166         }
167
168         if (cursor[-1] == '+')
169                 cursor--;
170
171         if (IsEmptyStr(cursor)) 
172                 return -1;
173         
174         return atol(number);
175 }
176
177 char *ReadString(long size, char *command)
178 {
179         long ret;
180         if (size < 1) {
181                 cprintf("NO %s: %ld BAD Message length must be at least 1.\r\n",
182                         command, size);
183                 CC->kill_me = 1;
184                 return NULL;
185         }
186         MGSVE->transmitted_message = malloc(size + 2);
187         if (MGSVE->transmitted_message == NULL) {
188                 cprintf("NO %s Cannot allocate memory.\r\n", command);
189                 CC->kill_me = 1;
190                 return NULL;
191         }
192         MGSVE->transmitted_length = size;
193         
194         ret = client_read(MGSVE->transmitted_message, size);
195         MGSVE->transmitted_message[size] = '\0';
196         
197         if (ret != 1) {
198                 cprintf("%s NO Read failed.\r\n", command);
199                 return NULL;
200         } 
201         return MGSVE->transmitted_message;
202
203 }
204 /* AUTHENTICATE command; 2.1 */
205 void cmd_mgsve_auth(int num_parms, char **parms, struct sdm_userdata *u)
206 {
207         if ((num_parms == 3) && !strncasecmp(parms[1], "PLAIN", 5))
208                 /* todo, check length*/
209         {
210                 char auth[SIZ];
211                 int retval;
212                 char *message;
213                 char *username;
214
215                 message = NULL;
216                 memset (auth, 0, SIZ);
217                 if (parms[2][0] == '{')
218                         message = ReadString(GetSizeToken(parms[2]), parms[0]);
219                 
220                 if (message != NULL) {/**< do we have tokenized login? */
221                         retval = CtdlDecodeBase64(auth, MGSVE->transmitted_message, SIZ);
222                 }
223                 else 
224                         retval = CtdlDecodeBase64(auth, parms[2], SIZ);
225                 username = auth;
226                 if ((*username == '\0') && (*(username + 1) != '\0'))
227                         username ++;
228                 
229                 if (login_ok == CtdlLoginExistingUser(NULL, username))
230                 {
231                         char *pass;
232                         pass = &(auth[strlen(auth)+1]);
233                         /* for some reason the php script sends us the username twice. y? */
234                         pass = &(pass[strlen(pass)+1]);
235                         
236                         if (pass_ok == CtdlTryPassword(pass))
237                         {
238                                 MGSVE->command_state = mgsve_password;
239                                 cprintf("OK\r\n");
240                                 return;
241                         }
242                 }
243         }
244         cprintf("NO \"Authentication Failure.\"\r\n");/* we just support auth plain. */
245         CC->kill_me = 1;
246 }
247
248
249 /**
250  * STARTTLS command chapter 2.2 
251  */
252 void cmd_mgsve_starttls(void)
253 { /** answer with OK, and fire off tls session. */
254         cprintf("OK\r\n");
255         CtdlModuleStartCryptoMsgs(NULL, NULL, NULL);
256         cmd_mgsve_caps();
257 }
258
259
260
261 /**
262  *LOGOUT command, see chapter 2.3 
263  */
264 void cmd_mgsve_logout(struct sdm_userdata *u)
265 {
266         cprintf("OK\r\n");
267         CtdlLogPrintf(CTDL_NOTICE, "MgSve bye.");
268         CC->kill_me = 1;
269 }
270
271
272 /**
273  * HAVESPACE command. see chapter 2.5 
274  */
275 void cmd_mgsve_havespace(void)
276 {
277 /* as we don't have quotas in citadel we should always answer with OK; 
278  * pherhaps we should have a max-scriptsize. 
279  */
280         if (MGSVE->command_state != mgsve_password)
281         {
282                 cprintf("NO\r\n");
283                 CC->kill_me = 1;
284         }
285         else
286         {
287                 cprintf("OK"); 
288 /* citadel doesn't have quotas. in case of change, please add code here. */
289
290         }
291 }
292
293 /**
294  * PUTSCRIPT command, see chapter 2.6 
295  */
296 void cmd_mgsve_putscript(int num_parms, char **parms, struct sdm_userdata *u)
297 {
298 /* "scriptname" {nnn+} */
299 /* AFTER we have the whole script overwrite existing scripts */
300 /* spellcheck the script before overwrite old ones, and reply with "no" */
301         if (num_parms == 3)
302         {
303                 char *ScriptName;
304                 char *Script;
305                 long slength;
306
307                 if (parms[1][0]=='"')
308                         ScriptName = &parms[1][1];
309                 else
310                         ScriptName = parms[1];
311                 
312                 slength = strlen (ScriptName);
313                 
314                 if (ScriptName[slength] == '"')
315                         ScriptName[slength] = '\0';
316
317                 Script = ReadString(GetSizeToken(parms[2]),parms[0]);
318
319                 if (Script == NULL) return;
320                 
321                 // TODO: do we spellcheck?
322                 msiv_putscript(u, ScriptName, Script);
323                 cprintf("OK\r\n");
324         }
325         else {
326                 cprintf("%s NO Read failed.\r\n", parms[0]);
327                 CC->kill_me = 1;
328                 return;
329         } 
330
331
332
333 }
334
335
336
337
338 /**
339  * LISTSCRIPT command. see chapter 2.7 
340  */
341 void cmd_mgsve_listscript(int num_parms, char **parms, struct sdm_userdata *u)
342 {
343
344         struct sdm_script *s;
345         long nScripts = 0;
346
347         MGSVE->imap_format_outstring = NULL;
348         for (s=u->first_script; s!=NULL; s=s->next) {
349                 if (s->script_content != NULL) {
350                         cprintf("\"%s\"%s\r\n", 
351                                 s->script_name, 
352                                 (s->script_active)?" ACTIVE":"");
353                         nScripts++;
354                 }
355         }
356         cprintf("OK\r\n");
357 }
358
359
360 /**
361  * \brief SETACTIVE command. see chapter 2.8 
362  */
363 void cmd_mgsve_setactive(int num_parms, char **parms, struct sdm_userdata *u)
364 {
365         if (num_parms == 2)
366         {
367                 if (msiv_setactive(u, parms[1]) == 0) {
368                         cprintf("OK\r\n");
369                 }
370                 else
371                         cprintf("No \"there is no script by that name %s \"\r\n", parms[1]);
372         }
373         else 
374                 cprintf("NO \"unexpected parameters.\"\r\n");
375
376 }
377
378
379 /**
380  * \brief GETSCRIPT command. see chapter 2.9 
381  */
382 void cmd_mgsve_getscript(int num_parms, char **parms, struct sdm_userdata *u)
383 {
384         if (num_parms == 2){
385                 char *script_content;
386                 long  slen;
387
388                 script_content = msiv_getscript(u, parms[1]);
389                 if (script_content != NULL){
390                         char *outbuf;
391
392                         slen = strlen(script_content);
393                         outbuf = malloc (slen + 64);
394                         snprintf(outbuf, slen + 64, "{%ld+}\r\n%s\r\nOK\r\n",slen, script_content);
395                         cprintf(outbuf);
396                 }
397                 else
398                         cprintf("No \"there is no script by that name %s \"\r\n", parms[1]);
399         }
400         else 
401                 cprintf("NO \"unexpected parameters.\"\r\n");
402 }
403
404
405 /**
406  * \brief DELETESCRIPT command. see chapter 2.10 
407  */
408 void cmd_mgsve_deletescript(int num_parms, char **parms, struct sdm_userdata *u)
409 {
410         int i=-1;
411
412         if (num_parms == 2)
413                 i = msiv_deletescript(u, parms[1]);
414         switch (i){             
415         case 0:
416                 cprintf("OK\r\n");
417                 break;
418         case 1:
419                 cprintf("NO \"no script by that name: %s\"\r\n", parms[1]);
420                 break;
421         case 2:
422                 cprintf("NO \"can't delete active Script: %s\"\r\n", parms[1]);
423                 break;
424         default:
425         case -1:
426                 cprintf("NO \"unexpected parameters.\"\r\n");
427                 break;
428         }
429 }
430
431
432 /**
433  * \brief Attempt to perform authenticated managesieve
434  */
435 void mgsve_auth(char *argbuf) {
436         char username_prompt[64];
437         char method[64];
438         char encoded_authstring[1024];
439
440         if (CC->logged_in) {
441                 cprintf("NO \"Already logged in.\"\r\n");
442                 return;
443         }
444
445         extract_token(method, argbuf, 0, ' ', sizeof method);
446
447         if (!strncasecmp(method, "login", 5) ) {
448                 if (strlen(argbuf) >= 7) {
449                 }
450                 else {
451                         CtdlEncodeBase64(username_prompt, "Username:", 9, 0);
452                         cprintf("334 %s\r\n", username_prompt);
453                 }
454                 return;
455         }
456
457         if (!strncasecmp(method, "plain", 5) ) {
458                 if (num_tokens(argbuf, ' ') < 2) {
459                         cprintf("334 \r\n");
460                         return;
461                 }
462                 extract_token(encoded_authstring, argbuf, 1, ' ', sizeof encoded_authstring);
463                 return;
464         }
465
466         if (strncasecmp(method, "login", 5) ) {
467                 cprintf("NO \"Unknown authentication method.\"\r\n");
468                 return;
469         }
470
471 }
472
473
474
475 /*
476  * implements the STARTTLS command (Citadel API version)
477  */
478 void _mgsve_starttls(void)
479 {
480         char ok_response[SIZ];
481         char nosup_response[SIZ];
482         char error_response[SIZ];
483
484         sprintf(ok_response,
485                 "200 2.0.0 Begin TLS negotiation now\r\n");
486         sprintf(nosup_response,
487                 "554 5.7.3 TLS not supported here\r\n");
488         sprintf(error_response,
489                 "554 5.7.3 Internal error\r\n");
490         CtdlModuleStartCryptoMsgs(ok_response, nosup_response, error_response);
491 }
492
493
494 /* 
495  * Main command loop for managesieve sessions.
496  */
497 void managesieve_command_loop(void) {
498         char cmdbuf[SIZ];
499         char *parms[SIZ];
500         int length;
501         int num_parms;
502         struct sdm_userdata u;
503         int changes_made = 0;
504
505         memset(&u, 0, sizeof(struct sdm_userdata));
506
507         time(&CC->lastcmd);
508         memset(cmdbuf, 0, sizeof cmdbuf); /* Clear it, just in case */
509         length = client_getln(cmdbuf, sizeof cmdbuf);
510         if (length >= 1) {
511                 num_parms = imap_parameterize(parms, cmdbuf);
512                 if (num_parms == 0) return;
513                 length = strlen(parms[0]);
514         }
515         if (length < 1) {
516                 CtdlLogPrintf(CTDL_CRIT, "Client disconnected: ending session.\n");
517                 CC->kill_me = 1;
518                 return;
519         }
520         CtdlLogPrintf(CTDL_INFO, "MANAGESIEVE: %s\n", cmdbuf);
521         if ((length>= 12) && (!strncasecmp(parms[0], "AUTHENTICATE", 12))){
522                 cmd_mgsve_auth(num_parms, parms, &u);
523         }
524
525 #ifdef HAVE_OPENSSL
526         else if ((length>= 8) && (!strncasecmp(parms[0], "STARTTLS", 8))){
527                 cmd_mgsve_starttls();
528         }
529 #endif
530         else if ((length>= 6) && (!strncasecmp(parms[0], "LOGOUT", 6))){
531                 cmd_mgsve_logout(&u);
532         }
533         else if ((length>= 6) && (!strncasecmp(parms[0], "CAPABILITY", 10))){
534                 cmd_mgsve_caps();
535         } 
536         /** these commands need to be authenticated. throw it out if it tries. */
537         else if (CC->logged_in != 0)
538         {
539                 msiv_load(&u);
540                 if ((length>= 9) && (!strncasecmp(parms[0], "HAVESPACE", 9))){
541                         cmd_mgsve_havespace();
542                 }
543                 else if ((length>= 6) && (!strncasecmp(parms[0], "PUTSCRIPT", 9))){
544                         cmd_mgsve_putscript(num_parms, parms, &u);
545                         changes_made = 1;
546                 }
547                 else if ((length>= 6) && (!strncasecmp(parms[0], "LISTSCRIPT", 10))){
548                         cmd_mgsve_listscript(num_parms, parms,&u);
549                 }
550                 else if ((length>= 6) && (!strncasecmp(parms[0], "SETACTIVE", 9))){
551                         cmd_mgsve_setactive(num_parms, parms,&u);
552                         changes_made = 1;
553                 }
554                 else if ((length>= 6) && (!strncasecmp(parms[0], "GETSCRIPT", 9))){
555                         cmd_mgsve_getscript(num_parms, parms, &u);
556                 }
557                 else if ((length>= 6) && (!strncasecmp(parms[0], "DELETESCRIPT", 11))){
558                         cmd_mgsve_deletescript(num_parms, parms, &u);
559                         changes_made = 1;
560                 }
561                 msiv_store(&u, changes_made);
562         }
563         else {
564                 cprintf("No Invalid access or command.\r\n");
565                 CtdlLogPrintf(CTDL_INFO, "illegal Managesieve command: %s", parms[0]);
566                 CC->kill_me = 1;
567         }
568
569
570 }
571
572 /*
573  * This cleanup function blows away the temporary memory and files used by
574  * the server.
575  */
576 void managesieve_cleanup_function(void) {
577
578         /* Don't do this stuff if this is not a managesieve session! */
579         if (CC->h_command_function != managesieve_command_loop) return;
580
581         CtdlLogPrintf(CTDL_DEBUG, "Performing managesieve cleanup hook\n");
582         free(MGSVE);
583 }
584
585
586
587 const char* CitadelServiceManageSieve = "ManageSieve";
588 CTDL_MODULE_INIT(managesieve)
589 {
590         if (!threading)
591         {
592                 CtdlRegisterServiceHook(config.c_managesieve_port,
593                                         NULL,
594                                         managesieve_greeting,
595                                         managesieve_command_loop,
596                                         NULL, 
597                                         CitadelServiceManageSieve);
598                 CtdlRegisterSessionHook(managesieve_cleanup_function, EVT_STOP);
599         }
600         
601         /* return our Subversion id for the Log */
602         return "$Id$";
603 }
604
605