#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <stdlib.h>
#include <stdio.h>
#include <sys/stat.h>
#include <errno.h>
#include <stdlib.h>
#include <limits.h>
#include <sys/param.h>
#include <sys/types.h>
#include <fcntl.h>
#include <syslog.h>
#include <dirent.h>
#include <ctype.h>
#include <unistd.h>
#include <string.h>
#include <retry.h>
#include "prot.h"
#include "tls.h"
#include "util.h"
#include "global.h"
#include "libconfig.h"
#include "xmalloc.h"
#include "sieve_interface.h"
#include "codes.h"
#include "actions.h"
#include "scripttest.h"
#include "sync_log.h"
extern int sieved_userisadmin;
char *sieve_dir = NULL;
const static char *sieved_userid = NULL;
int actions_init(void)
{
int sieve_usehomedir = 0;
sieve_usehomedir = config_getswitch(IMAPOPT_SIEVEUSEHOMEDIR);
if (!sieve_usehomedir) {
sieve_dir = (char *) config_getstring(IMAPOPT_SIEVEDIR);
} else {
syslog(LOG_ERR, "can't use home directories");
return TIMSIEVE_FAIL;
}
return TIMSIEVE_OK;
}
int actions_setuser(const char *userid)
{
char userbuf[1024], *user, *domain = NULL;
char *foo = sieve_dir;
size_t size = 1024, len;
int result;
sieve_dir = (char *) xzmalloc(size+1);
sieved_userid = xstrdup(userid);
user = (char *) userid;
if (config_virtdomains && strchr(user, '@')) {
strlcpy(userbuf, userid, sizeof(userbuf));
user = userbuf;
if ((domain = strrchr(user, '@'))) *domain++ = '\0';
}
len = strlcpy(sieve_dir, foo, size);
if (domain) {
char dhash = (char) dir_hash_c(domain);
len += snprintf(sieve_dir+len, size-len, "%s%c/%s",
FNAME_DOMAINDIR, dhash, domain);
}
if (sieved_userisadmin) {
strlcat(sieve_dir, "/global", size);
}
else {
char hash = (char) dir_hash_c(user);
snprintf(sieve_dir+len, size-len, "/%c/%s", hash, user);
}
result = chdir(sieve_dir);
if (result != 0) {
result = cyrus_mkdir(sieve_dir, 0755);
if (!result) result = mkdir(sieve_dir, 0755);
if (!result) result = chdir(sieve_dir);
if (result) {
syslog(LOG_ERR, "mkdir %s: %m", sieve_dir);
return TIMSIEVE_FAIL;
}
}
return TIMSIEVE_OK;
}
int scriptname_valid(mystring_t *name)
{
int lup;
char *ptr;
if (name->len < 1) return TIMSIEVE_FAIL;
ptr=string_DATAPTR(name);
for (lup=0;lup<name->len;lup++)
{
if ((ptr[lup]=='/') || (ptr[lup]=='\0'))
return TIMSIEVE_FAIL;
}
return TIMSIEVE_OK;
}
int capabilities(struct protstream *conn, sasl_conn_t *saslconn,
int starttls_done, int authenticated)
{
const char *sasllist;
int mechcount;
prot_printf(conn,
"\"IMPLEMENTATION\" \"Cyrus timsieved%s %s\"\r\n",
config_mupdate_server ? " (Murder)" : "", CYRUS_VERSION);
#ifdef APPLE_OS_X_SERVER
if ( config_getswitch( IMAPOPT_APPLE_AUTH ) == 0 )
{
#endif
if (!authenticated &&
sasl_listmech(saslconn, NULL,
"\"SASL\" \"", " ", "\"\r\n",
&sasllist,
NULL, &mechcount) == SASL_OK && mechcount > 0)
{
prot_printf(conn,"%s",sasllist);
}
#ifdef APPLE_OS_X_SERVER
}
else
{
int needSpace = FALSE;
char str[ 256 ];
strcpy( str, "\"SASL\" \"" );
if ( config_getswitch( IMAPOPT_IMAP_AUTH_PLAIN ) )
{
strcat( str, "PLAIN");
needSpace = TRUE;
}
if ( config_getswitch( IMAPOPT_IMAP_AUTH_LOGIN ) )
{
if ( needSpace )
{
strcat( str, " ");
}
strcat( str, "LOGIN");
needSpace = TRUE;
}
if ( config_getswitch( IMAPOPT_IMAP_AUTH_CRAM_MD5 ) )
{
if ( needSpace )
{
strcat( str, " ");
}
strcat( str, "CRAM-MD5");
needSpace = TRUE;
}
if ( config_getswitch( IMAPOPT_IMAP_AUTH_GSSAPI ) )
{
if ( needSpace )
{
strcat( str, " ");
}
strcat( str, "GSSAPI");
}
strcat( str, "\"\r\n" );
prot_printf( conn,"%s", str );
}
#endif
prot_printf(conn,"\"SIEVE\" \"%s\"\r\n",sieve_listextensions(interp));
if (tls_enabled() && !starttls_done && !authenticated) {
prot_printf(conn, "\"STARTTLS\"\r\n");
}
prot_printf(conn,"OK\r\n");
return TIMSIEVE_OK;
}
int getscript(struct protstream *conn, mystring_t *name)
{
FILE *stream;
struct stat filestats;
int size;
int result;
int cnt;
char path[1024];
result = scriptname_valid(name);
if (result!=TIMSIEVE_OK)
{
prot_printf(conn,"NO \"Invalid script name\"\r\n");
return result;
}
snprintf(path, 1023, "%s.script", string_DATAPTR(name));
result = stat(path, &filestats);
if (result != 0) {
prot_printf(conn,"NO \"Script doesn't exist\"\r\n");
return TIMSIEVE_NOEXIST;
}
size = filestats.st_size;
stream = fopen(path, "r");
if (stream == NULL) {
prot_printf(conn,"NO \"fopen failed\"\r\n");
return TIMSIEVE_NOEXIST;
}
prot_printf(conn, "{%d}\r\n", size);
cnt = 0;
while (cnt < size) {
char buf[BLOCKSIZE];
int amount=BLOCKSIZE;
if (size-cnt < BLOCKSIZE)
amount=size-cnt;
if (fread(buf, 1, BLOCKSIZE, stream) == 0) {
if (ferror(stream)) {
fatal("fatal error (fread)", 0);
}
}
prot_write(conn, buf, amount);
cnt += amount;
}
prot_printf(conn,"\r\n");
prot_printf(conn, "OK\r\n");
return TIMSIEVE_OK;
}
static int countscripts(char *name)
{
DIR *dp;
struct dirent *dir;
int length;
int number=0;
char myname[1024];
snprintf(myname, 1023, "%s.script", name);
if ((dp = opendir(".")) == NULL) {
return -1;
}
while ((dir=readdir(dp)) != NULL) {
length=strlen(dir->d_name);
if (length >= strlen(".script") &&
(strcmp(dir->d_name + (length - 7), ".script") == 0)) {
if (strcmp(myname, dir->d_name) != 0) {
number++;
}
}
}
return number;
}
int putscript(struct protstream *conn, mystring_t *name, mystring_t *data,
int verify_only)
{
FILE *stream;
char *dataptr;
char *errstr;
int lup;
int result;
char path[1024], p2[1024];
char bc_path[1024], bc_p2[1024];
int maxscripts;
sieve_script_t *s;
result = scriptname_valid(name);
if (result!=TIMSIEVE_OK)
{
prot_printf(conn,"NO \"Invalid script name\"\r\n");
return result;
}
if (verify_only)
stream = tmpfile();
else {
maxscripts = config_getint(IMAPOPT_SIEVE_MAXSCRIPTS);
if (countscripts(string_DATAPTR(name))+1 > maxscripts)
{
prot_printf(conn,
"NO (\"QUOTA\") \"You are only allowed %d scripts on this server\"\r\n",
maxscripts);
return TIMSIEVE_FAIL;
}
snprintf(path, 1023, "%s.script.NEW", string_DATAPTR(name));
stream = fopen(path, "w+");
}
if (stream == NULL) {
prot_printf(conn, "NO \"Unable to open script for writing (%s)\"\r\n",
path);
return TIMSIEVE_NOEXIST;
}
dataptr = string_DATAPTR(data);
for (lup=0;lup<= data->len / BLOCKSIZE; lup++) {
int amount = BLOCKSIZE;
if (lup*BLOCKSIZE+BLOCKSIZE > data->len)
amount=data->len % BLOCKSIZE;
fwrite(dataptr, 1, amount, stream);
dataptr += amount;
}
result = is_script_parsable(stream, &errstr, &s);
if (result != TIMSIEVE_OK) {
if (errstr && *errstr) {
prot_printf(conn, "NO {%d}\r\n%s\r\n", strlen(errstr), errstr);
free(errstr);
} else {
if (errstr) free(errstr);
prot_printf(conn, "NO \"parse failed\"\r\n");
}
fclose(stream);
unlink(path);
return result;
}
fflush(stream);
fclose(stream);
if (!verify_only) {
int fd;
bytecode_info_t *bc;
if(sieve_generate_bytecode(&bc, s) == -1) {
unlink(path);
sieve_script_free(&s);
prot_printf(conn, "NO \"bytecode generate failed\"\r\n");
return TIMSIEVE_FAIL;
}
snprintf(bc_path, 1023, "%s.bc.NEW", string_DATAPTR(name));
fd = open(bc_path, O_CREAT | O_TRUNC | O_WRONLY, 0600);
if(fd < 0) {
unlink(path);
sieve_free_bytecode(&bc);
sieve_script_free(&s);
prot_printf(conn, "NO \"couldn't open bytecode file\"\r\n");
return TIMSIEVE_FAIL;
}
if(sieve_emit_bytecode(fd, bc) == -1) {
close(fd);
unlink(path);
unlink(bc_path);
sieve_free_bytecode(&bc);
sieve_script_free(&s);
prot_printf(conn, "NO \"bytecode emit failed\"\r\n");
return TIMSIEVE_FAIL;
}
sieve_free_bytecode(&bc);
sieve_script_free(&s);
close(fd);
snprintf(p2, 1023, "%s.script", string_DATAPTR(name));
snprintf(bc_p2, 1023, "%s.bc", string_DATAPTR(name));
rename(path, p2);
rename(bc_path, bc_p2);
}
prot_printf(conn, "OK\r\n");
sync_log_sieve(sieved_userid);
return TIMSIEVE_OK;
}
static int deleteactive(struct protstream *conn)
{
if (unlink("defaultbc") != 0) {
prot_printf(conn,"NO \"Unable to unlink active script\"\r\n");
return TIMSIEVE_FAIL;
}
sync_log_sieve(sieved_userid);
return TIMSIEVE_OK;
}
static int isactive(char *name)
{
char filename[1024];
char activelink[1024];
snprintf(filename, 1023, "%s.bc", name);
memset(activelink, 0, sizeof(activelink));
if ((readlink("defaultbc", activelink, sizeof(activelink)-1) < 0) &&
(errno != ENOENT))
{
syslog(LOG_ERR, "readlink(defaultbc): %m");
return FALSE;
}
if (!strcmp(filename, activelink)) {
return TRUE;
} else {
return FALSE;
}
}
int deletescript(struct protstream *conn, mystring_t *name)
{
int result;
char path[1024];
result = scriptname_valid(name);
if (result!=TIMSIEVE_OK)
{
prot_printf(conn,"NO \"Invalid script name\"\r\n");
return result;
}
snprintf(path, 1023, "%s.script", string_DATAPTR(name));
if (isactive(string_DATAPTR(name)) && (deleteactive(conn)!=TIMSIEVE_OK)) {
return TIMSIEVE_FAIL;
}
result = unlink(path);
if (result != 0) {
prot_printf(conn,"NO \"Error deleting script\"\r\n");
return TIMSIEVE_FAIL;
}
snprintf(path, 1023, "%s.bc", string_DATAPTR(name));
result = unlink(path);
if (result != 0) {
prot_printf(conn,"NO \"Error deleting bytecode\"\r\n");
return TIMSIEVE_FAIL;
}
sync_log_sieve(sieved_userid);
prot_printf(conn,"OK\r\n");
return TIMSIEVE_OK;
}
int listscripts(struct protstream *conn)
{
DIR *dp;
struct dirent *dir;
int length;
dp=opendir(".");
if (dp==NULL)
{
prot_printf(conn,"NO \"Error opening directory\"\r\n");
return TIMSIEVE_FAIL;
}
while ((dir=readdir(dp)) != NULL)
{
length=strlen(dir->d_name);
if (length >= strlen(".script"))
{
if (strcmp(dir->d_name + (length - 7), ".script")==0)
{
char *namewo=(char *) xmalloc(length-6);
memcpy(namewo, dir->d_name, length-7);
namewo[length-7]='\0';
if (isactive(namewo)==TRUE)
prot_printf(conn,"\"%s\" ACTIVE\r\n", namewo);
else
prot_printf(conn,"\"%s\"\r\n", namewo);
free(namewo);
}
}
}
prot_printf(conn,"OK\r\n");
return TIMSIEVE_OK;
}
static int exists(char *str)
{
char filename[1024];
struct stat filestats;
int result;
snprintf(filename, 1023, "%s.script", str);
result = stat(filename,&filestats);
if (result != 0) {
return FALSE;
}
return TRUE;
}
int setactive(struct protstream *conn, mystring_t *name)
{
int result;
char filename[1024];
if (!strlen(string_DATAPTR(name))) {
if (deleteactive(conn) != TIMSIEVE_OK)
return TIMSIEVE_FAIL;
prot_printf(conn,"OK\r\n");
return TIMSIEVE_OK;
}
result = scriptname_valid(name);
if (result!=TIMSIEVE_OK)
{
prot_printf(conn,"NO \"Invalid script name\"\r\n");
return result;
}
if (exists(string_DATAPTR(name))==FALSE)
{
prot_printf(conn,"NO \"Script does not exist\"\r\n");
return TIMSIEVE_NOEXIST;
}
if (isactive(string_DATAPTR(name))==TRUE) {
prot_printf(conn,"OK\r\n");
return TIMSIEVE_OK;
}
snprintf(filename, sizeof(filename), "%s.bc", string_DATAPTR(name));
result = symlink(filename, "defaultbc.NEW");
if (result) {
syslog(LOG_ERR, "symlink(%s, defaultbc.NEW): %m", filename);
prot_printf(conn, "NO \"Can't make link\"\r\n");
return TIMSIEVE_FAIL;
}
result = rename("defaultbc.NEW", "defaultbc");
if (result) {
unlink("defaultbc.NEW");
syslog(LOG_ERR, "rename(defaultbc.NEW, defaultbc): %m");
prot_printf(conn,"NO \"Error renaming\"\r\n");
return TIMSIEVE_FAIL;
}
sync_log_sieve(sieved_userid);
prot_printf(conn,"OK\r\n");
return TIMSIEVE_OK;
}
int cmd_havespace(struct protstream *conn, mystring_t *sieve_name, unsigned long num)
{
int result;
int maxscripts;
unsigned long maxscriptsize;
result = scriptname_valid(sieve_name);
if (result!=TIMSIEVE_OK)
{
prot_printf(conn,"NO \"Invalid script name\"\r\n");
return result;
}
maxscriptsize = config_getint(IMAPOPT_SIEVE_MAXSCRIPTSIZE);
maxscriptsize *= 1024;
if (num > maxscriptsize)
{
prot_printf(conn,
"NO (\"QUOTA\") \"Script size is too large. "
"Max script size is %ld bytes\"\r\n",
maxscriptsize);
return TIMSIEVE_FAIL;
}
maxscripts = config_getint(IMAPOPT_SIEVE_MAXSCRIPTS);
if (countscripts(string_DATAPTR(sieve_name))+1 > maxscripts)
{
prot_printf(conn,
"NO (\"QUOTA\") \"You are only allowed %d scripts on this server\"\r\n",
maxscripts);
return TIMSIEVE_FAIL;
}
prot_printf(conn,"OK\r\n");
return TIMSIEVE_OK;
}