static int sieve_file_storage_create_tmp (struct sieve_file_storage *fstorage, const char *scriptname, const char **fpath_r) { struct sieve_storage *storage = &fstorage->storage; struct stat st; unsigned int prefix_len; const char *tmp_fname = NULL; string_t *path; int fd; path = t_str_new(256); str_append(path, fstorage->path); str_append(path, "/tmp/"); prefix_len = str_len(path); for (;;) { tmp_fname = sieve_generate_tmp_filename(scriptname); str_truncate(path, prefix_len); str_append(path, tmp_fname); /* stat() first to see if it exists. pretty much the only possibility of that happening is if time had moved backwards, but even then it's highly unlikely. */ if (stat(str_c(path), &st) == 0) { /* try another file name */ } else if (errno != ENOENT) { sieve_storage_set_critical(storage, "save: " "stat(%s) failed: %m", str_c(path)); return -1; } else { /* doesn't exist */ mode_t old_mask = umask(0777 & ~(fstorage->file_create_mode)); fd = open(str_c(path), O_WRONLY | O_CREAT | O_TRUNC | O_EXCL, 0777); umask(old_mask); if (fd != -1 || errno != EEXIST) break; /* race condition between stat() and open(). highly unlikely. */ } } *fpath_r = str_c(path); if (fd == -1) { if (ENOQUOTA(errno)) { sieve_storage_set_error(storage, SIEVE_ERROR_NO_QUOTA, "Not enough disk quota"); } else { sieve_storage_set_critical(storage, "save: " "open(%s) failed: %m", str_c(path)); } } return fd; }
struct sieve_storage_save_context * sieve_file_storage_save_init(struct sieve_storage *storage, const char *scriptname, struct istream *input) { struct sieve_file_storage *fstorage = (struct sieve_file_storage *)storage; struct sieve_file_save_context *fsctx; pool_t pool; const char *path; int fd; if ( sieve_file_storage_pre_modify(storage) < 0 ) return NULL; if ( scriptname != NULL ) { /* Prevent overwriting the active script link when it resides in the * sieve storage directory. */ i_assert( fstorage->link_path != NULL ); if ( *(fstorage->link_path) == '\0' ) { const char *svext; size_t namelen; svext = strrchr(fstorage->active_fname, '.'); namelen = svext - fstorage->active_fname; if ( svext != NULL && strncmp(svext+1, "sieve", 5) == 0 && strlen(scriptname) == namelen && strncmp(scriptname, fstorage->active_fname, namelen) == 0 ) { sieve_storage_set_error(storage, SIEVE_ERROR_BAD_PARAMS, "Script name `%s' is reserved for internal use.", scriptname); return NULL; } } } T_BEGIN { fd = sieve_file_storage_create_tmp(fstorage, scriptname, &path); if (fd == -1) { fsctx = NULL; } else { pool = pool_alloconly_create("sieve_file_save_context", 1024); fsctx = p_new(pool, struct sieve_file_save_context, 1); fsctx->context.scriptname = p_strdup(pool, scriptname); fsctx->context.input = input; fsctx->context.pool = pool; fsctx->fd = fd; fsctx->output = o_stream_create_fd(fsctx->fd, 0); fsctx->tmp_path = p_strdup(pool, path); } } T_END; return &fsctx->context; }
int sieve_file_storage_save_finish (struct sieve_storage_save_context *sctx) { struct sieve_file_save_context *fsctx = (struct sieve_file_save_context *)sctx; struct sieve_storage *storage = sctx->storage; int output_errno; if ( sctx->failed && fsctx->fd == -1 ) { /* tmp file creation failed */ return -1; } T_BEGIN { output_errno = fsctx->output->stream_errno; o_stream_destroy(&fsctx->output); if ( fsync(fsctx->fd) < 0 ) { sieve_storage_set_critical(storage, "save: " "fsync(%s) failed: %m", fsctx->tmp_path); sctx->failed = TRUE; } if ( close(fsctx->fd) < 0 ) { sieve_storage_set_critical(storage, "save: " "close(%s) failed: %m", fsctx->tmp_path); sctx->failed = TRUE; } fsctx->fd = -1; if ( sctx->failed ) { /* delete the tmp file */ if (unlink(fsctx->tmp_path) < 0 && errno != ENOENT) { sieve_storage_sys_warning(storage, "save: " "unlink(%s) failed: %m", fsctx->tmp_path); } fsctx->tmp_path = NULL; errno = output_errno; if ( ENOQUOTA(errno) ) { sieve_storage_set_error(storage, SIEVE_ERROR_NO_QUOTA, "Not enough disk quota"); } else if ( errno != 0 ) { sieve_storage_set_critical(storage, "save: " "write(%s) failed: %m", fsctx->tmp_path); } } } T_END; return ( sctx->failed ? -1 : 0 ); }
bool cmd_getscript(struct client_command_context *cmd) { struct client *client = cmd->client; struct cmd_getscript_context *ctx; const char *scriptname; enum sieve_error error; /* <scriptname> */ if ( !client_read_string_args(cmd, TRUE, 1, &scriptname) ) return FALSE; ctx = p_new(cmd->pool, struct cmd_getscript_context, 1); ctx->cmd = cmd; ctx->client = client; ctx->storage = client->storage; ctx->failed = FALSE; ctx->script = sieve_storage_open_script (client->storage, scriptname, NULL); if (ctx->script == NULL) { ctx->failed = TRUE; return cmd_getscript_finish(ctx); } if ( sieve_script_get_stream (ctx->script, &ctx->script_stream, &error) < 0 ) { if ( error == SIEVE_ERROR_NOT_FOUND ) sieve_storage_set_error(client->storage, error, "Script does not exist."); ctx->failed = TRUE; return cmd_getscript_finish(ctx); } if ( sieve_script_get_size(ctx->script, &ctx->script_size) <= 0 ) { sieve_storage_set_critical(ctx->storage, "failed to obtain script size for script `%s' from %s", sieve_script_name(ctx->script), sieve_script_location(ctx->script)); ctx->failed = TRUE; return cmd_getscript_finish(ctx); } i_assert(ctx->script_stream->v_offset == 0); client_send_line (client, t_strdup_printf("{%"PRIuUOFF_T"}", ctx->script_size)); client->command_pending = TRUE; cmd->func = cmd_getscript_continue; cmd->context = ctx; return cmd_getscript_continue(cmd); }
bool cmd_getscript(struct client_command_context *cmd) { struct client *client = cmd->client; struct cmd_getscript_context *ctx; const char *scriptname; enum sieve_error error; /* <scriptname> */ if ( !client_read_string_args(cmd, 1, TRUE, &scriptname) ) return FALSE; ctx = p_new(cmd->pool, struct cmd_getscript_context, 1); ctx->cmd = cmd; ctx->client = client; ctx->storage = client->storage; ctx->failed = FALSE; ctx->script = sieve_storage_script_init(client->storage, scriptname); if (ctx->script == NULL) { ctx->failed = TRUE; return cmd_getscript_finish(ctx); } ctx->script_stream = sieve_script_open(ctx->script, &error); if ( ctx->script_stream == NULL ) { ctx->failed = TRUE; if ( error == SIEVE_ERROR_NOT_FOUND ) sieve_storage_set_error(client->storage, error, "Script does not exist."); return cmd_getscript_finish(ctx); } ctx->script_size = sieve_script_get_size(ctx->script); ctx->script_offset = 0; client_send_line (client, t_strdup_printf("{%"PRIuUOFF_T"}", ctx->script_size)); client->command_pending = TRUE; cmd->func = cmd_getscript_continue; cmd->context = ctx; return cmd_getscript_continue(cmd); }
static int sieve_file_storage_script_move (struct sieve_file_save_context *fsctx, const char *dst) { struct sieve_storage_save_context *sctx = &fsctx->context; struct sieve_storage *storage = sctx->storage; int result = 0; T_BEGIN { /* Using rename() to ensure existing files are replaced * without conflicts with other processes using the same * file. The kernel wont fully delete the original until * all processes have closed the file. */ if (rename(fsctx->tmp_path, dst) == 0) result = 0; else { result = -1; if ( ENOQUOTA(errno) ) { sieve_storage_set_error(storage, SIEVE_ERROR_NO_QUOTA, "Not enough disk quota"); } else if ( errno == EACCES ) { sieve_storage_set_critical(storage, "save: " "Failed to save Sieve script: " "%s", eacces_error_get("rename", dst)); } else { sieve_storage_set_critical(storage, "save: " "rename(%s, %s) failed: %m", fsctx->tmp_path, dst); } } /* Always destroy temp file */ if (unlink(fsctx->tmp_path) < 0 && errno != ENOENT) { sieve_storage_sys_warning(storage, "save: " "unlink(%s) failed: %m", fsctx->tmp_path); } } T_END; return result; }
static int sieve_file_script_sequence_read_dir (struct sieve_file_script_sequence *fseq, const char *path) { struct sieve_storage *storage = fseq->seq.storage; DIR *dirp; int ret = 0; /* Open the directory */ if ( (dirp = opendir(path)) == NULL ) { switch ( errno ) { case ENOENT: sieve_storage_set_error(storage, SIEVE_ERROR_NOT_FOUND, "Script sequence location not found"); break; case EACCES: sieve_storage_set_error(storage, SIEVE_ERROR_NO_PERMISSION, "Script sequence location not accessible"); sieve_storage_sys_error(storage, "Failed to open sieve sequence: " "%s", eacces_error_get("stat", path)); break; default: sieve_storage_set_critical(storage, "Failed to open sieve sequence: " "opendir(%s) failed: %m", path); break; } return -1; } /* Read and sort script files */ for (;;) { const char *const *files; unsigned int count, i; const char *file; struct dirent *dp; struct stat st; errno = 0; if ( (dp=readdir(dirp)) == NULL ) break; if ( !sieve_script_file_has_extension(dp->d_name) ) continue; file = NULL; T_BEGIN { if ( path[strlen(path)-1] == '/' ) file = t_strconcat(path, dp->d_name, NULL); else file = t_strconcat(path, "/", dp->d_name, NULL); if ( stat(file, &st) == 0 && S_ISREG(st.st_mode) ) file = p_strdup(fseq->pool, dp->d_name); else file = NULL; } T_END; if (file == NULL) continue; /* Insert into sorted array */ files = array_get(&fseq->script_files, &count); for ( i = 0; i < count; i++ ) { if ( strcmp(file, files[i]) < 0 ) break; } if ( i == count ) array_append(&fseq->script_files, &file, 1); else array_insert(&fseq->script_files, i, &file, 1); } if ( errno != 0 ) { sieve_storage_set_critical(storage, "Failed to read sequence directory: " "readdir(%s) failed: %m", path); ret = -1; } /* Close the directory */ if ( dirp != NULL && closedir(dirp) < 0 ) { sieve_storage_sys_error(storage, "Failed to close sequence directory: " "closedir(%s) failed: %m", path); } return ret; }
struct sieve_script_sequence *sieve_file_storage_get_script_sequence (struct sieve_storage *storage, enum sieve_error *error_r) { struct sieve_file_storage *fstorage = (struct sieve_file_storage *)storage; struct sieve_file_script_sequence *fseq = NULL; const char *name = storage->script_name; const char *file; pool_t pool; struct stat st; /* Specified path can either be a regular file or a directory */ if ( stat(fstorage->path, &st) != 0 ) { switch ( errno ) { case ENOENT: sieve_storage_set_error(storage, SIEVE_ERROR_NOT_FOUND, "Script sequence location not found"); break; case EACCES: sieve_storage_set_error(storage, SIEVE_ERROR_NO_PERMISSION, "Script sequence location not accessible"); sieve_storage_sys_error(storage, "Failed to open sieve sequence: " "%s", eacces_error_get("stat", fstorage->path)); break; default: sieve_storage_set_critical(storage, "Failed to open sieve sequence: " "stat(%s) failed: %m", fstorage->path); break; } *error_r = storage->error_code; return NULL; } /* Create sequence object */ pool = pool_alloconly_create("sieve_file_script_sequence", 1024); fseq = p_new(pool, struct sieve_file_script_sequence, 1); fseq->pool = pool; sieve_script_sequence_init(&fseq->seq, storage); if ( S_ISDIR(st.st_mode) ) { i_array_init(&fseq->script_files, 16); /* Path is directory */ if (name == 0 || *name == '\0') { /* Read all '.sieve' files in directory */ if (sieve_file_script_sequence_read_dir (fseq, fstorage->path) < 0) { *error_r = storage->error_code; sieve_file_script_sequence_destroy(&fseq->seq); return NULL; } } else { /* Read specific script file */ file = sieve_script_file_from_name(name); file = p_strdup(pool, file); array_append(&fseq->script_files, &file, 1); } } else { /* Path is a file (apparently; we'll see about that once it is opened) */ fseq->storage_is_file = TRUE; } return &fseq->seq; }
static int sieve_file_storage_save_to(struct sieve_file_storage *fstorage, string_t *temp_path, struct istream *input, const char *target) { struct sieve_storage *storage = &fstorage->storage; struct ostream *output; int fd; // FIXME: move this to base class // FIXME: use io_stream_temp fd = safe_mkstemp_hostpid (temp_path, fstorage->file_create_mode, (uid_t)-1, (gid_t)-1); if ( fd < 0 ) { if ( errno == EACCES ) { sieve_storage_set_critical(storage, "Failed to create temporary file: %s", eacces_error_get_creating("open", str_c(temp_path))); } else { sieve_storage_set_critical(storage, "Failed to create temporary file: open(%s) failed: %m", str_c(temp_path)); } return -1; } output = o_stream_create_fd(fd, 0); switch ( o_stream_send_istream(output, input) ) { case OSTREAM_SEND_ISTREAM_RESULT_FINISHED: break; case OSTREAM_SEND_ISTREAM_RESULT_WAIT_INPUT: case OSTREAM_SEND_ISTREAM_RESULT_WAIT_OUTPUT: i_unreached(); case OSTREAM_SEND_ISTREAM_RESULT_ERROR_INPUT: sieve_storage_set_critical(storage, "read(%s) failed: %s", i_stream_get_name(input), i_stream_get_error(input)); o_stream_destroy(&output); i_unlink(str_c(temp_path)); return -1; case OSTREAM_SEND_ISTREAM_RESULT_ERROR_OUTPUT: sieve_storage_set_critical(storage, "write(%s) failed: %s", str_c(temp_path), o_stream_get_error(output)); o_stream_destroy(&output); i_unlink(str_c(temp_path)); return -1; } o_stream_destroy(&output); if ( rename(str_c(temp_path), target) < 0 ) { if ( ENOQUOTA(errno) ) { sieve_storage_set_error(storage, SIEVE_ERROR_NO_QUOTA, "Not enough disk quota"); } else if ( errno == EACCES ) { sieve_storage_set_critical(storage, "%s", eacces_error_get("rename", target)); } else { sieve_storage_set_critical(storage, "rename(%s, %s) failed: %m", str_c(temp_path), target); } i_unlink(str_c(temp_path)); } return 0; }