/* actions.c -- executes the commands for timsieved * Tim Martin * $Id: actions.c,v 1.5 2005/03/05 00:37:39 dasenbro Exp $ */ /* * Copyright (c) 1998-2003 Carnegie Mellon University. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * * 3. The name "Carnegie Mellon University" must not be used to * endorse or promote products derived from this software without * prior written permission. For permission or any other legal * details, please contact * Office of Technology Transfer * Carnegie Mellon University * 5000 Forbes Avenue * Pittsburgh, PA 15213-3890 * (412) 268-4387, fax: (412) 268-7395 * tech-transfer@andrew.cmu.edu * * 4. Redistributions of any form whatsoever must retain the following * acknowledgment: * "This product includes software developed by Computing Services * at Carnegie Mellon University (http://www.cmu.edu/computing/)." * * CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO * THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY * AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE * FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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" /* after a user has authentication, our current directory is their Sieve directory! */ char *sieve_dir = 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 { /* can't use home directories with timsieved */ syslog(LOG_ERR, "can't use home directories"); return TIMSIEVE_FAIL; } return TIMSIEVE_OK; } int actions_setuser(const char *userid) { char hash, *domain; char *foo=sieve_dir; int result; sieve_dir=(char *) xmalloc(1024); if (config_virtdomains && (domain = strchr(userid, '@'))) { char d = (char) dir_hash_c(domain+1); *domain = '\0'; /* split user@domain */ hash = (char) dir_hash_c(userid); snprintf(sieve_dir, 1023, "%s%s%c/%s/%c/%s", foo, FNAME_DOMAINDIR, d, domain+1, hash, userid); *domain = '@'; /* reassemble user@domain */ } else { hash = (char) dir_hash_c(userid); snprintf(sieve_dir, 1023, "%s/%c/%s", foo, hash,userid); } 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; } /* * * Everything but '/' and '\0' are valid. * */ int scriptname_valid(mystring_t *name) { int lup; char *ptr; /* must be at least one character long */ if (name->len < 1) return TIMSIEVE_FAIL; ptr=string_DATAPTR(name); for (lup=0;luplen;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; unsigned mechcount; /* implementation */ prot_printf(conn, "\"IMPLEMENTATION\" \"Cyrus timsieved %s\"\r\n", CYRUS_VERSION); /* SASL */ if ( config_getswitch( IMAPOPT_APPLE_AUTH ) == 0 ) { if (!authenticated && sasl_listmech(saslconn, NULL, "\"SASL\" \"", " ", "\"\r\n", &sasllist, NULL, &mechcount) == SASL_OK && mechcount > 0) { prot_printf(conn,"%s",sasllist); } } 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"); } strcpy( str, "\"\r\n" ); prot_printf( conn,"%s", str ); } /* Sieve capabilities */ prot_printf(conn,"\"SIEVE\" \"%s\"\r\n",sieve_listextensions()); 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; /* returned by stat */ int size; /* size of the file */ 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; } /* counts the number of scripts user has that are DIFFERENT from name. used for enforcing quotas */ 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)) { /* this is a sieve script */ if (strcmp(myname, dir->d_name) != 0) { /* and it's different from me */ number++; } } } return number; } /* save name as a sieve script */ 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 { /* see if this would put the user over quota */ 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; } /* let's make sure this is a valid script (no parse errors) */ 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; /* Now, generate the bytecode */ 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; } /* Now, open the new file */ 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; } /* Now, emit the bytecode */ 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); /* Now, rename! */ 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"); return TIMSIEVE_OK; } /* delete the active script */ static int deleteactive(struct protstream *conn) { if (unlink("defaultbc") != 0) { prot_printf(conn,"NO \"Unable to unlink active script\"\r\n"); return TIMSIEVE_FAIL; } return TIMSIEVE_OK; } /* is this the active script? */ 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; } } /* delete a sieve script */ 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; } prot_printf(conn,"OK\r\n"); return TIMSIEVE_OK; } /* list the scripts user has available */ int listscripts(struct protstream *conn) { DIR *dp; struct dirent *dir; int length; /* open the directory */ dp=opendir("."); if (dp==NULL) { prot_printf(conn,"NO \"Error opening directory\"\r\n"); return TIMSIEVE_FAIL; } while ((dir=readdir(dp)) != NULL) /* while there are files here */ { length=strlen(dir->d_name); if (length >= strlen(".script")) /* if ends in .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; } /* does the script 'str' exist return TRUE | FALSE */ static int exists(char *str) { char filename[1024]; struct stat filestats; /* returned by stat */ int result; snprintf(filename, 1023, "%s.script", str); result = stat(filename,&filestats); if (result != 0) { return FALSE; } return TRUE; } /* set the sieve script 'name' to be the active script */ int setactive(struct protstream *conn, mystring_t *name) { int result; char filename[1024]; /* if string name is empty, disable active script */ 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 script already is the active one just say ok */ if (isactive(string_DATAPTR(name))==TRUE) { prot_printf(conn,"OK\r\n"); return TIMSIEVE_OK; } /* get the name of the active sieve script */ snprintf(filename, sizeof(filename), "%s.bc", string_DATAPTR(name)); /* ok we want to do this atomically so let's - make .NEW as a hard link - rename it to */ 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; } 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; } /* see if the size of the script is too big */ 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; } /* see if this would put the user over quota */ 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; }