#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <stdlib.h>
#include <stdio.h>
#include <syslog.h>
#include <string.h>
#include <sasl/sasl.h>
#include <sasl/saslutil.h>
#include "libconfig.h"
#include "global.h"
#include "auth.h"
#include "mboxname.h"
#include "mboxlist.h"
#include "xmalloc.h"
#include "prot.h"
#include "tls.h"
#include "lex.h"
#include "actions.h"
#include "exitcodes.h"
#include "telemetry.h"
#include "AppleOD.h"
extern char sieved_clienthost[250];
extern int sieved_domainfromip;
extern sasl_conn_t *sieved_saslconn;
const char *referral_host = NULL;
int authenticated = 0;
int verify_only = 0;
int starttls_done = 0;
#ifdef HAVE_SSL
static SSL *tls_conn = NULL;
#endif
void fatal(const char *s, int code);
extern int sieved_logfd;
extern struct od_user_opts *gUserOpts;
static void cmd_logout(struct protstream *sieved_out,
struct protstream *sieved_in);
static int cmd_authenticate(struct protstream *sieved_out, struct protstream *sieved_in,
mystring_t *mechanism_name, mystring_t *initial_challenge, const char **errmsg);
static int cmd_od_authenticate(struct protstream *sieved_out, struct protstream *sieved_in,
mystring_t *mechanism_name, mystring_t *initial_challenge, const char **errmsg);
static int cmd_starttls(struct protstream *sieved_out, struct protstream *sieved_in);
int parser(struct protstream *sieved_out, struct protstream *sieved_in)
{
int token = EOL;
const char *error_msg = "Generic Error";
mystring_t *mechanism_name = NULL;
mystring_t *initial_challenge = NULL;
mystring_t *sieve_name = NULL;
mystring_t *sieve_data = NULL;
unsigned long num;
int ret = FALSE;
while(token == EOL)
token = timlex(NULL, NULL, sieved_in);
if (!authenticated && (token > 255) && (token!=AUTHENTICATE) &&
(token!=LOGOUT) && (token!=CAPABILITY) &&
(!tls_enabled() || (token!=STARTTLS)))
{
error_msg = "Authenticate first";
if (token!=EOL)
lex_setrecovering();
goto error;
}
if (verify_only && (token > 255) && (token!=PUTSCRIPT) && (token!=LOGOUT))
{
error_msg = "Script verification only";
if (token!=EOL)
lex_setrecovering();
goto error;
}
switch (token)
{
case AUTHENTICATE:
if (timlex(NULL, NULL, sieved_in)!=SPACE)
{
error_msg = "SPACE must occur after AUTHENTICATE";
goto error;
}
if (timlex(&mechanism_name, NULL, sieved_in)!=STRING)
{
error_msg = "Did not specify mechanism name";
goto error;
}
token = timlex(NULL, NULL, sieved_in);
if (token != EOL)
{
if (token!=SPACE)
{
error_msg = "Expected SPACE";
goto error;
}
if (timlex(&initial_challenge, NULL, sieved_in)!=STRING)
{
error_msg = "Expected string";
goto error;
}
token = timlex(NULL, NULL, sieved_in);
}
if (token != EOL)
{
error_msg = "Expected EOL";
goto error;
}
if (authenticated)
prot_printf(sieved_out, "NO \"Already authenticated\"\r\n");
else
{
if ( config_getswitch( IMAPOPT_APPLE_AUTH ) == 0 )
{
if (cmd_authenticate(sieved_out, sieved_in, mechanism_name,
initial_challenge, &error_msg)==FALSE)
{
error_msg = "Authentication Error";
goto error;
}
}
else
{
if (cmd_od_authenticate(sieved_out, sieved_in, mechanism_name,
initial_challenge, &error_msg)==FALSE)
{
error_msg = "Authentication Error";
goto error;
}
}
}
#if 0
if(referral_host)
goto do_referral;
#endif
break;
case CAPABILITY:
if (timlex(NULL, NULL, sieved_in)!=EOL)
{
error_msg = "Expected EOL";
goto error;
}
if(referral_host)
goto do_referral;
capabilities(sieved_out, sieved_saslconn, starttls_done, authenticated);
break;
case HAVESPACE:
if (timlex(NULL, NULL, sieved_in)!=SPACE)
{
error_msg = "SPACE must occur after HAVESPACE";
goto error;
}
if (timlex(&sieve_name, NULL, sieved_in)!=STRING)
{
error_msg = "Did not specify script name";
goto error;
}
if (timlex(NULL, NULL, sieved_in)!=SPACE)
{
error_msg = "Expected SPACE";
goto error;
}
if (timlex(NULL, &num, sieved_in)!=NUMBER)
{
error_msg = "Expected Number";
goto error;
}
if (timlex(NULL, NULL, sieved_in)!=EOL)
{
error_msg = "Expected EOL";
goto error;
}
if(referral_host)
goto do_referral;
cmd_havespace(sieved_out, sieve_name, num);
break;
case LOGOUT:
token = timlex(NULL, NULL, sieved_in);
if (token!=EOL && token!=EOF && token!=LOGOUT)
{
error_msg = "Garbage after logout command";
goto error;
}
cmd_logout(sieved_out, sieved_in);
ret = TRUE;
goto done;
break;
case GETSCRIPT:
if (timlex(NULL, NULL, sieved_in)!=SPACE)
{
error_msg = "SPACE must occur after GETSCRIPT";
goto error;
}
if (timlex(&sieve_name, NULL, sieved_in)!=STRING)
{
error_msg = "Did not specify script name";
goto error;
}
if (timlex(NULL, NULL, sieved_in)!=EOL)
{
error_msg = "Expected EOL";
goto error;
}
if(referral_host)
goto do_referral;
getscript(sieved_out, sieve_name);
break;
case PUTSCRIPT:
if (timlex(NULL, NULL, sieved_in)!=SPACE)
{
error_msg = "SPACE must occur after PUTSCRIPT";
goto error;
}
if (timlex(&sieve_name, NULL, sieved_in)!=STRING)
{
error_msg = "Did not specify script name";
goto error;
}
if (timlex(NULL, NULL, sieved_in)!=SPACE)
{
error_msg = "Expected SPACE";
goto error;
}
if (timlex(&sieve_data, NULL, sieved_in)!=STRING)
{
error_msg = "Did not specify legal script data length";
goto error;
}
if (timlex(NULL, NULL, sieved_in)!=EOL)
{
error_msg = "Expected EOL";
goto error;
}
if(referral_host)
goto do_referral;
putscript(sieved_out, sieve_name, sieve_data, verify_only);
break;
case SETACTIVE:
if (timlex(NULL, NULL, sieved_in)!=SPACE)
{
error_msg = "SPACE must occur after SETACTIVE";
goto error;
}
if (timlex(&sieve_name, NULL, sieved_in)!=STRING)
{
error_msg = "Did not specify script name";
goto error;
}
if (timlex(NULL, NULL, sieved_in)!=EOL)
{
error_msg = "Expected EOL";
goto error;
}
if(referral_host)
goto do_referral;
setactive(sieved_out, sieve_name);
break;
case DELETESCRIPT:
if (timlex(NULL, NULL, sieved_in)!=SPACE)
{
error_msg = "SPACE must occur after DELETESCRIPT";
goto error;
}
if (timlex(&sieve_name, NULL, sieved_in)!=STRING)
{
error_msg = "Did not specify script name";
goto error;
}
if (timlex(NULL, NULL, sieved_in)!=EOL)
{
error_msg = "Expected EOL";
goto error;
}
if(referral_host)
goto do_referral;
deletescript(sieved_out, sieve_name);
break;
case LISTSCRIPTS:
if (timlex(NULL, NULL, sieved_in)!=EOL)
{
error_msg = "Expected EOL";
goto error;
}
if(referral_host)
goto do_referral;
listscripts(sieved_out);
break;
case STARTTLS:
if (timlex(NULL, NULL, sieved_in)!=EOL)
{
error_msg = "Expected EOL";
goto error;
}
if(referral_host)
goto do_referral;
cmd_starttls(sieved_out, sieved_in);
break;
default:
error_msg="Expected a command. Got something else.";
goto error;
break;
}
done:
free(mechanism_name);
free(initial_challenge);
free(sieve_name);
free(sieve_data);
prot_flush(sieved_out);
return ret;
error:
free(mechanism_name);
free(initial_challenge);
free(sieve_name);
free(sieve_data);
prot_printf(sieved_out, "NO \"%s\"\r\n",error_msg);
prot_flush(sieved_out);
return FALSE;
do_referral:
{
char buf[4096];
char *c;
strlcpy(buf, referral_host, sizeof(buf));
c = strchr(buf, '!');
if(c) *c = '\0';
prot_printf(sieved_out, "BYE (REFERRAL \"sieve://%s\") \"Try Remote.\"\r\n",
buf);
ret = TRUE;
goto done;
}
}
void cmd_logout(struct protstream *sieved_out,
struct protstream *sieved_in __attribute__((unused)))
{
prot_printf(sieved_out, "OK \"Logout Complete\"\r\n");
prot_flush(sieved_out);
}
static sasl_ssf_t ssf = 0;
static char *authid = NULL;
extern int reset_saslconn(sasl_conn_t **conn, sasl_ssf_t ssf, char *authid);
static int cmd_authenticate(struct protstream *sieved_out,
struct protstream *sieved_in,
mystring_t *mechanism_name,
mystring_t *initial_challenge,
const char **errmsg)
{
int sasl_result;
char *mech = string_DATAPTR(mechanism_name);
mystring_t *clientinstr=NULL;
char *clientin = NULL;
unsigned int clientinlen = 0;
const char *serverout=NULL;
unsigned int serveroutlen;
const char *errstr=NULL;
const char *canon_user;
char *username;
int ret = TRUE;
clientinstr = initial_challenge;
if (clientinstr!=NULL)
{
clientin=(char *) malloc(clientinstr->len*2);
if (clientinstr->len) {
sasl_result=sasl_decode64(string_DATAPTR(clientinstr),
clientinstr->len,
clientin, clientinstr->len*2,
&clientinlen);
} else {
clientinlen = 0;
sasl_result = SASL_OK;
}
if (sasl_result!=SASL_OK)
{
*errmsg="error base64 decoding string";
syslog(LOG_NOTICE, "badlogin: %s %s %s",
sieved_clienthost, mech, "error base64 decoding string");
if(reset_saslconn(&sieved_saslconn, ssf, authid) != SASL_OK)
fatal("could not reset the sasl_conn_t after failure",
EC_TEMPFAIL);
return FALSE;
}
}
sasl_result = sasl_server_start(sieved_saslconn, mech,
clientin, clientinlen,
&serverout, &serveroutlen);
while (sasl_result==SASL_CONTINUE)
{
int token1;
int token2;
mystring_t *str, *blahstr;
char *inbase64;
unsigned int inbase64len;
inbase64 = xmalloc(serveroutlen*2+1);
sasl_encode64(serverout, serveroutlen,
inbase64, serveroutlen*2+1, &inbase64len);
prot_printf(sieved_out, "{%d}\r\n",inbase64len);
prot_write(sieved_out,inbase64,inbase64len);
prot_printf(sieved_out,"\r\n");
token1 = timlex(&str, NULL, sieved_in);
if (token1==STRING)
{
clientin=(char *) malloc(str->len*2);
if (str->len) {
sasl_result=sasl_decode64(string_DATAPTR(str), str->len,
clientin, str->len*2, &clientinlen);
} else {
clientinlen = 0;
sasl_result = SASL_OK;
}
if (sasl_result!=SASL_OK)
{
*errmsg="error base64 decoding string";
syslog(LOG_NOTICE, "badlogin: %s %s %s",
sieved_clienthost, mech, "error base64 decoding string");
if(reset_saslconn(&sieved_saslconn, ssf, authid) != SASL_OK)
fatal("could not reset the sasl_conn_t after failure",
EC_TEMPFAIL);
return FALSE;
}
} else {
*errmsg="Expected STRING-xxx1";
if(reset_saslconn(&sieved_saslconn, ssf, authid) != SASL_OK)
fatal("could not reset the sasl_conn_t after failure",
EC_TEMPFAIL);
return FALSE;
}
token2 = timlex(&blahstr, NULL, sieved_in);
if ((token1==STRING) && (token2==EOL))
{
sasl_result = sasl_server_step(sieved_saslconn,
clientin,
clientinlen,
&serverout, &serveroutlen);
} else {
*errmsg = "expected a STRING followed by an EOL";
syslog(LOG_NOTICE, "badlogin: %s %s %s",
sieved_clienthost, mech, "expected string");
if(reset_saslconn(&sieved_saslconn, ssf, authid) != SASL_OK)
fatal("could not reset the sasl_conn_t after failure",
EC_TEMPFAIL);
return FALSE;
}
}
if (sasl_result!=SASL_OK)
{
if(sasl_result == SASL_NOUSER)
sasl_result = SASL_BADAUTH;
*errmsg = (const char *) sasl_errstring(sasl_result,NULL,NULL);
if (errstr!=NULL) {
syslog(LOG_NOTICE, "badlogin: %s %s %d %s",
sieved_clienthost, mech, sasl_result, errstr);
} else {
syslog(LOG_NOTICE, "badlogin: %s %s %s",
sieved_clienthost, mech,
sasl_errstring(sasl_result, NULL, NULL));
}
if(reset_saslconn(&sieved_saslconn, ssf, authid) != SASL_OK)
fatal("could not reset the sasl_conn_t after failure",
EC_TEMPFAIL);
return FALSE;
}
sasl_result=sasl_getprop(sieved_saslconn, SASL_USERNAME,
(const void **) &canon_user);
if (sasl_result!=SASL_OK)
{
*errmsg = "Internal SASL error";
syslog(LOG_ERR, "SASL: sasl_getprop SASL_USERNAME: %s",
sasl_errstring(sasl_result, NULL, NULL));
if(reset_saslconn(&sieved_saslconn, ssf, authid) != SASL_OK)
fatal("could not reset the sasl_conn_t after failure",
EC_TEMPFAIL);
return FALSE;
}
username = xstrdup(canon_user);
verify_only = !strcmp(username, "anonymous");
if (!verify_only) {
struct namespace sieved_namespace;
char inboxname[MAX_MAILBOX_NAME];
char *server;
int type, r;
if ((r = mboxname_init_namespace(&sieved_namespace, 0)) != 0) {
syslog(LOG_ERR, error_message(r));
fatal(error_message(r), EC_CONFIG);
}
mboxname_hiersep_tointernal(&sieved_namespace, username,
config_virtdomains ?
strcspn(username, "@") : 0);
(*sieved_namespace.mboxname_tointernal)(&sieved_namespace, "INBOX",
username, inboxname);
r = mboxlist_detail(inboxname, &type, &server, NULL, NULL, NULL);
if(r) {
*errmsg = "mailbox unknown";
return FALSE;
}
if(type & MBTYPE_REMOTE) {
if (sieved_domainfromip) {
char *authname, *p;
free(username);
username = xstrdup(canon_user);
sasl_result=sasl_getprop(sieved_saslconn, SASL_AUTHUSER,
(const void **) &canon_user);
if (sasl_result!=SASL_OK)
{
*errmsg = "Internal SASL error";
syslog(LOG_ERR, "SASL: sasl_getprop SASL_AUTHUSER: %s",
sasl_errstring(sasl_result, NULL, NULL));
if(reset_saslconn(&sieved_saslconn, ssf, authid) != SASL_OK)
fatal("could not reset the sasl_conn_t after failure",
EC_TEMPFAIL);
ret = FALSE;
goto cleanup;
}
authname = xstrdup(canon_user);
if ((p = strchr(authname, '@'))) *p = '%';
if ((p = strchr(username, '@'))) *p = '%';
referral_host =
(char*) xmalloc(strlen(authname)+1+strlen(username)+1+
strlen(server)+1);
sprintf((char*) referral_host, "%s;%s@%s",
authname, username, server);
free(authname);
}
else
referral_host = xstrdup(server);
} else if (actions_setuser(username) != TIMSIEVE_OK) {
*errmsg = "internal error";
syslog(LOG_ERR, "error in actions_setuser()");
if(reset_saslconn(&sieved_saslconn, ssf, authid) != SASL_OK)
fatal("could not reset the sasl_conn_t after failure",
EC_TEMPFAIL);
ret = FALSE;
goto cleanup;
}
}
if(serverout) {
char *inbase64;
unsigned int inbase64len;
inbase64 = xmalloc(serveroutlen*2+1);
sasl_encode64(serverout, serveroutlen,
inbase64, serveroutlen*2+1, &inbase64len);
prot_printf(sieved_out, "OK (SASL \"%s\")\r\n", inbase64);
free(inbase64);
} else {
prot_printf(sieved_out, "OK\r\n");
}
syslog(LOG_NOTICE, "login: %s %s %s%s %s", sieved_clienthost, username,
mech, starttls_done ? "+TLS" : "", "User logged in");
authenticated = 1;
prot_setsasl(sieved_in, sieved_saslconn);
prot_setsasl(sieved_out, sieved_saslconn);
sieved_logfd = telemetry_log(username, sieved_in, sieved_out, 0);
cleanup:
free(username);
return ret;
}
static int cmd_od_authenticate ( struct protstream *sieved_out,
struct protstream *sieved_in,
mystring_t *mechanism_name,
mystring_t *initial_challenge,
const char **errmsg)
{
int r = 0;
char *mech = string_DATAPTR( mechanism_name );
char *chal = NULL;
const char *serverout = NULL;
unsigned int serveroutlen;
const char *errstr = NULL;
char *username;
int ret = TRUE;
if ( initial_challenge != NULL )
{
chal = string_DATAPTR( initial_challenge );
}
if ( strcasecmp( mech, "CRAM-MD5" ) == 0 )
{
mech = "SIEVE-CRAM-MD5";
}
else if ( strcasecmp( mech, "LOGIN" ) == 0 )
{
mech = "SIEVE-LOGIN";
}
else if ( strcasecmp( mech, "PLAIN" ) == 0 )
{
mech = "SIEVE-PLAIN";
}
r = odDoAuthenticate( mech, chal, "+ ", kXMLIMAP_Principal, sieved_in, sieved_out, gUserOpts );
if ( r != eAODNoErr )
{
syslog( LOG_NOTICE, "badlogin: %s %s (%d)", sieved_clienthost, mech, r );
return( FALSE );
}
else if ( gUserOpts->fAuthIDNamePtr != NULL )
{
int len = 0;
int isAdmin = 0;
char buf[ 1024 ];
const char *val = config_getstring( IMAPOPT_ADMINS );
if ( !val )
{
return( FALSE );
}
while ( *val )
{
char *p = NULL;
for( p = (char *) val; *p && !isspace((int) *p); p++ );
len = p-val;
if( len >= sizeof( buf ) )
{
len = sizeof( buf ) - 1;
}
memcpy( buf, val, len );
buf[len] = '\0';
if ( strcmp( gUserOpts->fAuthIDNamePtr, buf ) == 0 )
{
isAdmin = 1;
break;
}
val = p;
while ( *val && isspace( (int)*val ) )
{
val++;
}
}
if ( !isAdmin )
{
syslog(LOG_ERR, "illegal authorization attempt: - %s - is not an admin user", gUserOpts->fAuthIDNamePtr );
return( FALSE );
}
}
username = xstrdup( gUserOpts->fRecNamePtr );
verify_only = !strcmp(username, "anonymous");
if ( !verify_only )
{
struct namespace sieved_namespace;
char inboxname[ MAX_MAILBOX_NAME ];
char *server;
int type;
int r;
if ((r = mboxname_init_namespace(&sieved_namespace, 0)) != 0)
{
syslog(LOG_ERR, error_message(r));
fatal(error_message(r), EC_CONFIG);
}
mboxname_hiersep_tointernal(&sieved_namespace, username,
config_virtdomains ?
strcspn(username, "@") : 0);
(*sieved_namespace.mboxname_tointernal)(&sieved_namespace, "INBOX",
username, inboxname);
r = mboxlist_detail(inboxname, &type, &server, NULL, NULL, NULL);
if(r) {
*errmsg = "mailbox unknown";
return FALSE;
}
if(type & MBTYPE_REMOTE) {
fatal("remote host not supported", EC_UNAVAILABLE);
} else if (actions_setuser(username) != TIMSIEVE_OK) {
*errmsg = "internal error";
syslog(LOG_ERR, "error in actions_setuser()");
if(reset_saslconn(&sieved_saslconn, ssf, authid) != SASL_OK)
fatal("could not reset the sasl_conn_t after failure",
EC_TEMPFAIL);
ret = FALSE;
goto cleanup;
}
}
if(serverout) {
char *inbase64;
unsigned int inbase64len;
inbase64 = xmalloc(serveroutlen*2+1);
sasl_encode64(serverout, serveroutlen,
inbase64, serveroutlen*2+1, &inbase64len);
prot_printf(sieved_out, "OK (SASL \"%s\")\r\n", inbase64);
free(inbase64);
} else {
prot_printf(sieved_out, "OK\r\n");
}
syslog(LOG_NOTICE, "login: %s %s %s%s %s", sieved_clienthost, username,
mech, starttls_done ? "+TLS" : "", "User logged in");
authenticated = 1;
prot_setsasl(sieved_in, sieved_saslconn);
prot_setsasl(sieved_out, sieved_saslconn);
sieved_logfd = telemetry_log(username, sieved_in, sieved_out, 0);
cleanup:
free(username);
return ret;
}
#ifdef HAVE_SSL
static int cmd_starttls(struct protstream *sieved_out, struct protstream *sieved_in)
{
int result;
int *layerp;
layerp = (int *) &ssf;
if (starttls_done == 1)
{
prot_printf(sieved_out, "NO \"TLS already active\"\r\n");
return TIMSIEVE_FAIL;
}
result=tls_init_serverengine("sieve",
5,
1,
1);
if (result == -1) {
syslog(LOG_ERR, "error initializing TLS");
prot_printf(sieved_out, "NO \"Error initializing TLS\"\r\n");
return TIMSIEVE_FAIL;
}
prot_printf(sieved_out, "OK \"Begin TLS negotiation now\"\r\n");
prot_flush(sieved_out);
result=tls_start_servertls(0,
1,
layerp,
&authid,
&tls_conn);
if (result==-1) {
prot_printf(sieved_out, "NO \"Starttls failed\"\r\n");
syslog(LOG_NOTICE, "STARTTLS failed: %s", sieved_clienthost);
return TIMSIEVE_FAIL;
}
result = sasl_setprop(sieved_saslconn, SASL_SSF_EXTERNAL, &ssf);
if (result != SASL_OK) {
fatal("sasl_setprop() failed: cmd_starttls()", EC_TEMPFAIL);
}
result = sasl_setprop(sieved_saslconn, SASL_AUTH_EXTERNAL, authid);
if (result != SASL_OK) {
fatal("sasl_setprop() failed: cmd_starttls()", EC_TEMPFAIL);
}
prot_settls(sieved_in, tls_conn);
prot_settls(sieved_out, tls_conn);
starttls_done = 1;
return result;
}
#else
static int cmd_starttls(struct protstream *sieved_out, struct protstream *sieved_in)
{
fatal("cmd_starttls() called, but no OpenSSL", EC_SOFTWARE);
}
#endif