# HG changeset patch # User Timo Sirainen # Date 1214029364 -10800 # Node ID ca2ff54ee9b4824529c877f0d3668c71f68d3342 # Parent 8f3115354d14d9ff2dedf5cb28b80e147f006c8b Added support for IMAP ID extension. diff -r 8f3115354d14 -r ca2ff54ee9b4 README --- a/README Sat Jun 21 09:21:51 2008 +0300 +++ b/README Sat Jun 21 09:22:44 2008 +0300 @@ -31,6 +31,7 @@ 2177 - IMAP4 IDLE command 2221 - IMAP4 Login Referrals 2342 - IMAP4 Namespace + 2971 - IMAP4 ID extension 3348 - IMAP4 Child Mailbox Extension 3502 - IMAP4 MULTIAPPEND Extension 3691 - IMAP4 UNSELECT command diff -r 8f3115354d14 -r ca2ff54ee9b4 configure.in --- a/configure.in Sat Jun 21 09:21:51 2008 +0300 +++ b/configure.in Sat Jun 21 09:22:44 2008 +0300 @@ -19,7 +19,7 @@ sys/uio.h sys/sysmacros.h sys/resource.h sys/select.h libgen.h \ sys/quota.h sys/fs/ufs_quota.h ufs/ufs/quota.h jfs/quota.h sys/fs/quota_common.h \ mntent.h sys/mnttab.h sys/event.h sys/time.h sys/mkdev.h linux/dqblk_xfs.h \ - xfs/xqm.h sasl.h sasl/sasl.h execinfo.h ucontext.h malloc_np.h) + xfs/xqm.h sasl.h sasl/sasl.h execinfo.h ucontext.h malloc_np.h sys/utsname.h) AC_ARG_ENABLE(ipv6, [ --enable-ipv6 Enable IPv6 support (auto)], @@ -425,9 +425,11 @@ ]) AC_SUBST(LIBCAP) +AC_DEFINE(PACKAGE_WEBPAGE, "http://www.dovecot.org/", Support URL) + dnl * after -lsocket and -lnsl tests, inet_aton() may be in them AC_CHECK_FUNCS(fcntl flock lockf inet_aton sigaction getpagesize madvise \ - strcasecmp stricmp vsyslog writev pread \ + strcasecmp stricmp vsyslog writev pread uname \ setrlimit setproctitle seteuid setreuid setegid setresgid \ strtoull strtoll strtouq strtoq \ setpriority quotactl getmntent kqueue kevent backtrace_symbols \ @@ -2183,7 +2185,7 @@ dnl ** capabilities dnl ** -capability="IMAP4rev1 SASL-IR SORT THREAD=REFERENCES MULTIAPPEND UNSELECT LITERAL+ IDLE CHILDREN NAMESPACE LOGIN-REFERRALS UIDPLUS LIST-EXTENDED I18NLEVEL=1 ENABLE CONDSTORE QRESYNC ESEARCH SEARCHRES WITHIN CONTEXT=SEARCH" +capability="IMAP4rev1 SASL-IR SORT THREAD=REFERENCES MULTIAPPEND UNSELECT LITERAL+ IDLE CHILDREN NAMESPACE LOGIN-REFERRALS UIDPLUS LIST-EXTENDED I18NLEVEL=1 ENABLE CONDSTORE QRESYNC ESEARCH SEARCHRES WITHIN CONTEXT=SEARCH ID" AC_DEFINE_UNQUOTED(CAPABILITY_STRING, "$capability", IMAP capabilities) CFLAGS="$CFLAGS $EXTRA_CFLAGS" diff -r 8f3115354d14 -r ca2ff54ee9b4 dovecot-example.conf --- a/dovecot-example.conf Sat Jun 21 09:21:51 2008 +0300 +++ b/dovecot-example.conf Sat Jun 21 09:22:44 2008 +0300 @@ -544,6 +544,14 @@ # Override the IMAP CAPABILITY response. #imap_capability = + # ID field names and values to send to clients. Using * as the value makes + # Dovecot use the default value. The following fields have default values + # currently: name, version, os, os-version, support-url, support-email. + #imap_id_send = + + # ID fields sent by client to log. * means everything. + #imap_id_log = + # Workarounds for various client bugs: # delay-newmail: # Send EXISTS/RECENT new mail notifications only when replying to NOOP diff -r 8f3115354d14 -r ca2ff54ee9b4 src/imap-login/client.c --- a/src/imap-login/client.c Sat Jun 21 09:21:51 2008 +0300 +++ b/src/imap-login/client.c Sat Jun 21 09:22:44 2008 +0300 @@ -10,12 +10,15 @@ #include "str.h" #include "strescape.h" #include "imap-parser.h" +#include "imap-id.h" #include "client.h" #include "client-authenticate.h" #include "auth-client.h" #include "ssl-proxy.h" #include "imap-proxy.h" +#include + /* max. size of one parameter in line, or max reply length in SASL authentication */ #define MAX_INBUF_SIZE 4096 @@ -197,6 +200,27 @@ return 1; } +static int cmd_id(struct imap_client *client, const struct imap_arg *args) +{ + const char *env, *value; + + if (!client->id_logged) { + client->id_logged = TRUE; + env = getenv("IMAP_ID_LOG"); + value = imap_id_args_get_log_reply(args, env); + if (value != NULL) { + client_syslog(&client->common, + t_strdup_printf("ID sent: %s", value)); + } + } + + env = getenv("IMAP_ID_SEND"); + client_send_line(client, t_strdup_printf("* ID %s", + imap_id_reply_generate(env))); + client_send_tagline(client, "OK ID completed."); + return 1; +} + static int cmd_noop(struct imap_client *client) { client_send_tagline(client, "OK NOOP completed."); @@ -228,6 +252,8 @@ return cmd_capability(client); if (strcmp(cmd, "STARTTLS") == 0) return cmd_starttls(client); + if (strcmp(cmd, "ID") == 0) + return cmd_id(client, args); if (strcmp(cmd, "NOOP") == 0) return cmd_noop(client); if (strcmp(cmd, "LOGOUT") == 0) diff -r 8f3115354d14 -r ca2ff54ee9b4 src/imap-login/client.h --- a/src/imap-login/client.h Sat Jun 21 09:21:51 2008 +0300 +++ b/src/imap-login/client.h Sat Jun 21 09:22:44 2008 +0300 @@ -31,6 +31,7 @@ unsigned int input_blocked:1; unsigned int destroyed:1; unsigned int greeting_sent:1; + unsigned int id_logged:1; }; void client_destroy(struct imap_client *client, const char *reason); diff -r 8f3115354d14 -r ca2ff54ee9b4 src/imap/Makefile.am --- a/src/imap/Makefile.am Sat Jun 21 09:21:51 2008 +0300 +++ b/src/imap/Makefile.am Sat Jun 21 09:22:44 2008 +0300 @@ -49,6 +49,7 @@ cmd-examine.c \ cmd-expunge.c \ cmd-fetch.c \ + cmd-id.c \ cmd-idle.c \ cmd-list.c \ cmd-logout.c \ diff -r 8f3115354d14 -r ca2ff54ee9b4 src/imap/client.h --- a/src/imap/client.h Sat Jun 21 09:21:51 2008 +0300 +++ b/src/imap/client.h Sat Jun 21 09:22:44 2008 +0300 @@ -115,6 +115,7 @@ unsigned int destroyed:1; unsigned int handling_input:1; unsigned int syncing:1; + unsigned int id_logged:1; unsigned int input_skip_line:1; /* skip all the data until we've found a new line */ unsigned int modseqs_sent_since_sync:1; diff -r 8f3115354d14 -r ca2ff54ee9b4 src/imap/cmd-id.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/imap/cmd-id.c Sat Jun 21 09:22:44 2008 +0300 @@ -0,0 +1,26 @@ +/* Copyright (c) 2008 Dovecot authors, see the included COPYING file */ + +#include "common.h" +#include "imap-id.h" + +bool cmd_id(struct client_command_context *cmd) +{ + const struct imap_arg *args; + const char *value; + + if (!client_read_args(cmd, 0, 0, &args)) + return FALSE; + + if (!cmd->client->id_logged) { + cmd->client->id_logged = TRUE; + value = imap_id_args_get_log_reply(args, imap_id_log); + if (value != NULL) + i_info("ID sent: %s", value); + } + + client_send_line(cmd->client, t_strdup_printf( + "* ID %s", imap_id_reply_generate(imap_id_send))); + client_send_tagline(cmd, "OK ID completed."); + return TRUE; +} + diff -r 8f3115354d14 -r ca2ff54ee9b4 src/imap/commands.c --- a/src/imap/commands.c Sat Jun 21 09:21:51 2008 +0300 +++ b/src/imap/commands.c Sat Jun 21 09:22:44 2008 +0300 @@ -43,6 +43,7 @@ const struct command imap_ext_commands[] = { { "CANCELUPDATE", cmd_cancelupdate,0 }, { "ENABLE", cmd_enable, 0 }, + { "ID", cmd_id, 0 }, { "IDLE", cmd_idle, COMMAND_FLAG_BREAKS_SEQS }, { "NAMESPACE", cmd_namespace, 0 }, { "SORT", cmd_sort, COMMAND_FLAG_USES_SEQS }, diff -r 8f3115354d14 -r ca2ff54ee9b4 src/imap/commands.h --- a/src/imap/commands.h Sat Jun 21 09:21:51 2008 +0300 +++ b/src/imap/commands.h Sat Jun 21 09:22:44 2008 +0300 @@ -83,6 +83,7 @@ /* IMAP extensions: */ bool cmd_cancelupdate(struct client_command_context *cmd); bool cmd_enable(struct client_command_context *cmd); +bool cmd_id(struct client_command_context *cmd); bool cmd_idle(struct client_command_context *cmd); bool cmd_namespace(struct client_command_context *cmd); bool cmd_sort(struct client_command_context *cmd); diff -r 8f3115354d14 -r ca2ff54ee9b4 src/imap/common.h --- a/src/imap/common.h Sat Jun 21 09:21:51 2008 +0300 +++ b/src/imap/common.h Sat Jun 21 09:22:44 2008 +0300 @@ -31,6 +31,7 @@ extern unsigned int imap_max_line_length; extern enum client_workarounds client_workarounds; extern const char *logout_format; +extern const char *imap_id_send, *imap_id_log; extern string_t *capability_string; diff -r 8f3115354d14 -r ca2ff54ee9b4 src/imap/main.c --- a/src/imap/main.c Sat Jun 21 09:21:51 2008 +0300 +++ b/src/imap/main.c Sat Jun 21 09:22:44 2008 +0300 @@ -41,6 +41,7 @@ unsigned int imap_max_line_length; enum client_workarounds client_workarounds = 0; const char *logout_format; +const char *imap_id_send, *imap_id_log; static struct io *log_io = NULL; static struct module *modules = NULL; @@ -227,6 +228,9 @@ if (logout_format == NULL) logout_format = "bytes=%i/%o"; + imap_id_send = getenv("IMAP_ID_SEND"); + imap_id_log = getenv("IMAP_ID_LOG"); + parse_workarounds(); namespace_pool = pool_alloconly_create("namespaces", 1024); diff -r 8f3115354d14 -r ca2ff54ee9b4 src/lib-imap/Makefile.am --- a/src/lib-imap/Makefile.am Sat Jun 21 09:21:51 2008 +0300 +++ b/src/lib-imap/Makefile.am Sat Jun 21 09:22:44 2008 +0300 @@ -10,6 +10,7 @@ imap-bodystructure.c \ imap-date.c \ imap-envelope.c \ + imap-id.c \ imap-match.c \ imap-parser.c \ imap-quote.c \ @@ -21,6 +22,7 @@ imap-bodystructure.h \ imap-date.h \ imap-envelope.h \ + imap-id.h \ imap-match.h \ imap-parser.h \ imap-quote.h \ diff -r 8f3115354d14 -r ca2ff54ee9b4 src/lib-imap/imap-id.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib-imap/imap-id.c Sat Jun 21 09:22:44 2008 +0300 @@ -0,0 +1,174 @@ +/* Copyright (c) 2008 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "str.h" +#include "str-sanitize.h" +#include "istream.h" +#include "imap-parser.h" +#include "imap-quote.h" +#include "imap-id.h" + +#ifdef HAVE_SYS_UTSNAME_H +# include +#endif + +#ifdef HAVE_UNAME +static struct utsname utsname_result; +static bool utsname_set = FALSE; + +static const char *imap_id_get_uname(const char *key) +{ + if (!utsname_set) { + utsname_set = TRUE; + if (uname(&utsname_result) < 0) { + i_error("uname() failed: %m"); + memset(&utsname_result, 0, sizeof(utsname_result)); + } + } + + if (strcmp(key, "os") == 0) + return utsname_result.sysname; + if (strcmp(key, "os-version") == 0) + return utsname_result.release; + return NULL; +} +#endif + +static const char *imap_id_get_default(const char *key) +{ + if (strcasecmp(key, "name") == 0) + return PACKAGE_NAME; + if (strcasecmp(key, "version") == 0) + return PACKAGE_VERSION; + if (strcasecmp(key, "support-url") == 0) + return PACKAGE_WEBPAGE; + if (strcasecmp(key, "support-email") == 0) + return PACKAGE_BUGREPORT; +#ifdef HAVE_UNAME + return imap_id_get_uname(key); +#endif +} + +static const char * +imap_id_reply_generate_from_imap_args(const struct imap_arg *args) +{ + string_t *str; + const char *key, *value; + + if (args->type == IMAP_ARG_EOL) + return "NIL"; + + str = t_str_new(256); + str_append_c(str, '('); + for (; args->type != IMAP_ARG_EOL; args++) { + if (!IMAP_ARG_TYPE_IS_STRING(args->type)) { + /* broken input */ + if (args[1].type == IMAP_ARG_EOL) + break; + args++; + } else { + /* key */ + if (str_len(str) > 1) + str_append_c(str, ' '); + key = IMAP_ARG_STR_NONULL(args); + imap_dquote_append(str, key); + str_append_c(str, ' '); + /* value */ + if (args[1].type == IMAP_ARG_EOL) { + str_append(str, "NIL"); + break; + } + args++; + if (!IMAP_ARG_TYPE_IS_STRING(args->type)) + value = NULL; + else { + value = IMAP_ARG_STR_NONULL(args); + if (strcmp(value, "*") == 0) + value = imap_id_get_default(key); + } + + if (value == NULL) + str_append(str, "NIL"); + else + imap_quote_append_string(str, value, FALSE); + } + } + if (str_len(str) == 1) { + /* broken */ + return "NIL"; + } + str_append_c(str, ')'); + return str_c(str); +} + +const char *imap_id_reply_generate(const char *settings) +{ + struct istream *input; + struct imap_parser *parser; + const struct imap_arg *args; + const char *ret; + + if (settings == NULL) + return "NIL"; + + input = i_stream_create_from_data(settings, strlen(settings)); + (void)i_stream_read(input); + + parser = imap_parser_create(input, NULL, (size_t)-1); + if (imap_parser_finish_line(parser, 0, 0, &args) <= 0) + ret = "NIL"; + else + ret = imap_id_reply_generate_from_imap_args(args); + + imap_parser_destroy(&parser); + i_stream_destroy(&input); + return ret; +} + +const char *imap_id_args_get_log_reply(const struct imap_arg *args, + const char *settings) +{ + const char *const *keys, *key, *value; + string_t *reply; + bool log_all; + + if (settings == NULL || *settings == '\0' || + args->type != IMAP_ARG_LIST) + return NULL; + + args = IMAP_ARG_LIST_ARGS(args); + + log_all = strcmp(settings, "*") == 0; + reply = t_str_new(256); + keys = t_strsplit_spaces(settings, " "); + while (args->type != IMAP_ARG_EOL && args[1].type != IMAP_ARG_EOL) { + if (args->type != IMAP_ARG_STRING) { + /* broken input */ + args += 2; + continue; + } + key = IMAP_ARG_STR_NONULL(args); + args++; + if (strlen(key) > 30) { + /* broken: ID spec requires fields to be max. 30 + octets */ + args++; + continue; + } + + if (log_all || str_array_icase_find(keys, key)) { + if (IMAP_ARG_TYPE_IS_STRING(args->type)) + value = IMAP_ARG_STR_NONULL(args); + else if (args->type == IMAP_ARG_NIL) + value = "NIL"; + else + value = ""; + if (str_len(reply) > 0) + str_append(reply, ", "); + str_append(reply, str_sanitize(key, 30)); + str_append_c(reply, '='); + str_append(reply, str_sanitize(value, 80)); + } + } + return str_len(reply) == 0 ? NULL : str_c(reply); +} diff -r 8f3115354d14 -r ca2ff54ee9b4 src/lib-imap/imap-id.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib-imap/imap-id.h Sat Jun 21 09:22:44 2008 +0300 @@ -0,0 +1,11 @@ +#ifndef IMAP_ID_H +#define IMAP_ID_H + +/* Return ID reply based on given settings. */ +const char *imap_id_reply_generate(const char *settings); +/* Return a line that should be logged based on given args and settings. + Returns NULL if nothing should be logged. */ +const char *imap_id_args_get_log_reply(const struct imap_arg *args, + const char *settings); + +#endif diff -r 8f3115354d14 -r ca2ff54ee9b4 src/master/login-process.c --- a/src/master/login-process.c Sat Jun 21 09:21:51 2008 +0300 +++ b/src/master/login-process.c Sat Jun 21 09:22:44 2008 +0300 @@ -571,6 +571,8 @@ env_put(t_strconcat("LOG_FORMAT_ELEMENTS=", set->login_log_format_elements, NULL)); env_put(t_strconcat("LOG_FORMAT=", set->login_log_format, NULL)); + env_put(t_strconcat("IMAP_ID_SEND=", set->imap_id_send, NULL)); + env_put(t_strconcat("IMAP_ID_LOG=", set->imap_id_log, NULL)); if (set->login_greeting_capability) env_put("GREETING_CAPABILITY=1"); diff -r 8f3115354d14 -r ca2ff54ee9b4 src/master/mail-process.c --- a/src/master/mail-process.c Sat Jun 21 09:21:51 2008 +0300 +++ b/src/master/mail-process.c Sat Jun 21 09:22:44 2008 +0300 @@ -310,6 +310,8 @@ set->imap_client_workarounds, NULL)); env_put(t_strconcat("IMAP_LOGOUT_FORMAT=", set->imap_logout_format, NULL)); + env_put(t_strconcat("IMAP_ID_SEND=", set->imap_id_send, NULL)); + env_put(t_strconcat("IMAP_ID_LOG=", set->imap_id_log, NULL)); } if (set->protocol == MAIL_PROTOCOL_POP3) { env_put(t_strconcat("POP3_CLIENT_WORKAROUNDS=", diff -r 8f3115354d14 -r ca2ff54ee9b4 src/master/master-settings-defs.c --- a/src/master/master-settings-defs.c Sat Jun 21 09:21:51 2008 +0300 +++ b/src/master/master-settings-defs.c Sat Jun 21 09:22:44 2008 +0300 @@ -114,6 +114,8 @@ DEF_STR(imap_capability), DEF_STR(imap_client_workarounds), DEF_STR(imap_logout_format), + DEF_STR(imap_id_send), + DEF_STR(imap_id_log), /* pop3 */ DEF_BOOL(pop3_no_flag_updates), diff -r 8f3115354d14 -r ca2ff54ee9b4 src/master/master-settings.c --- a/src/master/master-settings.c Sat Jun 21 09:21:51 2008 +0300 +++ b/src/master/master-settings.c Sat Jun 21 09:22:44 2008 +0300 @@ -280,6 +280,8 @@ MEMBER(imap_capability) "", MEMBER(imap_client_workarounds) "", MEMBER(imap_logout_format) "bytes=%i/%o", + MEMBER(imap_id_send) "", + MEMBER(imap_id_log) "", /* pop3 */ MEMBER(pop3_no_flag_updates) FALSE, diff -r 8f3115354d14 -r ca2ff54ee9b4 src/master/master-settings.h --- a/src/master/master-settings.h Sat Jun 21 09:21:51 2008 +0300 +++ b/src/master/master-settings.h Sat Jun 21 09:22:44 2008 +0300 @@ -126,6 +126,8 @@ const char *imap_capability; const char *imap_client_workarounds; const char *imap_logout_format; + const char *imap_id_send; + const char *imap_id_log; /* pop3 */ bool pop3_no_flag_updates;