int master_service_settings_read(struct master_service *service, const struct master_service_settings_input *input, struct master_service_settings_output *output_r, const char **error_r) { ARRAY(const struct setting_parser_info *) all_roots; const struct setting_parser_info *tmp_root; struct setting_parser_context *parser; struct istream *istream; const char *path = NULL, *error; void **sets; unsigned int i; int ret, fd = -1; time_t now, timeout; bool use_environment, retry; memset(output_r, 0, sizeof(*output_r)); if (getenv("DOVECONF_ENV") == NULL && (service->flags & MASTER_SERVICE_FLAG_NO_CONFIG_SETTINGS) == 0) { retry = service->config_fd != -1; for (;;) { fd = master_service_open_config(service, input, &path, error_r); if (fd == -1) { if (errno == EACCES) output_r->permission_denied = TRUE; return -1; } if (config_send_request(service, input, fd, path, error_r) == 0) break; i_close_fd(&fd); if (!retry) { config_exec_fallback(service, input); return -1; } /* config process died, retry connecting */ retry = FALSE; } } if (service->set_pool != NULL) { if (service->set_parser != NULL) settings_parser_deinit(&service->set_parser); p_clear(service->set_pool); } else { service->set_pool = pool_alloconly_create("master service settings", 16384); } p_array_init(&all_roots, service->set_pool, 8); tmp_root = &master_service_setting_parser_info; array_append(&all_roots, &tmp_root, 1); if (service->want_ssl_settings) { tmp_root = &master_service_ssl_setting_parser_info; array_append(&all_roots, &tmp_root, 1); } if (input->roots != NULL) { for (i = 0; input->roots[i] != NULL; i++) array_append(&all_roots, &input->roots[i], 1); } parser = settings_parser_init_list(service->set_pool, array_idx(&all_roots, 0), array_count(&all_roots), SETTINGS_PARSER_FLAG_IGNORE_UNKNOWN_KEYS); if (fd != -1) { istream = i_stream_create_fd(fd, (size_t)-1, FALSE); now = time(NULL); timeout = now + CONFIG_READ_TIMEOUT_SECS; do { alarm(timeout - now); ret = config_read_reply_header(istream, path, service->set_pool, input, output_r, error_r); if (ret == 0) { ret = settings_parse_stream_read(parser, istream); if (ret < 0) *error_r = settings_parser_get_error(parser); } alarm(0); if (ret <= 0) break; /* most likely timed out, but just in case some other signal was delivered early check if we need to continue */ now = time(NULL); } while (now < timeout); i_stream_unref(&istream); if (ret != 0) { if (ret > 0) { *error_r = t_strdup_printf( "Timeout reading config from %s", path); } i_close_fd(&fd); config_exec_fallback(service, input); return -1; } if ((service->flags & MASTER_SERVICE_FLAG_KEEP_CONFIG_OPEN) != 0 && service->config_fd == -1 && input->config_path == NULL) service->config_fd = fd; else i_close_fd(&fd); use_environment = FALSE; } else { use_environment = TRUE; } if (use_environment || service->keep_environment) { if (settings_parse_environ(parser) < 0) { *error_r = settings_parser_get_error(parser); return -1; } } if (array_is_created(&service->config_overrides)) { if (master_service_apply_config_overrides(service, parser, error_r) < 0) return -1; } if (!settings_parser_check(parser, service->set_pool, &error)) { *error_r = t_strdup_printf("Invalid settings: %s", error); return -1; } sets = settings_parser_get_list(parser); service->set = sets[0]; service->set_parser = parser; if (service->set->version_ignore && (service->flags & MASTER_SERVICE_FLAG_STANDALONE) != 0) { /* running standalone. we want to ignore plugin versions. */ service->version_string = NULL; } if (service->set->shutdown_clients) master_service_set_die_with_master(master_service, TRUE); /* if we change any settings afterwards, they're in expanded form. especially all settings from userdb are already expanded. */ settings_parse_set_expanded(service->set_parser, TRUE); return 0; }
static int astream_decode_base64(struct attachment_istream *astream) { struct attachment_istream_part *part = &astream->part; buffer_t *extra_buf = NULL; struct istream *input, *base64_input; struct ostream *output; const unsigned char *data; size_t size; ssize_t ret; buffer_t *buf; int outfd; bool failed = FALSE; if (part->base64_bytes < astream->set.min_size || part->temp_output->offset > part->base64_bytes + BASE64_ATTACHMENT_MAX_EXTRA_BYTES) { /* only a small part of the MIME part is base64-encoded. */ return -1; } if (part->base64_line_blocks == 0) { /* only one line of base64 */ part->base64_line_blocks = part->cur_base64_blocks; i_assert(part->base64_line_blocks > 0); } /* decode base64 data and write it to another temp file */ outfd = astream->set.open_temp_fd(astream->context); if (outfd == -1) return -1; buf = buffer_create_dynamic(default_pool, 1024); input = i_stream_create_fd(part->temp_fd, IO_BLOCK_SIZE, FALSE); base64_input = i_stream_create_limit(input, part->base64_bytes); output = o_stream_create_fd_file(outfd, 0, FALSE); o_stream_cork(output); hash_format_reset(astream->set.hash_format); while ((ret = i_stream_read(base64_input)) > 0) { data = i_stream_get_data(base64_input, &size); buffer_set_used_size(buf, 0); if (base64_decode(data, size, &size, buf) < 0) { i_error("istream-attachment: BUG: " "Attachment base64 data unexpectedly broke"); failed = TRUE; break; } i_stream_skip(base64_input, size); o_stream_nsend(output, buf->data, buf->used); hash_format_loop(astream->set.hash_format, buf->data, buf->used); } if (ret != -1) { i_assert(failed); } else if (base64_input->stream_errno != 0) { i_error("istream-attachment: read(%s) failed: %m", i_stream_get_name(base64_input)); failed = TRUE; } if (o_stream_nfinish(output) < 0) { i_error("istream-attachment: write(%s) failed: %m", o_stream_get_name(output)); failed = TRUE; } buffer_free(&buf); i_stream_unref(&base64_input); o_stream_unref(&output); if (input->v_offset != part->temp_output->offset && !failed) { /* write the rest of the data to the message stream */ extra_buf = buffer_create_dynamic(default_pool, 1024); while ((ret = i_stream_read_data(input, &data, &size, 0)) > 0) { buffer_append(extra_buf, data, size); i_stream_skip(input, size); } i_assert(ret == -1); if (input->stream_errno != 0) { i_error("istream-attachment: read(%s) failed: %m", i_stream_get_name(base64_input)); failed = TRUE; } } i_stream_unref(&input); if (failed) { i_close_fd(&outfd); return -1; } /* successfully wrote it. switch to using it. */ o_stream_destroy(&part->temp_output); i_close_fd(&part->temp_fd); part->temp_fd = outfd; if (extra_buf != NULL) { stream_add_data(astream, extra_buf->data, extra_buf->used); buffer_free(&extra_buf); } return 0; }
void i_stream_destroy(struct istream **stream) { i_stream_close_full(*stream, FALSE); i_stream_unref(stream); }
static int cmd_execute_operation_execute (const struct sieve_runtime_env *renv, sieve_size_t *address) { const struct sieve_extension *this_ext = renv->oprtn->ext; struct sieve_side_effects_list *slist = NULL; int opt_code = 0; unsigned int is_test = 0; struct sieve_stringlist *args_list = NULL; string_t *pname = NULL, *input = NULL; struct sieve_variable_storage *var_storage = NULL; unsigned int var_index; bool have_input = FALSE; const char *program_name = NULL; const char *const *args = NULL; enum sieve_error error = SIEVE_ERROR_NONE; buffer_t *outbuf = NULL; struct sieve_extprogram *sprog = NULL; int ret; /* * Read operands */ /* The is_test flag */ if ( !sieve_binary_read_byte(renv->sblock, address, &is_test) ) { sieve_runtime_trace_error(renv, "invalid is_test flag"); return SIEVE_EXEC_BIN_CORRUPT; } /* Optional operands */ for (;;) { int opt; if ( (opt=sieve_action_opr_optional_read (renv, address, &opt_code, &ret, &slist)) < 0 ) return ret; if ( opt == 0 ) break; switch ( opt_code ) { case OPT_INPUT: ret = sieve_opr_string_read_ex (renv, address, "input", TRUE, &input, NULL); have_input = TRUE; break; case OPT_OUTPUT: ret = sieve_variable_operand_read (renv, address, "output", &var_storage, &var_index); break; default: sieve_runtime_trace_error(renv, "unknown optional operand"); return SIEVE_EXEC_BIN_CORRUPT; } if ( ret <= 0 ) return ret; } /* Fixed operands */ if ( (ret=sieve_extprogram_command_read_operands (renv, address, &pname, &args_list)) <= 0 ) return ret; program_name = str_c(pname); if ( args_list != NULL && sieve_stringlist_read_all(args_list, pool_datastack_create(), &args) < 0 ) { sieve_runtime_trace_error(renv, "failed to read args operand"); return args_list->exec_status; } /* * Perform operation */ /* Trace */ sieve_runtime_trace(renv, SIEVE_TRLVL_ACTIONS, "execute action"); sieve_runtime_trace_descend(renv); sieve_runtime_trace(renv, SIEVE_TRLVL_ACTIONS, "execute program `%s'", str_sanitize(program_name, 128)); sprog = sieve_extprogram_create (this_ext, renv->scriptenv, renv->msgdata, "execute", program_name, args, &error); if ( sprog != NULL ) { if ( var_storage != NULL ) { // FIXME: limit output size struct ostream *outdata; outbuf = buffer_create_dynamic(pool_datastack_create(), 1024); outdata = o_stream_create_buffer(outbuf); sieve_extprogram_set_output(sprog, outdata); o_stream_unref(&outdata); } if ( input == NULL && have_input ) { struct mail *mail = sieve_message_get_mail(renv->msgctx); if ( sieve_extprogram_set_input_mail(sprog, mail) < 0 ) { sieve_extprogram_destroy(&sprog); return sieve_runtime_mail_error(renv, mail, "execute action: failed to read input message"); } ret = 1; } else if ( input != NULL ) { struct istream *indata = i_stream_create_from_data(str_data(input), str_len(input)); sieve_extprogram_set_input(sprog, indata); i_stream_unref(&indata); ret = 1; } if ( ret >= 0 ) ret = sieve_extprogram_run(sprog); sieve_extprogram_destroy(&sprog); } else { ret = -1; } if ( ret > 0 ) { sieve_runtime_trace(renv, SIEVE_TRLVL_ACTIONS, "executed program successfully"); if ( var_storage != NULL ) { string_t *var; if ( sieve_variable_get_modifiable(var_storage, var_index, &var) ) { str_truncate(var, 0); str_append_str(var, outbuf); sieve_runtime_trace(renv, SIEVE_TRLVL_ACTIONS, "assigned output variable"); } // FIXME: handle failure } } else if ( ret < 0 ) { if ( error == SIEVE_ERROR_NOT_FOUND ) { sieve_runtime_error(renv, NULL, "execute action: program `%s' not found", str_sanitize(program_name, 80)); } else { sieve_extprogram_exec_error(renv->ehandler, sieve_runtime_get_full_command_location(renv), "execute action: failed to execute to program `%s'", str_sanitize(program_name, 80)); } } else { sieve_runtime_trace(renv, SIEVE_TRLVL_ACTIONS, "execute action: program indicated false result"); } if ( outbuf != NULL ) buffer_free(&outbuf); if ( is_test ) sieve_interpreter_set_test_result(renv->interp, ( ret > 0 )); return SIEVE_EXEC_OK; }
static int maildir_save_finish_real(struct mail_save_context *_ctx) { struct maildir_save_context *ctx = (struct maildir_save_context *)_ctx; struct mail_storage *storage = &ctx->mbox->storage->storage; const char *path; off_t real_size; uoff_t size; int output_errno; ctx->last_save_finished = TRUE; if (ctx->failed && ctx->fd == -1) { /* tmp file creation failed */ return -1; } path = t_strconcat(ctx->tmpdir, "/", ctx->file_last->tmp_name, NULL); if (!ctx->failed && o_stream_nfinish(_ctx->data.output) < 0) { if (!mail_storage_set_error_from_errno(storage)) { mail_storage_set_critical(storage, "write(%s) failed: %m", path); } ctx->failed = TRUE; } if (_ctx->data.save_date != (time_t)-1) { /* we can't change ctime, but we can add the date to cache */ struct index_mail *mail = (struct index_mail *)_ctx->dest_mail; uint32_t t = _ctx->data.save_date; index_mail_cache_add(mail, MAIL_CACHE_SAVE_DATE, &t, sizeof(t)); } if (maildir_save_finish_received_date(ctx, path) < 0) ctx->failed = TRUE; if (ctx->cur_dest_mail != NULL) { index_mail_cache_parse_deinit(ctx->cur_dest_mail, ctx->ctx.data.received_date, !ctx->failed); } i_stream_unref(&ctx->input); /* remember the size in case we want to add it to filename */ ctx->file_last->size = _ctx->data.output->offset; if (ctx->cur_dest_mail == NULL || mail_get_virtual_size(ctx->cur_dest_mail, &ctx->file_last->vsize) < 0) ctx->file_last->vsize = (uoff_t)-1; output_errno = _ctx->data.output->last_failed_errno; o_stream_destroy(&_ctx->data.output); if (storage->set->parsed_fsync_mode != FSYNC_MODE_NEVER && !ctx->failed) { if (fsync(ctx->fd) < 0) { if (!mail_storage_set_error_from_errno(storage)) { mail_storage_set_critical(storage, "fsync(%s) failed: %m", path); } ctx->failed = TRUE; } } real_size = lseek(ctx->fd, 0, SEEK_END); if (real_size == (off_t)-1) { mail_storage_set_critical(storage, "lseek(%s) failed: %m", path); } else if (real_size != (off_t)ctx->file_last->size && (!maildir_filename_get_size(ctx->file_last->dest_basename, MAILDIR_EXTRA_FILE_SIZE, &size) || size != ctx->file_last->size)) { /* e.g. zlib plugin was used. the "physical size" must be in the maildir filename, since stat() will return wrong size */ ctx->file_last->preserve_filename = FALSE; /* preserve the GUID if needed */ if (ctx->file_last->guid == NULL) ctx->file_last->guid = ctx->file_last->dest_basename; /* reset the base name as well, just in case there's a ,W=vsize */ ctx->file_last->dest_basename = ctx->file_last->tmp_name; } if (close(ctx->fd) < 0) { if (!mail_storage_set_error_from_errno(storage)) { mail_storage_set_critical(storage, "close(%s) failed: %m", path); } ctx->failed = TRUE; } ctx->fd = -1; if (ctx->failed) { /* delete the tmp file */ i_unlink_if_exists(path); errno = output_errno; if (ENOQUOTA(errno)) { mail_storage_set_error(storage, MAIL_ERROR_NOQUOTA, MAIL_ERRSTR_NO_QUOTA); } else if (errno != 0) { mail_storage_set_critical(storage, "write(%s) failed: %m", path); } maildir_save_remove_last_filename(ctx); return -1; } ctx->file_last = NULL; return 0; }
static void test_istream_crlf_input(const char *input) { string_t *output; const unsigned char *data; size_t size = 0; ssize_t ret1, ret2; unsigned int i, j, pos, input_len = strlen(input); struct istream *istream, *crlf_istream; output = t_str_new(256); for (j = 0; j < 4; j++) { istream = i_stream_create_from_data(input, input_len); str_truncate(output, 0); if (j%2 == 0) { /* drop CRs */ crlf_istream = i_stream_create_lf(istream); for (i = 0; i < input_len; i++) { if (input[i] == '\r' && (i == input_len-1 || input[i+1] == '\n')) ; else str_append_c(output, input[i]); } } else { /* add missing CRs */ crlf_istream = i_stream_create_crlf(istream); for (i = 0; i < input_len; i++) { if (input[i] == '\n' && (i == 0 || input[i-1] != '\r')) str_append_c(output, '\r'); str_append_c(output, input[i]); } } pos = 0; for (i = 1; i <= input_len; i++) { if (j >= 2) { i_stream_unref(&istream); i_stream_unref(&crlf_istream); istream = i_stream_create_from_data(input, input_len); crlf_istream = j%2 == 0 ? i_stream_create_lf(istream) : i_stream_create_crlf(istream); pos = 0; } istream->real_stream->pos = i; ret1 = i_stream_read(crlf_istream); if (crlf_istream->real_stream->buffer_size != 0) { /* this is pretty evil */ crlf_istream->real_stream->buffer_size = I_MAX(crlf_istream->real_stream->pos, i); } ret2 = i_stream_read(crlf_istream); data = i_stream_get_data(crlf_istream, &size); if (ret1 > 0 || ret2 > 0) { ret1 = I_MAX(ret1, 0) + I_MAX(ret2, 0); test_assert(pos + (unsigned int)ret1 == size); pos += ret1; } if (size > 0) test_assert_idx(memcmp(data, str_data(output), size) == 0, j*10000+i); } test_assert_idx(size == str_len(output), j*10000+i); i_stream_unref(&crlf_istream); i_stream_unref(&istream); } }
static void test_iostream_temp_istream(void) { struct istream *input, *input2, *temp_input; struct ostream *output; int fd; test_begin("iostream_temp istream"); fd = open(".temp.istream", O_RDWR | O_CREAT | O_TRUNC, 0600); if (fd == -1) i_fatal("create(.temp.istream) failed: %m"); test_assert(write(fd, "foobar", 6) == 6); test_assert(lseek(fd, 0, SEEK_SET) == 0); input = i_stream_create_fd_autoclose(&fd, 1024); /* a working fd-dup */ output = iostream_temp_create_sized(".nonexistent/", IOSTREAM_TEMP_FLAG_TRY_FD_DUP, "test", 1); test_assert(o_stream_send_istream(output, input) == OSTREAM_SEND_ISTREAM_RESULT_FINISHED); test_assert(output->offset == 6); temp_input = iostream_temp_finish(&output, 128); test_assert(i_stream_read(temp_input) == 6); i_stream_destroy(&temp_input); /* non-working fd-dup: write data before sending istream */ i_stream_seek(input, 0); output = iostream_temp_create_sized(".intentional-nonexistent-error/", IOSTREAM_TEMP_FLAG_TRY_FD_DUP, "test", 4); test_assert(o_stream_send(output, "1234", 4) == 4); test_assert(output->offset == 4); test_expect_error_string("safe_mkstemp"); test_assert(o_stream_send_istream(output, input) == OSTREAM_SEND_ISTREAM_RESULT_FINISHED); test_assert(output->offset == 10); test_expect_no_more_errors(); o_stream_destroy(&output); /* non-working fd-dup: write data after sending istream */ i_stream_seek(input, 0); output = iostream_temp_create_sized(".intentional-nonexistent-error/", IOSTREAM_TEMP_FLAG_TRY_FD_DUP, "test", 4); test_assert(o_stream_send_istream(output, input) == OSTREAM_SEND_ISTREAM_RESULT_FINISHED); test_assert(output->offset == 6); test_expect_error_string("safe_mkstemp"); test_assert(o_stream_send(output, "1", 1) == 1); test_assert(output->offset == 7); test_expect_no_more_errors(); o_stream_destroy(&output); /* non-working fd-dup: send two istreams */ i_stream_seek(input, 0); input2 = i_stream_create_limit(input, (uoff_t)-1); output = iostream_temp_create_sized(".intentional-nonexistent-error/", IOSTREAM_TEMP_FLAG_TRY_FD_DUP, "test", 4); test_assert(o_stream_send_istream(output, input) == OSTREAM_SEND_ISTREAM_RESULT_FINISHED); test_assert(output->offset == 6); test_expect_error_string("safe_mkstemp"); test_assert(o_stream_send_istream(output, input2) == OSTREAM_SEND_ISTREAM_RESULT_FINISHED); test_assert(output->offset == 12); test_expect_no_more_errors(); o_stream_destroy(&output); i_stream_unref(&input2); i_stream_destroy(&input); i_unlink(".temp.istream"); test_end(); }
static void test_ostream_file_send_istream_file(void) { struct istream *input, *input2; struct ostream *output; char buf[10]; int fd; test_begin("ostream file send istream file"); /* temp file istream */ fd = open(".temp.istream", O_RDWR | O_CREAT | O_TRUNC, 0600); if (fd == -1) i_fatal("creat(.temp.istream) failed: %m"); test_assert(write(fd, "1234567890", 10) == 10); test_assert(lseek(fd, 0, SEEK_SET) == 0); input = i_stream_create_fd_autoclose(&fd, 1024); /* temp file ostream */ fd = open(".temp.ostream", O_RDWR | O_CREAT | O_TRUNC, 0600); if (fd == -1) i_fatal("creat(.temp.ostream) failed: %m"); output = o_stream_create_fd(fd, 0); /* test that writing works between two files */ i_stream_seek(input, 3); input2 = i_stream_create_limit(input, 4); test_assert(o_stream_send_istream(output, input2) == OSTREAM_SEND_ISTREAM_RESULT_FINISHED); test_assert(output->offset == 4); test_assert(pread(fd, buf, sizeof(buf), 0) == 4 && memcmp(buf, "4567", 4) == 0); i_stream_unref(&input2); /* test that writing works within the same file */ i_stream_destroy(&input); input = i_stream_create_fd(fd, 1024); /* forwards: 4567 -> 4677 */ o_stream_seek(output, 1); i_stream_seek(input, 2); input2 = i_stream_create_limit(input, 2); test_assert(o_stream_send_istream(output, input2) == OSTREAM_SEND_ISTREAM_RESULT_FINISHED); test_assert(output->offset == 3); test_assert(pread(fd, buf, sizeof(buf), 0) == 4 && memcmp(buf, "4677", 4) == 0); i_stream_destroy(&input2); i_stream_destroy(&input); /* backwards: 1234 -> 11234 */ memcpy(buf, "1234", 4); test_assert(pwrite(fd, buf, 4, 0) == 4); input = i_stream_create_fd(fd, 1024); o_stream_seek(output, 1); test_assert(o_stream_send_istream(output, input) == OSTREAM_SEND_ISTREAM_RESULT_FINISHED); test_assert(output->offset == 5); test_assert(pread(fd, buf, sizeof(buf), 0) == 5 && memcmp(buf, "11234", 5) == 0); i_stream_destroy(&input); o_stream_destroy(&output); i_close_fd(&fd); i_unlink(".temp.istream"); i_unlink(".temp.ostream"); test_end(); }
static void test_istream_dot_one(const struct dot_test *test, bool send_last_lf, bool test_bufsize) { struct istream *test_input, *input; const unsigned char *data; size_t size; unsigned int i, outsize, input_len, output_len; string_t *str; uoff_t offset; int ret; test_input = test_istream_create(test->input); input = i_stream_create_dot(test_input, send_last_lf); input_len = strlen(test->input); output_len = strlen(test->output); if (!send_last_lf && (test->input[input_len-1] == '\n' || strstr(test->input, "\n.\n") != NULL || strstr(test->input, "\n.\r\n") != NULL)) { if (test->output[output_len-1] == '\n') { output_len--; if (output_len > 0 && test->output[output_len-1] == '\r') output_len--; } } str = t_str_new(256); if (!test_bufsize) { outsize = 1; i = 0; i_stream_set_max_buffer_size(input, outsize); test_istream_set_size(test_input, 1); while ((ret = i_stream_read(input)) != -1) { switch (ret) { case -2: i_stream_set_max_buffer_size(input, ++outsize); offset = test_input->v_offset; /* seek one byte backwards so stream gets reset */ i_stream_seek(test_input, offset - 1); /* go back to original position */ test_istream_set_size(test_input, offset); i_stream_skip(test_input, 1); /* and finally allow reading one more byte */ test_istream_set_size(test_input, offset + 1); break; case 0: test_istream_set_size(test_input, ++i); break; default: test_assert(ret > 0); data = i_stream_get_data(input, &size); str_append_n(str, data, size); i_stream_skip(input, size); } } test_istream_set_size(test_input, input_len); i_stream_read(test_input); } else { test_istream_set_size(test_input, input_len); size = 0; for (i = 1; i < output_len; i++) { i_stream_set_max_buffer_size(input, i); test_assert(i_stream_read(input) == 1); test_assert(i_stream_read(input) == -2); data = i_stream_get_data(input, &size); test_assert(memcmp(data, test->output, size) == 0); } i_stream_set_max_buffer_size(input, i+2); if (size < output_len) test_assert(i_stream_read(input) == 1); test_assert(i_stream_read(input) == -1); data = i_stream_get_data(input, &size); str_append_n(str, data, size); } test_assert(str_len(str) == output_len); test_assert(memcmp(str_data(str), test->output, output_len) == 0); data = i_stream_get_data(test_input, &size); test_assert(size == strlen(test->parent_input)); test_assert(memcmp(data, test->parent_input, size) == 0); i_stream_unref(&test_input); i_stream_unref(&input); }
void http_client_request_redirect(struct http_client_request *req, unsigned int status, const char *location) { struct http_url *url; const char *error, *target, *origin_url; /* parse URL */ if (http_url_parse(location, NULL, 0, pool_datastack_create(), &url, &error) < 0) { http_client_request_error(req, HTTP_CLIENT_REQUEST_ERROR_INVALID_REDIRECT, t_strdup_printf("Invalid redirect location: %s", error)); return; } if (++req->redirects > req->client->set.max_redirects) { if (req->client->set.max_redirects > 0) { http_client_request_error(req, HTTP_CLIENT_REQUEST_ERROR_INVALID_REDIRECT, t_strdup_printf("Redirected more than %d times", req->client->set.max_redirects)); } else { http_client_request_error(req, HTTP_CLIENT_REQUEST_ERROR_INVALID_REDIRECT, "Redirect refused"); } return; } /* rewind payload stream */ if (req->payload_input != NULL && req->payload_size > 0 && status != 303) { if (req->payload_input->v_offset != req->payload_offset && !req->payload_input->seekable) { http_client_request_error(req, HTTP_CLIENT_REQUEST_ERROR_ABORTED, "Redirect failed: Cannot resend payload; stream is not seekable"); return; } else { i_stream_seek(req->payload_input, req->payload_offset); } } /* drop payload output stream from previous attempt */ if (req->payload_output != NULL) o_stream_unref(&req->payload_output); target = http_url_create_target(url); http_url_copy(req->pool, &req->origin_url, url); req->target = p_strdup(req->pool, target); if (req->host_url == &req->origin_url) { req->authority = p_strdup(req->pool, http_url_create_authority(req->host_url)); } req->host = NULL; req->queue = NULL; req->conn = NULL; origin_url = http_url_create(&req->origin_url); http_client_request_debug(req, "Redirecting to %s%s", origin_url, target); req->label = p_strdup_printf(req->pool, "[%s %s%s]", req->method, origin_url, req->target); /* https://tools.ietf.org/html/draft-ietf-httpbis-p2-semantics-21 Section-7.4.4 -> A 303 `See Other' redirect status response is handled a bit differently. Basically, the response content is located elsewhere, but the original (POST) request is handled already. */ if (status == 303 && strcasecmp(req->method, "HEAD") != 0 && strcasecmp(req->method, "GET") != 0) { // FIXME: should we provide the means to skip this step? The original // request was already handled at this point. req->method = p_strdup(req->pool, "GET"); /* drop payload */ if (req->payload_input != NULL) i_stream_unref(&req->payload_input); req->payload_size = 0; req->payload_offset = 0; } /* resubmit */ req->state = HTTP_REQUEST_STATE_NEW; http_client_request_do_submit(req); }
static int mbox_mail_seek(struct index_mail *mail) { struct mbox_transaction_context *t = (struct mbox_transaction_context *)mail->mail.mail.transaction; struct mail *_mail = &mail->mail.mail; struct mbox_mailbox *mbox = (struct mbox_mailbox *)_mail->box; enum mbox_sync_flags sync_flags = 0; int ret, try; bool deleted; if (_mail->expunged || mbox->syncing) return -1; if (_mail->lookup_abort != MAIL_LOOKUP_ABORT_NEVER) { mail_set_aborted(_mail); return -1; } if (mbox->mbox_stream != NULL && istream_raw_mbox_is_corrupted(mbox->mbox_stream)) { /* clear the corruption by forcing a full resync */ sync_flags |= MBOX_SYNC_UNDIRTY | MBOX_SYNC_FORCE_SYNC; } for (try = 0; try < 2; try++) { if ((sync_flags & MBOX_SYNC_FORCE_SYNC) != 0) { /* dirty offsets are broken. make sure we can sync. */ mbox_prepare_resync(_mail); } if (mbox->mbox_lock_type == F_UNLCK) { i_assert(t->read_lock_id == 0); sync_flags |= MBOX_SYNC_LOCK_READING; if (mbox_sync(mbox, sync_flags) < 0) return -1; t->read_lock_id = mbox_get_cur_lock_id(mbox); i_assert(t->read_lock_id != 0); /* refresh index file after mbox has been locked to make sure we get only up-to-date mbox offsets. */ if (mail_index_refresh(mbox->box.index) < 0) { mailbox_set_index_error(&mbox->box); return -1; } i_assert(mbox->mbox_lock_type != F_UNLCK); } else if (t->read_lock_id == 0) { /* file is already locked by another transaction, but we must keep it locked for the entire transaction, so increase the lock counter. */ if (mbox_lock(mbox, mbox->mbox_lock_type, &t->read_lock_id) < 0) i_unreached(); } if (mbox_file_open_stream(mbox) < 0) return -1; ret = mbox_file_seek(mbox, _mail->transaction->view, _mail->seq, &deleted); if (ret > 0) { /* success */ break; } if (ret < 0) { if (deleted) mail_set_expunged(_mail); return -1; } /* we'll need to re-sync it completely */ sync_flags |= MBOX_SYNC_UNDIRTY | MBOX_SYNC_FORCE_SYNC; } if (ret == 0) { mail_storage_set_critical(&mbox->storage->storage, "Losing sync for mail uid=%u in mbox file %s", _mail->uid, mailbox_get_path(&mbox->box)); } return 0; } static int mbox_mail_get_received_date(struct mail *_mail, time_t *date_r) { struct index_mail *mail = (struct index_mail *)_mail; struct index_mail_data *data = &mail->data; struct mbox_mailbox *mbox = (struct mbox_mailbox *)_mail->box; if (index_mail_get_received_date(_mail, date_r) == 0) return 0; if (mbox_mail_seek(mail) < 0) return -1; data->received_date = istream_raw_mbox_get_received_time(mbox->mbox_stream); if (data->received_date == (time_t)-1) { /* it's broken and conflicts with our "not found" return value. change it. */ data->received_date = 0; } *date_r = data->received_date; return 0; } static int mbox_mail_get_save_date(struct mail *_mail, time_t *date_r) { struct index_mail *mail = (struct index_mail *)_mail; struct index_mail_data *data = &mail->data; if (index_mail_get_save_date(_mail, date_r) == 0) return 0; /* no way to know this. save the current time into cache and use that from now on. this works only as long as the index files are permanent */ data->save_date = ioloop_time; *date_r = data->save_date; return 0; } static int mbox_mail_get_md5_header(struct index_mail *mail, const char **value_r) { struct mail *_mail = &mail->mail.mail; static uint8_t empty_md5[16] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; struct mbox_mailbox *mbox = (struct mbox_mailbox *)_mail->box; const void *ext_data; if (mail->data.guid != NULL) { *value_r = mail->data.guid; return 1; } mail_index_lookup_ext(_mail->transaction->view, _mail->seq, mbox->md5hdr_ext_idx, &ext_data, NULL); if (ext_data != NULL && memcmp(ext_data, empty_md5, 16) != 0) { mail->data.guid = p_strdup(mail->mail.data_pool, binary_to_hex(ext_data, 16)); *value_r = mail->data.guid; return 1; } else if (mail_index_is_expunged(_mail->transaction->view, _mail->seq)) { mail_set_expunged(_mail); return -1; } else { return 0; } } static int mbox_mail_get_special(struct mail *_mail, enum mail_fetch_field field, const char **value_r) { struct index_mail *mail = (struct index_mail *)_mail; struct mbox_mailbox *mbox = (struct mbox_mailbox *)_mail->box; uoff_t offset; bool move_offset; int ret; switch (field) { case MAIL_FETCH_FROM_ENVELOPE: if (mbox_mail_seek(mail) < 0) return -1; *value_r = istream_raw_mbox_get_sender(mbox->mbox_stream); return 0; case MAIL_FETCH_GUID: case MAIL_FETCH_HEADER_MD5: if ((ret = mbox_mail_get_md5_header(mail, value_r)) != 0) return ret < 0 ? -1 : 0; /* i guess in theory the empty_md5 is valid and can happen, but it's almost guaranteed that it means the MD5 sum is missing. recalculate it. */ if (mbox->mbox_lock_type == F_UNLCK || mbox->mbox_stream == NULL) { offset = 0; move_offset = FALSE; } else { offset = istream_raw_mbox_get_start_offset(mbox->mbox_stream); move_offset = TRUE; } mbox->mbox_save_md5 = TRUE; if (mbox_sync(mbox, MBOX_SYNC_FORCE_SYNC | MBOX_SYNC_READONLY) < 0) return -1; if (move_offset) { if (istream_raw_mbox_seek(mbox->mbox_stream, offset) < 0) { i_error("mbox %s sync lost during MD5 syncing", _mail->box->name); return -1; } } if ((ret = mbox_mail_get_md5_header(mail, value_r)) == 0) { i_error("mbox %s resyncing didn't save header MD5 values", _mail->box->name); return -1; } return ret < 0 ? -1 : 0; default: break; } return index_mail_get_special(_mail, field, value_r); } static int mbox_mail_get_next_offset(struct index_mail *mail, uoff_t *next_offset_r) { struct mbox_mailbox *mbox = (struct mbox_mailbox *)mail->mail.mail.box; struct mail_index_view *view; const struct mail_index_header *hdr; uint32_t seq; int trailer_size; int ret = 1; *next_offset_r = (uoff_t)-1; hdr = mail_index_get_header(mail->mail.mail.transaction->view); if (mail->mail.mail.seq > hdr->messages_count) { /* we're appending a new message */ return 0; } /* We can't really trust trans_view. The next message may already be expunged from it. Also hdr.messages_count may be incorrect there. So refresh the index to get the latest changes and get the next message's offset using a new view. */ i_assert(mbox->mbox_lock_type != F_UNLCK); if (mbox_sync_header_refresh(mbox) < 0) return -1; view = mail_index_view_open(mail->mail.mail.box->index); hdr = mail_index_get_header(view); if (!mail_index_lookup_seq(view, mail->mail.mail.uid, &seq)) i_panic("Message unexpectedly expunged from index"); if (seq < hdr->messages_count) { if (mbox_file_lookup_offset(mbox, view, seq + 1, next_offset_r) <= 0) ret = -1; } else if (mail->mail.mail.box->input != NULL) { /* opened the mailbox as input stream. we can't trust the sync_size, since it's wrong with compressed mailboxes */ ret = 0; } else { /* last message, use the synced mbox size */ trailer_size = mbox->storage->storage.set->mail_save_crlf ? 2 : 1; *next_offset_r = mbox->mbox_hdr.sync_size - trailer_size; } mail_index_view_close(&view); return ret; } static int mbox_mail_get_physical_size(struct mail *_mail, uoff_t *size_r) { struct index_mail *mail = (struct index_mail *)_mail; struct index_mail_data *data = &mail->data; struct mbox_mailbox *mbox = (struct mbox_mailbox *)_mail->box; struct istream *input; struct message_size hdr_size; uoff_t old_offset, body_offset, body_size, next_offset; if (index_mail_get_physical_size(_mail, size_r) == 0) return 0; /* we want to return the header size as seen by mail_get_stream(). */ old_offset = data->stream == NULL ? 0 : data->stream->v_offset; if (mail_get_stream(_mail, &hdr_size, NULL, &input) < 0) return -1; /* our header size varies, so don't do any caching */ if (istream_raw_mbox_get_body_offset(mbox->mbox_stream, &body_offset) < 0) { mail_storage_set_critical(_mail->box->storage, "mbox %s: Couldn't get body offset for uid=%u", mailbox_get_path(&mbox->box), mail->mail.mail.uid); return -1; } /* use the next message's offset to avoid reading through the entire message body to find out its size */ if (mbox_mail_get_next_offset(mail, &next_offset) > 0) body_size = next_offset - body_offset; else body_size = (uoff_t)-1; /* verify that the calculated body size is correct */ if (istream_raw_mbox_get_body_size(mbox->mbox_stream, body_size, &body_size) < 0) { mail_storage_set_critical(_mail->box->storage, "mbox %s: Couldn't get body size for uid=%u", mailbox_get_path(&mbox->box), mail->mail.mail.uid); return -1; } data->physical_size = hdr_size.physical_size + body_size; *size_r = data->physical_size; i_stream_seek(input, old_offset); return 0; } static int mbox_mail_init_stream(struct index_mail *mail) { struct mbox_mailbox *mbox = (struct mbox_mailbox *)mail->mail.mail.box; struct istream *raw_stream; uoff_t hdr_offset, next_offset; int ret; if (mbox_mail_seek(mail) < 0) return -1; ret = mbox_mail_get_next_offset(mail, &next_offset); if (ret < 0) { if (mbox_mail_seek(mail) < 0) return -1; ret = mbox_mail_get_next_offset(mail, &next_offset); if (ret < 0) { i_warning("mbox %s: Can't find next message offset " "for uid=%u", mailbox_get_path(&mbox->box), mail->mail.mail.uid); } } raw_stream = mbox->mbox_stream; if (istream_raw_mbox_get_header_offset(raw_stream, &hdr_offset) < 0) { mail_storage_set_critical(mbox->box.storage, "mbox %s: Couldn't get header offset for uid=%u", mailbox_get_path(&mbox->box), mail->mail.mail.uid); return -1; } i_stream_seek(raw_stream, hdr_offset); if (next_offset != (uoff_t)-1) istream_raw_mbox_set_next_offset(raw_stream, next_offset); raw_stream = i_stream_create_limit(raw_stream, (uoff_t)-1); mail->data.stream = i_stream_create_header_filter(raw_stream, HEADER_FILTER_EXCLUDE | HEADER_FILTER_NO_CR, mbox_hide_headers, mbox_hide_headers_count, *null_header_filter_callback, (void *)NULL); i_stream_unref(&raw_stream); return 0; } static int mbox_mail_get_stream(struct mail *_mail, bool get_body ATTR_UNUSED, struct message_size *hdr_size, struct message_size *body_size, struct istream **stream_r) { struct index_mail *mail = (struct index_mail *)_mail; if (mail->data.stream == NULL) { if (mbox_mail_init_stream(mail) < 0) return -1; } return index_mail_init_stream(mail, hdr_size, body_size, stream_r); } static void mbox_mail_set_seq(struct mail *_mail, uint32_t seq, bool saving) { struct index_mail *mail = (struct index_mail *)_mail; index_mail_set_seq(_mail, seq, saving); mail->data.dont_cache_fetch_fields |= MAIL_FETCH_PHYSICAL_SIZE; } static bool mbox_mail_set_uid(struct mail *_mail, uint32_t uid) { struct index_mail *mail = (struct index_mail *)_mail; bool ret; ret = index_mail_set_uid(_mail, uid); mail->data.dont_cache_fetch_fields |= MAIL_FETCH_PHYSICAL_SIZE; return ret; } struct mail_vfuncs mbox_mail_vfuncs = { index_mail_close, index_mail_free, mbox_mail_set_seq, mbox_mail_set_uid, index_mail_set_uid_cache_updates, index_mail_prefetch, index_mail_precache, index_mail_add_temp_wanted_fields, index_mail_get_flags, index_mail_get_keywords, index_mail_get_keyword_indexes, index_mail_get_modseq, index_mail_get_pvt_modseq, index_mail_get_parts, index_mail_get_date, mbox_mail_get_received_date, mbox_mail_get_save_date, index_mail_get_virtual_size, mbox_mail_get_physical_size, index_mail_get_first_header, index_mail_get_headers, index_mail_get_header_stream, mbox_mail_get_stream, index_mail_get_binary_stream, mbox_mail_get_special, index_mail_get_real_mail, index_mail_update_flags, index_mail_update_keywords, index_mail_update_modseq, index_mail_update_pvt_modseq, NULL, index_mail_expunge, index_mail_set_cache_corrupted, index_mail_opened };
static int http_client_request_continue_payload(struct http_client_request **_req, const unsigned char *data, size_t size) { struct ioloop *prev_ioloop = current_ioloop; struct http_client_request *req = *_req; struct http_client_connection *conn = req->conn; struct http_client *client = req->client; int ret; i_assert(req->state == HTTP_REQUEST_STATE_NEW || req->state == HTTP_REQUEST_STATE_PAYLOAD_OUT); i_assert(req->payload_input == NULL); if (conn != NULL) http_client_connection_ref(conn); http_client_request_ref(req); req->payload_wait = TRUE; if (data == NULL) { req->payload_input = NULL; if (req->state == HTTP_REQUEST_STATE_PAYLOAD_OUT) http_client_request_finish_payload_out(req); } else { req->payload_input = i_stream_create_from_data(data, size); i_stream_set_name(req->payload_input, "<HTTP request payload>"); } req->payload_size = 0; req->payload_chunked = TRUE; if (req->state == HTTP_REQUEST_STATE_NEW) http_client_request_submit(req); /* Wait for payload data to be written */ i_assert(client->ioloop == NULL); client->ioloop = io_loop_create(); http_client_switch_ioloop(client); while (req->state < HTTP_REQUEST_STATE_FINISHED) { http_client_request_debug(req, "Waiting for request to finish"); if (req->state == HTTP_REQUEST_STATE_PAYLOAD_OUT) o_stream_set_flush_pending(req->payload_output, TRUE); io_loop_run(client->ioloop); if (req->state == HTTP_REQUEST_STATE_PAYLOAD_OUT && req->payload_input->eof) { i_stream_unref(&req->payload_input); req->payload_input = NULL; break; } } io_loop_set_current(prev_ioloop); http_client_switch_ioloop(client); io_loop_set_current(client->ioloop); io_loop_destroy(&client->ioloop); if (req->state == HTTP_REQUEST_STATE_FINISHED) ret = 1; else ret = (req->state == HTTP_REQUEST_STATE_ABORTED ? -1 : 0); req->payload_wait = FALSE; http_client_request_unref(_req); if (conn != NULL) http_client_connection_unref(&conn); /* Return status */ return ret; }
static int cmd_setmetadata_entry(struct imap_setmetadata_context *ctx, const char *entry_name, const struct imap_arg *entry_value) { struct istream *inputs[2]; enum mail_attribute_type type; const char *key; struct mail_attribute_value value; string_t *path; int ret; switch (entry_value->type) { case IMAP_ARG_NIL: case IMAP_ARG_ATOM: case IMAP_ARG_STRING: /* we have the value already */ imap_metadata_entry2key(entry_name, ctx->key_prefix, &type, &key); if (ctx->failed) return 1; memset(&value, 0, sizeof(value)); value.value = imap_arg_as_nstring(entry_value); ret = value.value == NULL ? mailbox_attribute_unset(ctx->trans, type, key) : mailbox_attribute_set(ctx->trans, type, key, &value); if (ret < 0) { /* delay reporting the failure so we'll finish reading the command input */ ctx->storage_failure = TRUE; ctx->failed = TRUE; } return 1; case IMAP_ARG_LITERAL_SIZE: o_stream_nsend(ctx->cmd->client->output, "+ OK\r\n", 6); o_stream_nflush(ctx->cmd->client->output); o_stream_uncork(ctx->cmd->client->output); o_stream_cork(ctx->cmd->client->output); /* fall through */ case IMAP_ARG_LITERAL_SIZE_NONSYNC: i_free(ctx->entry_name); ctx->entry_name = i_strdup(entry_name); ctx->entry_value_len = imap_arg_as_literal_size(entry_value); inputs[0] = i_stream_create_limit(ctx->cmd->client->input, ctx->entry_value_len); inputs[1] = NULL; path = t_str_new(128); mail_user_set_get_temp_prefix(path, ctx->cmd->client->user->set); ctx->input = i_stream_create_seekable_path(inputs, METADATA_MAX_INMEM_SIZE, str_c(path)); i_stream_set_name(ctx->input, i_stream_get_name(inputs[0])); i_stream_unref(&inputs[0]); return cmd_setmetadata_entry_read_stream(ctx); case IMAP_ARG_LITERAL: case IMAP_ARG_LIST: case IMAP_ARG_EOL: break; } i_unreached(); }
static int dbox_file_fix_write_stream(struct dbox_file *file, uoff_t start_offset, const char *temp_path, struct ostream *output) { struct dbox_message_header msg_hdr; uoff_t offset, msg_size, hdr_offset, body_offset; bool pre, write_header, have_guid; struct message_size body; struct istream *body_input; uint8_t guid_128[MAIL_GUID_128_SIZE]; int ret; i_stream_seek(file->input, 0); if (start_offset > 0) { /* copy the valid data */ if (stream_copy(file, output, temp_path, start_offset) < 0) return -1; } else { /* the file header is broken. recreate it */ if (dbox_file_header_write(file, output) < 0) { dbox_file_set_syscall_error(file, "write()"); return -1; } } while ((ret = dbox_file_find_next_magic(file, &offset, &pre)) > 0) { msg_size = offset - file->input->v_offset; if (msg_size < 256 && pre) { /* probably some garbage or some broken headers. we most likely don't miss anything by skipping over this data. */ i_stream_skip(file->input, msg_size); hdr_offset = file->input->v_offset; ret = dbox_file_read_mail_header(file, &msg_size); if (ret <= 0) { if (ret < 0) return -1; dbox_file_skip_broken_header(file); body_offset = file->input->v_offset; msg_size = (uoff_t)-1; } else { i_stream_skip(file->input, file->msg_header_size); body_offset = file->input->v_offset; i_stream_skip(file->input, msg_size); } ret = dbox_file_find_next_magic(file, &offset, &pre); if (ret <= 0) break; if (!pre && msg_size == offset - body_offset) { /* msg header ok, copy it */ i_stream_seek(file->input, hdr_offset); if (stream_copy(file, output, temp_path, file->msg_header_size) < 0) return -1; write_header = FALSE; } else { /* msg header is broken. write our own. */ i_stream_seek(file->input, body_offset); if (msg_size != (uoff_t)-1) { /* previous magic find might have skipped too much. seek back and make sure */ ret = dbox_file_find_next_magic(file, &offset, &pre); if (ret <= 0) break; } write_header = TRUE; msg_size = offset - body_offset; } } else { /* treat this data as a separate message. */ write_header = TRUE; body_offset = file->input->v_offset; } /* write msg header */ if (write_header) { dbox_msg_header_fill(&msg_hdr, msg_size); (void)o_stream_send(output, &msg_hdr, sizeof(msg_hdr)); } /* write msg body */ i_assert(file->input->v_offset == body_offset); if (stream_copy(file, output, temp_path, msg_size) < 0) return -1; i_assert(file->input->v_offset == offset); /* get message body size */ i_stream_seek(file->input, body_offset); body_input = i_stream_create_limit(file->input, msg_size); ret = message_get_body_size(body_input, &body, NULL); i_stream_unref(&body_input); if (ret < 0) { errno = output->stream_errno; mail_storage_set_critical(&file->storage->storage, "read(%s) failed: %m", file->cur_path); return -1; } /* write msg metadata. */ i_assert(file->input->v_offset == offset); ret = dbox_file_metadata_skip_header(file); if (ret < 0) return -1; o_stream_send_str(output, DBOX_MAGIC_POST); if (ret == 0) have_guid = FALSE; else dbox_file_copy_metadata(file, output, &have_guid); if (!have_guid) { mail_generate_guid_128(guid_128); o_stream_send_str(output, t_strdup_printf("%c%s\n", DBOX_METADATA_GUID, binary_to_hex(guid_128, sizeof(guid_128)))); } o_stream_send_str(output, t_strdup_printf("%c%llx\n", DBOX_METADATA_VIRTUAL_SIZE, (unsigned long long)body.virtual_size)); o_stream_send_str(output, "\n"); if (output->stream_errno != 0) { errno = output->stream_errno; mail_storage_set_critical(&file->storage->storage, "write(%s) failed: %m", temp_path); return -1; } } return ret; }
static int cmd_append_handle_args(struct client_command_context *cmd, const struct imap_arg *args, bool *nonsync_r) { struct client *client = cmd->client; struct cmd_append_context *ctx = cmd->context; const struct imap_arg *flags_list; const struct imap_arg *cat_list = NULL; enum mail_flags flags; const char *const *keywords_list; struct mail_keywords *keywords; struct istream *input; const char *internal_date_str; time_t internal_date; int ret, timezone_offset; bool valid; /* [<flags>] */ if (!imap_arg_get_list(args, &flags_list)) flags_list = NULL; else args++; /* [<internal date>] */ if (args->type != IMAP_ARG_STRING) internal_date_str = NULL; else { internal_date_str = imap_arg_as_astring(args); args++; } /* <message literal> | CATENATE (..) */ valid = FALSE; *nonsync_r = FALSE; ctx->catenate = FALSE; if (imap_arg_get_literal_size(args, &ctx->literal_size)) { *nonsync_r = args->type == IMAP_ARG_LITERAL_SIZE_NONSYNC; ctx->binary_input = args->literal8; valid = TRUE; } else if (!imap_arg_atom_equals(args, "CATENATE")) { /* invalid */ } else if (!imap_arg_get_list(++args, &cat_list)) { /* invalid */ } else { valid = TRUE; ctx->catenate = TRUE; /* We'll do BINARY conversion only if the CATENATE's first part is a literal8. If it doesn't and a literal8 is seen later we'll abort the append with UNKNOWN-CTE. */ ctx->binary_input = imap_arg_atom_equals(&cat_list[0], "TEXT") && cat_list[1].literal8; } if (!IMAP_ARG_IS_EOL(&args[1])) valid = FALSE; if (!valid) { client->input_skip_line = TRUE; if (!ctx->failed) client_send_command_error(cmd, "Invalid arguments."); return -1; } if (flags_list == NULL || ctx->failed) { flags = 0; keywords = NULL; } else { if (!client_parse_mail_flags(cmd, flags_list, &flags, &keywords_list)) return -1; if (keywords_list == NULL) keywords = NULL; else if (mailbox_keywords_create(ctx->box, keywords_list, &keywords) < 0) { /* invalid keywords - delay failure */ client_send_box_error(cmd, ctx->box); ctx->failed = TRUE; keywords = NULL; } } if (internal_date_str == NULL || ctx->failed) { /* no time given, default to now. */ internal_date = (time_t)-1; timezone_offset = 0; } else if (!imap_parse_datetime(internal_date_str, &internal_date, &timezone_offset)) { client_send_command_error(cmd, "Invalid internal date."); if (keywords != NULL) mailbox_keywords_unref(&keywords); return -1; } if (internal_date != (time_t)-1 && internal_date > ioloop_time + INTERNALDATE_MAX_FUTURE_SECS) { /* the client specified a time in the future, set it to now. */ internal_date = (time_t)-1; timezone_offset = 0; } if (cat_list != NULL) { ctx->cat_msg_size = 0; ctx->input = i_stream_create_chain(&ctx->catchain); } else { if (ctx->literal_size == 0) { /* no message data, abort */ if (!ctx->failed) { client_send_tagline(cmd, "NO Can't save a zero byte message."); ctx->failed = TRUE; } if (!*nonsync_r) { if (keywords != NULL) mailbox_keywords_unref(&keywords); return -1; } /* {0+} used. although there isn't any point in using MULTIAPPEND here and adding more messages, it is technically valid so we'll continue parsing.. */ } ctx->litinput = i_stream_create_limit(client->input, ctx->literal_size); ctx->input = ctx->litinput; i_stream_ref(ctx->input); } if (ctx->binary_input) { input = i_stream_create_binary_converter(ctx->input); i_stream_unref(&ctx->input); ctx->input = input; } if (!ctx->failed) { /* save the mail */ ctx->save_ctx = mailbox_save_alloc(ctx->t); mailbox_save_set_flags(ctx->save_ctx, flags, keywords); mailbox_save_set_received_date(ctx->save_ctx, internal_date, timezone_offset); if (mailbox_save_begin(&ctx->save_ctx, ctx->input) < 0) { /* save initialization failed */ client_send_box_error(cmd, ctx->box); ctx->failed = TRUE; } } if (keywords != NULL) mailbox_keywords_unref(&keywords); ctx->count++; if (cat_list == NULL) { /* normal APPEND */ return 1; } else if (cat_list->type == IMAP_ARG_EOL) { /* zero parts */ if (!ctx->failed) client_send_command_error(cmd, "Empty CATENATE list."); client->input_skip_line = TRUE; return -1; } else if ((ret = cmd_append_catenate(cmd, cat_list, nonsync_r)) < 0) { /* invalid parameters, abort immediately */ return -1; } else if (ret == 0) { /* CATENATE consisted only of URLs */ return 0; } else { /* TEXT part found from CATENATE */ return 1; } }
static void test_istream_tee_tailing(const char *str) { struct istream *test_input, *child_input[CHILD_COUNT]; struct tee_istream *tee; unsigned int i, len; test_input = test_istream_create(str); test_istream_set_max_buffer_size(test_input, TEST_BUF_SIZE); test_begin("istream tee tailing"); tee = tee_i_stream_create(test_input); for (i = 0; i < CHILD_COUNT; i++) child_input[i] = tee_i_stream_create_child(tee); test_istream_set_allow_eof(test_input, FALSE); for (len = 1; len < TEST_BUF_SIZE; len++) { test_istream_set_size(test_input, len); for (i = 0; i < CHILD_COUNT; i++) { test_assert(i_stream_read(child_input[i]) == 1); test_assert(!tee_i_stream_child_is_waiting(child_input[i])); test_assert(i_stream_read(child_input[i]) == 0); test_assert(!tee_i_stream_child_is_waiting(child_input[i])); } } test_istream_set_size(test_input, len); for (i = 0; i < CHILD_COUNT; i++) { test_assert(i_stream_read(child_input[i]) == 1); test_assert(i_stream_read(child_input[i]) == -2); test_assert(!tee_i_stream_child_is_waiting(child_input[i])); } for (len++; len <= TEST_STR_LEN; len++) { test_istream_set_size(test_input, len); for (i = 0; i < CHILD_COUNT; i++) { test_assert(i_stream_read(child_input[i]) == -2); test_assert(!tee_i_stream_child_is_waiting(child_input[i])); } for (i = 0; i < CHILD_COUNT-1; i++) { i_stream_skip(child_input[i], 1); test_assert(i_stream_read(child_input[i]) == 0); test_assert(tee_i_stream_child_is_waiting(child_input[i])); } i_stream_skip(child_input[i], 1); for (i = 0; i < CHILD_COUNT; i++) { test_assert(i_stream_read(child_input[i]) == 1); test_assert(i_stream_read(child_input[i]) == -2); test_assert(!tee_i_stream_child_is_waiting(child_input[i])); } } for (i = 0; i < CHILD_COUNT-1; i++) { i_stream_skip(child_input[i], 1); test_assert(i_stream_read(child_input[i]) == 0); test_assert(tee_i_stream_child_is_waiting(child_input[i])); } i_stream_skip(child_input[i], 1); test_assert(i_stream_read(child_input[i]) == 0); test_assert(!tee_i_stream_child_is_waiting(child_input[i])); test_istream_set_allow_eof(test_input, TRUE); for (i = 0; i < CHILD_COUNT; i++) { test_assert(i_stream_read(child_input[i]) == -1); i_stream_unref(&child_input[i]); } i_stream_unref(&test_input); test_end(); }
static bool cmd_append_continue_message(struct client_command_context *cmd) { struct client *client = cmd->client; struct cmd_append_context *ctx = cmd->context; int ret = 0; if (cmd->cancel) { /* cancel the command immediately (disconnection) */ cmd_append_finish(ctx); return TRUE; } if (ctx->save_ctx != NULL) { while (ctx->litinput->v_offset != ctx->literal_size) { ret = i_stream_read(ctx->litinput); if (mailbox_save_continue(ctx->save_ctx) < 0) { /* we still have to finish reading the message from client */ mailbox_save_cancel(&ctx->save_ctx); break; } if (ret == -1 || ret == 0) break; } } if (ctx->save_ctx == NULL) { /* saving has already failed, we're just eating away the literal */ (void)i_stream_read(ctx->litinput); i_stream_skip(ctx->litinput, i_stream_get_data_size(ctx->litinput)); } if (ctx->litinput->eof || client->input->closed) { uoff_t lit_offset = ctx->litinput->v_offset; /* finished - do one more read, to make sure istream-chain unreferences its stream, which is needed for litinput's unreferencing to seek the client->input to correct position. the seek is needed to avoid trying to seek backwards in the ctx->input's parent stream. */ i_stream_seek(ctx->input, ctx->input->v_offset); (void)i_stream_read(ctx->input); i_stream_unref(&ctx->litinput); if (ctx->failed) { if (ctx->save_ctx != NULL) mailbox_save_cancel(&ctx->save_ctx); } else if (ctx->save_ctx == NULL) { /* failed above */ client_send_box_error(cmd, ctx->box); ctx->failed = TRUE; } else if (lit_offset != ctx->literal_size) { /* client disconnected before it finished sending the whole message. */ ctx->failed = TRUE; mailbox_save_cancel(&ctx->save_ctx); client_disconnect(client, get_disconnect_reason(ctx, lit_offset)); } else if (ctx->catenate) { /* CATENATE isn't finished yet */ } else if (mailbox_save_finish(&ctx->save_ctx) < 0) { client_send_box_error(cmd, ctx->box); ctx->failed = TRUE; } if (client->input->closed) { cmd_append_finish(ctx); return TRUE; } /* prepare for the next message (or its part with catenate) */ ctx->message_input = FALSE; imap_parser_reset(ctx->save_parser); if (ctx->catenate) { cmd->func = cmd_append_continue_catenate; return cmd_append_continue_catenate(cmd); } i_stream_unref(&ctx->input); cmd->func = cmd_append_parse_new_msg; return cmd_append_parse_new_msg(cmd); } return FALSE; }
static void client_state_reset(struct client *client) { i_stream_unref(&client->state.data_input); i_zero(&client->state); }
int mail_send_rejection(struct mail_deliver_context *ctx, const char *recipient, const char *reason) { struct mail *mail = ctx->src_mail; struct istream *input; struct smtp_client *smtp_client; struct ostream *output; const char *return_addr, *hdr; const char *value, *msgid, *orig_msgid, *boundary; string_t *str; int ret; if (mail_get_first_header(mail, "Message-ID", &orig_msgid) < 0) orig_msgid = NULL; if (mail_get_first_header(mail, "Auto-Submitted", &value) > 0 && strcasecmp(value, "no") != 0) { i_info("msgid=%s: Auto-submitted message discarded: %s", orig_msgid == NULL ? "" : str_sanitize(orig_msgid, 80), str_sanitize(reason, 512)); return 0; } return_addr = mail_deliver_get_return_address(ctx); if (return_addr == NULL) { i_info("msgid=%s: Return-Path missing, rejection reason: %s", orig_msgid == NULL ? "" : str_sanitize(orig_msgid, 80), str_sanitize(reason, 512)); return 0; } if (mailbox_get_settings(mail->box)->mail_debug) { i_debug("Sending a rejection to %s: %s", recipient, str_sanitize(reason, 512)); } smtp_client = smtp_client_open(ctx->set, return_addr, NULL, &output); msgid = mail_deliver_get_new_message_id(ctx); boundary = t_strdup_printf("%s/%s", my_pid, ctx->set->hostname); str = t_str_new(512); str_printfa(str, "Message-ID: %s\r\n", msgid); str_printfa(str, "Date: %s\r\n", message_date_create(ioloop_time)); str_printfa(str, "From: Mail Delivery Subsystem <%s>\r\n", ctx->set->postmaster_address); str_printfa(str, "To: <%s>\r\n", return_addr); str_append(str, "MIME-Version: 1.0\r\n"); str_printfa(str, "Content-Type: " "multipart/report; report-type=%s;\r\n" "\tboundary=\"%s\"\r\n", ctx->dsn ? "delivery-status" : "disposition-notification", boundary); str_append(str, "Subject: "); var_expand(str, ctx->set->rejection_subject, get_var_expand_table(mail, reason, recipient)); str_append(str, "\r\n"); str_append(str, "Auto-Submitted: auto-replied (rejected)\r\n"); str_append(str, "Precedence: bulk\r\n"); str_append(str, "\r\nThis is a MIME-encapsulated message\r\n\r\n"); /* human readable status report */ str_printfa(str, "--%s\r\n", boundary); str_append(str, "Content-Type: text/plain; charset=utf-8\r\n"); str_append(str, "Content-Disposition: inline\r\n"); str_append(str, "Content-Transfer-Encoding: 8bit\r\n\r\n"); var_expand(str, ctx->set->rejection_reason, get_var_expand_table(mail, reason, recipient)); str_append(str, "\r\n"); if (ctx->dsn) { /* DSN status report: For LDA rejects. currently only used when user is out of quota */ str_printfa(str, "--%s\r\n" "Content-Type: message/delivery-status\r\n\r\n", boundary); str_printfa(str, "Reporting-MTA: dns; %s\r\n", ctx->set->hostname); if (mail_get_first_header(mail, "Original-Recipient", &hdr) > 0) str_printfa(str, "Original-Recipient: rfc822; %s\r\n", hdr); str_printfa(str, "Final-Recipient: rfc822; %s\r\n", recipient); str_append(str, "Action: failed\r\n"); str_printfa(str, "Status: %s\r\n", ctx->mailbox_full ? "5.2.2" : "5.2.0"); } else { /* MDN status report: For Sieve "reject" */ str_printfa(str, "--%s\r\n" "Content-Type: message/disposition-notification\r\n\r\n", boundary); str_printfa(str, "Reporting-UA: %s; Dovecot Mail Delivery Agent\r\n", ctx->set->hostname); if (mail_get_first_header(mail, "Original-Recipient", &hdr) > 0) str_printfa(str, "Original-Recipient: rfc822; %s\r\n", hdr); str_printfa(str, "Final-Recipient: rfc822; %s\r\n", recipient); if (orig_msgid != NULL) str_printfa(str, "Original-Message-ID: %s\r\n", orig_msgid); str_append(str, "Disposition: " "automatic-action/MDN-sent-automatically; deleted\r\n"); } str_append(str, "\r\n"); /* original message's headers */ str_printfa(str, "--%s\r\nContent-Type: message/rfc822\r\n\r\n", boundary); o_stream_nsend(output, str_data(str), str_len(str)); if (mail_get_hdr_stream(mail, NULL, &input) == 0) { /* Note: If you add more headers, they need to be sorted. We'll drop Content-Type because we're not including the message body, and having a multipart Content-Type may confuse some MIME parsers when they don't see the message boundaries. */ static const char *const exclude_headers[] = { "Content-Type" }; input = i_stream_create_header_filter(input, HEADER_FILTER_EXCLUDE | HEADER_FILTER_NO_CR | HEADER_FILTER_HIDE_BODY, exclude_headers, N_ELEMENTS(exclude_headers), *null_header_filter_callback, (void *)NULL); ret = o_stream_send_istream(output, input); i_stream_unref(&input); i_assert(ret != 0); } str_truncate(str, 0); str_printfa(str, "\r\n\r\n--%s--\r\n", boundary); o_stream_nsend(output, str_data(str), str_len(str)); return smtp_client_close(smtp_client); }
struct maildir_filename * maildir_save_add(struct mail_save_context *_ctx, const char *tmp_fname, struct mail *src_mail) { struct maildir_save_context *ctx = (struct maildir_save_context *)_ctx; struct mail_save_data *mdata = &_ctx->data; struct maildir_filename *mf; struct istream *input; unsigned int keyword_count; i_assert(*tmp_fname != '\0'); /* allow caller to specify recent flag only when uid is specified (we're replicating, converting, etc.). */ if (mdata->uid == 0) mdata->flags |= MAIL_RECENT; else if ((mdata->flags & MAIL_RECENT) == 0 && ctx->last_nonrecent_uid < mdata->uid) ctx->last_nonrecent_uid = mdata->uid; /* now, we want to be able to rollback the whole append session, so we'll just store the name of this temp file and move it later into new/ or cur/. */ /* @UNSAFE */ keyword_count = mdata->keywords == NULL ? 0 : mdata->keywords->count; mf = p_malloc(ctx->pool, sizeof(*mf) + sizeof(unsigned int) * keyword_count); mf->tmp_name = mf->dest_basename = p_strdup(ctx->pool, tmp_fname); mf->flags = mdata->flags; mf->size = (uoff_t)-1; mf->vsize = (uoff_t)-1; ctx->file_last = mf; i_assert(*ctx->files_tail == NULL); *ctx->files_tail = mf; ctx->files_tail = &mf->next; ctx->files_count++; if (mdata->keywords != NULL) { /* @UNSAFE */ mf->keywords_count = keyword_count; memcpy(mf + 1, mdata->keywords->idx, sizeof(unsigned int) * keyword_count); ctx->have_keywords = TRUE; } if (mdata->pop3_uidl != NULL) mf->pop3_uidl = p_strdup(ctx->pool, mdata->pop3_uidl); mf->pop3_order = mdata->pop3_order; /* insert into index */ mail_index_append(ctx->trans, mdata->uid, &ctx->seq); mail_index_update_flags(ctx->trans, ctx->seq, MODIFY_REPLACE, mdata->flags & ~MAIL_RECENT); if (mdata->keywords != NULL) { mail_index_update_keywords(ctx->trans, ctx->seq, MODIFY_REPLACE, mdata->keywords); } if (mdata->min_modseq != 0) { mail_index_update_modseq(ctx->trans, ctx->seq, mdata->min_modseq); } if (ctx->first_seq == 0) { ctx->first_seq = ctx->seq; i_assert(ctx->files->next == NULL); } if (_ctx->dest_mail == NULL) { if (ctx->mail == NULL) ctx->mail = mail_alloc(_ctx->transaction, 0, NULL); _ctx->dest_mail = ctx->mail; } mail_set_seq_saving(_ctx->dest_mail, ctx->seq); if (ctx->input == NULL) { /* copying with hardlinking. */ i_assert(src_mail != NULL); index_copy_cache_fields(_ctx, src_mail, ctx->seq); ctx->cur_dest_mail = NULL; } else { input = index_mail_cache_parse_init(_ctx->dest_mail, ctx->input); i_stream_unref(&ctx->input); ctx->input = input; ctx->cur_dest_mail = _ctx->dest_mail; } return mf; }
static int cmd_filter_operation_execute (const struct sieve_runtime_env *renv, sieve_size_t *address) { const struct sieve_extension *this_ext = renv->oprtn->ext; unsigned int is_test = 0; struct sieve_stringlist *args_list = NULL; enum sieve_error error = SIEVE_ERROR_NONE; string_t *pname = NULL; const char *program_name = NULL; const char *const *args = NULL; struct istream *newmsg = NULL; struct sieve_extprogram *sprog; int ret; /* * Read operands */ /* The is_test flag */ if ( !sieve_binary_read_byte(renv->sblock, address, &is_test) ) { sieve_runtime_trace_error(renv, "invalid is_test flag"); return SIEVE_EXEC_BIN_CORRUPT; } /* Optional operands */ if ( sieve_action_opr_optional_read(renv, address, NULL, &ret, NULL) != 0 ) return ret; /* Fixed operands */ if ( (ret=sieve_extprogram_command_read_operands (renv, address, &pname, &args_list)) <= 0 ) return ret; program_name = str_c(pname); if ( args_list != NULL && sieve_stringlist_read_all(args_list, pool_datastack_create(), &args) < 0 ) { sieve_runtime_trace_error(renv, "failed to read args operand"); return args_list->exec_status; } /* * Perform operation */ /* Trace */ sieve_runtime_trace(renv, SIEVE_TRLVL_ACTIONS, "filter action"); sieve_runtime_trace_descend(renv); sieve_runtime_trace(renv, SIEVE_TRLVL_ACTIONS, "execute program `%s'", str_sanitize(program_name, 128)); sprog = sieve_extprogram_create (this_ext, renv->scriptenv, renv->msgdata, "filter", program_name, args, &error); if ( sprog != NULL ) { struct mail *mail = sieve_message_get_mail(renv->msgctx); if ( sieve_extprogram_set_input_mail(sprog, mail) < 0 ) { sieve_extprogram_destroy(&sprog); return sieve_runtime_mail_error(renv, mail, "filter action: failed to read input message"); } sieve_extprogram_set_output_seekable(sprog); ret = sieve_extprogram_run(sprog); } else { ret = -1; } if ( ret > 0 ) newmsg = sieve_extprogram_get_output_seekable(sprog); if ( sprog != NULL ) sieve_extprogram_destroy(&sprog); if ( ret > 0 && newmsg != NULL ) { sieve_runtime_trace(renv, SIEVE_TRLVL_ACTIONS, "executed program successfully"); i_stream_set_name(newmsg, t_strdup_printf("filter %s output", program_name)); newmsg->blocking = TRUE; if ( (ret=sieve_message_substitute(renv->msgctx, newmsg)) >= 0 ) { sieve_runtime_trace(renv, SIEVE_TRLVL_ACTIONS, "changed message"); } else { sieve_runtime_critical(renv, NULL, "filter action", "filter action: failed to substitute message"); } i_stream_unref(&newmsg); } else if ( ret < 0 ) { if ( error == SIEVE_ERROR_NOT_FOUND ) { sieve_runtime_error(renv, NULL, "filter action: program `%s' not found", str_sanitize(program_name, 80)); } else { sieve_extprogram_exec_error(renv->ehandler, sieve_runtime_get_full_command_location(renv), "filter action: failed to execute to program `%s'", str_sanitize(program_name, 80)); } } else { sieve_runtime_trace(renv, SIEVE_TRLVL_ACTIONS, "filter action: program indicated false result"); } if ( is_test > 0 ) { sieve_interpreter_set_test_result(renv->interp, ( ret > 0 )); return SIEVE_EXEC_OK; } return ( ret >= 0 ? SIEVE_EXEC_OK : SIEVE_EXEC_FAILURE ); }
static int acl_backend_vfile_read(struct acl_object *aclobj, bool global, const char *path, struct acl_vfile_validity *validity, bool try_retry, bool *is_dir_r) { struct istream *input; struct stat st; struct acl_rights rights; const char *line, *error; unsigned int linenum; int fd, ret = 0; *is_dir_r = FALSE; fd = nfs_safe_open(path, O_RDONLY); if (fd == -1) { if (errno == ENOENT || errno == ENOTDIR) { if (aclobj->backend->debug) i_debug("acl vfile: file %s not found", path); validity->last_mtime = ACL_VFILE_VALIDITY_MTIME_NOTFOUND; } else if (errno == EACCES) { if (aclobj->backend->debug) i_debug("acl vfile: no access to file %s", path); acl_object_remove_all_access(aclobj); validity->last_mtime = ACL_VFILE_VALIDITY_MTIME_NOACCESS; } else { i_error("open(%s) failed: %m", path); return -1; } validity->last_size = 0; validity->last_read_time = ioloop_time; return 1; } if (fstat(fd, &st) < 0) { if (errno == ESTALE && try_retry) { i_close_fd(&fd); return 0; } i_error("fstat(%s) failed: %m", path); i_close_fd(&fd); return -1; } if (S_ISDIR(st.st_mode)) { /* we opened a directory. */ *is_dir_r = TRUE; i_close_fd(&fd); return 0; } if (aclobj->backend->debug) i_debug("acl vfile: reading file %s", path); input = i_stream_create_fd(fd, (size_t)-1, FALSE); i_stream_set_return_partial_line(input, TRUE); linenum = 1; while ((line = i_stream_read_next_line(input)) != NULL) { T_BEGIN { ret = acl_rights_parse_line(line, aclobj->rights_pool, &rights, &error); rights.global = global; if (ret < 0) { i_error("ACL file %s line %u: %s", path, linenum, error); } else { array_append(&aclobj->rights, &rights, 1); } } T_END; if (ret < 0) break; linenum++; } if (ret < 0) { /* parsing failure */ } else if (input->stream_errno != 0) { if (input->stream_errno == ESTALE && try_retry) ret = 0; else { ret = -1; i_error("read(%s) failed: %m", path); } } else { if (fstat(fd, &st) < 0) { if (errno == ESTALE && try_retry) ret = 0; else { ret = -1; i_error("fstat(%s) failed: %m", path); } } else { ret = 1; validity->last_read_time = ioloop_time; validity->last_mtime = st.st_mtime; validity->last_size = st.st_size; } } i_stream_unref(&input); if (close(fd) < 0) { if (errno == ESTALE && try_retry) return 0; i_error("close(%s) failed: %m", path); return -1; } return ret; }