static void script_execute_finish(void) { const char *keys_str, *username, *const *keys, *value; string_t *reply = t_str_new(512); ssize_t ret; keys_str = getenv(ENV_USERDB_KEYS); if (keys_str == NULL) i_fatal(ENV_USERDB_KEYS" environment missing"); username = getenv("USER"); if (username == NULL) i_fatal("USER environment missing"); str_append(reply, username); for (keys = t_strsplit_spaces(keys_str, " "); *keys != NULL; keys++) { value = getenv(t_str_ucase(*keys)); if (value != NULL) { str_append_c(reply, '\t'); str_tabescape_write(reply, t_strconcat(t_str_lcase(*keys), "=", value, NULL)); } } str_append_c(reply, '\n'); ret = fd_send(SCRIPT_COMM_FD, STDOUT_FILENO, str_data(reply), str_len(reply)); if (ret < 0) i_fatal("fd_send() failed: %m"); else if (ret != (ssize_t)str_len(reply)) i_fatal("fd_send() sent partial output"); }
static int arg_modseq_set_ext(struct mail_search_build_context *ctx, struct mail_search_arg *sarg, const char *name) { const char *value; name = t_str_lcase(name); if (strncmp(name, "/flags/", 7) != 0) return 0; name += 7; /* set name */ if (*name == '\\') { /* system flag */ sarg->value.flags = imap_parse_system_flag(name); if (sarg->value.flags == 0 || sarg->value.flags == MAIL_RECENT) { ctx->_error = "Invalid MODSEQ system flag"; return -1; } } else { sarg->value.str = p_strdup(ctx->pool, name); } /* set type */ if (mail_search_parse_string(ctx->parser, &value) < 0) return -1; if (arg_modseq_set_type(ctx, sarg->value.modseq, value) < 0) return -1; return 1; }
bool mail_user_hash(const char *username, const char *format, unsigned int *hash_r, const char **error_r) { unsigned char md5[MD5_RESULTLEN]; unsigned int i, hash = 0; char *error_dup = NULL; int ret = 1; if (strcmp(format, "%u") == 0) { /* fast path */ md5_get_digest(username, strlen(username), md5); } else if (strcmp(format, "%Lu") == 0) { /* almost as fast path */ T_BEGIN { md5_get_digest(t_str_lcase(username), strlen(username), md5); } T_END; } else T_BEGIN {
static void script_execute_finish(void) { const char *keys_str, *username, *const *keys, *value; string_t *reply = t_str_new(512); ssize_t ret; keys_str = getenv(ENV_USERDB_KEYS); if (keys_str == NULL) i_fatal(ENV_USERDB_KEYS" environment missing"); username = getenv("USER"); if (username == NULL) i_fatal("USER environment missing"); str_append(reply, username); for (keys = t_strsplit_spaces(keys_str, " "); *keys != NULL; keys++) { value = getenv(t_str_ucase(*keys)); if (value != NULL) { str_append_c(reply, '\t'); str_append_tabescaped(reply, t_strconcat(t_str_lcase(*keys), "=", value, NULL)); } } str_append_c(reply, '\n'); /* finish by sending the fd to the mail process */ ret = fd_send(SCRIPT_COMM_FD, STDOUT_FILENO, str_data(reply), str_len(reply)); if (ret == (ssize_t)str_len(reply)) { /* success */ } else { if (ret < 0) i_error("fd_send() failed: %m"); else i_error("fd_send() sent partial output"); /* exit with 0 even though we failed. non-0 exit just makes master log an unnecessary error. */ } }
static bool imap_metadata_entry2key(struct imap_metadata_transaction *imtrans, const char *entry, enum mail_attribute_type *type_r, const char **key_r) { const char *key_prefix = (imtrans->server ? MAILBOX_ATTRIBUTE_PREFIX_DOVECOT_PVT_SERVER : NULL); /* names are case-insensitive so we'll always lowercase them */ entry = t_str_lcase(entry); if (strncmp(entry, IMAP_METADATA_PRIVATE_PREFIX, strlen(IMAP_METADATA_PRIVATE_PREFIX)) == 0) { *key_r = entry + strlen(IMAP_METADATA_PRIVATE_PREFIX); *type_r = MAIL_ATTRIBUTE_TYPE_PRIVATE; } else { i_assert(strncmp(entry, IMAP_METADATA_SHARED_PREFIX, strlen(IMAP_METADATA_SHARED_PREFIX)) == 0); *key_r = entry + strlen(IMAP_METADATA_SHARED_PREFIX); *type_r = MAIL_ATTRIBUTE_TYPE_SHARED; } if ((*key_r)[0] == '\0') { /* /private or /shared prefix has no value itself */ } else { i_assert((*key_r)[0] == '/'); *key_r += 1; } if (strncmp(*key_r, MAILBOX_ATTRIBUTE_PREFIX_DOVECOT_PVT, strlen(MAILBOX_ATTRIBUTE_PREFIX_DOVECOT_PVT)) == 0) { /* Dovecot's internal attribute (mailbox or server). don't allow accessing this. */ return FALSE; } /* Add the server-prefix (after checking for the above internal attribute). */ if (key_prefix != NULL) *key_r = t_strconcat(key_prefix, *key_r, NULL); return TRUE; }
unsigned int mail_user_hash(const char *username, const char *format) { static struct var_expand_table static_tab[] = { { 'u', NULL, "user" }, { 'n', NULL, "username" }, { 'd', NULL, "domain" }, { '\0', NULL, NULL } }; struct var_expand_table *tab; unsigned char md5[MD5_RESULTLEN]; unsigned int i, hash = 0; if (strcmp(format, "%u") == 0) { /* fast path */ md5_get_digest(username, strlen(username), md5); } else if (strcmp(format, "%Lu") == 0) { /* almost as fast path */ T_BEGIN { md5_get_digest(t_str_lcase(username), strlen(username), md5); } T_END; } else T_BEGIN {
static bool imap_metadata_entry2key(struct imap_metadata_transaction *imtrans, const char *entry, enum mail_attribute_type *type_r, const char **key_r) { const char *key_prefix = (imtrans->server ? MAILBOX_ATTRIBUTE_PREFIX_DOVECOT_PVT_SERVER : NULL); /* names are case-insensitive so we'll always lowercase them */ entry = t_str_lcase(entry); if (strncmp(entry, IMAP_METADATA_PRIVATE_PREFIX, strlen(IMAP_METADATA_PRIVATE_PREFIX)) == 0) { *key_r = entry + strlen(IMAP_METADATA_PRIVATE_PREFIX); *type_r = MAIL_ATTRIBUTE_TYPE_PRIVATE; } else { i_assert(strncmp(entry, IMAP_METADATA_SHARED_PREFIX, strlen(IMAP_METADATA_SHARED_PREFIX)) == 0); *key_r = entry + strlen(IMAP_METADATA_SHARED_PREFIX); *type_r = MAIL_ATTRIBUTE_TYPE_SHARED; } if ((*key_r)[0] == '\0') { /* /private or /shared prefix has no value itself */ } else { i_assert((*key_r)[0] == '/'); *key_r += 1; } if (key_prefix != NULL) *key_r = t_strconcat(key_prefix, *key_r, NULL); /* skip over dovecot's internal attributes. (server metadata is handled inside the private metadata.) */ return (imtrans->server || strncmp(*key_r, MAILBOX_ATTRIBUTE_PREFIX_DOVECOT_PVT, strlen(MAILBOX_ATTRIBUTE_PREFIX_DOVECOT_PVT)) != 0); }
static void cmd_mailbox_metadata_parse_key(const char *arg, enum mail_attribute_type *type_r, const char **key_r) { arg = t_str_lcase(arg); if (strncmp(arg, "/private/", 9) == 0) { *type_r = MAIL_ATTRIBUTE_TYPE_PRIVATE; *key_r = arg + 9; } else if (strncmp(arg, "/shared/", 8) == 0) { *type_r = MAIL_ATTRIBUTE_TYPE_SHARED; *key_r = arg + 8; } else if (strcmp(arg, "/private") == 0) { *type_r = MAIL_ATTRIBUTE_TYPE_PRIVATE; *key_r = ""; } else if (strcmp(arg, "/shared") == 0) { *type_r = MAIL_ATTRIBUTE_TYPE_SHARED; *key_r = ""; } else { i_fatal_status(EX_USAGE, "Invalid metadata key '%s': " "Must begin with /private or /shared", arg); } }
static bool test_parse_header_line(struct test_parser *parser, struct test *test, const char *line, const char **error_r) { struct test_connection *test_conn; const char *key, *value; unsigned int idx; value = strchr(line, ':'); if (value == NULL) { *error_r = "Missing ':'"; return FALSE; } for (key = value; key[-1] == ' '; key--) ; key = t_str_lcase(t_strdup_until(line, key)); for (value++; *value == ' '; value++) ; if (strcmp(key, "capabilities") == 0) { test->required_capabilities = (const char *const *) p_strsplit_spaces(parser->pool, value, " "); return TRUE; } if (strcmp(key, "connections") == 0) { test->connection_count = strcmp(value, "n") == 0 ? 2 : strtoul(value, NULL, 10); return TRUE; } if (strncmp(key, "user ", 5) == 0 && str_to_uint(key+5, &idx) == 0 && idx != 0) { /* FIXME: kludgy kludgy */ if (strcmp(value, "$user2") == 0 || strcmp(value, "${user2}") == 0) { test->require_user2 = TRUE; value = conf.username2_template; } test_conn = array_idx_modifiable(&test->connections, idx-1); test_conn->username = p_strdup(parser->pool, value); return TRUE; } if (strcmp(key, "messages") == 0) { test->message_count = strcmp(value, "all") == 0 ? UINT_MAX : strtoul(value, NULL, 10); return TRUE; } if (strcmp(key, "state") == 0) { if (strcasecmp(value, "nonauth") == 0) test->startup_state = TEST_STARTUP_STATE_NONAUTH; else if (strcasecmp(value, "auth") == 0) test->startup_state = TEST_STARTUP_STATE_DELETED; else if (strcasecmp(value, "created") == 0) test->startup_state = TEST_STARTUP_STATE_CREATED; else if (strcasecmp(value, "appended") == 0) test->startup_state = TEST_STARTUP_STATE_APPENDED; else if (strcasecmp(value, "selected") == 0) test->startup_state = TEST_STARTUP_STATE_SELECTED; else { *error_r = "Unknown state value"; return FALSE; } return TRUE; } *error_r = "Unknown setting"; return FALSE; }
static bool cmd_compress(struct client_command_context *cmd) { struct client *client = cmd->client; struct zlib_client *zclient = IMAP_ZLIB_IMAP_CONTEXT(client); const struct compression_handler *handler; const struct imap_arg *args; struct istream *old_input; struct ostream *old_output; const char *mechanism, *value; unsigned int level; /* <mechanism> */ if (!client_read_args(cmd, 0, 0, &args)) return FALSE; if (!imap_arg_get_atom(args, &mechanism) || !IMAP_ARG_IS_EOL(&args[1])) { client_send_command_error(cmd, "Invalid arguments."); return TRUE; } if (zclient->handler != NULL) { client_send_tagline(cmd, t_strdup_printf( "NO [COMPRESSIONACTIVE] COMPRESSION=%s already enabled.", t_str_ucase(zclient->handler->name))); return TRUE; } if (client->tls_compression) { client_send_tagline(cmd, "NO [COMPRESSIONACTIVE] TLS compression already enabled."); return TRUE; } handler = compression_lookup_handler(t_str_lcase(mechanism)); if (handler == NULL || handler->create_istream == NULL) { client_send_tagline(cmd, "NO Unknown compression mechanism."); return TRUE; } imap_zlib_client_skip_line(client); client_send_tagline(cmd, "OK Begin compression."); value = mail_user_plugin_getenv(client->user, "imap_zlib_compress_level"); if (value == NULL || str_to_uint(value, &level) < 0 || level <= 0 || level > 9) level = IMAP_COMPRESS_DEFAULT_LEVEL; old_input = client->input; old_output = client->output; client->input = handler->create_istream(old_input, FALSE); client->output = handler->create_ostream(old_output, level); /* preserve output offset so that the bytes out counter in logout message doesn't get reset here */ client->output->offset = old_output->offset; i_stream_unref(&old_input); o_stream_unref(&old_output); client_update_imap_parser_streams(client); zclient->handler = handler; return TRUE; }
static ssize_t i_stream_decrypt_key(struct decrypt_istream *stream, const char *malg, unsigned int rounds, const unsigned char *data, const unsigned char *end, buffer_t *key, size_t key_len) { const char *error; enum dcrypt_key_type ktype; int keys; bool have_key = FALSE; unsigned char dgst[32]; uint32_t val; buffer_t buf; if (data == end) return 0; keys = *data++; /* if we have a key, prefab the digest */ if (stream->key_callback == NULL) { if (stream->priv_key == NULL) { io_stream_set_error(&stream->istream.iostream, "Decryption error: no private key available"); return -1; } buffer_create_from_data(&buf, dgst, sizeof(dgst)); dcrypt_key_id_private(stream->priv_key, "sha256", &buf, NULL); } /* for each key */ for(;keys>0;keys--) { if ((size_t)(end-data) < 1 + (ssize_t)sizeof(dgst)) return 0; ktype = *data++; if (stream->key_callback != NULL) { const char *hexdgst = binary_to_hex(data, sizeof(dgst)); /* digest length */ /* hope you going to give us right key.. */ int ret = stream->key_callback(hexdgst, &(stream->priv_key), &error, stream->key_context); if (ret < 0) { io_stream_set_error(&stream->istream.iostream, "Private key not available: %s", error); return -1; } if (ret > 0) { dcrypt_key_ref_private(stream->priv_key); have_key = TRUE; break; } } else { /* see if key matches to the one we have */ if (memcmp(dgst, data, sizeof(dgst)) == 0) { have_key = TRUE; break; } } data += sizeof(dgst); /* wasn't correct key, skip over some data */ if (!get_msb32(&data, end, &val) || !get_msb32(&data, end, &val)) return 0; } /* didn't find matching key */ if (!have_key) { io_stream_set_error(&stream->istream.iostream, "Decryption error: no private key available"); return -1; } data += sizeof(dgst); const unsigned char *ephemeral_key; uint32_t ep_key_len; const unsigned char *encrypted_key; uint32_t eklen; const unsigned char *ekhash; uint32_t ekhash_len; /* read ephemeral key (can be missing for RSA) */ if (!get_msb32(&data, end, &ep_key_len) || (size_t)(end-data) < ep_key_len) return 0; ephemeral_key = data; data += ep_key_len; /* read encrypted key */ if (!get_msb32(&data, end, &eklen) || (size_t)(end-data) < eklen) return 0; encrypted_key = data; data += eklen; /* read key data hash */ if (!get_msb32(&data, end, &ekhash_len) || (size_t)(end-data) < ekhash_len) return 0; ekhash = data; data += ekhash_len; /* decrypt the seed */ if (ktype == DCRYPT_KEY_RSA) { if (!dcrypt_rsa_decrypt(stream->priv_key, encrypted_key, eklen, key, &error)) { io_stream_set_error(&stream->istream.iostream, "key decryption error: %s", error); return -1; } } else if (ktype == DCRYPT_KEY_EC) { /* perform ECDHE */ buffer_t *temp_key = buffer_create_dynamic(pool_datastack_create(), 256); buffer_t *secret = buffer_create_dynamic(pool_datastack_create(), 256); buffer_t peer_key; buffer_create_from_const_data(&peer_key, ephemeral_key, ep_key_len); if (!dcrypt_ecdh_derive_secret_local(stream->priv_key, &peer_key, secret, &error)) { io_stream_set_error(&stream->istream.iostream, "Key decryption error: corrupted header"); return -1; } /* use shared secret and peer key to generate decryption key, AES-256-CBC has 32 byte key and 16 byte IV */ if (!dcrypt_pbkdf2(secret->data, secret->used, peer_key.data, peer_key.used, malg, rounds, temp_key, 32+16, &error)) { safe_memset(buffer_get_modifiable_data(secret, 0), 0, secret->used); io_stream_set_error(&stream->istream.iostream, "Key decryption error: %s", error); return -1; } safe_memset(buffer_get_modifiable_data(secret, 0), 0, secret->used); if (temp_key->used != 32+16) { safe_memset(buffer_get_modifiable_data(temp_key, 0), 0, temp_key->used); io_stream_set_error(&stream->istream.iostream, "Cannot perform key decryption: invalid temporary key"); return -1; } struct dcrypt_context_symmetric *dctx; if (!dcrypt_ctx_sym_create("AES-256-CBC", DCRYPT_MODE_DECRYPT, &dctx, &error)) { safe_memset(buffer_get_modifiable_data(temp_key, 0), 0, temp_key->used); io_stream_set_error(&stream->istream.iostream, "Key decryption error: %s", error); return -1; } const unsigned char *ptr = temp_key->data; /* we use ephemeral_key for IV */ dcrypt_ctx_sym_set_key(dctx, ptr, 32); dcrypt_ctx_sym_set_iv(dctx, ptr+32, 16); safe_memset(buffer_get_modifiable_data(temp_key, 0), 0, temp_key->used); int ec = 0; if (!dcrypt_ctx_sym_init(dctx, &error) || !dcrypt_ctx_sym_update(dctx, encrypted_key, eklen, key, &error) || !dcrypt_ctx_sym_final(dctx, key, &error)) { io_stream_set_error(&stream->istream.iostream, "Cannot perform key decryption: %s", error); ec = -1; } if (key->used != key_len) { io_stream_set_error(&stream->istream.iostream, "Cannot perform key decryption: invalid key length"); ec = -1; } dcrypt_ctx_sym_destroy(&dctx); if (ec != 0) return ec; } else { io_stream_set_error(&stream->istream.iostream, "Decryption error: unsupported key type 0x%02x", ktype); return -1; } /* make sure we were able to decrypt the encrypted key correctly */ const struct hash_method *hash = hash_method_lookup(t_str_lcase(malg)); if (hash == NULL) { safe_memset(buffer_get_modifiable_data(key, 0), 0, key->used); io_stream_set_error(&stream->istream.iostream, "Decryption error: unsupported hash algorithm: %s", malg); return -1; } unsigned char hctx[hash->context_size]; unsigned char hres[hash->digest_size]; hash->init(hctx); hash->loop(hctx, key->data, key->used); hash->result(hctx, hres); for(int i = 1; i < 2049; i++) { uint32_t i_msb = htonl(i); hash->init(hctx); hash->loop(hctx, hres, sizeof(hres)); hash->loop(hctx, &i_msb, sizeof(i_msb)); hash->result(hctx, hres); } /* do the comparison */ if (memcmp(ekhash, hres, I_MIN(ekhash_len, sizeof(hres))) != 0) { safe_memset(buffer_get_modifiable_data(key, 0), 0, key->used); io_stream_set_error(&stream->istream.iostream, "Decryption error: corrupted header ekhash"); return -1; } return 1; }
static int cmd_setmetadata_parse_entryvalue(struct imap_setmetadata_context *ctx, const char **entry_r, const struct imap_arg **value_r) { const struct imap_arg *args; const char *name, *error; int ret; bool fatal; /* parse the entry name */ ret = imap_parser_read_args(ctx->parser, 1, IMAP_PARSE_FLAG_INSIDE_LIST, &args); if (ret >= 0) { if (ret == 0) { /* ')' found */ *entry_r = NULL; return 1; } if (!imap_arg_get_astring(args, &name)) { client_send_command_error(ctx->cmd, "Entry name isn't astring"); return -1; } ret = imap_parser_read_args(ctx->parser, 2, IMAP_PARSE_FLAG_INSIDE_LIST | IMAP_PARSE_FLAG_LITERAL_SIZE | IMAP_PARSE_FLAG_LITERAL8, &args); } if (ret < 0) { if (ret == -2) return 0; error = imap_parser_get_error(ctx->parser, &fatal); if (fatal) { client_disconnect_with_error(ctx->cmd->client, error); return -1; } client_send_command_error(ctx->cmd, error); return -1; } if (args[1].type == IMAP_ARG_EOL) { client_send_command_error(ctx->cmd, "Entry value missing"); return -1; } if (args[1].type == IMAP_ARG_LIST) { client_send_command_error(ctx->cmd, "Entry value can't be a list"); return -1; } if (!ctx->cmd_error_sent && !imap_metadata_verify_entry_name(name, &error)) { client_send_command_error(ctx->cmd, error); ctx->cmd_error_sent = TRUE; } if (ctx->cmd_error_sent) { ctx->cmd->param_error = FALSE; ctx->cmd->state = CLIENT_COMMAND_STATE_WAIT_INPUT; ctx->failed = TRUE; if (args[1].type == IMAP_ARG_LITERAL_SIZE) { /* client won't see "+ OK", so we can abort immediately */ ctx->cmd->client->input_skip_line = FALSE; return -1; } } /* entry names are case-insensitive. handle this by using only lowercase names. */ *entry_r = t_str_lcase(name); *value_r = &args[1]; return 1; }
int main(void) { string_t *str; const char *user, *home, *authorized; const char *extra_env, *key, *value, *const *tmp; bool uid_found = FALSE, gid_found = FALSE; lib_init(); str = t_str_new(1024); user = getenv("USER"); if (user != NULL) { if (strchr(user, '\t') != NULL) { i_error("checkpassword: USER contains TAB"); return 1; } str_printfa(str, "user="******"HOME"); if (home != NULL) { if (strchr(home, '\t') != NULL) { i_error("checkpassword: HOME contains TAB"); return 1; } str_printfa(str, "userdb_home="); str_append_tabescaped(str, home); str_append_c(str, '\t'); } extra_env = getenv("EXTRA"); if (extra_env != NULL) { for (tmp = t_strsplit(extra_env, " "); *tmp != NULL; tmp++) { value = getenv(*tmp); if (value != NULL) { key = t_str_lcase(*tmp); if (strcmp(key, "userdb_uid") == 0) uid_found = TRUE; else if (strcmp(key, "userdb_gid") == 0) gid_found = TRUE; str_append_tabescaped(str, key); str_append_c(str, '='); str_append_tabescaped(str, value); str_append_c(str, '\t'); } } } if (!uid_found) str_printfa(str, "userdb_uid=%s\t", dec2str(getuid())); if (!gid_found) str_printfa(str, "userdb_gid=%s\t", dec2str(getgid())); i_assert(str_len(str) > 0); if (write_full(4, str_data(str), str_len(str)) < 0) { i_error("checkpassword: write_full() failed: %m"); exit(111); } authorized = getenv("AUTHORIZED"); if (authorized == NULL) { /* authentication */ return 0; } else if (strcmp(authorized, "2") == 0) { /* successful passdb/userdb lookup */ return 2; } else { i_error("checkpassword: Script doesn't support passdb/userdb lookup"); return 111; } }