EXPORTED int sieve_rebuild(const char *script_fname, const char *bc_fname, int force, char **out_parse_errors) { char new_bc_fname[MAX_MAILBOX_PATH + 1] = {0}; char *freeme = NULL; FILE *script_file = NULL; char *parse_errors = NULL; sieve_script_t *script = NULL; bytecode_info_t *bc = NULL; int script_fd = -1, bc_fd = -1; int r; size_t len; if (!script_fname && !bc_fname) return SIEVE_FAIL; /* XXX assert? */ if (!script_fname) script_fname = freeme = sieve_getscriptfname(bc_fname); if (!bc_fname) bc_fname = freeme = sieve_getbcfname(script_fname); if (!script_fname || !bc_fname) return SIEVE_FAIL; /* open and lock the script file */ script_fd = open(script_fname, O_RDWR); if (script_fd == -1) { syslog(LOG_ERR, "IOERROR: unable to open %s for reading: %m", script_fname); r = IMAP_IOERROR; goto done; } r = lock_setlock(script_fd, /* exclusive */ 1, /* nonblocking */ 0, script_fname); if (r) { syslog(LOG_ERR, "IOERROR: unable to obtain lock on %s: %m", script_fname); r = IMAP_IOERROR; goto done; } /* exit early if bc is up to date */ if (!force) { struct stat script_stat, bc_stat; r = fstat(script_fd, &script_stat); if (r) { syslog(LOG_DEBUG, "%s: stat %s: %m", __func__, script_fname); r = SIEVE_FAIL; goto done; } r = stat(bc_fname, &bc_stat); if (r && errno != ENOENT) { syslog(LOG_DEBUG, "%s: stat %s: %m", __func__, bc_fname); r = SIEVE_FAIL; goto done; } if (!r && bc_stat.st_mtime >= script_stat.st_mtime) { sieve_execute_t *exe = NULL; r = sieve_script_load(bc_fname, &exe); if (!r) { int version; bc_header_parse((bytecode_input_t *) exe->bc_cur->data, &version, NULL); if (version == BYTECODE_VERSION) { syslog(LOG_DEBUG, "%s: %s is up to date\n", __func__, bc_fname); r = SIEVE_OK; sieve_script_unload(&exe); goto done; } } sieve_script_unload(&exe); } } len = strlcpy(new_bc_fname, bc_fname, sizeof(new_bc_fname)); if (len >= sizeof(new_bc_fname)) { syslog(LOG_DEBUG, "%s: filename too long: %s", __func__, bc_fname); r = SIEVE_FAIL; goto done; } len = strlcat(new_bc_fname, ".NEW", sizeof(new_bc_fname)); if (len >= sizeof(new_bc_fname)) { syslog(LOG_DEBUG, "%s: filename too long: %s", __func__, bc_fname); r = SIEVE_FAIL; goto done; } /* make sure no stray hardlink is lying around */ unlink(new_bc_fname); bc_fd = open(new_bc_fname, O_CREAT|O_TRUNC|O_WRONLY, S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH); if (bc_fd < 0) { syslog(LOG_ERR, "IOERROR: unable to open %s for writing: %m", new_bc_fname); r = IMAP_IOERROR; goto done; } /* if an error occurs after this point, we need to unlink new_bc_fname */ script_file = fdopen(script_fd, "r"); if (!script_file) { syslog(LOG_ERR, "IOERROR: unable to fdopen %s for reading: %m", script_fname); r = IMAP_IOERROR; goto done; } r = sieve_script_parse_only(script_file, &parse_errors, &script); if (r != SIEVE_OK) { syslog(LOG_DEBUG, "%s: %s parse failed: %s", __func__, script_fname, parse_errors); goto done; } if (sieve_generate_bytecode(&bc, script) == -1) { syslog(LOG_DEBUG, "%s: %s bytecode generation failed: %s", __func__, script_fname, "unknown error"); r = SIEVE_FAIL; goto done; } if (sieve_emit_bytecode(bc_fd, bc) == -1) { syslog(LOG_DEBUG, "%s: unable to emit bytecode to %s: %s", __func__, bc_fname, "unknown error"); r = SIEVE_FAIL; goto done; } if (fsync(bc_fd) < 0) { r = errno; syslog(LOG_ERR, "IOERROR: fsync %s: %m", new_bc_fname); goto done; } if (rename(new_bc_fname, bc_fname) < 0) { r = errno; syslog(LOG_ERR, "IOERROR: rename %s -> %s: %m", new_bc_fname, bc_fname); goto done; } syslog(LOG_DEBUG, "%s: %s rebuilt from %s", __func__, bc_fname, script_fname); done: if (r && new_bc_fname[0] != '\0') unlink(new_bc_fname); if (bc_fd >= 0) close(bc_fd); lock_unlock(script_fd, script_fname); if (script_file) { fclose(script_file); /* also closes underlying fd */ } else if (script_fd >= 0) { close(script_fd); } if (parse_errors) { if (out_parse_errors) *out_parse_errors = parse_errors; else free(parse_errors); } if (bc) sieve_free_bytecode(&bc); if (script) sieve_script_free(&script); if (freeme) free(freeme); return r; }
static int autocreate_sieve(const char *userid, const char *source_script) { /* XXX - this is really ugly, but too much work to tidy up right now -- Bron */ sieve_script_t *s = NULL; bytecode_info_t *bc = NULL; char *err = NULL; FILE *in_stream, *out_fp; int out_fd, in_fd, r, k; int do_compile = 0; const char *compiled_source_script = NULL; const char *sievename = get_script_name(source_script); const char *sieve_script_dir = NULL; char sieve_script_name[MAX_FILENAME]; char sieve_bcscript_name[MAX_FILENAME]; char sieve_default[MAX_FILENAME]; char sieve_tmpname[MAX_FILENAME]; char sieve_bctmpname[MAX_FILENAME]; char sieve_bclink_name[MAX_FILENAME]; char buf[4096]; mode_t oldmask; struct stat statbuf; /* We don't support using the homedirectory, like timsieved */ if (config_getswitch(IMAPOPT_SIEVEUSEHOMEDIR)) { syslog(LOG_WARNING,"autocreate_sieve: autocreate_sieve does not work with sieveusehomedir option in imapd.conf"); return 1; } /* Check if sievedir is defined in imapd.conf */ if(!config_getstring(IMAPOPT_SIEVEDIR)) { syslog(LOG_WARNING, "autocreate_sieve: sievedir option is not defined. Check imapd.conf"); return 1; } /* Check if autocreate_sieve_compiledscript is defined in imapd.conf */ if(!(compiled_source_script = config_getstring(IMAPOPT_AUTOCREATE_SIEVE_SCRIPT_COMPILED))) { syslog(LOG_WARNING, "autocreate_sieve: autocreate_sieve_compiledscript option is not defined. Compiling it"); do_compile = 1; } if (!(sieve_script_dir = user_sieve_path(userid))) { syslog(LOG_WARNING, "autocreate_sieve: unable to determine sieve directory for user %s", userid); return 1; } if(snprintf(sieve_tmpname, MAX_FILENAME, "%s/%s.script.NEW",sieve_script_dir, sievename) >= MAX_FILENAME) { syslog(LOG_WARNING, "autocreate_sieve: Invalid sieve path %s, %s, %s", sieve_script_dir, sievename, userid); return 1; } if(snprintf(sieve_bctmpname, MAX_FILENAME, "%s/%s.bc.NEW",sieve_script_dir, sievename) >= MAX_FILENAME) { syslog(LOG_WARNING, "autocreate_sieve: Invalid sieve path %s, %s, %s", sieve_script_dir, sievename, userid); return 1; } if(snprintf(sieve_script_name, MAX_FILENAME, "%s/%s.script",sieve_script_dir, sievename) >= MAX_FILENAME) { syslog(LOG_WARNING, "autocreate_sieve: Invalid sieve path %s, %s, %s", sieve_script_dir, sievename, userid); return 1; } if(snprintf(sieve_bcscript_name, MAX_FILENAME, "%s/%s.bc",sieve_script_dir, sievename) >= MAX_FILENAME) { syslog(LOG_WARNING, "autocreate_sieve: Invalid sieve path %s, %s, %s", sieve_script_dir, sievename, userid); return 1; } if(snprintf(sieve_default, MAX_FILENAME, "%s/%s",sieve_script_dir,"defaultbc") >= MAX_FILENAME) { syslog(LOG_WARNING, "autocreate_sieve: Invalid sieve path %s, %s, %s", sieve_script_dir, sievename, userid); return 1; } /* XXX no directory? umm */ if(snprintf(sieve_bclink_name, MAX_FILENAME, "%s.bc", sievename) >= MAX_FILENAME) { syslog(LOG_WARNING, "autocreate_sieve: Invalid sieve path %s, %s, %s", sieve_script_dir, sievename, userid); return 1; } /* Check if a default sieve filter alrady exists */ if(!stat(sieve_default,&statbuf)) { syslog(LOG_WARNING,"autocreate_sieve: Default sieve script already exists"); return 1; } /* Open the source script. if there is a problem with that exit */ in_stream = fopen(source_script, "r"); if(!in_stream) { syslog(LOG_WARNING,"autocreate_sieve: Unable to open sieve script %s. Check permissions",source_script); return 1; } /* * At this point we start the modifications of the filesystem */ /* Create the directory where the sieve scripts will reside */ r = cyrus_mkdir(sieve_bctmpname, 0755); if(r == -1) { /* If this fails we just leave */ fclose(in_stream); return 1; } /* * We open the file that will be used as the bc file. If this file exists, overwrite it * since something bad has happened. We open the file here so that this error checking is * done before we try to open the rest of the files to start copying etc. */ out_fd = open(sieve_bctmpname, O_CREAT|O_TRUNC|O_WRONLY, S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH); if(out_fd < 0) { if(errno == EEXIST) { syslog(LOG_WARNING,"autocreate_sieve: File %s already exists. Probaly left over. Ignoring",sieve_bctmpname); } else if (errno == EACCES) { syslog(LOG_WARNING,"autocreate_sieve: No access to create file %s. Check permissions",sieve_bctmpname); fclose(in_stream); return 1; } else { syslog(LOG_WARNING,"autocreate_sieve: Unable to create %s: %m",sieve_bctmpname); fclose(in_stream); return 1; } } if(!do_compile && compiled_source_script && (in_fd = open(compiled_source_script, O_RDONLY)) != -1) { while((r = read(in_fd, buf, sizeof(buf))) > 0) { if((k=write(out_fd, buf,r)) < 0) { syslog(LOG_WARNING, "autocreate_sieve: Error writing to file %s: %m", sieve_bctmpname); close(out_fd); close(in_fd); fclose(in_stream); unlink(sieve_bctmpname); return 1; } } if(r == 0) { /* EOF */ xclose(out_fd); xclose(in_fd); } else if (r < 0) { syslog(LOG_WARNING, "autocreate_sieve: Error reading compiled script file %s: %m. Will try to compile it", compiled_source_script); xclose(in_fd); do_compile = 1; if(lseek(out_fd, 0, SEEK_SET)) { syslog(LOG_WARNING, "autocreate_sieve: Major IO problem (lseek: %m). Aborting"); xclose(out_fd); return 1; } } xclose(in_fd); } else { if(compiled_source_script) syslog(LOG_WARNING,"autocreate_sieve: Problem opening compiled script file: %s. Compiling it", compiled_source_script); do_compile = 1; } /* Because we failed to open a precompiled bc sieve script, we compile one */ if(do_compile) { if(is_script_parsable(in_stream,&err, &s) == TIMSIEVE_FAIL) { if(err && *err) { syslog(LOG_WARNING,"autocreate_sieve: Error while parsing script %s.",err); free(err); } else syslog(LOG_WARNING,"autocreate_sieve: Error while parsing script"); unlink(sieve_bctmpname); fclose(in_stream); close(out_fd); return 1; } /* generate the bytecode */ if(sieve_generate_bytecode(&bc, s) == TIMSIEVE_FAIL) { syslog(LOG_WARNING,"autocreate_sieve: problem compiling sieve script"); /* removing the copied script and cleaning up memory */ unlink(sieve_bctmpname); sieve_script_free(&s); fclose(in_stream); close(out_fd); return 1; } if(sieve_emit_bytecode(out_fd, bc) == TIMSIEVE_FAIL) { syslog(LOG_WARNING,"autocreate_sieve: problem emiting sieve script"); /* removing the copied script and cleaning up memory */ unlink(sieve_bctmpname); sieve_free_bytecode(&bc); sieve_script_free(&s); fclose(in_stream); close(out_fd); return 1; } /* clean up the memory */ sieve_free_bytecode(&bc); sieve_script_free(&s); } xclose(out_fd); rewind(in_stream); /* Copy the initial script */ oldmask = umask(077); if((out_fp = fopen(sieve_tmpname, "w")) == NULL) { syslog(LOG_WARNING,"autocreate_sieve: Unable to open destination sieve script %s: %m", sieve_tmpname); unlink(sieve_bctmpname); umask(oldmask); fclose(in_stream); return 1; } umask(oldmask); while((r = fread(buf,sizeof(char), sizeof(buf), in_stream)) > 0) { if( fwrite(buf,sizeof(char), r, out_fp) != (unsigned)r) { syslog(LOG_WARNING,"autocreate_sieve: Problem writing to sieve script file %s: %m",sieve_tmpname); fclose(out_fp); unlink(sieve_tmpname); unlink(sieve_bctmpname); fclose(in_stream); return 1; } } if(feof(in_stream)) { fclose(out_fp); fclose(in_stream); } else { /* ferror */ fclose(out_fp); unlink(sieve_tmpname); unlink(sieve_bctmpname); fclose(in_stream); return 1; } /* Renaming the necessary stuff */ if(rename(sieve_tmpname, sieve_script_name)) { unlink(sieve_tmpname); unlink(sieve_bctmpname); return 1; } if(rename(sieve_bctmpname, sieve_bcscript_name)) { unlink(sieve_bctmpname); unlink(sieve_bcscript_name); return 1; } /* end now with the symlink */ if(symlink(sieve_bclink_name, sieve_default)) { if(errno != EEXIST) { syslog(LOG_WARNING, "autocreate_sieve: problem making the default link (symlink: %m)."); /* Lets delete the files */ unlink(sieve_script_name); unlink(sieve_bcscript_name); } } /* * If everything has succeeded AND we have compiled the script AND we have requested * to generate the global script so that it is not compiled each time then we create it. */ if(do_compile && config_getswitch(IMAPOPT_AUTOCREATE_SIEVE_SCRIPT_COMPILE)) { if(!compiled_source_script) { syslog(LOG_WARNING, "autocreate_sieve: To save a compiled sieve script, autocreate_sieve_compiledscript must have been defined in imapd.conf"); return 0; } if(snprintf(sieve_tmpname, MAX_FILENAME, "%s.NEW", compiled_source_script) >= MAX_FILENAME) return 0; /* * Copy everything from the newly created bc sieve sieve script. */ if((in_fd = open(sieve_bcscript_name, O_RDONLY))<0) { return 0; } if((out_fd = open(sieve_tmpname, O_CREAT|O_EXCL|O_WRONLY, S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH)) < 0) { if(errno == EEXIST) { /* Someone is already doing this so just bail out. */ syslog(LOG_WARNING, "autocreate_sieve: %s already exists. Some other instance processing it, or it is left over", sieve_tmpname); close(in_fd); return 0; } else if (errno == EACCES) { syslog(LOG_WARNING,"autocreate_sieve: No access to create file %s. Check permissions",sieve_tmpname); close(in_fd); return 0; } else { syslog(LOG_WARNING,"autocreate_sieve: Unable to create %s: %m",sieve_tmpname); close(in_fd); return 0; } } while((r = read(in_fd, buf, sizeof(buf))) > 0) { if((k = write(out_fd,buf,r)) < 0) { syslog(LOG_WARNING, "autocreate_sieve: Error writing to file: %s: %m", sieve_tmpname); close(out_fd); close(in_fd); unlink(sieve_tmpname); return 0; } } if(r == 0 ) { /*EOF */ xclose(out_fd); xclose(in_fd); } else if (r < 0) { syslog(LOG_WARNING, "autocreate_sieve: Error reading file: %s: %m", sieve_bcscript_name); xclose(out_fd); xclose(in_fd); unlink(sieve_tmpname); return 0; } /* Rename the temporary created sieve script to its final name. */ if(rename(sieve_tmpname, compiled_source_script)) { if(errno != EEXIST) { unlink(sieve_tmpname); unlink(compiled_source_script); } return 0; } syslog(LOG_NOTICE, "autocreate_sieve: Compiled sieve script was successfully saved in %s", compiled_source_script); } return 0; }
/* 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 {" SIZE_T_FMT "}\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"); sync_log_sieve(sieved_userid); return TIMSIEVE_OK; }
/* save name as a sieve script */ int putscript(struct protstream *conn, const struct buf *name, const struct buf *data, int verify_only) { FILE *stream; const char *dataptr; char *errstr; unsigned int i; int last_was_r = 0; 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(name->s)+1 > maxscripts) { prot_printf(conn, "NO (QUOTA/MAXSCRIPTS) \"You are only allowed %d scripts on this server\"\r\n", maxscripts); return TIMSIEVE_FAIL; } snprintf(path, 1023, "%s.script.NEW", name->s); 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 = data->s; /* copy data to file - replacing any lone \r or \n with the * \r\n pair so notify messages are SMTP compatible */ for (i = 0; i < data->len; i++) { if (last_was_r) { if (dataptr[i] != '\n') putc('\n', stream); } else { if (dataptr[i] == '\n') putc('\r', stream); } putc(dataptr[i], stream); last_was_r = (dataptr[i] == '\r'); } if (last_was_r) putc('\n', stream); /* 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 "); prot_printstring(conn, errstr); prot_printf(conn, "\r\n"); } else { prot_printf(conn, "NO \"parse failed\"\r\n"); } free(errstr); 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", name->s); 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", name->s); snprintf(bc_p2, 1023, "%s.bc", name->s); rename(path, p2); rename(bc_path, bc_p2); } prot_printf(conn, "OK\r\n"); sync_log_sieve(sieved_userid); return TIMSIEVE_OK; }