Back to index

citadel  8.12
serv_managesieve.c
Go to the documentation of this file.
00001 /*
00002  * This module is an managesieve implementation for the Citadel system.
00003  * It is compliant with all of the following:
00004  *
00005  * http://tools.ietf.org/html/draft-martin-managesieve-06
00006  * as this draft expires with this writing, you might need to search for
00007  * the new one.
00008  *
00009  * Copyright (c) 2007-2012 by the citadel.org team
00010  *
00011  *  This program is open source software; you can redistribute it and/or modify
00012  *  it under the terms of the GNU General Public License version 3.
00013  *  
00014  *  
00015  *
00016  *  This program is distributed in the hope that it will be useful,
00017  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
00018  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00019  *  GNU General Public License for more details.
00020  *
00021  *  
00022  *  
00023  *  
00024  */
00025 
00026 #include "sysdep.h"
00027 #include <stdlib.h>
00028 #include <unistd.h>
00029 #include <stdio.h>
00030 #include <fcntl.h>
00031 #include <signal.h>
00032 #include <pwd.h>
00033 #include <errno.h>
00034 #include <sys/types.h>
00035 #include <syslog.h>
00036 
00037 #if TIME_WITH_SYS_TIME
00038 # include <sys/time.h>
00039 # include <time.h>
00040 #else
00041 # if HAVE_SYS_TIME_H
00042 #  include <sys/time.h>
00043 # else
00044 #  include <time.h>
00045 # endif
00046 #endif
00047 
00048 #include <sys/wait.h>
00049 #include <ctype.h>
00050 #include <string.h>
00051 #include <limits.h>
00052 #include <sys/socket.h>
00053 #include <netinet/in.h>
00054 #include <arpa/inet.h>
00055 #include <libcitadel.h>
00056 #include "citadel.h"
00057 #include "server.h"
00058 #include "citserver.h"
00059 #include "support.h"
00060 #include "config.h"
00061 #include "control.h"
00062 #include "user_ops.h"
00063 #include "database.h"
00064 #include "msgbase.h"
00065 #include "internet_addressing.h"
00066 #include "genstamp.h"
00067 #include "domain.h"
00068 #include "clientsocket.h"
00069 #include "locate_host.h"
00070 #include "citadel_dirs.h"
00071 
00072 #ifndef HAVE_SNPRINTF
00073 #include "snprintf.h"
00074 #endif
00075 
00076 
00077 #include "ctdl_module.h"
00078 #include "serv_sieve.h"
00079 
00080 
00088 struct citmgsve {           
00089        int command_state;             
00090        char *transmitted_message;
00091        size_t transmitted_length;
00092        char *imap_format_outstring;
00093        int imap_outstring_length;
00094 };
00095 
00096 enum {        
00097        mgsve_command,
00098        mgsve_tls,
00099        mgsve_user,
00100        mgsve_password,
00101        mgsve_plain
00102 };
00103 
00104 #define MGSVE          ((struct citmgsve *)CC->session_specific_data)
00105 
00106 int old_imap_parameterize(char** args, char *in)
00107 {
00108        char* out = in;
00109        int num = 0;
00110 
00111        for (;;)
00112        {
00113               /* Skip whitespace. */
00114 
00115               while (isspace(*in))
00116                      in++;
00117               if (*in == 0)
00118                      break;
00119 
00120               /* Found the start of a token. */
00121               
00122               args[num++] = out;
00123 
00124               /* Read in the token. */
00125 
00126               for (;;)
00127               {
00128                      int c = *in++;
00129                      if (isspace(c))
00130                             break;
00131                      
00132                      if (c == '\"')
00133                      {
00134                             /* Found a quoted section. */
00135 
00136                             for (;;)
00137                             {
00138                                    c = *in++;
00139                                    if (c == '\"')
00140                                           break;
00141                                    else if (c == '\\')
00142                                           c = *in++;
00143 
00144                                    *out++ = c;
00145                                    if (c == 0)
00146                                           return num;
00147                             }
00148                      }
00149                      else if (c == '\\')
00150                      {
00151                             c = *in++;
00152                             *out++ = c;
00153                      }
00154                      else
00155                             *out++ = c;
00156 
00157                      if (c == 0)
00158                             return num;
00159               }
00160               *out++ = '\0';
00161        }
00162 
00163        return num;
00164 }
00165 
00166 /*****************************************************************************/
00167 /*                      MANAGESIEVE Server                                   */
00168 /*****************************************************************************/
00169 
00170 
00171 void sieve_outbuf_append(char *str)
00172 {
00173         size_t newlen = strlen(str)+1;
00174         size_t oldlen = (MGSVE->imap_format_outstring==NULL)? 0 : strlen(MGSVE->imap_format_outstring)+2;
00175         char *buf = malloc ( newlen + oldlen + 10 );
00176         buf[0]='\0';
00177 
00178         if (oldlen!=0)
00179                 sprintf(buf,"%s%s",MGSVE->imap_format_outstring, str);
00180         else
00181                 memcpy(buf, str, newlen);
00182         
00183         if (oldlen != 0) free (MGSVE->imap_format_outstring);
00184         MGSVE->imap_format_outstring = buf;
00185 }
00186 
00187 
00192 void cmd_mgsve_caps(void)
00193 { 
00194        cprintf("\"IMPLEMENTATION\" \"CITADEL Sieve " PACKAGE_VERSION "\"\r\n" 
00195               "\"SASL\" \"PLAIN\"\r\n" /*DIGEST-MD5 GSSAPI  SASL sucks.*/
00196 #ifdef HAVE_OPENSSL
00197 /* if TLS is already there, should we say that again? */
00198               "\"STARTTLS\"\r\n"
00199 #endif
00200               "\"SIEVE\" \"%s\"\r\n"
00201               "OK\r\n", msiv_extensions);
00202 }
00203 
00204 
00205 /*
00206  * Here's where our managesieve session begins its happy day.
00207  */
00208 void managesieve_greeting(void) {
00209 
00210        strcpy(CC->cs_clientname, "Managesieve session");
00211 
00212        CC->internal_pgm = 0;
00213        CC->cs_flags |= CS_STEALTH;
00214        CC->session_specific_data = malloc(sizeof(struct citmgsve));
00215        memset(MGSVE, 0, sizeof(struct citmgsve));
00216        cmd_mgsve_caps();
00217 }
00218 
00219 
00220 long GetSizeToken(char * token)
00221 {
00222        char *cursor = token;
00223        char *number;
00224 
00225        while (!IsEmptyStr(cursor) && 
00226               (*cursor != '{'))
00227        {
00228               cursor++;
00229        }
00230        if (IsEmptyStr(cursor)) 
00231               return -1;
00232        number = cursor + 1;
00233        while ((*cursor != '\0') && 
00234               (*cursor != '}'))
00235        {
00236               cursor++;
00237        }
00238 
00239        if (cursor[-1] == '+')
00240               cursor--;
00241 
00242        if (IsEmptyStr(cursor)) 
00243               return -1;
00244        
00245        return atol(number);
00246 }
00247 
00248 char *ReadString(long size, char *command)
00249 {
00250        long ret;
00251        if (size < 1) {
00252               cprintf("NO %s: %ld BAD Message length must be at least 1.\r\n",
00253                      command, size);
00254               CC->kill_me = KILLME_READSTRING_FAILED;
00255               return NULL;
00256        }
00257        MGSVE->transmitted_message = malloc(size + 2);
00258        if (MGSVE->transmitted_message == NULL) {
00259               cprintf("NO %s Cannot allocate memory.\r\n", command);
00260               CC->kill_me = KILLME_MALLOC_FAILED;
00261               return NULL;
00262        }
00263        MGSVE->transmitted_length = size;
00264        
00265        ret = client_read(MGSVE->transmitted_message, size);
00266        MGSVE->transmitted_message[size] = '\0';
00267        
00268        if (ret != 1) {
00269               cprintf("%s NO Read failed.\r\n", command);
00270               return NULL;
00271        } 
00272        return MGSVE->transmitted_message;
00273 
00274 }
00275 /* AUTHENTICATE command; 2.1 */
00276 void cmd_mgsve_auth(int num_parms, char **parms, struct sdm_userdata *u)
00277 {
00278        if ((num_parms == 3) && !strncasecmp(parms[1], "PLAIN", 5))
00279               /* todo, check length*/
00280        {
00281               char auth[SIZ];
00282               char *message;
00283               char *username;
00284 
00285               message = NULL;
00286               memset (auth, 0, SIZ);
00287               if (parms[2][0] == '{')
00288                      message = ReadString(GetSizeToken(parms[2]), parms[0]);
00289               
00290               if (message != NULL) {
00291                      CtdlDecodeBase64(auth, MGSVE->transmitted_message, SIZ);
00292               }
00293               else 
00294                      CtdlDecodeBase64(auth, parms[2], SIZ);
00295               username = auth;
00296               if ((*username == '\0') && (*(username + 1) != '\0'))
00297                      username ++;
00298               
00299               if (login_ok == CtdlLoginExistingUser(NULL, username))
00300               {
00301                      char *pass;
00302 
00303                      pass = &(auth[strlen(auth)+1]);
00304                      /* for some reason the php script sends us the username twice. y? */
00305                      pass = &(pass[strlen(pass)+1]);
00306                      
00307                      if (pass_ok == CtdlTryPassword(pass, strlen(pass)))
00308                      {
00309                             MGSVE->command_state = mgsve_password;
00310                             cprintf("OK\r\n");
00311                             return;
00312                      }
00313               }
00314        }
00315        cprintf("NO \"Authentication Failure.\"\r\n");/* we just support auth plain. */
00316        CC->kill_me = KILLME_AUTHFAILED;
00317 }
00318 
00319 
00323 void cmd_mgsve_starttls(void)
00324 { 
00325        cprintf("OK\r\n");
00326        CtdlModuleStartCryptoMsgs(NULL, NULL, NULL);
00327        cmd_mgsve_caps();
00328 }
00329 
00330 
00331 
00332 /*
00333  * LOGOUT command, see chapter 2.3 
00334  */
00335 void cmd_mgsve_logout(struct sdm_userdata *u)
00336 {
00337        cprintf("OK\r\n");
00338        syslog(LOG_NOTICE, "MgSve bye.");
00339        CC->kill_me = KILLME_CLIENT_LOGGED_OUT;
00340 }
00341 
00342 
00343 /*
00344  * HAVESPACE command. see chapter 2.5 
00345  */
00346 void cmd_mgsve_havespace(void)
00347 {
00348 /* as we don't have quotas in citadel we should always answer with OK; 
00349  * pherhaps we should have a max-scriptsize. 
00350  */
00351        if (MGSVE->command_state != mgsve_password)
00352        {
00353               cprintf("NO\r\n");
00354               CC->kill_me = KILLME_QUOTA;
00355        }
00356        else
00357        {
00358               cprintf("OK"); 
00359 /* citadel doesn't have quotas. in case of change, please add code here. */
00360 
00361        }
00362 }
00363 
00364 /*
00365  * PUTSCRIPT command, see chapter 2.6 
00366  */
00367 void cmd_mgsve_putscript(int num_parms, char **parms, struct sdm_userdata *u)
00368 {
00369 /* "scriptname" {nnn+} */
00370 /* AFTER we have the whole script overwrite existing scripts */
00371 /* spellcheck the script before overwrite old ones, and reply with "no" */
00372        if (num_parms == 3)
00373        {
00374               char *ScriptName;
00375               char *Script;
00376               long slength;
00377 
00378               if (parms[1][0]=='"')
00379                      ScriptName = &parms[1][1];
00380               else
00381                      ScriptName = parms[1];
00382               
00383               slength = strlen (ScriptName);
00384               
00385               if (ScriptName[slength] == '"')
00386                      ScriptName[slength] = '\0';
00387 
00388               Script = ReadString(GetSizeToken(parms[2]),parms[0]);
00389 
00390               if (Script == NULL) return;
00391               
00392               // TODO: do we spellcheck?
00393               msiv_putscript(u, ScriptName, Script);
00394               cprintf("OK\r\n");
00395        }
00396        else {
00397               cprintf("%s NO Read failed.\r\n", parms[0]);
00398               CC->kill_me = KILLME_READ_FAILED;
00399               return;
00400        } 
00401 
00402 
00403 
00404 }
00405 
00406 
00407 
00408 
00412 void cmd_mgsve_listscript(int num_parms, char **parms, struct sdm_userdata *u)
00413 {
00414 
00415        struct sdm_script *s;
00416        long nScripts = 0;
00417 
00418        MGSVE->imap_format_outstring = NULL;
00419        for (s=u->first_script; s!=NULL; s=s->next) {
00420               if (s->script_content != NULL) {
00421                      cprintf("\"%s\"%s\r\n", 
00422                             s->script_name, 
00423                             (s->script_active)?" ACTIVE":"");
00424                      nScripts++;
00425               }
00426        }
00427        cprintf("OK\r\n");
00428 }
00429 
00430 
00434 void cmd_mgsve_setactive(int num_parms, char **parms, struct sdm_userdata *u)
00435 {
00436        if (num_parms == 2)
00437        {
00438               if (msiv_setactive(u, parms[1]) == 0) {
00439                      cprintf("OK\r\n");
00440               }
00441               else
00442                      cprintf("No \"there is no script by that name %s \"\r\n", parms[1]);
00443        }
00444        else 
00445               cprintf("NO \"unexpected parameters.\"\r\n");
00446 
00447 }
00448 
00449 
00453 void cmd_mgsve_getscript(int num_parms, char **parms, struct sdm_userdata *u)
00454 {
00455        if (num_parms == 2){
00456               char *script_content;
00457               long  slen;
00458 
00459               script_content = msiv_getscript(u, parms[1]);
00460               if (script_content != NULL){
00461                      char *outbuf;
00462 
00463                      slen = strlen(script_content);
00464                      outbuf = malloc (slen + 64);
00465                      snprintf(outbuf, slen + 64, "{%ld+}\r\n%s\r\nOK\r\n",slen, script_content);
00466                      cprintf("%s", outbuf);
00467               }
00468               else
00469                      cprintf("No \"there is no script by that name %s \"\r\n", parms[1]);
00470        }
00471        else 
00472               cprintf("NO \"unexpected parameters.\"\r\n");
00473 }
00474 
00475 
00479 void cmd_mgsve_deletescript(int num_parms, char **parms, struct sdm_userdata *u)
00480 {
00481        int i=-1;
00482 
00483        if (num_parms == 2)
00484               i = msiv_deletescript(u, parms[1]);
00485        switch (i){          
00486        case 0:
00487               cprintf("OK\r\n");
00488               break;
00489        case 1:
00490               cprintf("NO \"no script by that name: %s\"\r\n", parms[1]);
00491               break;
00492        case 2:
00493               cprintf("NO \"can't delete active Script: %s\"\r\n", parms[1]);
00494               break;
00495        default:
00496        case -1:
00497               cprintf("NO \"unexpected parameters.\"\r\n");
00498               break;
00499        }
00500 }
00501 
00502 
00506 void mgsve_auth(char *argbuf) {
00507        char username_prompt[64];
00508        char method[64];
00509        char encoded_authstring[1024];
00510 
00511        if (CC->logged_in) {
00512               cprintf("NO \"Already logged in.\"\r\n");
00513               return;
00514        }
00515 
00516        extract_token(method, argbuf, 0, ' ', sizeof method);
00517 
00518        if (!strncasecmp(method, "login", 5) ) {
00519               if (strlen(argbuf) >= 7) {
00520               }
00521               else {
00522                      CtdlEncodeBase64(username_prompt, "Username:", 9, 0);
00523                      cprintf("334 %s\r\n", username_prompt);
00524               }
00525               return;
00526        }
00527 
00528        if (!strncasecmp(method, "plain", 5) ) {
00529               if (num_tokens(argbuf, ' ') < 2) {
00530                      cprintf("334 \r\n");
00531                      return;
00532               }
00533               extract_token(encoded_authstring, argbuf, 1, ' ', sizeof encoded_authstring);
00534               return;
00535        }
00536 
00537        if (strncasecmp(method, "login", 5) ) {
00538               cprintf("NO \"Unknown authentication method.\"\r\n");
00539               return;
00540        }
00541 
00542 }
00543 
00544 
00545 
00546 /*
00547  * implements the STARTTLS command (Citadel API version)
00548  */
00549 void _mgsve_starttls(void)
00550 {
00551        char ok_response[SIZ];
00552        char nosup_response[SIZ];
00553        char error_response[SIZ];
00554 
00555        sprintf(ok_response,
00556               "200 2.0.0 Begin TLS negotiation now\r\n");
00557        sprintf(nosup_response,
00558               "554 5.7.3 TLS not supported here\r\n");
00559        sprintf(error_response,
00560               "554 5.7.3 Internal error\r\n");
00561        CtdlModuleStartCryptoMsgs(ok_response, nosup_response, error_response);
00562 }
00563 
00564 
00565 /* 
00566  * Main command loop for managesieve sessions.
00567  */
00568 void managesieve_command_loop(void) {
00569        char cmdbuf[SIZ];
00570        char *parms[SIZ];
00571        int length;
00572        int num_parms;
00573        struct sdm_userdata u;
00574        int changes_made = 0;
00575 
00576        memset(&u, 0, sizeof(struct sdm_userdata));
00577 
00578        time(&CC->lastcmd);
00579        memset(cmdbuf, 0, sizeof cmdbuf); /* Clear it, just in case */
00580        length = client_getln(cmdbuf, sizeof cmdbuf);
00581        if (length >= 1) {
00582               num_parms = old_imap_parameterize(parms, cmdbuf);
00583               if (num_parms == 0) return;
00584               length = strlen(parms[0]);
00585        }
00586        if (length < 1) {
00587               syslog(LOG_CRIT, "managesieve: client disconnected: ending session.\n");
00588               CC->kill_me = KILLME_CLIENT_DISCONNECTED;
00589               return;
00590        }
00591        syslog(LOG_INFO, "MANAGESIEVE: %s\n", cmdbuf);
00592        if ((length>= 12) && (!strncasecmp(parms[0], "AUTHENTICATE", 12))){
00593               cmd_mgsve_auth(num_parms, parms, &u);
00594        }
00595 
00596 #ifdef HAVE_OPENSSL
00597        else if ((length>= 8) && (!strncasecmp(parms[0], "STARTTLS", 8))){
00598               cmd_mgsve_starttls();
00599        }
00600 #endif
00601        else if ((length>= 6) && (!strncasecmp(parms[0], "LOGOUT", 6))){
00602               cmd_mgsve_logout(&u);
00603        }
00604        else if ((length>= 6) && (!strncasecmp(parms[0], "CAPABILITY", 10))){
00605               cmd_mgsve_caps();
00606        } 
00608        else if (CC->logged_in != 0)
00609        {
00610               msiv_load(&u);
00611               if ((length>= 9) && (!strncasecmp(parms[0], "HAVESPACE", 9))){
00612                      cmd_mgsve_havespace();
00613               }
00614               else if ((length>= 6) && (!strncasecmp(parms[0], "PUTSCRIPT", 9))){
00615                      cmd_mgsve_putscript(num_parms, parms, &u);
00616                      changes_made = 1;
00617               }
00618               else if ((length>= 6) && (!strncasecmp(parms[0], "LISTSCRIPT", 10))){
00619                      cmd_mgsve_listscript(num_parms, parms,&u);
00620               }
00621               else if ((length>= 6) && (!strncasecmp(parms[0], "SETACTIVE", 9))){
00622                      cmd_mgsve_setactive(num_parms, parms,&u);
00623                      changes_made = 1;
00624               }
00625               else if ((length>= 6) && (!strncasecmp(parms[0], "GETSCRIPT", 9))){
00626                      cmd_mgsve_getscript(num_parms, parms, &u);
00627               }
00628               else if ((length>= 6) && (!strncasecmp(parms[0], "DELETESCRIPT", 11))){
00629                      cmd_mgsve_deletescript(num_parms, parms, &u);
00630                      changes_made = 1;
00631               }
00632               msiv_store(&u, changes_made);
00633        }
00634        else {
00635               cprintf("No Invalid access or command.\r\n");
00636               syslog(LOG_INFO, "illegal Managesieve command: %s", parms[0]);
00637               CC->kill_me = KILLME_ILLEGAL_MANAGESIEVE_COMMAND;
00638        }
00639 
00640 
00641 }
00642 
00643 /*
00644  * This cleanup function blows away the temporary memory and files used by
00645  * the server.
00646  */
00647 void managesieve_cleanup_function(void) {
00648 
00649        /* Don't do this stuff if this is not a managesieve session! */
00650        if (CC->h_command_function != managesieve_command_loop) return;
00651 
00652        syslog(LOG_DEBUG, "Performing managesieve cleanup hook\n");
00653        free(MGSVE);
00654 }
00655 
00656 
00657 
00658 const char* CitadelServiceManageSieve = "ManageSieve";
00659 CTDL_MODULE_INIT(managesieve)
00660 {
00661        if (!threading)
00662        {
00663               CtdlRegisterServiceHook(config.c_managesieve_port,
00664                                    NULL,
00665                                    managesieve_greeting,
00666                                    managesieve_command_loop,
00667                                    NULL, 
00668                                    CitadelServiceManageSieve);
00669               CtdlRegisterSessionHook(managesieve_cleanup_function, EVT_STOP, PRIO_STOP + 30);
00670        }
00671        
00672        /* return our module name for the log */
00673        return "managesieve";
00674 }
00675 
00676