[Commits] [SCM] claws branch, master, updated. 3.11.1-91-gf35429a
ticho at claws-mail.org
ticho at claws-mail.org
Sat Apr 25 13:31:19 CEST 2015
The branch, master has been updated
via f35429aec1f56ec8bfbba16f6ff736c5f20f19fd (commit)
via 952c6d7e35c3f6733d5fb7b4edcdc9c9b2e52a27 (commit)
via 9b928dfc0e70dfd7010becf1801ccbcfd4ab7bfa (commit)
via b0978b8f43fa2da616a3171001ba75e7c89f9c34 (commit)
from 56422f5638fedb2bb8e0e3312403d0bce577161e (commit)
Summary of changes:
AUTHORS | 1 +
README | 3 +
configure.ac | 15 +
doc/src/readme.txt | 1 +
doc/src/rfc5804.txt | 2747 ++++++++++++++++++++
src/common/session.c | 15 +-
src/common/session.h | 2 +
src/message_search.c | 75 +-
src/message_search.h | 11 +
src/plugins/Makefile.am | 1 +
.../{libravatar => managesieve}/Makefile.am | 23 +-
src/plugins/{libravatar => managesieve}/claws.def | 2 +-
src/plugins/managesieve/managesieve.c | 1031 ++++++++
src/plugins/managesieve/managesieve.h | 193 ++
src/plugins/{libravatar => managesieve}/plugin.def | 0
src/plugins/managesieve/sieve_editor.c | 675 +++++
src/plugins/managesieve/sieve_editor.h | 50 +
src/plugins/managesieve/sieve_manager.c | 803 ++++++
.../managesieve/sieve_manager.h} | 40 +-
src/plugins/managesieve/sieve_plugin.c | 154 ++
src/plugins/managesieve/sieve_prefs.c | 520 ++++
src/plugins/managesieve/sieve_prefs.h | 51 +
src/plugins/{tnef_parse => managesieve}/version.rc | 4 +-
23 files changed, 6338 insertions(+), 79 deletions(-)
create mode 100644 doc/src/rfc5804.txt
copy src/plugins/{libravatar => managesieve}/Makefile.am (76%)
copy src/plugins/{libravatar => managesieve}/claws.def (97%)
create mode 100644 src/plugins/managesieve/managesieve.c
create mode 100644 src/plugins/managesieve/managesieve.h
copy src/plugins/{libravatar => managesieve}/plugin.def (100%)
create mode 100644 src/plugins/managesieve/sieve_editor.c
create mode 100644 src/plugins/managesieve/sieve_editor.h
create mode 100644 src/plugins/managesieve/sieve_manager.c
copy src/{viewtypes.h => plugins/managesieve/sieve_manager.h} (58%)
create mode 100644 src/plugins/managesieve/sieve_plugin.c
create mode 100644 src/plugins/managesieve/sieve_prefs.c
create mode 100644 src/plugins/managesieve/sieve_prefs.h
copy src/plugins/{tnef_parse => managesieve}/version.rc (83%)
- Log -----------------------------------------------------------------
commit f35429aec1f56ec8bfbba16f6ff736c5f20f19fd
Author: Andrej Kacian <ticho at claws-mail.org>
Date: Sat Apr 25 13:29:51 2015 +0200
Update AUTHORS and README regarding the Managesieve plugin.
diff --git a/AUTHORS b/AUTHORS
index 2f1d7f5..4b83479 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -312,3 +312,4 @@ contributors (in addition to the above; based on Changelog)
Anton Butanaev
Daniel Zaoui
Johan Vromans
+ Charles Lehner
diff --git a/README b/README
index 4646af4..0ed6b9a 100644
--- a/README
+++ b/README
@@ -147,6 +147,9 @@ All plugin preferences can be found under
files can be added to the folder tree and used like regular mailboxes.
LibEtPan! is available from http://www.etpan.org/.
+ o Managesieve
+ Manage sieve filters on a server using the ManageSieve protocol.
+
o Newmail
Writes a header summary to a log file for each mail received after
sorting. The file for the summary is ~/Mail/NewLog.
commit 952c6d7e35c3f6733d5fb7b4edcdc9c9b2e52a27
Author: Charles Lehner <cel at celehner.com>
Date: Sat Nov 22 22:32:35 2014 -0500
Add ManageSieve plugin
diff --git a/configure.ac b/configure.ac
index 9439980..60cca19 100644
--- a/configure.ac
+++ b/configure.ac
@@ -973,6 +973,10 @@ AC_ARG_ENABLE(mailmbox-plugin,
[ --disable-mailmbox-plugin Do not build mailmbox plugin],
[enable_mailmbox_plugin=$enableval], [enable_mailmbox_plugin=auto])
+AC_ARG_ENABLE(managesieve-plugin,
+ [ --disable-managesieve-plugin Do not build managesieve plugin],
+ [enable_managesieve_plugin=$enableval], [enable_managesieve_plugin=auto])
+
AC_ARG_ENABLE(newmail-plugin,
[ --disable-newmail-plugin Do not build newmail plugin],
[enable_newmail_plugin=$enableval], [enable_newmail_plugin=auto])
@@ -1517,6 +1521,15 @@ else
AC_MSG_RESULT(no)
fi
+AC_MSG_CHECKING([whether to build managesieve plugin])
+if test x"$enable_managesieve_plugin" != xno; then
+ PLUGINS="$PLUGINS managesieve"
+ AC_MSG_RESULT(yes)
+else
+ DISABLED_PLUGINS="$DISABLED_PLUGINS managesieve"
+ AC_MSG_RESULT(no)
+fi
+
AC_MSG_CHECKING([whether to build newmail plugin])
if test x"$enable_newmail_plugin" != xno; then
PLUGINS="$PLUGINS newmail"
@@ -1870,6 +1883,7 @@ AM_CONDITIONAL(BUILD_GDATA_PLUGIN, test x"$enable_gdata_plugin" != xno)
AM_CONDITIONAL(BUILD_GEOLOCATION_PLUGIN, test x"$enable_geolocation_plugin" != xno)
AM_CONDITIONAL(BUILD_LIBRAVATAR_PLUGIN, test x"$enable_libravatar_plugin" != xno)
AM_CONDITIONAL(BUILD_MAILMBOX_PLUGIN, test x"$enable_mailmbox_plugin" != xno)
+AM_CONDITIONAL(BUILD_MANAGESIEVE_PLUGIN, test x"$enable_managesieve_plugin" != xno)
AM_CONDITIONAL(BUILD_NEWMAIL_PLUGIN, test x"$enable_newmail_plugin" != xno)
AM_CONDITIONAL(BUILD_NOTIFICATION_PLUGIN, test x"$enable_notification_plugin" != xno)
AM_CONDITIONAL(BUILD_HOTKEYS, test x"$enable_notification_plugin" != xno -a x"$HAVE_HOTKEYS" = xyes)
@@ -1918,6 +1932,7 @@ src/plugins/gdata/Makefile
src/plugins/geolocation/Makefile
src/plugins/libravatar/Makefile
src/plugins/mailmbox/Makefile
+src/plugins/managesieve/Makefile
src/plugins/newmail/Makefile
src/plugins/notification/Makefile
src/plugins/notification/gtkhotkey/Makefile
diff --git a/doc/src/readme.txt b/doc/src/readme.txt
index a2c8694..9e2e1b7 100644
--- a/doc/src/readme.txt
+++ b/doc/src/readme.txt
@@ -16,3 +16,4 @@ rfc2822.txt Internet Message Format
rfc2980.txt Common NNTP Extensions
rfc3156.txt MIME Security with OpenPGP
rfc3501.txt INTERNET MESSAGE ACCESS PROTOCOL - VERSION 4rev1
+rfc5804.txt A Protocol for Remotely Managing Sieve Scripts
diff --git a/doc/src/rfc5804.txt b/doc/src/rfc5804.txt
new file mode 100644
index 0000000..d6deaa8
--- /dev/null
+++ b/doc/src/rfc5804.txt
@@ -0,0 +1,2747 @@
+
+
+
+
+
+
+Internet Engineering Task Force (IETF) A. Melnikov, Ed.
+Request for Comments: 5804 Isode Limited
+Category: Standards Track T. Martin
+ISSN: 2070-1721 BeThereBeSquare, Inc.
+ July 2010
+
+
+ A Protocol for Remotely Managing Sieve Scripts
+
+Abstract
+
+ Sieve scripts allow users to filter incoming email. Message stores
+ are commonly sealed servers so users cannot log into them, yet users
+ must be able to update their scripts on them. This document
+ describes a protocol "ManageSieve" for securely managing Sieve
+ scripts on a remote server. This protocol allows a user to have
+ multiple scripts, and also alerts a user to syntactically flawed
+ scripts.
+
+Status of This Memo
+
+ This is an Internet Standards Track document.
+
+ This document is a product of the Internet Engineering Task Force
+ (IETF). It represents the consensus of the IETF community. It has
+ received public review and has been approved for publication by the
+ Internet Engineering Steering Group (IESG). Further information on
+ Internet Standards is available in Section 2 of RFC 5741.
+
+ Information about the current status of this document, any errata,
+ and how to provide feedback on it may be obtained at
+ http://www.rfc-editor.org/info/rfc5804.
+
+Copyright Notice
+
+ Copyright (c) 2010 IETF Trust and the persons identified as the
+ document authors. All rights reserved.
+
+ This document is subject to BCP 78 and the IETF Trust's Legal
+ Provisions Relating to IETF Documents
+ (http://trustee.ietf.org/license-info) in effect on the date of
+ publication of this document. Please review these documents
+ carefully, as they describe your rights and restrictions with respect
+ to this document. Code Components extracted from this document must
+ include Simplified BSD License text as described in Section 4.e of
+ the Trust Legal Provisions and are provided without warranty as
+ described in the Simplified BSD License.
+
+
+
+
+Melnikov & Martin Standards Track [Page 1]
+
+RFC 5804 ManageSieve July 2010
+
+
+Table of Contents
+
+ 1. Introduction ....................................................3
+ 1.1. Commands and Responses .....................................3
+ 1.2. Syntax .....................................................3
+ 1.3. Response Codes .............................................3
+ 1.4. Active Script ..............................................6
+ 1.5. Quotas .....................................................6
+ 1.6. Script Names ...............................................6
+ 1.7. Capabilities ...............................................7
+ 1.8. Transport ..................................................9
+ 1.9. Conventions Used in This Document .........................10
+ 2. Commands .......................................................10
+ 2.1. AUTHENTICATE Command ......................................11
+ 2.1.1. Use of SASL PLAIN Mechanism over TLS ...............16
+ 2.2. STARTTLS Command ..........................................16
+ 2.2.1. Server Identity Check ..............................17
+ 2.3. LOGOUT Command ............................................20
+ 2.4. CAPABILITY Command ........................................20
+ 2.5. HAVESPACE Command .........................................20
+ 2.6. PUTSCRIPT Command .........................................21
+ 2.7. LISTSCRIPTS Command .......................................23
+ 2.8. SETACTIVE Command .........................................24
+ 2.9. GETSCRIPT Command .........................................25
+ 2.10. DELETESCRIPT Command .....................................25
+ 2.11. RENAMESCRIPT Command .....................................26
+ 2.12. CHECKSCRIPT Command ......................................27
+ 2.13. NOOP Command .............................................28
+ 2.14. Recommended Extensions ...................................28
+ 2.14.1. UNAUTHENTICATE Command ............................28
+ 3. Sieve URL Scheme ...............................................29
+ 4. Formal Syntax ..................................................31
+ 5. Security Considerations ........................................37
+ 6. IANA Considerations ............................................38
+ 6.1. ManageSieve Capability Registration Template ..............39
+ 6.2. Registration of Initial ManageSieve Capabilities ..........39
+ 6.3. ManageSieve Response Code Registration Template ...........41
+ 6.4. Registration of Initial ManageSieve Response Codes ........41
+ 7. Internationalization Considerations ............................46
+ 8. Acknowledgements ...............................................46
+ 9. References .....................................................47
+ 9.1. Normative References ......................................47
+ 9.2. Informative References ....................................48
+
+
+
+
+
+
+
+
+Melnikov & Martin Standards Track [Page 2]
+
+RFC 5804 ManageSieve July 2010
+
+
+1. Introduction
+
+1.1. Commands and Responses
+
+ A ManageSieve connection consists of the establishment of a client/
+ server network connection, an initial greeting from the server, and
+ client/server interactions. These client/server interactions consist
+ of a client command, server data, and a server completion result
+ response.
+
+ All interactions transmitted by client and server are in the form of
+ lines, that is, strings that end with a CRLF. The protocol receiver
+ of a ManageSieve client or server is either reading a line or reading
+ a sequence of octets with a known count followed by a line.
+
+1.2. Syntax
+
+ ManageSieve is a line-oriented protocol much like [IMAP] or [ACAP],
+ which runs over TCP. There are three data types: atoms, numbers and
+ strings. Strings may be quoted or literal. See [ACAP] for detailed
+ descriptions of these types.
+
+ Each command consists of an atom (the command name) followed by zero
+ or more strings and numbers terminated by CRLF.
+
+ All client queries are replied to with either an OK, NO, or BYE
+ response. Each response may be followed by a response code (see
+ Section 1.3) and by a string consisting of human-readable text in the
+ local language (as returned by the LANGUAGE capability; see
+ Section 1.7), encoded in UTF-8 [UTF-8]. The contents of the string
+ SHOULD be shown to the user ,and implementations MUST NOT attempt to
+ parse the message for meaning.
+
+ The BYE response SHOULD be used if the server wishes to close the
+ connection. A server may wish to do this because the client was idle
+ for too long or there were too many failed authentication attempts.
+ This response can be issued at any time and should be immediately
+ followed by a server hang-up of the connection. If a server has an
+ inactivity timeout resulting in client autologout, it MUST be no less
+ than 30 minutes after successful authentication. The inactivity
+ timeout MAY be less before authentication.
+
+1.3. Response Codes
+
+ An OK, NO, or BYE response from the server MAY contain a response
+ code to describe the event in a more detailed machine-parsable
+ fashion. A response code consists of data inside parentheses in the
+ form of an atom, possibly followed by a space and arguments.
+
+
+
+Melnikov & Martin Standards Track [Page 3]
+
+RFC 5804 ManageSieve July 2010
+
+
+ Response codes are defined when there is a specific action that a
+ client can take based upon the additional information. In order to
+ support future extension, the response code is represented as a
+ slash-separated (Solidus, %x2F) hierarchy with each level of
+ hierarchy representing increasing detail about the error. Response
+ codes MUST NOT start with the Solidus character. Clients MUST
+ tolerate additional hierarchical response code detail that they don't
+ understand. For example, if the client supports the "QUOTA" response
+ code, but doesn't understand the "QUOTA/MAXSCRIPTS" response code, it
+ should treat "QUOTA/MAXSCRIPTS" as "QUOTA".
+
+ Client implementations MUST tolerate (ignore) response codes that
+ they do not recognize.
+
+ The currently defined response codes are the following:
+
+ AUTH-TOO-WEAK
+
+ This response code is returned in the NO or BYE response from an
+ AUTHENTICATE command. It indicates that site security policy forbids
+ the use of the requested mechanism for the specified authentication
+ identity.
+
+ ENCRYPT-NEEDED
+
+ This response code is returned in the NO or BYE response from an
+ AUTHENTICATE command. It indicates that site security policy
+ requires the use of a strong encryption mechanism for the specified
+ authentication identity and mechanism.
+
+ QUOTA
+
+ If this response code is returned in the NO/BYE response, it means
+ that the command would have placed the user above the site-defined
+ quota constraints. If this response code is returned in the OK
+ response, it can mean that the user's storage is near its quota, or
+ it can mean that the account exceeded its quota but that the
+ condition is being allowed by the server (the server supports
+ so-called soft quotas). The QUOTA response code has two more
+ detailed variants: "QUOTA/MAXSCRIPTS" (the maximum number of per-user
+ scripts) and "QUOTA/MAXSIZE" (the maximum script size).
+
+ REFERRAL
+
+ This response code may be returned with a BYE result from any
+ command, and includes a mandatory parameter that indicates what
+ server to access to manage this user's Sieve scripts. The server
+ will be specified by a Sieve URL (see Section 3). The scriptname
+
+
+
+Melnikov & Martin Standards Track [Page 4]
+
+RFC 5804 ManageSieve July 2010
+
+
+ portion of the URL MUST NOT be specified. The client should
+ authenticate to the specified server and use it for all further
+ commands in the current session.
+
+ SASL
+
+ This response code can occur in the OK response to a successful
+ AUTHENTICATE command and includes the optional final server response
+ data from the server as specified by [SASL].
+
+ TRANSITION-NEEDED
+
+ This response code occurs in a NO response of an AUTHENTICATE
+ command. It indicates that the user name is valid, but the entry in
+ the authentication database needs to be updated in order to permit
+ authentication with the specified mechanism. This is typically done
+ by establishing a secure channel using TLS, verifying server identity
+ as specified in Section 2.2.1, and finally authenticating once using
+ the [PLAIN] authentication mechanism. The selected mechanism SHOULD
+ then work for authentications in subsequent sessions.
+
+ This condition can happen if a user has an entry in a system
+ authentication database such as Unix /etc/passwd, but does not have
+ credentials suitable for use by the specified mechanism.
+
+ TRYLATER
+
+ A command failed due to a temporary server failure. The client MAY
+ continue using local information and try the command later. This
+ response code only makes sense when returned in a NO/BYE response.
+
+ ACTIVE
+
+ A command failed because it is not allowed on the active script, for
+ example, DELETESCRIPT on the active script. This response code only
+ makes sense when returned in a NO/BYE response.
+
+ NONEXISTENT
+
+ A command failed because the referenced script name doesn't exist.
+ This response code only makes sense when returned in a NO/BYE
+ response.
+
+ ALREADYEXISTS
+
+ A command failed because the referenced script name already exists.
+ This response code only makes sense when returned in a NO/BYE
+ response.
+
+
+
+Melnikov & Martin Standards Track [Page 5]
+
+RFC 5804 ManageSieve July 2010
+
+
+ TAG
+
+ This response code name is followed by a string specified in the
+ command. See Section 2.13 for a possible use case.
+
+ WARNINGS
+
+ This response code MAY be returned by the server in the OK response
+ (but it might be returned with the NO/BYE response as well) and
+ signals the client that even though the script is syntactically
+ valid, it might contain errors not intended by the script writer.
+ This response code is typically returned in response to PUTSCRIPT
+ and/or CHECKSCRIPT commands. A client seeing such response code
+ SHOULD present the returned warning text to the user.
+
+1.4. Active Script
+
+ A user may have multiple Sieve scripts on the server, yet only one
+ script may be used for filtering of incoming messages. This is the
+ active script. Users may have zero or one active script and MUST use
+ the SETACTIVE command described below for changing the active script
+ or disabling Sieve processing. For example, users may have an
+ everyday script they normally use and a special script they use when
+ they go on vacation. Users can change which script is being used
+ without having to download and upload a script stored somewhere else.
+
+1.5. Quotas
+
+ Servers SHOULD impose quotas to prevent malicious users from
+ overflowing available storage. If a command would place a user over
+ a quota setting, servers that impose such quotas MUST reply with a NO
+ response containing the QUOTA response code. Client implementations
+ MUST be able to handle commands failing because of quota
+ restrictions.
+
+1.6. Script Names
+
+ A Sieve script name is a sequence of Unicode characters encoded in
+ UTF-8 [UTF-8]. A script name MUST comply with Net-Unicode Definition
+ (Section 2 of [NET-UNICODE]), with the additional restriction of
+ prohibiting the following Unicode characters:
+
+ o 0000-001F; [CONTROL CHARACTERS]
+
+ o 007F; DELETE
+
+ o 0080-009F; [CONTROL CHARACTERS]
+
+
+
+
+Melnikov & Martin Standards Track [Page 6]
+
+RFC 5804 ManageSieve July 2010
+
+
+ o 2028; LINE SEPARATOR
+
+ o 2029; PARAGRAPH SEPARATOR
+
+ Sieve script names MUST be at least one octet (and hence Unicode
+ character) long. Zero octets script name has a special meaning (see
+ Section 2.8). Servers MUST allow names of up to 128 Unicode
+ characters in length (which can take up to 512 bytes when encoded in
+ UTF-8, not counting the terminating NUL), and MAY allow longer names.
+ A server that receives a script name longer than its internal limit
+ MUST reject the corresponding operation, in particular it MUST NOT
+ truncate the script name.
+
+1.7. Capabilities
+
+ Server capabilities are sent automatically by the server upon a
+ client connection, or after successful STARTTLS and AUTHENTICATE
+ (which establishes a Simple Authentication and Security Layer (SASL))
+ commands. Capabilities may change immediately after a successfully
+ completed STARTTLS command, and/or immediately after a successfully
+ completed AUTHENTICATE command, and/or after a successfully completed
+ UNAUTHENTICATE command (see Section 2.14.1). Capabilities MUST
+ remain static at all other times.
+
+ Clients MAY request the capabilities at a later time by issuing the
+ CAPABILITY command described later. The capabilities consist of a
+ series of lines each with one or two strings. The first string is
+ the name of the capability, which is case-insensitive. The second
+ optional string is the value associated with that capability. Order
+ of capabilities is arbitrary, but each capability name can appear at
+ most once.
+
+ The following capabilities are defined in this document:
+
+ IMPLEMENTATION - Name of implementation and version. This capability
+ MUST always be returned by the server.
+
+ SASL - List of SASL mechanisms supported by the server, each
+ separated by a space. This list can be empty if and only if STARTTLS
+ is also advertised. This means that the client must negotiate TLS
+ encryption with STARTTLS first, at which point the SASL capability
+ will list a non-empty list of SASL mechanisms.
+
+ SIEVE - List of space-separated Sieve extensions (as listed in Sieve
+ "require" action [SIEVE]) supported by the Sieve engine. This
+ capability MUST always be returned by the server.
+
+
+
+
+
+Melnikov & Martin Standards Track [Page 7]
+
+RFC 5804 ManageSieve July 2010
+
+
+ STARTTLS - If TLS [TLS] is supported by this implementation. Before
+ advertising this capability a server MUST verify to the best of its
+ ability that TLS can be successfully negotiated by a client with
+ common cipher suites. Specifically, a server should verify that a
+ server certificate has been installed and that the TLS subsystem has
+ successfully initialized. This capability SHOULD NOT be advertised
+ once STARTTLS or AUTHENTICATE command completes successfully. Client
+ and server implementations MUST implement the STARTTLS extension.
+
+ MAXREDIRECTS - Specifies the limit on the number of Sieve "redirect"
+ actions a script can perform during a single evaluation. Note that
+ this is different from the total number of "redirect" actions a
+ script can contain. The value is a non-negative number represented
+ as a ManageSieve string.
+
+ NOTIFY - A space-separated list of URI schema parts for supported
+ notification methods. This capability MUST be specified if the Sieve
+ implementation supports the "enotify" extension [NOTIFY].
+
+ LANGUAGE - The language (<Language-Tag> from [RFC5646]) currently
+ used for human-readable error messages. If this capability is not
+ returned, the "i-default" [RFC2277] language is assumed. Note that
+ the current language MAY be per-user configurable (i.e., it MAY
+ change after authentication).
+
+ OWNER - The canonical name of the logged-in user (SASL "authorization
+ identity") encoded in UTF-8. This capability MUST NOT be returned in
+ unauthenticated state and SHOULD be returned once the AUTHENTICATE
+ command succeeds.
+
+ VERSION - This capability MUST be returned by servers compliant with
+ this document or its successor. For servers compliant with this
+ document, the capability value is the string "1.0". Lack of this
+ capability means that the server predates this specification and thus
+ doesn't support the following commands: RENAMESCRIPT, CHECKSCRIPT,
+ and NOOP.
+
+ Section 2.14 defines some additional ManageSieve extensions and their
+ respective capabilities.
+
+ A server implementation MUST return SIEVE, IMPLEMENTATION, and
+ VERSION capabilities.
+
+ A client implementation MUST ignore any listed capabilities that it
+ does not understand.
+
+
+
+
+
+
+Melnikov & Martin Standards Track [Page 8]
+
+RFC 5804 ManageSieve July 2010
+
+
+ Example:
+
+ S: "IMPlemENTATION" "Example1 ManageSieved v001"
+ S: "SASl" "DIGEST-MD5 GSSAPI"
+ S: "SIeVE" "fileinto vacation"
+ S: "StaRTTLS"
+ S: "NOTIFY" "xmpp mailto"
+ S: "MAXREdIRECTS" "5"
+ S: "VERSION" "1.0"
+ S: OK
+
+ After successful authentication, this might look like this:
+
+ Example:
+
+ S: "IMPlemENTATION" "Example1 ManageSieved v001"
+ S: "SASl" "DIGEST-MD5 GSSAPI"
+ S: "SIeVE" "fileinto vacation"
+ S: "NOTIFY" "xmpp mailto"
+ S: "OWNER" "alexey at example.com"
+ S: "MAXREdIRECTS" "5"
+ S: "VERSION" "1.0"
+ S: OK
+
+1.8. Transport
+
+ The ManageSieve protocol assumes a reliable data stream such as that
+ provided by TCP. When TCP is used, a ManageSieve server typically
+ listens on port 4190.
+
+ Before opening the TCP connection, the ManageSieve client first MUST
+ resolve the Domain Name System (DNS) hostname associated with the
+ receiving entity and determine the appropriate TCP port for
+ communication with the receiving entity. The process is as follows:
+
+ 1. Attempt to resolve the hostname using a [DNS-SRV] Service of
+ "sieve" and a Proto of "tcp" for the target domain (e.g.,
+ "example.net"), resulting in resource records such as
+ "_sieve._tcp.example.net.". The result of the SRV lookup, if
+ successful, will be one or more combinations of a port and
+ hostname; the ManageSieve client MUST resolve the returned
+ hostnames to IPv4/IPv6 addresses according to returned SRV record
+ weight. IP addresses from the first successfully resolved
+ hostname (with the corresponding port number returned by SRV
+ lookup) are used to connect to the server. If connection using
+ one of the IP addresses fails, the next resolved IP address is
+
+
+
+
+
+Melnikov & Martin Standards Track [Page 9]
+
+RFC 5804 ManageSieve July 2010
+
+
+ used to connect. If connection to all resolved IP addresses
+ fails, then the resolution/connect is repeated for the next
+ hostname returned by SRV lookup.
+
+ 2. If the SRV lookup fails, the fallback SHOULD be a normal IPv4 or
+ IPv6 address record resolution to determine the IP address, where
+ the port used is the default ManageSieve port of 4190.
+
+1.9. Conventions Used in This Document
+
+ The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT",
+ "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this
+ document are to be interpreted as described in [KEYWORDS].
+
+ In examples, "C:" and "S:" indicate lines sent by the client and
+ server respectively. Line breaks that do not start a new "C:" or
+ "S:" exist for editorial reasons.
+
+ Examples of authentication in this document are using DIGEST-MD5
+ [DIGEST-MD5] and GSSAPI [GSSAPI] SASL mechanisms.
+
+2. Commands
+
+ This section and its subsections describe valid ManageSieve commands.
+ Upon initial connection to the server, the client's session is in
+ non-authenticated state. Prior to successful authentication, only
+ the AUTHENTICATE, CAPABILITY, STARTTLS, LOGOUT, and NOOP (see Section
+ 2.13) commands are valid. ManageSieve extensions MAY define other
+ commands that are valid in non-authenticated state. Servers MUST
+ reject all other commands with a NO response. Clients may pipeline
+ commands (send more than one command at a time without waiting for
+ completion of the first command). However, a group of commands sent
+ together MUST NOT have an AUTHENTICATE (*), a STARTTLS, or a
+ HAVESPACE command anywhere but the last command in the list.
+
+ (*) - The only exception to this rule is when the AUTHENTICATE
+ command contains an initial response for a SASL mechanism that allows
+ clients to send data first, the mechanism is known to complete in one
+ round trip, and the mechanism doesn't negotiate a SASL security
+ layer. Two examples of such SASL mechanisms are PLAIN [PLAIN] and
+ EXTERNAL [SASL].
+
+
+
+
+
+
+
+
+
+
+Melnikov & Martin Standards Track [Page 10]
+
+RFC 5804 ManageSieve July 2010
+
+
+2.1. AUTHENTICATE Command
+
+ Arguments: String - mechanism
+ String - initial data (optional)
+
+ The AUTHENTICATE command indicates a SASL [SASL] authentication
+ mechanism to the server. If the server supports the requested
+ authentication mechanism, it performs an authentication protocol
+ exchange to identify and authenticate the user. Optionally, it also
+ negotiates a security layer for subsequent protocol interactions. If
+ the requested authentication mechanism is not supported, the server
+ rejects the AUTHENTICATE command by sending the NO response.
+
+ The authentication protocol exchange consists of a series of server
+ challenges and client responses that are specific to the selected
+ authentication mechanism. A server challenge consists of a string
+ (quoted or literal) followed by a CRLF. The contents of the string
+ is a base-64 encoding [BASE64] of the SASL data. A client response
+ consists of a string (quoted or literal) with the base-64 encoding of
+ the SASL data followed by a CRLF. If the client wishes to cancel the
+ authentication exchange, it issues a string containing a single "*".
+ If the server receives such a response, it MUST reject the
+ AUTHENTICATE command by sending a NO reply.
+
+ Note that an empty challenge/response is sent as an empty string. If
+ the mechanism dictates that the final response is sent by the server,
+ this data MAY be placed within the data portion of the SASL response
+ code to save a round trip.
+
+ The optional initial-response argument to the AUTHENTICATE command is
+ used to save a round trip when using authentication mechanisms that
+ are defined to send no data in the initial challenge. When the
+ initial-response argument is used with such a mechanism, the initial
+ empty challenge is not sent to the client and the server uses the
+ data in the initial-response argument as if it were sent in response
+ to the empty challenge. If the initial-response argument to the
+ AUTHENTICATE command is used with a mechanism that sends data in the
+ initial challenge, the server MUST reject the AUTHENTICATE command by
+ sending the NO response.
+
+ The service name specified by this protocol's profile of SASL is
+ "sieve".
+
+ Reauthentication is not supported by ManageSieve protocol's profile
+ of SASL. That is, after a successfully completed AUTHENTICATE
+ command, no more AUTHENTICATE commands may be issued in the same
+ session. After a successful AUTHENTICATE command completes, a server
+ MUST reject any further AUTHENTICATE commands with a NO reply.
+
+
+
+Melnikov & Martin Standards Track [Page 11]
+
+RFC 5804 ManageSieve July 2010
+
+
+ However, note that a server may implement the UNAUTHENTICATE
+ extension described in Section 2.14.1.
+
+ If a security layer is negotiated through the SASL authentication
+ exchange, it takes effect immediately following the CRLF that
+ concludes the successful authentication exchange for the client, and
+ the CRLF of the OK response for the server.
+
+ When a security layer takes effect, the ManageSieve protocol is reset
+ to the initial state (the state in ManageSieve after a client has
+ connected to the server). The server MUST discard any knowledge
+ obtained from the client that was not obtained from the SASL (or TLS)
+ negotiation itself. Likewise, the client MUST discard any knowledge
+ obtained from the server, such as the list of ManageSieve extensions,
+ that was not obtained from the SASL (and/or TLS) negotiation itself.
+ (Note that a client MAY compare the advertised SASL mechanisms before
+ and after authentication in order to detect an active down-
+ negotiation attack. See below.)
+
+ Once a SASL security layer is established, the server MUST re-issue
+ the capability results, followed by an OK response. This is
+ necessary to protect against man-in-the-middle attacks that alter the
+ capabilities list prior to SASL negotiation. The capability results
+ MUST include all SASL mechanisms the server was capable of
+ negotiating with that client. This is done in order to allow the
+ client to detect an active down-negotiation attack. If a user-
+ oriented client detects such a down-negotiation attack, it SHOULD
+ either notify the user (it MAY give the user the opportunity to
+ continue with the ManageSieve session in this case) or close the
+ transport connection and indicate that a down-negotiation attack
+ might be in progress. If an automated client detects a down-
+ negotiation attack, it SHOULD return or log an error indicating that
+ a possible attack might be in progress and/or SHOULD close the
+ transport connection.
+
+ When both [TLS] and SASL security layers are in effect, the TLS
+ encoding MUST be applied (when sending data) after the SASL encoding.
+
+ Server implementations SHOULD support SASL proxy authentication so
+ that an administrator can administer a user's scripts. Proxy
+ authentication is when a user authenticates as herself/himself but
+ requests the server to act (authorize) as another user.
+
+ The authorization identity generated by this [SASL] exchange is a
+ "simple username" (in the sense defined in [SASLprep]), and both
+ client and server MUST use the [SASLprep] profile of the [StringPrep]
+ algorithm to prepare these names for transmission or comparison. If
+ preparation of the authorization identity fails or results in an
+
+
+
+Melnikov & Martin Standards Track [Page 12]
+
+RFC 5804 ManageSieve July 2010
+
+
+ empty string (unless it was transmitted as the empty string), the
+ server MUST fail the authentication.
+
+ If an AUTHENTICATE command fails with a NO response, the client MAY
+ try another authentication mechanism by issuing another AUTHENTICATE
+ command. In other words, the client may request authentication types
+ in decreasing order of preference.
+
+ Note that a failed (NO) response to the AUTHENTICATE command may
+ contain one of the following response codes: AUTH-TOO-WEAK, ENCRYPT-
+ NEEDED, or TRANSITION-NEEDED. See Section 1.3 for detailed
+ description of the relevant conditions.
+
+ To ensure interoperability, both client and server implementations of
+ the ManageSieve protocol MUST implement the SCRAM-SHA-1 [SCRAM] SASL
+ mechanism, as well as [PLAIN] over [TLS].
+
+ Note: use of PLAIN over TLS reflects current use of PLAIN over TLS in
+ other email-related protocols; however, a longer-term goal is to
+ migrate email-related protocols from using PLAIN over TLS to SCRAM-
+ SHA-1 mechanism.
+
+ Examples (Note that long lines are folded for readability and are not
+ part of protocol exchange):
+
+ S: "IMPLEMENTATION" "Example1 ManageSieved v001"
+ S: "SASL" "DIGEST-MD5 GSSAPI"
+ S: "SIEVE" "fileinto vacation"
+ S: "STARTTLS"
+ S: "VERSION" "1.0"
+ S: OK
+ C: Authenticate "DIGEST-MD5"
+ S: "cmVhbG09ImVsd29vZC5pbm5vc29mdC5leGFtcGxlLmNvbSIsbm9uY2U9Ik
+ 9BNk1HOXRFUUdtMmhoIixxb3A9ImF1dGgiLGFsZ29yaXRobT1tZDUtc2Vz
+ cyxjaGFyc2V0PXV0Zi04"
+ C: "Y2hhcnNldD11dGYtOCx1c2VybmFtZT0iY2hyaXMiLHJlYWxtPSJlbHdvb2
+ QuaW5ub3NvZnQuZXhhbXBsZS5jb20iLG5vbmNlPSJPQTZNRzl0RVFHbTJo
+ aCIsbmM9MDAwMDAwMDEsY25vbmNlPSJPQTZNSFhoNlZxVHJSayIsZGlnZX
+ N0LXVyaT0ic2lldmUvZWx3b29kLmlubm9zb2Z0LmV4YW1wbGUuY29tIixy
+ ZXNwb25zZT1kMzg4ZGFkOTBkNGJiZDc2MGExNTIzMjFmMjE0M2FmNyxxb3
+ A9YXV0aA=="
+ S: OK (SASL "cnNwYXV0aD1lYTQwZjYwMzM1YzQyN2I1NTI3Yjg0ZGJhYmNkZ
+ mZmZA==")
+
+
+
+
+
+
+
+
+Melnikov & Martin Standards Track [Page 13]
+
+RFC 5804 ManageSieve July 2010
+
+
+ A slightly different variant of the same authentication exchange is:
+
+ S: "IMPLEMENTATION" "Example1 ManageSieved v001"
+ S: "SASL" "DIGEST-MD5 GSSAPI"
+ S: "SIEVE" "fileinto vacation"
+ S: "VERSION" "1.0"
+ S: "STARTTLS"
+ S: OK
+ C: Authenticate "DIGEST-MD5"
+ S: {136}
+ S: cmVhbG09ImVsd29vZC5pbm5vc29mdC5leGFtcGxlLmNvbSIsbm9uY2U9Ik
+ 9BNk1HOXRFUUdtMmhoIixxb3A9ImF1dGgiLGFsZ29yaXRobT1tZDUtc2Vz
+ cyxjaGFyc2V0PXV0Zi04
+ C: {300+}
+ C: Y2hhcnNldD11dGYtOCx1c2VybmFtZT0iY2hyaXMiLHJlYWxtPSJlbHdvb2
+ QuaW5ub3NvZnQuZXhhbXBsZS5jb20iLG5vbmNlPSJPQTZNRzl0RVFHbTJo
+ aCIsbmM9MDAwMDAwMDEsY25vbmNlPSJPQTZNSFhoNlZxVHJSayIsZGlnZX
+ N0LXVyaT0ic2lldmUvZWx3b29kLmlubm9zb2Z0LmV4YW1wbGUuY29tIixy
+ ZXNwb25zZT1kMzg4ZGFkOTBkNGJiZDc2MGExNTIzMjFmMjE0M2FmNyxxb3
+ A9YXV0aA==
+ S: {56}
+ S: cnNwYXV0aD1lYTQwZjYwMzM1YzQyN2I1NTI3Yjg0ZGJhYmNkZmZmZA==
+ C: ""
+ S: OK
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Melnikov & Martin Standards Track [Page 14]
+
+RFC 5804 ManageSieve July 2010
+
+
+ Another example demonstrating use of SASL PLAIN mechanism under TLS
+ follows. This example also demonstrate use of SASL "initial
+ response" (the second parameter to the Authenticate command):
+
+ S: "IMPLEMENTATION" "Example1 ManageSieved v001"
+ S: "VERSION" "1.0"
+ S: "SASL" ""
+ S: "SIEVE" "fileinto vacation"
+ S: "STARTTLS"
+ S: OK
+ C: STARTTLS
+ S: OK
+ <TLS negotiation, further commands are under TLS layer>
+ S: "IMPLEMENTATION" "Example1 ManageSieved v001"
+ S: "VERSION" "1.0"
+ S: "SASL" "PLAIN"
+ S: "SIEVE" "fileinto vacation"
+ S: OK
+ C: Authenticate "PLAIN" "QJIrweAPyo6Q1T9xu"
+ S: NO
+ C: Authenticate "PLAIN" "QJIrweAPyo6Q1T9xz"
+ S: NO
+ C: Authenticate "PLAIN" "QJIrweAPyo6Q1T9xy"
+ S: BYE "Too many failed authentication attempts"
+ <Server closes connection>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Melnikov & Martin Standards Track [Page 15]
+
+RFC 5804 ManageSieve July 2010
+
+
+ The following example demonstrates use of SASL "initial response".
+ It also demonstrates that an empty response can be sent as a literal
+ and that negotiating a SASL security layer results in the server
+ re-issuing server capabilities:
+
+ C: AUTHENTICATE "GSSAPI" {1488+}
+ C: YIIE[...1480 octets here ...]dA==
+ S: {208}
+ S: YIGZBgkqhkiG9xIBAgICAG+BiTCBhqADAgEFoQMCAQ+iejB4oAMCARKic
+ [...114 octets here ...]
+ /yzpAy9p+Y0LanLskOTvMc0MnjgAa4YEr3eJ6
+ C: {0+}
+ C:
+ S: {44}
+ S: BQQF/wAMAAwAAAAAYRGFAo6W0vIHti8i1UXODgEAEAA=
+ C: {44+}
+ C: BQQE/wAMAAwAAAAAIsT1iv9UkZApw471iXt6cwEAAAE=
+ S: OK
+ <Further commands/responses are under SASL security layer>
+ S: "IMPLEMENTATION" "Example1 ManageSieved v001"
+ S: "VERSION" "1.0"
+ S: "SASL" "PLAIN DIGEST-MD5 GSSAPI"
+ S: "SIEVE" "fileinto vacation"
+ S: "LANGUAGE" "ru"
+ S: "MAXREDIRECTS" "3"
+ S: ok
+
+2.1.1. Use of SASL PLAIN Mechanism over TLS
+
+ This section is normative for ManageSieve client implementations that
+ support SASL [PLAIN] over [TLS].
+
+ If a ManageSieve client is willing to use SASL PLAIN over TLS to
+ authenticate to the ManageSieve server, the client MUST verify the
+ server identity (see Section 2.2.1). If the server identity can't be
+ verified (e.g., the server has not provided any certificate, or if
+ the certificate verification fails), the client MUST NOT attempt to
+ authenticate using the SASL PLAIN mechanism.
+
+2.2. STARTTLS Command
+
+ Support for STARTTLS command in servers is optional. Its
+ availability is advertised with "STARTTLS" capability as described in
+ Section 1.7.
+
+ The STARTTLS command requests commencement of a TLS [TLS]
+ negotiation. The negotiation begins immediately after the CRLF in
+ the OK response. After a client issues a STARTTLS command, it MUST
+
+
+
+Melnikov & Martin Standards Track [Page 16]
+
+RFC 5804 ManageSieve July 2010
+
+
+ NOT issue further commands until a server response is seen and the
+ TLS negotiation is complete.
+
+ The STARTTLS command is only valid in non-authenticated state. The
+ server remains in non-authenticated state, even if client credentials
+ are supplied during the TLS negotiation. The SASL [SASL] EXTERNAL
+ mechanism MAY be used to authenticate once TLS client credentials are
+ successfully exchanged, but servers supporting the STARTTLS command
+ are not required to support the EXTERNAL mechanism.
+
+ After the TLS layer is established, the server MUST re-issue the
+ capability results, followed by an OK response. This is necessary to
+ protect against man-in-the-middle attacks that alter the capabilities
+ list prior to STARTTLS. This capability result MUST NOT include the
+ STARTTLS capability.
+
+ The client MUST discard cached capability information and replace it
+ with the new information. The server MAY advertise different
+ capabilities after STARTTLS.
+
+ Example:
+
+ C: StartTls
+ S: oK
+ <TLS negotiation, further commands are under TLS layer>
+ S: "IMPLEMENTATION" "Example1 ManageSieved v001"
+ S: "SASL" "PLAIN DIGEST-MD5 GSSAPI"
+ S: "SIEVE" "fileinto vacation"
+ S: "VERSION" "1.0"
+ S: "LANGUAGE" "fr"
+ S: ok
+
+2.2.1. Server Identity Check
+
+ During the TLS negotiation, the ManageSieve client MUST check its
+ understanding of the server hostname/IP address against the server's
+ identity as presented in the server Certificate message, in order to
+ prevent man-in-the-middle attacks. In this section, the client's
+ understanding of the server's identity is called the "reference
+ identity".
+
+ Checking is performed according to the following rules:
+
+ o If the reference identity is a hostname:
+
+ 1. If a subjectAltName extension of the SRVName [X509-SRV],
+ dNSName [X509] (in that order of preference) type is present
+ in the server's certificate, then it SHOULD be used as the
+
+
+
+Melnikov & Martin Standards Track [Page 17]
+
+RFC 5804 ManageSieve July 2010
+
+
+ source of the server's identity. Matching is performed as
+ described in Section 2.2.1.1, with the exception that no
+ wildcard matching is allowed for SRVName type. If the
+ certificate contains multiple names (e.g., more than one
+ dNSName field), then a match with any one of the fields is
+ considered acceptable.
+
+ 2. The client MAY use other types of subjectAltName for
+ performing comparison.
+
+ 3. The server's identity MAY also be verified by comparing the
+ reference identity to the Common Name (CN) [RFC4519] value in
+ the leaf Relative Distinguished Name (RDN) of the subjectName
+ field of the server's certificate. This comparison is
+ performed using the rules for comparison of DNS names in
+ Section 2.2.1.1, below. Although the use of the Common Name
+ value is existing practice, it is deprecated, and
+ Certification Authorities are encouraged to provide
+ subjectAltName values instead. Note that the TLS
+ implementation may represent DNs in certificates according to
+ X.500 or other conventions. For example, some X.500
+ implementations order the RDNs in a DN using a left-to-right
+ (most significant to least significant) convention instead of
+ LDAP's right-to-left convention.
+
+ o When the reference identity is an IP address, the iPAddress
+ subjectAltName SHOULD be used by the client for comparison. The
+ comparison is performed as described in Section 2.2.1.2.
+
+ If the server identity check fails, user-oriented clients SHOULD
+ either notify the user (clients MAY give the user the opportunity to
+ continue with the ManageSieve session in this case) or close the
+ transport connection and indicate that the server's identity is
+ suspect. Automated clients SHOULD return or log an error indicating
+ that the server's identity is suspect and/or SHOULD close the
+ transport connection. Automated clients MAY provide a configuration
+ setting that disables this check, but MUST provide a setting that
+ enables it.
+
+ Beyond the server identity check described in this section, clients
+ should be prepared to do further checking to ensure that the server
+ is authorized to provide the service it is requested to provide. The
+ client may need to make use of local policy information in making
+ this determination.
+
+
+
+
+
+
+
+Melnikov & Martin Standards Track [Page 18]
+
+RFC 5804 ManageSieve July 2010
+
+
+2.2.1.1. Comparison of DNS Names
+
+ If the reference identity is an internationalized domain name,
+ conforming implementations MUST convert it to the ASCII Compatible
+ Encoding (ACE) format as specified in Section 4 of RFC 3490 [RFC3490]
+ before comparison with subjectAltName values of type dNSName.
+ Specifically, conforming implementations MUST perform the conversion
+ operation specified in Section 4 of [RFC3490] as follows:
+
+ o in step 1, the domain name SHALL be considered a "stored string";
+
+ o in step 3, set the flag called "UseSTD3ASCIIRules";
+
+ o in step 4, process each label with the "ToASCII" operation; and
+
+ o in step 5, change all label separators to U+002E (full stop).
+
+ After performing the "to-ASCII" conversion, the DNS labels and names
+ MUST be compared for equality according to the rules specified in
+ Section 3 of [RFC3490]; i.e., once all label separators are replaced
+ with U+002E (dot) they are compared in the case-insensitive manner.
+
+ The '*' (ASCII 42) wildcard character is allowed in subjectAltName
+ values of type dNSName, and then only as the left-most (least
+ significant) DNS label in that value. This wildcard matches any
+ left-most DNS label in the server name. That is, the subject
+ *.example.com matches the server names a.example.com and
+ b.example.com, but does not match example.com or a.b.example.com.
+
+2.2.1.2. Comparison of IP Addresses
+
+ When the reference identity is an IP address, the identity MUST be
+ converted to the "network byte order" octet string representation
+ [RFC791][RFC2460]. For IP Version 4, as specified in RFC 791, the
+ octet string will contain exactly four octets. For IP Version 6, as
+ specified in RFC 2460, the octet string will contain exactly sixteen
+ octets. This octet string is then compared against subjectAltName
+ values of type iPAddress. A match occurs if the reference identity
+ octet string and value octet strings are identical.
+
+2.2.1.3. Comparison of Other subjectName Types
+
+ Client implementations MAY support matching against subjectAltName
+ values of other types as described in other documents.
+
+
+
+
+
+
+
+Melnikov & Martin Standards Track [Page 19]
+
+RFC 5804 ManageSieve July 2010
+
+
+2.3. LOGOUT Command
+
+ The client sends the LOGOUT command when it is finished with a
+ connection and wishes to terminate it. The server MUST reply with an
+ OK response. The server MUST ignore commands issued by the client
+ after the LOGOUT command.
+
+ The client SHOULD wait for the OK response before closing the
+ connection. This avoids the TCP connection going into the TIME_WAIT
+ state on the server. In order to avoid going into the TIME_WAIT TCP
+ state, the server MAY wait for a short while for the client to close
+ the TCP connection first. Whether or not the server waits for the
+ client to close the connection, it MUST then close the connection
+ itself.
+
+ Example:
+
+ C: Logout
+ S: Ok
+ <connection is terminated>
+
+2.4. CAPABILITY Command
+
+ The CAPABILITY command requests the server capabilities as described
+ earlier in this document. It has no parameters.
+
+ Example:
+
+ C: CAPABILITY
+ S: "IMPLEMENTATION" "Example1 ManageSieved v001"
+ S: "VERSION" "1.0"
+ S: "SASL" "PLAIN SCRAM-SHA-1 GSSAPI"
+ S: "SIEVE" "fileinto vacation"
+ S: "STARTTLS"
+ S: OK
+
+2.5. HAVESPACE Command
+
+ Arguments: String - name
+ Number - script size
+
+ The HAVESPACE command is used to query the server for available
+ space. Clients specify the name they wish to save the script as and
+ its size in octets. Both parameters can be used by the server to see
+ if the script with the specified name and size is within a user's
+ quota(s). For example, the server MAY use the script name to check
+ if a script would be replaced or a new one would be created. Servers
+ respond with a NO if storing a script with that name and size would
+
+
+
+Melnikov & Martin Standards Track [Page 20]
+
+RFC 5804 ManageSieve July 2010
+
+
+ fail or OK otherwise. Clients SHOULD issue this command before
+ attempting to place a script on the server.
+
+ Note that the OK response from the HAVESPACE command does not
+ constitute a guarantee of success as server disk space conditions
+ could change between the client issuing the HAVESPACE and the client
+ issuing the PUTSCRIPT commands. A QUOTA response code (see
+ Section 1.3) remains a possible (albeit unlikely) response to a
+ subsequent PUTSCRIPT with the same name and size.
+
+ Example:
+
+ C: HAVESPACE "myscript" 999999
+ S: NO (QUOTA/MAXSIZE) "Quota exceeded"
+
+ C: HAVESPACE "foobar" 435
+ S: OK
+
+2.6. PUTSCRIPT Command
+
+ Arguments: String - Script name
+ String - Script content
+
+ The PUTSCRIPT command is used by the client to submit a Sieve script
+ to the server.
+
+ If the script already exists, upon success the old script will be
+ overwritten. The old script MUST NOT be overwritten if PUTSCRIPT
+ fails in any way. A script of zero length SHOULD be disallowed.
+
+ This command places the script on the server. It does not affect
+ whether the script is processed on incoming mail, unless it replaces
+ the script that is already active. The SETACTIVE command is used to
+ mark a script as active.
+
+ When submitting large scripts, clients SHOULD use the HAVESPACE
+ command beforehand to query if the server is willing to accept a
+ script of that size.
+
+ The server MUST check the submitted script for validity, which
+ includes checking that the script complies with the Sieve grammar
+ [SIEVE] and that all Sieve extensions mentioned in the script's
+ "require" statement(s) are supported by the Sieve interpreter. (Note
+ that if the Sieve interpreter supports the Sieve "ihave" extension
+ [I-HAVE], any unrecognized/unsupported extension mentioned in the
+ "ihave" test MUST NOT cause the validation failure.) Other checks
+ such as validating the supplied command arguments for each command
+ MAY be performed. Essentially, the performed validation SHOULD be
+
+
+
+Melnikov & Martin Standards Track [Page 21]
+
+RFC 5804 ManageSieve July 2010
+
+
+ the same as performed when compiling the script for execution.
+ Implementations that use a binary representation to store compiled
+ scripts can extend the validation to a full compilation, in order to
+ avoid validating uploaded scripts multiple times.
+
+ If the script fails the validation, the server MUST reply with a NO
+ response. Any script that fails the validity test MUST NOT be stored
+ on the server. The message given with a NO response MUST be human
+ readable and SHOULD contain a specific error message giving the line
+ number of the first error. Implementors should strive to produce
+ helpful error messages similar to those given by programming language
+ compilers. Client implementations should note that this may be a
+ multiline literal string with more than one error message separated
+ by CRLFs. The human-readable message is in the language returned in
+ the latest LANGUAGE capability (or in "i-default"; see Section 1.7),
+ encoded in UTF-8 [UTF-8].
+
+ An OK response MAY contain the WARNINGS response code. In such a
+ case the human-readable message that follows the OK response SHOULD
+ contain a specific warning message (or messages) giving the line
+ number(s) in the script that might contain errors not intended by the
+ script writer. The human-readable message is in the language
+ returned in the latest LANGUAGE capability (or in "i-default"; see
+ Section 1.7), encoded in UTF-8 [UTF-8]. A client seeing such a
+ response code SHOULD present the message to the user.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Melnikov & Martin Standards Track [Page 22]
+
+RFC 5804 ManageSieve July 2010
+
+
+ Examples:
+
+ C: Putscript "foo" {31+}
+ C: #comment
+ C: InvalidSieveCommand
+ C:
+ S: NO "line 2: Syntax error"
+
+ C: Putscript "mysievescript" {110+}
+ C: require ["fileinto"];
+ C:
+ C: if envelope :contains "to" "tmartin+sent" {
+ C: fileinto "INBOX.sent";
+ C: }
+ S: OK
+
+ C: Putscript "myforwards" {190+}
+ C: redirect "111 at example.net";
+ C:
+ C: if size :under 10k {
+ C: redirect "mobile at cell.example.com";
+ C: }
+ C:
+ C: if envelope :contains "to" "tmartin+lists" {
+ C: redirect "lists at groups.example.com";
+ C: }
+ S: OK (WARNINGS) "line 8: server redirect action
+ limit is 2, this redirect might be ignored"
+
+2.7. LISTSCRIPTS Command
+
+ This command lists the scripts the user has on the server. Upon
+ success, a list of CRLF-separated script names (each represented as a
+ quoted or literal string) is returned followed by an OK response. If
+ there exists an active script, the atom ACTIVE is appended to the
+ corresponding script name. The atom ACTIVE MUST NOT appear on more
+ than one response line.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Melnikov & Martin Standards Track [Page 23]
+
+RFC 5804 ManageSieve July 2010
+
+
+ Example:
+
+ C: Listscripts
+ S: "summer_script"
+ S: "vacation_script"
+ S: {13}
+ S: clever"script
+ S: "main_script" ACTIVE
+ S: OK
+
+ C: listscripts
+ S: "summer_script"
+ S: "main_script" active
+ S: OK
+
+2.8. SETACTIVE Command
+
+ Arguments: String - script name
+
+ This command sets a script active. If the script name is the empty
+ string (i.e., ""), then any active script is disabled. Disabling an
+ active script when there is no script active is not an error and MUST
+ result in an OK reply.
+
+ If the script does not exist on the server, then the server MUST
+ reply with a NO response. Such a reply SHOULD contain the
+ NONEXISTENT response code.
+
+ Examples:
+
+ C: Setactive "vacationscript"
+ S: Ok
+
+ C: Setactive ""
+ S: Ok
+
+ C: Setactive "baz"
+ S: No (NONEXISTENT) "There is no script by that name"
+
+ C: Setactive "baz"
+ S: No (NONEXISTENT) {31}
+ S: There is no script by that name
+
+
+
+
+
+
+
+
+
+Melnikov & Martin Standards Track [Page 24]
+
+RFC 5804 ManageSieve July 2010
+
+
+2.9. GETSCRIPT Command
+
+ Arguments: String - script name
+
+ This command gets the contents of the specified script. If the
+ script does not exist, the server MUST reply with a NO response.
+ Such a reply SHOULD contain the NONEXISTENT response code.
+
+ Upon success, a string with the contents of the script is returned
+ followed by an OK response.
+
+ Example:
+
+ C: Getscript "myscript"
+ S: {54}
+ S: #this is my wonderful script
+ S: reject "I reject all";
+ S:
+ S: OK
+
+2.10. DELETESCRIPT Command
+
+ Arguments: String - script name
+
+ This command is used to delete a user's Sieve script. Servers MUST
+ reply with a NO response if the script does not exist. Such
+ responses SHOULD include the NONEXISTENT response code.
+
+ The server MUST NOT allow the client to delete an active script, so
+ the server MUST reply with a NO response if attempted. Such a
+ response SHOULD contain the ACTIVE response code. If a client wishes
+ to delete an active script, it should use the SETACTIVE command to
+ disable the script first.
+
+ Example:
+
+ C: Deletescript "foo"
+ S: Ok
+
+ C: Deletescript "baz"
+ S: No (ACTIVE) "You may not delete an active script"
+
+
+
+
+
+
+
+
+
+
+Melnikov & Martin Standards Track [Page 25]
+
+RFC 5804 ManageSieve July 2010
+
+
+2.11. RENAMESCRIPT Command
+
+ Arguments: String - Old Script name
+ String - New Script name
+
+ This command is used to rename a user's Sieve script. Servers MUST
+ reply with a NO response if the old script does not exist (in which
+ case the NONEXISTENT response code SHOULD be included), or a script
+ with the new name already exists (in which case the ALREADYEXISTS
+ response code SHOULD be included). Renaming the active script is
+ allowed; the renamed script remains active.
+
+ Example:
+
+ C: Renamescript "foo" "bar"
+ S: Ok
+
+ C: Renamescript "baz" "bar"
+ S: No "bar already exists"
+
+ If the server doesn't support the RENAMESCRIPT command, the client
+ can emulate it by performing the following steps:
+
+ 1. List available scripts with LISTSCRIPTS. If the script with the
+ new script name exists, then the client should ask the user
+ whether to abort the operation, to replace the script (by issuing
+ the DELETESCRIPT <newname> after that), or to choose a different
+ name.
+
+ 2. Download the old script with GETSCRIPT <oldname>.
+
+ 3. Upload the old script with the new name: PUTSCRIPT <newname>.
+
+ 4. If the old script was active (as reported by LISTSCRIPTS in step
+ 1), then make the new script active: SETACTIVE <newname>.
+
+ 5. Delete the old script: DELETESCRIPT <oldname>.
+
+ Note that these steps don't describe how to handle various other
+ error conditions (for example, NO response containing QUOTA response
+ code in step 3). Error handling is left as an exercise for the
+ reader.
+
+
+
+
+
+
+
+
+
+Melnikov & Martin Standards Track [Page 26]
+
+RFC 5804 ManageSieve July 2010
+
+
+2.12. CHECKSCRIPT Command
+
+ Arguments: String - Script content
+
+ The CHECKSCRIPT command is used by the client to verify Sieve script
+ validity without storing the script on the server.
+
+ The server MUST check the submitted script for syntactic validity,
+ which includes checking that all Sieve extensions mentioned in Sieve
+ script "require" statement(s) are supported by the Sieve interpreter.
+ (Note that if the Sieve interpreter supports the Sieve "ihave"
+ extension [I-HAVE], any unrecognized/unsupported extension mentioned
+ in the "ihave" test MUST NOT cause the syntactic validation failure.)
+ If the script fails this test, the server MUST reply with a NO
+ response. The message given with a NO response MUST be human
+ readable and SHOULD contain a specific error message giving the line
+ number of the first error. Implementors should strive to produce
+ helpful error messages similar to those given by programming language
+ compilers. Client implementations should note that this may be a
+ multiline literal string with more than one error message separated
+ by CRLFs. The human-readable message is in the language returned in
+ the latest LANGUAGE capability (or in "i-default"; see Section 1.7),
+ encoded in UTF-8 [UTF-8].
+
+ Examples:
+
+ C: CheckScript {31+}
+ C: #comment
+ C: InvalidSieveCommand
+ C:
+ S: NO "line 2: Syntax error"
+
+ A ManageSieve server supporting this command MUST NOT check if the
+ script will put the current user over its quota limit.
+
+ An OK response MAY contain the WARNINGS response code. In such a
+ case, the human-readable message that follows the OK response SHOULD
+ contain a specific warning message (or messages) giving the line
+ number(s) in the script that might contain errors not intended by the
+ script writer. The human-readable message is in the language
+ returned in the latest LANGUAGE capability (or in "i-default"; see
+ Section 1.7), encoded in UTF-8 [UTF-8]. A client seeing such a
+ response code SHOULD present the message to the user.
+
+
+
+
+
+
+
+
+Melnikov & Martin Standards Track [Page 27]
+
+RFC 5804 ManageSieve July 2010
+
+
+2.13. NOOP Command
+
+ Arguments: String - tag to echo back (optional)
+
+ The NOOP command does nothing, beyond returning a response to the
+ client. It may be used by clients for protocol re-synchronization or
+ to reset any inactivity auto-logout timer on the server.
+
+ The response to the NOOP command is always OK, followed by the TAG
+ response code together with the supplied string. If no string was
+ supplied in the NOOP command, the TAG response code MUST NOT be
+ included.
+
+ Examples:
+
+ C: NOOP
+ S: OK "NOOP completed"
+
+ C: NOOP "STARTTLS-SYNC-42"
+ S: OK (TAG {16}
+ S: STARTTLS-SYNC-42) "Done"
+
+2.14. Recommended Extensions
+
+ The UNAUTHENTICATE extension (advertised as the "UNAUTHENTICATE"
+ capability with no parameters) defines a new UNAUTHENTICATE command,
+ which allows a client to return the server to non-authenticated
+ state. Support for this extension is RECOMMENDED.
+
+2.14.1. UNAUTHENTICATE Command
+
+ The UNAUTHENTICATE command returns the server to the
+ non-authenticated state. It doesn't affect any previously
+ established TLS [TLS] or SASL (Section 2.1) security layer.
+
+ The UNAUTHENTICATE command is only valid in authenticated state. If
+ issued in a wrong state, the server MUST reject it with a NO
+ response.
+
+ The UNAUTHENTICATE command has no parameters.
+
+ When issued in the authenticated state, the UNAUTHENTICATE command
+ MUST NOT fail (i.e., it must never return anything other than OK or
+ BYE).
+
+
+
+
+
+
+
+Melnikov & Martin Standards Track [Page 28]
+
+RFC 5804 ManageSieve July 2010
+
+
+3. Sieve URL Scheme
+
+ URI scheme name: sieve
+
+ Status: permanent
+
+ URI scheme syntax: Described using ABNF [ABNF]. Some ABNF
+ productions not defined below are from [URI-GEN].
+
+ sieveurl = sieveurl-server / sieveurl-list-scripts /
+ sieveurl-script
+
+ sieveurl-server = "sieve://" authority
+
+ sieveurl-list-scripts = "sieve://" authority ["/"]
+
+ sieveurl-script = "sieve://" authority "/"
+ [owner "/"] scriptname
+
+ authority = <defined in [URI-GEN]>
+
+ owner = *ochar
+ ;; %-encoded version of [SASL] authorization
+ ;; identity (script owner) or "userid".
+ ;;
+ ;; Empty owner is used to reference
+ ;; global scripts.
+ ;;
+ ;; Note that ASCII characters such as " ", ";",
+ ;; "&", "=", "/" and "?" must be %-encoded
+ ;; as per rule specified in [URI-GEN].
+
+ scriptname = 1*ochar
+ ;; %-encoded version of UTF-8 representation
+ ;; of the script name.
+ ;; Note that ASCII characters such as " ", ";",
+ ;; "&", "=", "/" and "?" must be %-encoded
+ ;; as per rule specified in [URI-GEN].
+
+ ochar = unreserved / pct-encoded / sub-delims-sh /
+ ":" / "@"
+ ;; Same as [URI-GEN] 'pchar',
+ ;; but without ";", "&" and "=".
+
+ unreserved = <defined in [URI-GEN]>
+
+ pct-encoded = <defined in [URI-GEN]>
+
+
+
+
+Melnikov & Martin Standards Track [Page 29]
+
+RFC 5804 ManageSieve July 2010
+
+
+ sub-delims-sh = "!" / "$" / "'" / "(" / ")" /
+ "*" / "+" / ","
+ ;; Same as [URI-GEN] sub-delims,
+ ;; but without ";", "&" and "=".
+
+ URI scheme semantics:
+
+ A Sieve URL identifies a Sieve server or a Sieve script on a Sieve
+ server. The latter form is associated with the application/sieve
+ MIME type defined in [SIEVE]. There is no MIME type associated
+ with the former form of Sieve URI.
+
+ The server form is used in the REFERRAL response code (see Section
+ 1.3) in order to designate another server where the client should
+ perform its operations.
+
+ The script form allows to retrieve (GETSCRIPT), update
+ (PUTSCRIPT), delete (DELETESCRIPT), or activate (SETACTIVE) the
+ named script; however, the most typical action would be to
+ retrieve the script. If the script name is empty (omitted), the
+ URI requests that the client lists available scripts using the
+ LISTSCRIPTS command.
+
+ Encoding considerations:
+
+ The script name and/or the owner, if present, is in UTF-8. Non--
+ US-ASCII UTF-8 octets MUST be percent-encoded as described in
+ [URI-GEN]. US-ASCII characters such as " " (space), ";", "&",
+ "=", "/" and "?" MUST be %-encoded as described in [URI-GEN].
+ Note that "&" and "?" are in this list in order to allow for
+ future extensions.
+
+ Note that the empty owner (e.g., sieve://example.com//script) is
+ different from the missing owner (e.g.,
+ sieve://example.com/script) and is reserved for referencing global
+ scripts.
+
+ The user name (in the "authority" part), if present, is in UTF-8.
+ Non-US-ASCII UTF-8 octets MUST be percent-encoded as described in
+ [URI-GEN].
+
+ Applications/protocols that use this URI scheme name:
+ ManageSieve [RFC5804] clients and servers. Clients that can store
+ user preferences in protocols such as [LDAP] or [ACAP].
+
+ Interoperability considerations: None.
+
+
+
+
+
+Melnikov & Martin Standards Track [Page 30]
+
+RFC 5804 ManageSieve July 2010
+
+
+ Security considerations:
+ The <scriptname> part of a ManageSieve URL might potentially disclose
+ some confidential information about the author of the script or,
+ depending on a ManageSieve implementation, about configuration of the
+ mail system. The latter might be used to prepare for a more complex
+ attack on the mail system.
+
+ Clients resolving ManageSieve URLs that wish to achieve data
+ confidentiality and/or integrity SHOULD use the STARTTLS command (if
+ supported by the server) before starting authentication, or use a
+ SASL mechanism, such as GSSAPI, that provides a confidentiality
+ security layer.
+
+ Contact: Alexey Melnikov <alexey.melnikov at isode.com>
+
+ Author/Change controller: IESG.
+
+ References: This document and RFC 5228 [SIEVE].
+
+4. Formal Syntax
+
+ The following syntax specification uses the Augmented Backus-Naur
+ Form (BNF) notation as specified in [ABNF]. This uses the ABNF core
+ rules as specified in Appendix A of the ABNF specification [ABNF].
+ "UTF8-2", "UTF8-3", and "UTF8-4" non-terminal are defined in [UTF-8].
+
+ Except as noted otherwise, all alphabetic characters are case-
+ insensitive. The use of upper- or lowercase characters to define
+ token strings is for editorial clarity only. Implementations MUST
+ accept these strings in a case-insensitive fashion.
+
+ SAFE-CHAR = %x01-09 / %x0B-0C / %x0E-21 / %x23-5B /
+ %x5D-7F
+ ;; any TEXT-CHAR except QUOTED-SPECIALS
+
+ QUOTED-CHAR = SAFE-UTF8-CHAR / "\" QUOTED-SPECIALS
+
+ QUOTED-SPECIALS = DQUOTE / "\"
+
+ SAFE-UTF8-CHAR = SAFE-CHAR / UTF8-2 / UTF8-3 / UTF8-4
+ ;; <UTF8-2>, <UTF8-3>, and <UTF8-4>
+ ;; are defined in [UTF-8].
+
+ ATOM-CHAR = "!" / %x23-27 / %x2A-5B / %x5D-7A / %x7C-7E
+ ;; Any CHAR except ATOM-SPECIALS
+
+ ATOM-SPECIALS = "(" / ")" / "{" / SP / CTL / QUOTED-SPECIALS
+
+
+
+
+Melnikov & Martin Standards Track [Page 31]
+
+RFC 5804 ManageSieve July 2010
+
+
+ NZDIGIT = %x31-39
+ ;; 1-9
+
+ atom = 1*1024ATOM-CHAR
+
+ iana-token = atom
+ ;; MUST be registered with IANA
+
+ auth-type = DQUOTE auth-type-name DQUOTE
+
+ auth-type-name = iana-token
+ ;; as defined in SASL [SASL]
+
+ command = (command-any / command-auth /
+ command-nonauth) CRLF
+ ;; Modal based on state
+
+ command-any = command-capability / command-logout /
+ command-noop
+ ;; Valid in all states
+
+ command-auth = command-getscript / command-setactive /
+ command-listscripts / command-deletescript /
+ command-putscript / command-checkscript /
+ command-havespace /
+ command-renamescript /
+ command-unauthenticate
+ ;; Valid only in Authenticated state
+
+ command-nonauth = command-authenticate / command-starttls
+ ;; Valid only when in Non-Authenticated
+ ;; state
+
+ command-authenticate = "AUTHENTICATE" SP auth-type [SP string]
+ *(CRLF string)
+
+ command-capability = "CAPABILITY"
+
+ command-deletescript = "DELETESCRIPT" SP sieve-name
+
+ command-getscript = "GETSCRIPT" SP sieve-name
+
+ command-havespace = "HAVESPACE" SP sieve-name SP number
+
+ command-listscripts = "LISTSCRIPTS"
+
+ command-noop = "NOOP" [SP string]
+
+
+
+
+Melnikov & Martin Standards Track [Page 32]
+
+RFC 5804 ManageSieve July 2010
+
+
+ command-logout = "LOGOUT"
+
+ command-putscript = "PUTSCRIPT" SP sieve-name SP sieve-script
+
+ command-checkscript = "CHECKSCRIPT" SP sieve-script
+
+ sieve-script = string
+
+ command-renamescript = "RENAMESCRIPT" SP old-sieve-name SP
+ new-sieve-name
+
+ old-sieve-name = sieve-name
+
+ new-sieve-name = sieve-name
+
+ command-setactive = "SETACTIVE" SP active-sieve-name
+
+ command-starttls = "STARTTLS"
+
+ command-unauthenticate= "UNAUTHENTICATE"
+
+ extend-token = atom
+ ;; MUST be defined by a Standards Track or
+ ;; IESG-approved experimental protocol
+ ;; extension
+
+ extension-data = extension-item *(SP extension-item)
+
+ extension-item = extend-token / string / number /
+ "(" [extension-data] ")"
+
+ literal-c2s = "{" number "+}" CRLF *OCTET
+ ;; The number represents the number of
+ ;; octets.
+ ;; This type of literal can only be sent
+ ;; from the client to the server.
+
+ literal-s2c = "{" number "}" CRLF *OCTET
+ ;; Almost identical to literal-c2s,
+ ;; but with no '+' character.
+ ;; The number represents the number of
+ ;; octets.
+ ;; This type of literal can only be sent
+ ;; from the server to the client.
+
+
+
+
+
+
+
+Melnikov & Martin Standards Track [Page 33]
+
+RFC 5804 ManageSieve July 2010
+
+
+ number = (NZDIGIT *DIGIT) / "0"
+ ;; A 32-bit unsigned number
+ ;; with no extra leading zeros.
+ ;; (0 <= n < 4,294,967,296)
+
+ number-str = string
+ ;; <number> encoded as a <string>.
+
+ quoted = DQUOTE *1024QUOTED-CHAR DQUOTE
+ ;; limited to 1024 octets between the <">s
+
+ resp-code = "AUTH-TOO-WEAK" / "ENCRYPT-NEEDED" / "QUOTA"
+ ["/" ("MAXSCRIPTS" / "MAXSIZE")] /
+ resp-code-sasl /
+ resp-code-referral /
+ "TRANSITION-NEEDED" / "TRYLATER" /
+ "ACTIVE" / "NONEXISTENT" /
+ "ALREADYEXISTS" / "WARNINGS" /
+ "TAG" SP string /
+ resp-code-ext
+
+ resp-code-referral = "REFERRAL" SP sieveurl
+
+ resp-code-sasl = "SASL" SP string
+
+ resp-code-name = iana-token
+ ;; The response code name is hierarchical,
+ ;; separated by '/'.
+ ;; The response code name MUST NOT start
+ ;; with '/'.
+
+ resp-code-ext = resp-code-name [SP extension-data]
+ ;; unknown response codes MUST be tolerated
+ ;; by the client.
+
+ response = response-authenticate /
+ response-logout /
+ response-getscript /
+ response-setactive /
+ response-listscripts /
+ response-deletescript /
+ response-putscript /
+ response-checkscript /
+ response-capability /
+ response-havespace /
+ response-starttls /
+ response-renamescript /
+ response-noop /
+
+
+
+Melnikov & Martin Standards Track [Page 34]
+
+RFC 5804 ManageSieve July 2010
+
+
+ response-unauthenticate
+
+ response-authenticate = *(string CRLF)
+ ((response-ok [response-capability]) /
+ response-nobye)
+ ;; <response-capability> is REQUIRED if a
+ ;; SASL security layer was negotiated and
+ ;; MUST be omitted otherwise.
+
+ response-capability = *(single-capability) response-oknobye
+
+ single-capability = capability-name [SP string] CRLF
+
+ capability-name = string
+
+ ;; Note that literal-s2c is allowed.
+
+ initial-capabilities = DQUOTE "IMPLEMENTATION" DQUOTE SP string /
+ DQUOTE "SASL" DQUOTE SP sasl-mechs /
+ DQUOTE "SIEVE" DQUOTE SP sieve-extensions /
+ DQUOTE "MAXREDIRECTS" DQUOTE SP number-str /
+ DQUOTE "NOTIFY" DQUOTE SP notify-mechs /
+ DQUOTE "STARTTLS" DQUOTE /
+ DQUOTE "LANGUAGE" DQUOTE SP language /
+ DQUOTE "VERSION" DQUOTE SP version /
+ DQUOTE "OWNER" DQUOTE SP string
+ ;; Each capability conforms to
+ ;; the syntax for single-capability.
+ ;; Also, note that the capability name
+ ;; can be returned as either literal-s2c
+ ;; or quoted, even though only "quoted"
+ ;; string is shown above.
+
+ version = ( DQUOTE "1.0" DQUOTE ) / version-ext
+
+ version-ext = DQUOTE ver-major "." ver-minor DQUOTE
+ ; Future versions specified in updates
+ ; to this document. An increment to
+ ; the ver-major means a backward-incompatible
+ ; change to the protocol, e.g., "3.5" (ver-major "3")
+ ; is not backward-compatible with any "2.X" version.
+ ; Any version "Z.W" MUST be backward compatible
+ ; with any version "Z.Q", where Q < W.
+ ; For example, version "2.4" is backward compatible
+ ; with version "2.0", "2.1", "2.2", and "2.3".
+
+ ver-major = number
+
+
+
+
+Melnikov & Martin Standards Track [Page 35]
+
+RFC 5804 ManageSieve July 2010
+
+
+ ver-minor = number
+
+ sasl-mechs = string
+ ; Space-separated list of SASL mechanisms,
+ ; each SASL mechanism name complies with rules
+ ; specified in [SASL].
+ ; Can be empty.
+
+ sieve-extensions = string
+ ; Space-separated list of supported SIEVE extensions.
+ ; Can be empty.
+
+ language = string
+ ; Contains <Language-Tag> from [RFC5646].
+
+
+ notify-mechs = string
+ ; Space-separated list of URI schema parts
+ ; for supported notification [NOTIFY] methods.
+ ; MUST NOT be empty.
+
+ response-deletescript = response-oknobye
+
+ response-getscript = (sieve-script CRLF response-ok) /
+ response-nobye
+
+ response-havespace = response-oknobye
+
+ response-listscripts = *(sieve-name [SP "ACTIVE"] CRLF)
+ response-oknobye
+ ;; ACTIVE may only occur with one sieve-name
+
+ response-logout = response-oknobye
+
+ response-unauthenticate= response-oknobye
+ ;; "NO" response can only be returned when
+ ;; the command is issued in a wrong state
+ ;; or has a wrong number of parameters
+
+ response-ok = "OK" [SP "(" resp-code ")"]
+ [SP string] CRLF
+ ;; The string contains human-readable text
+ ;; encoded as UTF-8.
+
+ response-nobye = ("NO" / "BYE") [SP "(" resp-code ")"]
+ [SP string] CRLF
+ ;; The string contains human-readable text
+ ;; encoded as UTF-8.
+
+
+
+Melnikov & Martin Standards Track [Page 36]
+
+RFC 5804 ManageSieve July 2010
+
+
+ response-oknobye = response-ok / response-nobye
+
+ response-noop = response-ok
+
+ response-putscript = response-oknobye
+
+ response-checkscript = response-oknobye
+
+ response-renamescript = response-oknobye
+
+ response-setactive = response-oknobye
+
+ response-starttls = (response-ok response-capability) /
+ response-nobye
+
+ sieve-name = string
+ ;; See Section 1.6 for the full list of
+ ;; prohibited characters.
+ ;; Empty string is not allowed.
+
+ active-sieve-name = string
+ ;; See Section 1.6 for the full list of
+ ;; prohibited characters.
+ ;; This is similar to <sieve-name>, but
+ ;; empty string is allowed and has a special
+ ;; meaning.
+
+ string = quoted / literal-c2s / literal-s2c
+ ;; literal-c2s is only allowed when sent
+ ;; from the client to the server.
+ ;; literal-s2c is only allowed when sent
+ ;; from the server to the client.
+ ;; quoted is allowed in either direction.
+
+5. Security Considerations
+
+ The AUTHENTICATE command uses SASL [SASL] to provide authentication
+ and authorization services. Integrity and privacy services can be
+ provided by [SASL] and/or [TLS]. When a SASL mechanism is used, the
+ security considerations for that mechanism apply.
+
+ This protocol's transactions are susceptible to passive observers or
+ man-in-the-middle attacks that alter the data, unless the optional
+ encryption and integrity services of the SASL (via the AUTHENTICATE
+ command) and/or [TLS] (via the STARTTLS command) are enabled, or an
+ external security mechanism is used for protection. It may be useful
+ to allow configuration of both clients and servers to refuse to
+ transfer sensitive information in the absence of strong encryption.
+
+
+
+Melnikov & Martin Standards Track [Page 37]
+
+RFC 5804 ManageSieve July 2010
+
+
+ If an implementation supports SASL mechanisms that are vulnerable to
+ passive eavesdropping attacks (such as [PLAIN]), then the
+ implementation MUST support at least one configuration where these
+ SASL mechanisms are not advertised or used without the presence of an
+ external security layer such as [TLS].
+
+ Some response codes returned on failed AUTHENTICATE command may
+ disclose whether or not the username is valid (e.g., TRANSITION-
+ NEEDED), so server implementations SHOULD provide the ability to
+ disable these features (or make them not conditional on a per-user
+ basis) for sites concerned about such disclosure. In the case of
+ ENCRYPT-NEEDED, if it is applied to all identities then no extra
+ information is disclosed, but if it is applied on a per-user basis it
+ can disclose information.
+
+ A compromised or malicious server can use the TRANSITION-NEEDED
+ response code to force the client that is configured to use a
+ mechanism that does not disclose the user's password to the server
+ (e.g., Kerberos), to send the bare password to the server. Clients
+ SHOULD have the ability to disable the password transition feature,
+ or disclose that risk to the user and offer the user an option of how
+ to proceed.
+
+6. IANA Considerations
+
+ IANA has reserved TCP port number 4190 for use with the ManageSieve
+ protocol described in this document.
+
+ IANA has registered the "sieve" URI scheme defined in Section 3 of
+ this document.
+
+ IANA has registered "sieve" in the "GSSAPI/Kerberos/SASL Service
+ Names" registry.
+
+ IANA has created a new registry for ManageSieve capabilities. The
+ registration template for ManageSieve capabilities is specified in
+ Section 6.1. ManageSieve protocol capabilities MUST be specified in
+ a Standards-Track or IESG-approved Experimental RFC.
+
+ IANA has created a new registry for ManageSieve response codes. The
+ registration template for ManageSieve response codes is specified in
+ Section 6.3. ManageSieve protocol response codes MUST be specified
+ in a Standards-Track or IESG-approved Experimental RFC.
+
+
+
+
+
+
+
+
+Melnikov & Martin Standards Track [Page 38]
+
+RFC 5804 ManageSieve July 2010
+
+
+6.1. ManageSieve Capability Registration Template
+
+ To: iana at iana.org
+ Subject: ManageSieve Capability Registration
+
+ Please register the following ManageSieve capability:
+
+ Capability name:
+ Description:
+ Relevant publications:
+ Person & email address to contact for further information:
+ Author/Change controller:
+
+6.2. Registration of Initial ManageSieve Capabilities
+
+ To: iana at iana.org
+ Subject: ManageSieve Capability Registration
+
+ Please register the following ManageSieve capabilities:
+
+ Capability name: IMPLEMENTATION
+ Description: Its value contains the name of the server
+ implementation and its version.
+ Relevant publications: this RFC, Section 1.7.
+ Person & email address to contact for further information:
+ Alexey Melnikov <alexey.melnikov at isode.com>
+ Author/Change controller: IESG.
+
+ Capability name: SASL
+ Description: Its value contains a space-separated list of SASL
+ mechanisms supported by the server.
+ Relevant publications: this RFC, Sections 1.7 and 2.1.
+ Person & email address to contact for further information:
+ Alexey Melnikov <alexey.melnikov at isode.com>
+ Author/Change controller: IESG.
+
+ Capability name: SIEVE
+ Description: Its value contains a space-separated list of supported
+ SIEVE extensions.
+ Relevant publications: this RFC, Section 1.7. Also [SIEVE].
+ Person & email address to contact for further information:
+ Alexey Melnikov <alexey.melnikov at isode.com>
+ Author/Change controller: IESG.
+
+
+
+
+
+
+
+
+Melnikov & Martin Standards Track [Page 39]
+
+RFC 5804 ManageSieve July 2010
+
+
+ Capability name: STARTTLS
+ Description: This capability is returned if the server supports TLS
+ (STARTTLS command).
+ Relevant publications: this RFC, Sections 1.7 and 2.2.
+ Person & email address to contact for further information:
+ Alexey Melnikov <alexey.melnikov at isode.com>
+ Author/Change controller: IESG.
+
+ Capability name: NOTIFY
+ Description: This capability is returned if the server supports the
+ 'enotify' [NOTIFY] Sieve extension.
+ Relevant publications: this RFC, Section 1.7.
+ Person & email address to contact for further information:
+ Alexey Melnikov <alexey.melnikov at isode.com>
+ Author/Change controller: IESG.
+
+ Capability name: MAXREDIRECTS
+ Description: This capability returns the limit on the number of
+ Sieve "redirect" actions a script can perform during a
+ single evaluation. The value is a non-negative number
+ represented as a ManageSieve string.
+ Relevant publications: this RFC, Section 1.7.
+ Person & email address to contact for further information:
+ Alexey Melnikov <alexey.melnikov at isode.com>
+ Author/Change controller: IESG.
+
+ Capability name: LANGUAGE
+ Description: The language (<Language-Tag> from [RFC5646]) currently
+ used for human-readable error messages.
+ Relevant publications: this RFC, Section 1.7.
+ Person & email address to contact for further information:
+ Alexey Melnikov <alexey.melnikov at isode.com>
+ Author/Change controller: IESG.
+
+ Capability name: OWNER
+ Description: Its value contains the UTF-8-encoded name of the
+ currently logged-in user ("authorization identity"
+ according to RFC 4422).
+ Relevant publications: this RFC, Section 1.7.
+ Person & email address to contact for further information:
+ Alexey Melnikov <alexey.melnikov at isode.com>
+ Author/Change controller: IESG.
+
+
+
+
+
+
+
+
+
+Melnikov & Martin Standards Track [Page 40]
+
+RFC 5804 ManageSieve July 2010
+
+
+ Capability name: VERSION
+ Description: This capability is returned if the server is compliant
+ with RFC 5804; i.e., that it supports RENAMESCRIPT,
+ CHECKSCRIPT, and NOOP commands.
+ Relevant publications: this RFC, Sections 2.11, 2.12, and 2.13.
+ Person & email address to contact for further information:
+ Alexey Melnikov <alexey.melnikov at isode.com>
+ Author/Change controller: IESG.
+
+6.3. ManageSieve Response Code Registration Template
+
+ To: iana at iana.org
+ Subject: ManageSieve Response Code Registration
+
+ Please register the following ManageSieve response code:
+
+ Response Code:
+ Arguments (use ABNF to specify syntax, or the word NONE if none
+ can be specified):
+ Purpose:
+ Published Specification(s):
+ Person & email address to contact for further information:
+ Author/Change controller:
+
+6.4. Registration of Initial ManageSieve Response Codes
+
+ To: iana at iana.org
+ Subject: ManageSieve Response Code Registration
+
+ Please register the following ManageSieve response codes:
+
+ Response Code: AUTH-TOO-WEAK
+ Arguments (use ABNF to specify syntax, or the word NONE if none can
+ be specified): NONE
+ Purpose: This response code is returned in the NO response from
+ an AUTHENTICATE command. It indicates that site
+ security policy forbids the use of the requested
+ mechanism for the specified authentication identity.
+ Published Specification(s): [RFC5804]
+ Person & email address to contact for further information:
+ Alexey Melnikov <alexey.melnikov at isode.com>
+ Author/Change controller: IESG.
+
+
+
+
+
+
+
+
+
+Melnikov & Martin Standards Track [Page 41]
+
+RFC 5804 ManageSieve July 2010
+
+
+ Response Code: ENCRYPT-NEEDED
+ Arguments (use ABNF to specify syntax, or the word NONE if none can
+ be specified): NONE
+ Purpose: This response code is returned in the NO response from
+ an AUTHENTICATE command. It indicates that site
+ security policy requires the use of a strong
+ encryption mechanism for the specified authentication
+ identity and mechanism.
+ Published Specification(s): [RFC5804]
+ Person & email address to contact for further information:
+ Alexey Melnikov <alexey.melnikov at isode.com>
+ Author/Change controller: IESG.
+
+ Response Code: QUOTA
+ Arguments (use ABNF to specify syntax, or the word NONE if none can
+ be specified): NONE
+ Purpose: If this response code is returned in the NO/BYE
+ response, it means that the command would have placed
+ the user above the site-defined quota constraints. If
+ this response code is returned in the OK response, it
+ can mean that the user is near its quota or that the
+ user exceeded its quota, but the server supports soft
+ quotas.
+ Published Specification(s): [RFC5804]
+ Person & email address to contact for further information:
+ Alexey Melnikov <alexey.melnikov at isode.com>
+ Author/Change controller: IESG.
+
+ Response Code: QUOTA/MAXSCRIPTS
+ Arguments (use ABNF to specify syntax, or the word NONE if none can
+ be specified): NONE
+ Purpose: If this response code is returned in the NO/BYE
+ response, it means that the command would have placed
+ the user above the site-defined limit on the number of
+ Sieve scripts. If this response code is returned in
+ the OK response, it can mean that the user is near its
+ quota or that the user exceeded its quota, but the
+ server supports soft quotas. This response code is a
+ more specific version of the QUOTA response code.
+ Published Specification(s): [RFC5804]
+ Person & email address to contact for further information:
+ Alexey Melnikov <alexey.melnikov at isode.com>
+ Author/Change controller: IESG.
+
+
+
+
+
+
+
+
+Melnikov & Martin Standards Track [Page 42]
+
+RFC 5804 ManageSieve July 2010
+
+
+ Response Code: QUOTA/MAXSIZE
+ Arguments (use ABNF to specify syntax, or the word NONE if none can
+ be specified): NONE
+ Purpose: If this response code is returned in the NO/BYE
+ response, it means that the command would have placed
+ the user above the site-defined maximum script size.
+ If this response code is returned in the OK response,
+ it can mean that the user is near its quota or that
+ the user exceeded its quota, but the server supports
+ soft quotas. This response code is a more specific
+ version of the QUOTA response code.
+ Published Specification(s): [RFC5804]
+ Person & email address to contact for further information:
+ Alexey Melnikov <alexey.melnikov at isode.com>
+ Author/Change controller: IESG.
+
+ Response Code: REFERRAL
+ Arguments (use ABNF to specify syntax, or the word NONE if none can
+ be specified): <sieveurl>
+ Purpose: This response code may be returned with a BYE result
+ from any command, and includes a mandatory parameter
+ that indicates what server to access to manage this
+ user's Sieve scripts. The server will be specified by
+ a Sieve URL (see Section 3). The scriptname portion
+ of the URL MUST NOT be specified. The client should
+ authenticate to the specified server and use it for
+ all further commands in the current session.
+ Published Specification(s): [RFC5804]
+ Person & email address to contact for further information:
+ Alexey Melnikov <alexey.melnikov at isode.com>
+ Author/Change controller: IESG.
+
+ Response Code: SASL
+ Arguments (use ABNF to specify syntax, or the word NONE if none can
+ be specified): <string>
+ Purpose: This response code can occur in the OK response to a
+ successful AUTHENTICATE command and includes the
+ optional final server response data from the server as
+ specified by [SASL].
+ Published Specification(s): [RFC5804]
+ Person & email address to contact for further information:
+ Alexey Melnikov <alexey.melnikov at isode.com>
+ Author/Change controller: IESG.
+
+
+
+
+
+
+
+
+Melnikov & Martin Standards Track [Page 43]
+
+RFC 5804 ManageSieve July 2010
+
+
+ Response Code: TRANSITION-NEEDED
+ Arguments (use ABNF to specify syntax, or the word NONE if none can
+ be specified): NONE
+ Purpose: This response code occurs in a NO response of an
+ AUTHENTICATE command. It indicates that the user name
+ is valid, but the entry in the authentication database
+ needs to be updated in order to permit authentication
+ with the specified mechanism. This is typically done
+ by establishing a secure channel using TLS, followed
+ by authenticating once using the [PLAIN]
+ authentication mechanism. The selected mechanism
+ SHOULD then work for authentications in subsequent
+ sessions.
+ Published Specification(s): [RFC5804]
+ Person & email address to contact for further information:
+ Alexey Melnikov <alexey.melnikov at isode.com>
+ Author/Change controller: IESG.
+
+ Response Code: TRYLATER
+ Arguments (use ABNF to specify syntax, or the word NONE if none can
+ be specified): NONE
+ Purpose: A command failed due to a temporary server failure.
+ The client MAY continue using local information and
+ try the command later. This response code only make
+ sense when returned in a NO/BYE response.
+ Published Specification(s): [RFC5804]
+ Person & email address to contact for further information:
+ Alexey Melnikov <alexey.melnikov at isode.com>
+ Author/Change controller: IESG.
+
+ Response Code: ACTIVE
+ Arguments (use ABNF to specify syntax, or the word NONE if none can
+ be specified): NONE
+ Purpose: A command failed because it is not allowed on the
+ active script, for example, DELETESCRIPT on the active
+ script. This response code only makes sense when
+ returned in a NO/BYE response.
+ Published Specification(s): [RFC5804]
+ Person & email address to contact for further information:
+ Alexey Melnikov <alexey.melnikov at isode.com>
+ Author/Change controller: IESG.
+
+
+
+
+
+
+
+
+
+
+Melnikov & Martin Standards Track [Page 44]
+
+RFC 5804 ManageSieve July 2010
+
+
+ Response Code: NONEXISTENT
+ Arguments (use ABNF to specify syntax, or the word NONE if none can
+ be specified): NONE
+ Purpose: A command failed because the referenced script name
+ doesn't exist. This response code only makes sense
+ when returned in a NO/BYE response.
+ Published Specification(s): [RFC5804]
+ Person & email address to contact for further information:
+ Alexey Melnikov <alexey.melnikov at isode.com>
+ Author/Change controller: IESG.
+
+ Response Code: ALREADYEXISTS
+ Arguments (use ABNF to specify syntax, or the word NONE if none can
+ be specified): NONE
+ Purpose: A command failed because the referenced script name
+ already exists. This response code only makes sense
+ when returned in a NO/BYE response.
+ Published Specification(s): [RFC5804]
+ Person & email address to contact for further information:
+ Alexey Melnikov <alexey.melnikov at isode.com>
+ Author/Change controller: IESG.
+
+ Response Code: WARNINGS
+ Arguments (use ABNF to specify syntax, or the word NONE if none can
+ be specified): NONE
+ Purpose: This response code MAY be returned by the server in
+ the OK response (but it might be returned with the NO/
+ BYE response as well) and signals the client that even
+ though the script is syntactically valid, it might
+ contain errors not intended by the script writer.
+ Published Specification(s): [RFC5804]
+ Person & email address to contact for further information:
+ Alexey Melnikov <alexey.melnikov at isode.com>
+ Author/Change controller: IESG.
+
+ Response Code: TAG
+ Arguments (use ABNF to specify syntax, or the word NONE if none can
+ be specified): string
+ Purpose: This response code name is followed by a string
+ specified in the command that caused this response.
+ It is typically used for client state synchronization.
+ Published Specification(s): [RFC5804]
+ Person & email address to contact for further information:
+ Alexey Melnikov <alexey.melnikov at isode.com>
+ Author/Change controller: IESG.
+
+
+
+
+
+
+Melnikov & Martin Standards Track [Page 45]
+
+RFC 5804 ManageSieve July 2010
+
+
+7. Internationalization Considerations
+
+ The LANGUAGE capability (see Section 1.7) allows a client to discover
+ the current language used in all human-readable responses that might
+ be returned at the end of any OK/NO/BYE response. Human-readable
+ text in OK responses typically doesn't need to be shown to the user,
+ unless it is returned in response to a PUTSCRIPT or CHECKSCRIPT
+ command that also contains the WARNINGS response code (Section 1.3).
+ Human-readable text from NO/BYE responses is intended be shown to the
+ user, unless the client can automatically handle failure of the
+ command that caused such a response. Clients SHOULD use response
+ codes (Section 1.3) for automatic error handling. Response codes MAY
+ also be used by the client to present error messages in a language
+ understood by the user, for example, if the LANGUAGE capability
+ doesn't return a language understood by the user.
+
+ Note that the human-readable text from OK (WARNINGS) or NO/BYE
+ responses for PUTSCRIPT/CHECKSCRIPT commands is intended for advanced
+ users that understand Sieve language. Such advanced users are often
+ sophisticated enough to be able to handle whatever language the
+ server is using, even if it is not their preferred language, and will
+ want to see error/warning text no matter what language the server
+ puts it in.
+
+ A client that generates Sieve script automatically, for example, if
+ the script is generated without user intervention or from a UI that
+ presents an abstract list of conditions and corresponding actions,
+ SHOULD NOT present warning/error messages to the user, because the
+ user might not even be aware that the client is using Sieve
+ underneath. However, if the client has a debugging mode, such
+ warnings/errors SHOULD be available in the debugging mode.
+
+ Note that this document doesn't provide a way to modify the currently
+ used language. It is expected that a future extension will address
+ that.
+
+8. Acknowledgements
+
+ Thanks to Simon Josefsson, Larry Greenfield, Allen Johnson, Chris
+ Newman, Lyndon Nerenberg, Tim Showalter, Sarah Robeson, Walter Wong,
+ Barry Leiba, Arnt Gulbrandsen, Stephan Bosch, Ken Murchison, Phil
+ Pennock, Ned Freed, Jeffrey Hutzelman, Mark E. Mallett, Dilyan
+ Palauzov, Dave Cridland, Aaron Stone, Robert Burrell Donkin, Patrick
+ Ben Koetter, Bjoern Hoehrmann, Martin Duerst, Pasi Eronen, Magnus
+ Westerlund, Tim Polk, and Julien Coloos for help with this document.
+ Special thank you to Phil Pennock for providing text for the NOOP
+ command, as well as finding various bugs in the document.
+
+
+
+
+Melnikov & Martin Standards Track [Page 46]
+
+RFC 5804 ManageSieve July 2010
+
+
+9. References
+
+9.1. Normative References
+
+ [ABNF] Crocker, D. and P. Overell, "Augmented BNF for Syntax
+ Specifications: ABNF", STD 68, RFC 5234, January 2008.
+
+ [ACAP] Newman, C. and J. Myers, "ACAP -- Application
+ Configuration Access Protocol", RFC 2244, November
+ 1997.
+
+ [BASE64] Josefsson, S., "The Base16, Base32, and Base64 Data
+ Encodings", RFC 4648, October 2006.
+
+ [DNS-SRV] Gulbrandsen, A., Vixie, P., and L. Esibov, "A DNS RR
+ for specifying the location of services (DNS SRV)",
+ RFC 2782, February 2000.
+
+ [KEYWORDS] Bradner, S., "Key words for use in RFCs to Indicate
+ Requirement Levels", BCP 14, RFC 2119, March 1997.
+
+ [NET-UNICODE] Klensin, J. and M. Padlipsky, "Unicode Format for
+ Network Interchange", RFC 5198, March 2008.
+
+ [NOTIFY] Melnikov, A., Leiba, B., Segmuller, W., and T. Martin,
+ "Sieve Email Filtering: Extension for Notifications",
+ RFC 5435, January 2009.
+
+ [RFC2277] Alvestrand, H., "IETF Policy on Character Sets and
+ Languages", BCP 18, RFC 2277, January 1998.
+
+ [RFC2460] Deering, S. and R. Hinden, "Internet Protocol, Version
+ 6 (IPv6) Specification", RFC 2460, December 1998.
+
+ [RFC3490] Faltstrom, P., Hoffman, P., and A. Costello,
+ "Internationalizing Domain Names in Applications
+ (IDNA)", RFC 3490, March 2003.
+
+ [RFC4519] Sciberras, A., "Lightweight Directory Access Protocol
+ (LDAP): Schema for User Applications", RFC 4519, June
+ 2006.
+
+ [RFC5646] Phillips, A. and M. Davis, "Tags for Identifying
+ Languages", BCP 47, RFC 5646, September 2009.
+
+ [RFC791] Postel, J., "Internet Protocol", STD 5, RFC 791,
+ September 1981.
+
+
+
+
+Melnikov & Martin Standards Track [Page 47]
+
+RFC 5804 ManageSieve July 2010
+
+
+ [SASL] Melnikov, A. and K. Zeilenga, "Simple Authentication
+ and Security Layer (SASL)", RFC 4422, June 2006.
+
+ [SASLprep] Zeilenga, K., "SASLprep: Stringprep Profile for User
+ Names and Passwords", RFC 4013, February 2005.
+
+ [SCRAM] Menon-Sen, A., Melnikov, A., Newman, C., and N.
+ Williams, "Salted Challenge Response Authentication
+ Mechanism (SCRAM) SASL and GSS-API Mechanisms", RFC
+ 5802, July 2010.
+
+ [SIEVE] Guenther, P. and T. Showalter, "Sieve: An Email
+ Filtering Language", RFC 5228, January 2008.
+
+ [StringPrep] Hoffman, P. and M. Blanchet, "Preparation of
+ Internationalized Strings ("stringprep")", RFC 3454,
+ December 2002.
+
+ [TLS] Dierks, T. and E. Rescorla, "The Transport Layer
+ Security (TLS) Protocol Version 1.2", RFC 5246, August
+ 2008.
+
+ [URI-GEN] Berners-Lee, T., Fielding, R., and L. Masinter,
+ "Uniform Resource Identifier (URI): Generic Syntax",
+ STD 66, RFC 3986, January 2005.
+
+ [UTF-8] Yergeau, F., "UTF-8, a transformation format of ISO
+ 10646", STD 63, RFC 3629, November 2003.
+
+ [X509] Cooper, D., Santesson, S., Farrell, S., Boeyen, S.,
+ Housley, R., and W. Polk, "Internet X.509 Public Key
+ Infrastructure Certificate and Certificate Revocation
+ List (CRL) Profile", RFC 5280, May 2008.
+
+ [X509-SRV] Santesson, S., "Internet X.509 Public Key
+ Infrastructure Subject Alternative Name for Expression
+ of Service Name", RFC 4985, August 2007.
+
+9.2. Informative References
+
+ [DIGEST-MD5] Leach, P. and C. Newman, "Using Digest Authentication
+ as a SASL Mechanism", RFC 2831, May 2000.
+
+ [GSSAPI] Melnikov, A., "The Kerberos V5 ("GSSAPI") Simple
+ Authentication and Security Layer (SASL) Mechanism",
+ RFC 4752, November 2006.
+
+
+
+
+
+Melnikov & Martin Standards Track [Page 48]
+
+RFC 5804 ManageSieve July 2010
+
+
+ [I-HAVE] Freed, N., "Sieve Email Filtering: Ihave Extension",
+ RFC 5463, March 2009.
+
+ [IMAP] Crispin, M., "INTERNET MESSAGE ACCESS PROTOCOL -
+ VERSION 4rev1", RFC 3501, March 2003.
+
+ [LDAP] Zeilenga, K., "Lightweight Directory Access Protocol
+ (LDAP): Technical Specification Road Map", RFC 4510,
+ June 2006.
+
+ [PLAIN] Zeilenga, K., "The PLAIN Simple Authentication and
+ Security Layer (SASL) Mechanism", RFC 4616, August
+ 2006.
+
+Authors' Addresses
+
+ Alexey Melnikov (editor)
+ Isode Limited
+ 5 Castle Business Village
+ 36 Station Road
+ Hampton, Middlesex TW12 2BX
+ UK
+
+ EMail: Alexey.Melnikov at isode.com
+
+
+ Tim Martin
+ BeThereBeSquare, Inc.
+ 672 Haight st.
+ San Francisco, CA 94117
+ USA
+
+ Phone: +1 510 260-4175
+ EMail: timmartin at alumni.cmu.edu
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Melnikov & Martin Standards Track [Page 49]
+
diff --git a/src/plugins/Makefile.am b/src/plugins/Makefile.am
index e87e4bd..53abd25 100644
--- a/src/plugins/Makefile.am
+++ b/src/plugins/Makefile.am
@@ -19,6 +19,7 @@ SUBDIRS = \
geolocation \
libravatar \
mailmbox \
+ managesieve \
newmail \
notification \
pdf_viewer \
diff --git a/src/plugins/managesieve/Makefile.am b/src/plugins/managesieve/Makefile.am
new file mode 100644
index 0000000..5758aaf
--- /dev/null
+++ b/src/plugins/managesieve/Makefile.am
@@ -0,0 +1,84 @@
+# Copyright 1999-2014 the Claws Mail team.
+# This file is part of Claws Mail package, and distributed under the
+# terms of the General Public License version 3 (or later).
+# See COPYING file for license details.
+
+EXTRA_DIST = claws.def plugin.def version.rc
+
+IFLAGS = \
+ -I$(top_srcdir)/src \
+ -I$(top_srcdir)/src/common \
+ -I$(top_builddir)/src/common \
+ -I$(top_srcdir)/src/gtk
+
+if OS_WIN32
+
+LTRCCOMPILE = $(LIBTOOL) --mode=compile --tag=RC $(RC) \
+ `echo $(DEFS) $(DEFAULT_INCLUDES) $(IFLAGS) | \
+ sed -e 's/-I/--include-dir /g;s/-D/--define /g'`
+
+%.lo : %.rc
+ $(LTRCCOMPILE) -i $< -o $@
+
+plugin_res = version.lo
+plugin_res_ldflag = -Wl,.libs/version.o
+
+export_symbols = -export-symbols $(srcdir)/plugin.def
+
+plugin_deps = libclaws.a $(plugin_res) plugin.def
+
+libclaws.a: claws.def
+ $(DLLTOOL) --output-lib $@ --def $<
+
+plugin_ldadd = -L. -lclaws
+
+else
+plugin_res =
+plugin_res_ldflag =
+export_symbols =
+plugin_deps =
+plugin_ldadd =
+endif
+
+if PLATFORM_WIN32
+no_undefined = -no-undefined
+else
+no_undefined =
+endif
+
+if CYGWIN
+cygwin_export_lib = -L$(top_builddir)/src -lclaws-mail
+else
+cygwin_export_lib =
+endif
+
+plugindir = $(pkglibdir)/plugins
+
+if BUILD_MANAGESIEVE_PLUGIN
+plugin_LTLIBRARIES = managesieve.la
+endif
+
+managesieve_la_LDFLAGS = \
+ $(plugin_res_ldflag) $(no_undefined) $(export_symbols) \
+ -avoid-version -module \
+ $(GTK_LIBS) \
+ $(CURL_LIBS)
+
+managesieve_la_DEPENDENCIES = $(plugin_deps)
+
+managesieve_la_LIBADD = $(plugin_ldadd) $(cygwin_export_lib) \
+ $(GTK_LIBS)
+
+managesieve_la_CPPFLAGS = \
+ $(IFLAGS) \
+ $(GLIB_CFLAGS) \
+ $(GTK_CFLAGS) \
+ $(CURL_CFLAGS)
+
+managesieve_la_SOURCES = \
+ managesieve.c managesieve.h \
+ sieve_plugin.c sieve_plugin.h \
+ sieve_prefs.c sieve_prefs.h \
+ sieve_manager.c sieve_manager.h \
+ sieve_editor.c sieve_editor.h
+
diff --git a/src/plugins/managesieve/claws.def b/src/plugins/managesieve/claws.def
new file mode 100644
index 0000000..cd46de6
--- /dev/null
+++ b/src/plugins/managesieve/claws.def
@@ -0,0 +1,33 @@
+LIBRARY CLAWS-MAIL.EXE
+EXPORTS
+auto_configure_service_sync
+extract_address
+get_locale_dir
+check_plugin_version
+conv_codeset_strdup
+conv_get_locale_charset_str_no_utf8
+debug_print_real
+debug_srcname
+file_exist
+get_rc_dir
+hooks_register_hook
+hooks_unregister_hook
+is_dir_exist
+line_has_quote_char
+make_dir
+pref_get_escaped_pref
+pref_get_unescaped_pref
+prefs_common
+prefs_file_close
+prefs_file_close_revert
+prefs_gtk_register_page
+prefs_gtk_unregister_page
+prefs_read_config
+prefs_set_block_label
+prefs_set_default
+prefs_write_open
+prefs_write_param
+prefs_common_get_prefs
+procmsg_msginfo_add_avatar
+procmsg_msginfo_get_avatar
+md5_hex_digest
diff --git a/src/plugins/managesieve/managesieve.c b/src/plugins/managesieve/managesieve.c
new file mode 100644
index 0000000..7dc23b1
--- /dev/null
+++ b/src/plugins/managesieve/managesieve.c
@@ -0,0 +1,1031 @@
+/*
+ * Claws Mail -- a GTK+ based, lightweight, and fast e-mail client
+ * Copyright (C) 1999-2015 the Claws Mail Team
+ * Copyright (C) 2014-2015 Charles Lehner
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <glib.h>
+#include <glib/gi18n.h>
+
+#include "claws.h"
+#include "account.h"
+#include "gtk/inputdialog.h"
+#include "md5.h"
+#include "utils.h"
+#include "log.h"
+#include "session.h"
+
+#include "managesieve.h"
+#include "sieve_editor.h"
+#include "sieve_prefs.h"
+
+GSList *sessions = NULL;
+
+static void sieve_session_destroy(Session *session);
+static gint sieve_pop_send_queue(SieveSession *session);
+static void sieve_session_reset(SieveSession *session);
+
+void sieve_sessions_close()
+{
+ if (sessions) {
+ g_slist_free_full(sessions, (GDestroyNotify)session_destroy);
+ sessions = NULL;
+ }
+}
+
+void noop_data_cb_fn(SieveSession *session, gpointer cb_data,
+ gpointer user_data)
+{
+ /* noop */
+}
+
+/* remove all command callbacks with a given data pointer */
+void sieve_sessions_discard_callbacks(gpointer user_data)
+{
+ GSList *item;
+ GSList *queue;
+ SieveSession *session;
+ SieveCommand *cmd;
+
+ for (item = sessions; item; item = item->next) {
+ session = (SieveSession *)item->data;
+ cmd = session->current_cmd;
+ if (cmd && cmd->data == user_data)
+ cmd->cb = noop_data_cb_fn;
+ for (queue = session->send_queue; queue; queue = queue->next) {
+ cmd = (SieveCommand *)item->data;
+ if (cmd && cmd->data == user_data)
+ cmd->cb = noop_data_cb_fn;
+ }
+ }
+}
+
+void command_free(SieveCommand *cmd) {
+ g_free(cmd->msg);
+ g_free(cmd);
+}
+
+void sieve_session_handle_status(SieveSession *session,
+ sieve_session_error_cb_fn on_error,
+ sieve_session_connected_cb_fn on_connected,
+ gpointer data)
+{
+ session->on_error = on_error;
+ session->on_connected = on_connected;
+ session->cb_data = data;
+}
+
+static void sieve_error(SieveSession *session, const gchar *msg)
+{
+ if (session->on_error)
+ session->on_error(session, msg, session->cb_data);
+}
+
+static void sieve_connected(SieveSession *session, gboolean connected)
+{
+ if (session->on_connected)
+ session->on_connected(session, connected, session->cb_data);
+}
+
+static gint sieve_auth_recv(SieveSession *session, const gchar *msg)
+{
+ gchar buf[MESSAGEBUFSIZE], *tmp;
+
+ switch (session->auth_type) {
+ case SIEVEAUTH_LOGIN:
+ session->state = SIEVE_AUTH_LOGIN_USER;
+
+ if (strstr(msg, "VXNlcm5hbWU6")) {
+ tmp = g_base64_encode(session->user, strlen(session->user));
+ g_snprintf(buf, sizeof(buf), "\"%s\"", tmp);
+
+ if (session_send_msg(SESSION(session), SESSION_MSG_NORMAL, buf) < 0) {
+ g_free(tmp);
+ return SE_ERROR;
+ }
+ g_free(tmp);
+ log_print(LOG_PROTOCOL, "Sieve> [USERID]\n");
+ } else {
+ /* Server rejects AUTH */
+ if (session_send_msg(SESSION(session), SESSION_MSG_NORMAL,
+ "\"*\"") < 0)
+ return SE_ERROR;
+ log_print(LOG_PROTOCOL, "Sieve> *\n");
+ }
+ break;
+ case SIEVEAUTH_CRAM_MD5:
+ session->state = SIEVE_AUTH_CRAM_MD5;
+
+ if (msg[0] == '"') {
+ gchar *response;
+ gchar *response64;
+ gchar *challenge, *tmp;
+ gsize challengelen;
+ guchar hexdigest[33];
+
+ tmp = g_base64_decode(msg + 1, &challengelen);
+ challenge = g_strndup(tmp, challengelen);
+ g_free(tmp);
+ log_print(LOG_PROTOCOL, "Sieve< [Decoded: %s]\n", challenge);
+
+ g_snprintf(buf, sizeof(buf), "%s", session->pass);
+ md5_hex_hmac(hexdigest, challenge, challengelen,
+ buf, strlen(session->pass));
+ g_free(challenge);
+
+ response = g_strdup_printf
+ ("%s %s", session->user, hexdigest);
+ log_print(LOG_PROTOCOL, "Sieve> [Encoded: %s]\n", response);
+
+ response64 = g_base64_encode(response, strlen(response));
+ g_free(response);
+
+ response = g_strdup_printf("\"%s\"", response64);
+ g_free(response64);
+
+ if (session_send_msg(SESSION(session), SESSION_MSG_NORMAL,
+ response) < 0) {
+ g_free(response);
+ return SE_ERROR;
+ }
+ log_print(LOG_PROTOCOL, "Sieve> %s\n", response);
+ g_free(response);
+ } else {
+ /* Server rejects AUTH */
+ if (session_send_msg(SESSION(session), SESSION_MSG_NORMAL,
+ "\"*\"") < 0)
+ return SE_ERROR;
+ log_print(LOG_PROTOCOL, "Sieve> *\n");
+ }
+ break;
+ default:
+ /* stop sieve_auth when no correct authtype */
+ if (session_send_msg(SESSION(session), SESSION_MSG_NORMAL, "*") < 0)
+ return SE_ERROR;
+ log_print(LOG_PROTOCOL, "Sieve> *\n");
+ break;
+ }
+
+ return SE_OK;
+}
+
+static gint sieve_auth_login_user_recv(SieveSession *session, const gchar *msg)
+{
+ gchar *tmp, *tmp2;
+
+ session->state = SIEVE_AUTH_LOGIN_PASS;
+
+ if (strstr(msg, "UGFzc3dvcmQ6")) {
+ tmp2 = g_base64_encode(session->pass, strlen(session->pass));
+ tmp = g_strdup_printf("\"%s\"", tmp2);
+ g_free(tmp2);
+ } else {
+ /* Server rejects AUTH */
+ tmp = g_strdup("\"*\"");
+ }
+
+ if (session_send_msg(SESSION(session), SESSION_MSG_NORMAL, tmp) < 0) {
+ g_free(tmp);
+ return SE_ERROR;
+ }
+ g_free(tmp);
+
+ log_print(LOG_PROTOCOL, "Sieve> [PASSWORD]\n");
+
+ return SE_OK;
+}
+
+
+static gint sieve_auth_cram_md5(SieveSession *session)
+{
+ session->state = SIEVE_AUTH;
+ session->auth_type = SIEVEAUTH_CRAM_MD5;
+
+ if (session_send_msg(SESSION(session), SESSION_MSG_NORMAL,
+ "Authenticate \"CRAM-MD5\"") < 0)
+ return SE_ERROR;
+ log_print(LOG_PROTOCOL, "Sieve> Authenticate CRAM-MD5\n");
+
+ return SE_OK;
+}
+
+static gint sieve_auth_plain(SieveSession *session)
+{
+ gchar buf[MESSAGEBUFSIZE], *b64buf, *out;
+ gint len;
+
+ session->state = SIEVE_AUTH_PLAIN;
+ session->auth_type = SIEVEAUTH_PLAIN;
+
+ memset(buf, 0, sizeof buf);
+
+ /* "\0user\0password" */
+ len = sprintf(buf, "%c%s%c%s", '\0', session->user, '\0', session->pass);
+ b64buf = g_base64_encode(buf, len);
+ out = g_strconcat("Authenticate \"PLAIN\" \"", b64buf, "\"", NULL);
+ g_free(b64buf);
+
+ if (session_send_msg(SESSION(session), SESSION_MSG_NORMAL, out) < 0) {
+ g_free(out);
+ return SE_ERROR;
+ }
+
+ g_free(out);
+
+ log_print(LOG_PROTOCOL, "Sieve> [Authenticate PLAIN]\n");
+
+ return SE_OK;
+}
+
+static gint sieve_auth_login(SieveSession *session)
+{
+ session->state = SIEVE_AUTH;
+ session->auth_type = SIEVEAUTH_LOGIN;
+
+ if (session_send_msg(SESSION(session), SESSION_MSG_NORMAL,
+ "Authenticate \"LOGIN\"") < 0)
+ return SE_ERROR;
+ log_print(LOG_PROTOCOL, "Sieve> Authenticate LOGIN\n");
+
+ return SE_OK;
+}
+
+static gint sieve_auth(SieveSession *session)
+{
+ SieveAuthType forced_auth_type = session->forced_auth_type;
+
+ if (!session->use_auth) {
+ session->state = SIEVE_READY;
+ sieve_connected(session, TRUE);
+ return sieve_pop_send_queue(session);
+ }
+
+ session->state = SIEVE_AUTH;
+ sieve_error(session, _("Authenticating..."));
+
+ if ((forced_auth_type == SIEVEAUTH_CRAM_MD5 || forced_auth_type == 0) &&
+ (session->avail_auth_type & SIEVEAUTH_CRAM_MD5) != 0)
+ return sieve_auth_cram_md5(session);
+ else if ((forced_auth_type == SIEVEAUTH_LOGIN || forced_auth_type == 0) &&
+ (session->avail_auth_type & SIEVEAUTH_LOGIN) != 0)
+ return sieve_auth_login(session);
+ else if ((forced_auth_type == SIEVEAUTH_PLAIN || forced_auth_type == 0) &&
+ (session->avail_auth_type & SIEVEAUTH_PLAIN) != 0)
+ return sieve_auth_plain(session);
+ else if (forced_auth_type == 0) {
+ log_warning(LOG_PROTOCOL, _("No Sieve auth method available\n"));
+ session->state = SIEVE_RETRY_AUTH;
+ return SE_AUTHFAIL;
+ } else {
+ log_warning(LOG_PROTOCOL, _("Selected Sieve auth method not available\n"));
+ session->state = SIEVE_RETRY_AUTH;
+ return SE_AUTHFAIL;
+ }
+
+ return SE_OK;
+}
+
+void sieve_session_putscript_cb(SieveSession *session, SieveResult *result)
+{
+ /* Remove script name from the beginning the response,
+ * which are added by Dovecot/Pigeonhole */
+ gchar *start, *desc = result->description;
+ if (desc) {
+ if (g_str_has_prefix(desc, "NULL_") && (start = strchr(desc+5, ':'))) {
+ desc = start+1;
+ while (*desc == ' ')
+ desc++;
+ /* TODO: match against known script name, in case it contains
+ * weird text like ": line " */
+ } else if ((start = strstr(desc, ": line ")) ||
+ (start = strstr(desc, ": error"))) {
+ desc = start+2;
+ }
+ result->description = desc;
+ }
+ /* pass along the callback */
+ session->current_cmd->cb(session, result, session->current_cmd->data);
+}
+
+inline gboolean response_is_ok(const char *msg)
+{
+ return !strncmp(msg, "OK", 2) && (!msg[2] || msg[2] == ' ');
+}
+
+inline gboolean response_is_no(const char *msg)
+{
+ return !strncmp(msg, "NO", 2) && (!msg[2] || msg[2] == ' ');
+}
+
+inline gboolean response_is_bye(const char *msg)
+{
+ return !strncmp(msg, "BYE", 3) && (!msg[3] || msg[3] == ' ');
+}
+
+void sieve_got_capability(SieveSession *session, gchar *cap_name,
+ gchar *cap_value)
+{
+ if (strcmp(cap_name, "SASL") == 0) {
+ SieveAuthType auth_type = 0;
+ gchar *auth, *end;
+ for (auth = cap_value; auth && auth[0]; auth = end) {
+ if ((end = strchr(auth, ' ')))
+ *end++ = '\0';
+ if (strcmp(auth, "PLAIN") == 0) {
+ auth_type |= SIEVEAUTH_PLAIN;
+ } else if (strcmp(auth, "CRAM-MD5") == 0) {
+ auth_type |= SIEVEAUTH_CRAM_MD5;
+ } else if (strcmp(auth, "LOGIN") == 0) {
+ auth_type |= SIEVEAUTH_LOGIN;
+ }
+ }
+ session->avail_auth_type = auth_type;
+
+ } else if (strcmp(cap_name, "STARTTLS") == 0) {
+ session->capability.starttls = TRUE;
+ }
+}
+
+static void log_send(SieveSession *session, SieveCommand *cmd)
+{
+ gchar *end, *msg = cmd->msg;
+ if (cmd->next_state == SIEVE_PUTSCRIPT && (end = strchr(msg, '\n'))) {
+ /* Don't log the script data */
+ msg = g_strndup(msg, end - msg);
+ log_print(LOG_PROTOCOL, "Sieve> %s\n", msg);
+ g_free(msg);
+ msg = "[Data]";
+ }
+ log_print(LOG_PROTOCOL, "Sieve> %s\n", msg);
+}
+
+static gint sieve_pop_send_queue(SieveSession *session)
+{
+ SieveCommand *cmd;
+ GSList *send_queue = session->send_queue;
+
+ if (!send_queue)
+ return SE_OK;
+
+ cmd = (SieveCommand *)send_queue->data;
+ session->send_queue = g_slist_next(send_queue);
+ g_slist_free_1(send_queue);
+
+ log_send(session, cmd);
+ session->state = cmd->next_state;
+ if (session->current_cmd)
+ command_free(session->current_cmd);
+ session->current_cmd = cmd;
+ if (session_send_msg(SESSION(session), SESSION_SEND, cmd->msg) < 0)
+ return SE_ERROR;
+
+ return SE_OK;
+}
+
+static void parse_split(gchar *line, gchar **first_word, gchar **second_word)
+{
+ gchar *first = line;
+ gchar *second;
+ gchar *end;
+
+ /* get first */
+ if (line[0] == '"' && ((second = strchr(line + 1, '"')))) {
+ *second++ = '\0';
+ first++;
+ if (second[0] == ' ')
+ second++;
+ } else if ((second = strchr(line, ' '))) {
+ *second++ = '\0';
+ }
+
+ /* unquote second */
+ if (second && second[0] == '"' &&
+ ((end = strchr(second + 1, '"')))) {
+ second++;
+ *end = '\0';
+ }
+
+ *first_word = first;
+ *second_word = second;
+}
+
+static void unquote_inplace(gchar *str)
+{
+ gchar *src, *dest;
+ if (*str != '"')
+ return;
+ for (src = str+1, dest = str; src && *src && *src != '"'; src++) {
+ if (*src == '\\') {
+ src++;
+ }
+ *dest++ = *src;
+ }
+ *dest = '\0';
+}
+
+static void parse_response(gchar *msg, SieveResult *result)
+{
+ /* response status */
+ gchar *end = strchr(msg, ' ');
+ if (end)
+ *end++ = '\0';
+ result->success = strcmp(msg, "OK") == 0;
+ result->has_status = TRUE;
+ if (!end) {
+ result->code = SIEVE_CODE_NONE;
+ result->description = NULL;
+ result->has_octets = FALSE;
+ result->octets = 0;
+ return;
+ }
+ while (*end == ' ')
+ end++;
+ msg = end;
+
+ /* response code */
+ if (msg[0] == '(' && (end = strchr(msg, ')'))) {
+ msg++;
+ *end++ = '\0';
+ result->code =
+ strcmp(msg, "WARNINGS") == 0 ? SIEVE_CODE_WARNINGS :
+ strcmp(msg, "TRYLATER") == 0 ? SIEVE_CODE_TRYLATER :
+ SIEVE_CODE_UNKNOWN;
+ while (*end == ' ')
+ end++;
+ msg = end;
+ } else {
+ result->code = SIEVE_CODE_NONE;
+ }
+
+ /* s2c octets */
+ if (msg[0] == '{' && (end = strchr(msg, '}'))) {
+ msg++;
+ *end++ = '\0';
+ if (msg[0] == '0' && msg+1 == end) {
+ result->has_octets = TRUE;
+ result->octets = 0;
+ } else {
+ result->has_octets =
+ (result->octets = g_ascii_strtoll(msg, NULL, 10)) != 0;
+ }
+ while (*end == ' ')
+ end++;
+ msg = end;
+ } else {
+ result->has_octets = FALSE;
+ result->octets = 0;
+ }
+
+ /* text */
+ if (*msg) {
+ unquote_inplace(msg);
+ result->description = msg;
+ } else {
+ result->description = NULL;
+ }
+}
+
+static gint sieve_session_recv_msg(Session *session, const gchar *msg)
+{
+ SieveSession *sieve_session = SIEVE_SESSION(session);
+ SieveResult result;
+ gint ret = 0;
+
+ switch (sieve_session->state) {
+ case SIEVE_GETSCRIPT_DATA:
+ log_print(LOG_PROTOCOL, "Sieve< [GETSCRIPT data]\n");
+ break;
+ default:
+ log_print(LOG_PROTOCOL, "Sieve< %s\n", msg);
+ if (response_is_bye(msg)) {
+ gchar *status;
+ parse_response((gchar *)msg, &result);
+ if (!result.description)
+ status = g_strdup(_("Disconnected"));
+ else if (g_str_has_prefix(result.description, "Disconnected"))
+ status = g_strdup(result.description);
+ else
+ status = g_strdup_printf(_("Disconnected: %s"), result.description);
+ sieve_session->error = SE_ERROR;
+ sieve_error(sieve_session, status);
+ sieve_session->state = SIEVE_DISCONNECTED;
+ g_free(status);
+ return -1;
+ }
+ }
+
+ switch (sieve_session->state) {
+ case SIEVE_CAPABILITIES:
+ if (response_is_ok(msg)) {
+ /* capabilities list done */
+
+#ifdef USE_GNUTLS
+ if (sieve_session->tls_init_done == FALSE &&
+ sieve_session->config->tls_type != SIEVE_TLS_NO) {
+ if (sieve_session->capability.starttls) {
+ log_print(LOG_PROTOCOL, "Sieve> STARTTLS\n");
+ session_send_msg(session, SESSION_SEND, "STARTTLS");
+ sieve_session->state = SIEVE_STARTTLS;
+ } else if (sieve_session->config->tls_type == SIEVE_TLS_YES) {
+ log_warning(LOG_PROTOCOL, "Sieve: does not support STARTTLS\n");
+ sieve_session->state = SIEVE_ERROR;
+ } else {
+ log_warning(LOG_PROTOCOL, "Sieve: continuing without TLS\n");
+ sieve_session->state = SIEVE_CAPABILITIES;
+ }
+ break;
+ }
+#endif
+ /* authenticate after getting capabilities */
+ if (!sieve_session->authenticated) {
+ ret = sieve_auth(sieve_session);
+ } else {
+ sieve_session->state = SIEVE_READY;
+ sieve_connected(sieve_session, TRUE);
+ ret = sieve_pop_send_queue(sieve_session);
+ }
+ } else {
+ /* got a capability */
+ gchar *cap_name, *cap_value;
+ parse_split((gchar *)msg, &cap_name, &cap_value);
+ sieve_got_capability(sieve_session, cap_name, cap_value);
+ }
+ break;
+ case SIEVE_READY:
+ log_warning(LOG_PROTOCOL,
+ _("unhandled message on Sieve session: %s\n"), msg);
+ break;
+ case SIEVE_STARTTLS:
+#ifdef USE_GNUTLS
+ if (session_start_tls(session) < 0) {
+ sieve_session->state = SIEVE_ERROR;
+ sieve_session->error = SE_ERROR;
+ sieve_error(sieve_session, _("TLS failed"));
+ return -1;
+ }
+ sieve_session->tls_init_done = TRUE;
+ sieve_session->state = SIEVE_CAPABILITIES;
+ ret = SE_OK;
+#endif
+ break;
+ case SIEVE_AUTH:
+ ret = sieve_auth_recv(sieve_session, msg);
+ break;
+ case SIEVE_AUTH_LOGIN_USER:
+ ret = sieve_auth_login_user_recv(sieve_session, msg);
+ break;
+ case SIEVE_AUTH_PLAIN:
+ case SIEVE_AUTH_LOGIN_PASS:
+ case SIEVE_AUTH_CRAM_MD5:
+ if (response_is_no(msg)) {
+ log_print(LOG_PROTOCOL, "Sieve auth failed\n");
+ session->state = SIEVE_RETRY_AUTH;
+ ret = SE_AUTHFAIL;
+ } else if (response_is_ok(msg)) {
+ log_print(LOG_PROTOCOL, "Sieve auth completed\n");
+ sieve_error(sieve_session, _(""));
+ sieve_session->authenticated = TRUE;
+ sieve_session->state = SIEVE_READY;
+ sieve_connected(sieve_session, TRUE);
+ ret = sieve_pop_send_queue(sieve_session);
+ }
+ break;
+ case SIEVE_NOOP:
+ if (!response_is_ok(msg)) {
+ sieve_session->state = SIEVE_ERROR;
+ }
+ sieve_session->state = SIEVE_READY;
+ break;
+ case SIEVE_LISTSCRIPTS:
+ if (response_is_no(msg)) {
+ /* got an error. probably not authenticated. */
+ sieve_session->current_cmd->cb(sieve_session, NULL,
+ sieve_session->current_cmd->data);
+ sieve_session->state = SIEVE_READY;
+ ret = sieve_pop_send_queue(sieve_session);
+ } else if (response_is_ok(msg)) {
+ /* end of list */
+ sieve_session->state = SIEVE_READY;
+ sieve_session->error = SE_OK;
+ sieve_session->current_cmd->cb(sieve_session,
+ (gpointer)&(SieveScript){0},
+ sieve_session->current_cmd->data);
+ ret = sieve_pop_send_queue(sieve_session);
+ } else {
+ /* got a script name */
+ SieveScript script;
+ gchar *script_status;
+
+ parse_split((gchar *)msg, &script.name, &script_status);
+ script.active = (script_status &&
+ strcasecmp(script_status, "active") == 0);
+
+ sieve_session->current_cmd->cb(sieve_session, (gpointer)&script,
+ sieve_session->current_cmd->data);
+ ret = SE_OK;
+ }
+ break;
+ case SIEVE_RENAMESCRIPT:
+ if (response_is_no(msg)) {
+ /* error */
+ sieve_session->current_cmd->cb(sieve_session, NULL,
+ sieve_session->current_cmd->data);
+ } else if (response_is_ok(msg)) {
+ sieve_session->current_cmd->cb(sieve_session, (void*)TRUE,
+ sieve_session->current_cmd->data);
+ } else {
+ log_warning(LOG_PROTOCOL, _("error occurred on SIEVE session\n"));
+ }
+ sieve_session->state = SIEVE_READY;
+ break;
+ case SIEVE_SETACTIVE:
+ if (response_is_no(msg)) {
+ /* error */
+ sieve_session->current_cmd->cb(sieve_session, NULL,
+ sieve_session->current_cmd->data);
+ } else if (response_is_ok(msg)) {
+ sieve_session->current_cmd->cb(sieve_session, (void*)TRUE,
+ sieve_session->current_cmd->data);
+ } else {
+ log_warning(LOG_PROTOCOL, _("error occurred on SIEVE session\n"));
+ }
+ sieve_session->state = SIEVE_READY;
+ break;
+ case SIEVE_GETSCRIPT:
+ if (response_is_no(msg)) {
+ sieve_session->current_cmd->cb(sieve_session, (void *)-1,
+ sieve_session->current_cmd->data);
+ sieve_session->state = SIEVE_READY;
+ } else {
+ parse_response((gchar *)msg, &result);
+ sieve_session->state = SIEVE_GETSCRIPT_DATA;
+ }
+ ret = SE_OK;
+ break;
+ case SIEVE_GETSCRIPT_DATA:
+ if (response_is_ok(msg)) {
+ sieve_session->state = SIEVE_READY;
+ sieve_session->current_cmd->cb(sieve_session, NULL,
+ sieve_session->current_cmd->data);
+ } else {
+ sieve_session->current_cmd->cb(sieve_session, (gchar *)msg,
+ sieve_session->current_cmd->data);
+ }
+ ret = SE_OK;
+ break;
+ case SIEVE_PUTSCRIPT:
+ parse_response((gchar *)msg, &result);
+ if (result.has_octets) {
+ sieve_session->state = SIEVE_PUTSCRIPT_DATA;
+ } else {
+ sieve_session->state = SIEVE_READY;
+ }
+ sieve_session_putscript_cb(sieve_session, &result);
+ ret = SE_OK;
+ break;
+ case SIEVE_PUTSCRIPT_DATA:
+ if (!msg[0]) {
+ sieve_session->state = SIEVE_READY;
+ } else {
+ result.has_status = FALSE;
+ result.has_octets = FALSE;
+ result.success = -1;
+ result.code = SIEVE_CODE_NONE;
+ result.description = (gchar *)msg;
+ sieve_session_putscript_cb(sieve_session, &result);
+ }
+ ret = SE_OK;
+ break;
+ case SIEVE_DELETESCRIPT:
+ parse_response((gchar *)msg, &result);
+ if (!result.success) {
+ sieve_session->current_cmd->cb(sieve_session, result.description,
+ sieve_session->current_cmd->data);
+ } else {
+ sieve_session->current_cmd->cb(sieve_session, NULL,
+ sieve_session->current_cmd->data);
+ }
+ sieve_session->state = SIEVE_READY;
+ break;
+ case SIEVE_ERROR:
+ log_warning(LOG_PROTOCOL, _("error occurred on Sieve session. data: %s\n"), msg);
+ sieve_session->error = SE_ERROR;
+ break;
+ case SIEVE_RETRY_AUTH:
+ log_warning(LOG_PROTOCOL, _("unhandled message on Sieve session: %s\n"),
+ msg);
+ ret = sieve_auth(sieve_session);
+ break;
+ default:
+ log_warning(LOG_PROTOCOL, _("unhandled message on Sieve session: %d\n"),
+ sieve_session->state);
+ sieve_session->error = SE_ERROR;
+ return -1;
+ }
+
+ if (ret == SE_OK)
+ return session_recv_msg(session);
+ else if (ret == SE_AUTHFAIL) {
+ sieve_error(sieve_session, _("Auth failed"));
+ sieve_session->state = SIEVE_ERROR;
+ sieve_session->error = SE_ERROR;
+ }
+
+ return 0;
+}
+
+static gint sieve_recv_message(Session *session, const gchar *msg,
+ gpointer user_data)
+{
+ return 0;
+}
+
+static gint sieve_cmd_noop(SieveSession *session)
+{
+ log_print(LOG_PROTOCOL, "Sieve> NOOP\n");
+ session->state = SIEVE_NOOP;
+ if (session_send_msg(SESSION(session), SESSION_SEND, "NOOP") < 0) {
+ session->state = SIEVE_ERROR;
+ session->error = SE_ERROR;
+ return 1;
+ }
+ return 0;
+}
+
+static gboolean sieve_ping(gpointer data)
+{
+ Session *session = SESSION(data);
+ SieveSession *sieve_session = SIEVE_SESSION(session);
+
+ if (sieve_session->state == SIEVE_ERROR || session->state == SESSION_ERROR)
+ return FALSE;
+ if (sieve_session->state != SIEVE_READY)
+ return TRUE;
+
+ return sieve_cmd_noop(sieve_session) == 0;
+}
+
+static void sieve_session_destroy(Session *session)
+{
+ SieveSession *sieve_session = SIEVE_SESSION(session);
+ g_free(sieve_session->pass);
+ if (sieve_session->current_cmd)
+ command_free(sieve_session->current_cmd);
+ sessions = g_slist_remove(sessions, (gconstpointer)session);
+}
+
+static void sieve_connect_finished(Session *session, gboolean success)
+{
+ if (!success) {
+ sieve_connected(SIEVE_SESSION(session), FALSE);
+ }
+}
+
+static gint sieve_session_connect(SieveSession *session)
+{
+ session->state = SIEVE_CAPABILITIES;
+ session->authenticated = FALSE;
+ session->tls_init_done = FALSE;
+ return session_connect(SESSION(session), session->host,
+ session->port);
+}
+
+static SieveSession *sieve_session_new(PrefsAccount *account)
+{
+ SieveSession *session;
+ session = g_new0(SieveSession, 1);
+ session_init(SESSION(session), account, FALSE);
+
+ session->account = account;
+
+ SESSION(session)->recv_msg = sieve_session_recv_msg;
+ SESSION(session)->destroy = sieve_session_destroy;
+ SESSION(session)->connect_finished = sieve_connect_finished;
+ session_set_recv_message_notify(SESSION(session), sieve_recv_message, NULL);
+
+ sieve_session_reset(session);
+ return session;
+}
+
+static void sieve_session_reset(SieveSession *session)
+{
+ PrefsAccount *account = session->account;
+ SieveAccountConfig *config = sieve_prefs_account_get_config(account);
+ gboolean reuse_auth = (config->auth == SIEVEAUTH_REUSE);
+
+ g_slist_free_full(session->send_queue, (GDestroyNotify)command_free);
+
+ session_disconnect(SESSION(session));
+
+ SESSION(session)->ssl_cert_auto_accept = account->ssl_certs_auto_accept;
+ SESSION(session)->nonblocking = account->use_nonblocking_ssl;
+ session->authenticated = FALSE;
+ session->current_cmd = NULL;
+ session->send_queue = NULL;
+ session->state = SIEVE_CAPABILITIES;
+ session->tls_init_done = FALSE;
+ session->avail_auth_type = 0;
+ session->auth_type = 0;
+ session->config = config;
+ session->host = config->use_host ? config->host : account->recv_server;
+ session->port = config->use_port ? config->port : SIEVE_PORT;
+ session->user = reuse_auth ? account->userid : session->config->userid;
+ session->forced_auth_type = config->auth_type;
+ session_register_ping(SESSION(session), sieve_ping);
+
+ if (session->pass)
+ g_free(session->pass);
+ if (config->auth == SIEVEAUTH_NONE) {
+ session->pass = NULL;
+ } else if (reuse_auth && account->passwd) {
+ session->pass = g_strdup(account->passwd);
+ } else if (config->passwd && config->passwd[0]) {
+ session->pass = g_strdup(config->passwd);
+ } else if (password_get(session->user, session->host, "sieve",
+ session->port, &session->pass)) {
+ } else {
+ session->pass = input_dialog_query_password_keep(session->host,
+ session->user, &(session->pass));
+ }
+ if (!session->pass) {
+ session->pass = g_strdup("");
+ session->use_auth = FALSE;
+ } else {
+ session->use_auth = TRUE;
+ }
+
+#ifdef USE_GNUTLS
+ SESSION(session)->ssl_type =
+ (config->tls_type == SIEVE_TLS_NO) ? SSL_NONE : SSL_STARTTLS;
+#endif
+}
+
+/* When an account config is changed, reset associated sessions. */
+void sieve_account_prefs_updated(PrefsAccount *account)
+{
+ GSList *item;
+ SieveSession *session;
+
+ for (item = sessions; item; item = item->next) {
+ session = (SieveSession *)item->data;
+ if (session->account == account) {
+ log_print(LOG_PROTOCOL, "Sieve: resetting session\n");
+ sieve_session_reset(session);
+ }
+ }
+}
+
+SieveSession *sieve_session_get_for_account(PrefsAccount *account)
+{
+ SieveSession *session;
+ GSList *item;
+
+ /* find existing */
+ for (item = sessions; item; item = item->next) {
+ session = (SieveSession *)item->data;
+ if (session->account == account) {
+ return session;
+ }
+ }
+
+ /* create new */
+ session = sieve_session_new(account);
+ sessions = g_slist_prepend(sessions, session);
+
+ return session;
+}
+
+static void sieve_queue_send(SieveSession *session, SieveState next_state,
+ gchar *msg, sieve_session_data_cb_fn cb, gpointer data)
+{
+ gboolean queue = FALSE;
+ SieveCommand *cmd = g_new0(SieveCommand, 1);
+ cmd->next_state = next_state;
+ cmd->msg = msg;
+ cmd->data = data;
+ cmd->cb = cb;
+
+ if (!session_is_connected(SESSION(session))) {
+ log_print(LOG_PROTOCOL, "Sieve: connecting to %s:%hu\n",
+ session->host, session->port);
+ if (sieve_session_connect(session) < 0) {
+ sieve_connect_finished(SESSION(session), FALSE);
+ }
+ queue = TRUE;
+ } else if (session->state == SIEVE_RETRY_AUTH) {
+ log_print(LOG_PROTOCOL, _("Sieve: retrying auth\n"));
+ if (sieve_auth(session) == SE_AUTHFAIL)
+ sieve_error(session, _("Auth method not available"));
+ queue = TRUE;
+ } else if (session->state != SIEVE_READY) {
+ log_print(LOG_PROTOCOL, "Sieve: in state %d\n", session->state);
+ queue = TRUE;
+ }
+
+ if (queue) {
+ session->send_queue = g_slist_prepend(session->send_queue, cmd);
+ } else {
+ if (session->current_cmd)
+ command_free(session->current_cmd);
+ session->current_cmd = cmd;
+ session->state = next_state;
+ log_send(session, cmd);
+ if (session_send_msg(SESSION(session), SESSION_SEND, cmd->msg) < 0) {
+ /* error */
+ }
+ }
+}
+
+void sieve_session_list_scripts(SieveSession *session,
+ sieve_session_data_cb_fn cb, gpointer data)
+{
+ gchar *msg = g_strdup("LISTSCRIPTS");
+ sieve_queue_send(session, SIEVE_LISTSCRIPTS, msg, cb, data);
+}
+
+void sieve_session_add_script(SieveSession *session, const gchar *filter_name,
+ sieve_session_data_cb_fn cb, gpointer data)
+{
+/*
+ gchar *msg = g_strdup("LISTSCRIPTS");
+ sieve_queue_send(session, SIEVE_LISTSCRIPTS, msg, cb, data);
+*/
+}
+
+void sieve_session_set_active_script(SieveSession *session,
+ const gchar *filter_name,
+ sieve_session_data_cb_fn cb, gpointer data)
+{
+ gchar *msg = g_strdup_printf("SETACTIVE \"%s\"",
+ filter_name ? filter_name : "");
+ if (!msg) {
+ cb(session, (void*)FALSE, data);
+ return;
+ }
+
+ sieve_queue_send(session, SIEVE_SETACTIVE, msg, cb, data);
+}
+
+void sieve_session_rename_script(SieveSession *session,
+ const gchar *name_old, const char *name_new,
+ sieve_session_data_cb_fn cb, gpointer data)
+{
+ gchar *msg = g_strdup_printf("RENAMESCRIPT \"%s\" \"%s\"",
+ name_old, name_new);
+
+ sieve_queue_send(session, SIEVE_RENAMESCRIPT, msg, cb, data);
+}
+
+void sieve_session_get_script(SieveSession *session, const gchar *filter_name,
+ sieve_session_data_cb_fn cb, gpointer data)
+{
+ gchar *msg = g_strdup_printf("GETSCRIPT \"%s\"",
+ filter_name);
+
+ sieve_queue_send(session, SIEVE_GETSCRIPT, msg, cb, data);
+}
+
+void sieve_session_put_script(SieveSession *session, const gchar *filter_name,
+ gint len, const gchar *script_contents,
+ sieve_session_data_cb_fn cb, gpointer data)
+{
+ /* TODO: refactor so don't have to copy the whole script here */
+ gchar *msg = g_strdup_printf("PUTSCRIPT \"%s\" {%u+}\r\n%s",
+ filter_name, len, script_contents);
+
+ sieve_queue_send(session, SIEVE_PUTSCRIPT, msg, cb, data);
+}
+
+void sieve_session_check_script(SieveSession *session,
+ gint len, const gchar *script_contents,
+ sieve_session_data_cb_fn cb, gpointer data)
+{
+ gchar *msg = g_strdup_printf("CHECKSCRIPT {%u+}\r\n%s",
+ len, script_contents);
+
+ sieve_queue_send(session, SIEVE_PUTSCRIPT, msg, cb, data);
+}
+
+void sieve_session_delete_script(SieveSession *session,
+ const gchar *filter_name,
+ sieve_session_data_cb_fn cb, gpointer data)
+{
+ gchar *msg = g_strdup_printf("DELETESCRIPT \"%s\"",
+ filter_name);
+
+ sieve_queue_send(session, SIEVE_DELETESCRIPT, msg, cb, data);
+}
diff --git a/src/plugins/managesieve/managesieve.h b/src/plugins/managesieve/managesieve.h
new file mode 100644
index 0000000..b775f90
--- /dev/null
+++ b/src/plugins/managesieve/managesieve.h
@@ -0,0 +1,193 @@
+/*
+ * Claws Mail -- a GTK+ based, lightweight, and fast e-mail client
+ * Copyright (C) 1999-2015 the Claws Mail team
+ * Copyright (C) 2014-2015 Charles Lehner
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef MANAGESIEVE_H
+#define MANAGESIEVE_H
+
+#include "prefs_account.h"
+#include "socket.h"
+#include "session.h"
+
+#define SIEVE_SESSION(obj) ((SieveSession *)obj)
+#define SIEVE_PORT 4190
+
+typedef struct SieveSession SieveSession;
+typedef struct SieveCommand SieveCommand;
+typedef struct SieveScript SieveScript;
+typedef struct SieveResult SieveResult;
+
+typedef enum
+{
+ SE_OK = 0,
+ SE_ERROR = 128,
+ SE_UNRECOVERABLE = 129,
+ SE_AUTHFAIL = 130
+} SieveErrorValue;
+
+typedef enum
+{
+ SIEVEAUTH_NONE = 0,
+ SIEVEAUTH_REUSE = 1,
+ SIEVEAUTH_CUSTOM = 2
+} SieveAuth;
+
+typedef enum
+{
+ SIEVEAUTH_AUTO = 0,
+ SIEVEAUTH_PLAIN = 1 << 0,
+ SIEVEAUTH_LOGIN = 1 << 1,
+ SIEVEAUTH_CRAM_MD5 = 1 << 2,
+} SieveAuthType;
+
+typedef enum
+{
+ SIEVE_CAPABILITIES,
+ SIEVE_READY,
+ SIEVE_LISTSCRIPTS,
+ SIEVE_STARTTLS,
+ SIEVE_NOOP,
+ SIEVE_RETRY_AUTH,
+ SIEVE_AUTH,
+ SIEVE_AUTH_PLAIN,
+ SIEVE_AUTH_LOGIN_USER,
+ SIEVE_AUTH_LOGIN_PASS,
+ SIEVE_AUTH_CRAM_MD5,
+ SIEVE_RENAMESCRIPT,
+ SIEVE_SETACTIVE,
+ SIEVE_GETSCRIPT,
+ SIEVE_GETSCRIPT_DATA,
+ SIEVE_PUTSCRIPT,
+ SIEVE_PUTSCRIPT_DATA,
+ SIEVE_DELETESCRIPT,
+ SIEVE_ERROR,
+ SIEVE_DISCONNECTED,
+
+ N_SIEVE_PHASE
+} SieveState;
+
+typedef enum
+{
+ SIEVE_CODE_NONE,
+ SIEVE_CODE_WARNINGS,
+ SIEVE_CODE_TRYLATER,
+ SIEVE_CODE_UNKNOWN
+} SieveResponseCode;
+
+typedef enum {
+ SIEVE_TLS_NO,
+ SIEVE_TLS_MAYBE,
+ SIEVE_TLS_YES
+} SieveTLSType;
+
+typedef void (*sieve_session_cb_fn) (SieveSession *session, gpointer data);
+typedef void (*sieve_session_data_cb_fn) (SieveSession *session,
+ gpointer cb_data, gpointer user_data);
+typedef void (*sieve_session_error_cb_fn) (SieveSession *session,
+ const gchar *msg, gpointer user_data);
+typedef void (*sieve_session_connected_cb_fn) (SieveSession *session,
+ gboolean connected, gpointer user_data);
+
+struct SieveSession
+{
+ Session session;
+ PrefsAccount *account;
+ struct SieveAccountConfig *config;
+ SieveState state;
+ gboolean authenticated;
+ GSList *send_queue;
+ SieveErrorValue error;
+ SieveCommand *current_cmd;
+
+ gboolean use_auth;
+ SieveAuthType avail_auth_type;
+ SieveAuthType forced_auth_type;
+ SieveAuthType auth_type;
+
+ gchar *host;
+ gushort port;
+ gchar *user;
+ gchar *pass;
+
+#ifdef USE_GNUTLS
+ gboolean tls_init_done;
+#endif
+
+ struct {
+ gboolean starttls;
+ } capability;
+
+ sieve_session_error_cb_fn on_error;
+ sieve_session_connected_cb_fn on_connected;
+ gpointer cb_data;
+};
+
+struct SieveCommand {
+ SieveState next_state;
+ gchar *msg;
+ sieve_session_data_cb_fn cb;
+ gpointer data;
+};
+
+struct SieveScript {
+ gchar *name;
+ gboolean active;
+};
+
+struct SieveResult {
+ gboolean has_status;
+ gboolean success;
+ SieveResponseCode code;
+ gchar *description;
+ gboolean has_octets;
+ guint octets;
+};
+
+void sieve_sessions_close();
+
+void sieve_account_prefs_updated(PrefsAccount *account);
+SieveSession *sieve_session_get_for_account(PrefsAccount *account);
+void sieve_sessions_discard_callbacks(gpointer user_data);
+void sieve_session_handle_status(SieveSession *session,
+ sieve_session_error_cb_fn on_error,
+ sieve_session_connected_cb_fn on_connected,
+ gpointer data);
+void sieve_session_list_scripts(SieveSession *session,
+ sieve_session_data_cb_fn got_script_name_cb, gpointer data);
+void sieve_session_add_script(SieveSession *session, const gchar *filter_name,
+ sieve_session_data_cb_fn cb, gpointer data);
+void sieve_session_set_active_script(SieveSession *session,
+ const gchar *filter_name,
+ sieve_session_data_cb_fn cb, gpointer data);
+void sieve_session_rename_script(SieveSession *session,
+ const gchar *name_old, const char *name_new,
+ sieve_session_data_cb_fn cb, gpointer data);
+void sieve_session_get_script(SieveSession *session, const gchar *filter_name,
+ sieve_session_data_cb_fn cb, gpointer data);
+void sieve_session_put_script(SieveSession *session, const gchar *filter_name,
+ gint len, const gchar *script_contents,
+ sieve_session_data_cb_fn cb, gpointer data);
+void sieve_session_check_script(SieveSession *session,
+ gint len, const gchar *script_contents,
+ sieve_session_data_cb_fn cb, gpointer data);
+void sieve_session_delete_script(SieveSession *session,
+ const gchar *filter_name,
+ sieve_session_data_cb_fn cb, gpointer data);
+
+#endif /* MANAGESIEVE_H */
diff --git a/src/plugins/managesieve/plugin.def b/src/plugins/managesieve/plugin.def
new file mode 100644
index 0000000..8471df1
--- /dev/null
+++ b/src/plugins/managesieve/plugin.def
@@ -0,0 +1,10 @@
+EXPORTS
+ plugin_desc
+ plugin_done
+ plugin_init
+ plugin_licence
+ plugin_name
+ plugin_type
+ plugin_provides
+ plugin_version
+
diff --git a/src/plugins/managesieve/sieve_editor.c b/src/plugins/managesieve/sieve_editor.c
new file mode 100644
index 0000000..28f75f5
--- /dev/null
+++ b/src/plugins/managesieve/sieve_editor.c
@@ -0,0 +1,675 @@
+/*
+ * Claws Mail -- a GTK+ based, lightweight, and fast e-mail client
+ * Copyright (C) 2004-2015 the Claws Mail team
+ * Copyright (C) 2014-2015 Charles Lehner
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#include "claws-features.h"
+#endif
+
+#include <gtk/gtk.h>
+#include <glib.h>
+#include <glib/gi18n.h>
+
+#include "defs.h"
+#include "gtk/gtkutils.h"
+#include "gtk/combobox.h"
+#include "gtk/manage_window.h"
+#include "alertpanel.h"
+#include "undo.h"
+#include "menu.h"
+#include "utils.h"
+#include "prefs.h"
+#include "prefs_common.h"
+#include "account.h"
+#include "mainwindow.h"
+#include "message_search.h"
+#include "managesieve.h"
+#include "sieve_editor.h"
+
+GSList *editors = NULL;
+
+static void sieve_editor_destroy(SieveEditorPage *page);
+
+void sieve_editor_set_position(void *obj, gint pos);
+gboolean sieve_editor_search_string(void *obj,
+ const gchar *str, gboolean case_sens);
+gboolean sieve_editor_search_string_backward(void *obj,
+ const gchar *str, gboolean case_sens);
+static void sieve_editor_save_cb(GtkAction *action, SieveEditorPage *page);
+static void sieve_editor_check_cb(GtkAction *action, SieveEditorPage *page);
+static void sieve_editor_revert_cb(GtkAction *action, SieveEditorPage *page);
+static void sieve_editor_close_cb(GtkAction *action, SieveEditorPage *page);
+static void sieve_editor_undo_cb(GtkAction *action, SieveEditorPage *page);
+static void sieve_editor_redo_cb(GtkAction *action, SieveEditorPage *page);
+static void sieve_editor_cut_cb(GtkAction *action, SieveEditorPage *page);
+static void sieve_editor_copy_cb(GtkAction *action, SieveEditorPage *page);
+static void sieve_editor_paste_cb(GtkAction *action, SieveEditorPage *page);
+static void sieve_editor_allsel_cb(GtkAction *action, SieveEditorPage *page);
+static void sieve_editor_find_cb(GtkAction *action, SieveEditorPage *page);
+static void sieve_editor_set_modified(SieveEditorPage *page,
+ gboolean modified);
+
+static SearchInterface search_interface = {
+ .set_position = sieve_editor_set_position,
+ .search_string_backward = sieve_editor_search_string_backward,
+ .search_string = sieve_editor_search_string,
+};
+
+static GtkActionEntry sieve_editor_entries[] =
+{
+ {"Menu", NULL, "Menu" },
+/* menus */
+ {"Filter", NULL, N_("_Filter") },
+ {"Edit", NULL, N_("_Edit") },
+/* Filter menu */
+
+ {"Filter/Save", NULL, N_("_Save"), "<control>S", NULL, G_CALLBACK(sieve_editor_save_cb) },
+ {"Filter/CheckSyntax", NULL, N_("Chec_k Syntax"), "<control>K", NULL, G_CALLBACK(sieve_editor_check_cb) },
+ {"Filter/Revert", NULL, N_("Re_vert"), NULL, NULL, G_CALLBACK(sieve_editor_revert_cb) },
+ {"Filter/Close", NULL, N_("_Close"), "<control>W", NULL, G_CALLBACK(sieve_editor_close_cb) },
+
+/* Edit menu */
+ {"Edit/Undo", NULL, N_("_Undo"), "<control>Z", NULL, G_CALLBACK(sieve_editor_undo_cb) },
+ {"Edit/Redo", NULL, N_("_Redo"), "<control>Y", NULL, G_CALLBACK(sieve_editor_redo_cb) },
+ /* {"Edit/---", NULL, "---" }, */
+
+ {"Edit/Cut", NULL, N_("Cu_t"), "<control>X", NULL, G_CALLBACK(sieve_editor_cut_cb) },
+ {"Edit/Copy", NULL, N_("_Copy"), "<control>C", NULL, G_CALLBACK(sieve_editor_copy_cb) },
+ {"Edit/Paste", NULL, N_("_Paste"), "<control>V", NULL, G_CALLBACK(sieve_editor_paste_cb) },
+
+ {"Edit/SelectAll", NULL, N_("Select _all"), "<control>A", NULL, G_CALLBACK(sieve_editor_allsel_cb) },
+
+ {"Edit/---", NULL, "---" },
+ {"Edit/Find", NULL, N_("_Find"), "<control>F", NULL, G_CALLBACK(sieve_editor_find_cb) },
+};
+
+
+void sieve_editors_close()
+{
+ if (editors) {
+ g_slist_free_full(editors, (GDestroyNotify)sieve_editor_close);
+ editors = NULL;
+ }
+}
+
+void sieve_editor_append_text(SieveEditorPage *page, gchar *text, gint len)
+{
+ GtkTextBuffer *buffer;
+ GtkTextIter iter;
+ gboolean was_modified = page->modified;
+
+ undo_block(page->undostruct);
+ buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(page->text));
+ gtk_text_buffer_get_end_iter(buffer, &iter);
+ gtk_text_buffer_insert(buffer, &iter, text, len);
+ undo_unblock(page->undostruct);
+ sieve_editor_set_modified(page, was_modified);
+}
+
+static gint sieve_editor_get_text(SieveEditorPage *page, gchar **text)
+{
+ GtkTextBuffer *buffer;
+ GtkTextIter start, end;
+
+ buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(page->text));
+ gtk_text_buffer_get_start_iter(buffer, &start);
+ gtk_text_buffer_get_end_iter(buffer, &end);
+ *text = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
+ return gtk_text_iter_get_offset(&end) - gtk_text_iter_get_offset(&start);
+}
+
+static void sieve_editor_set_status(SieveEditorPage *page, const gchar *status)
+{
+ gtk_label_set_text(GTK_LABEL(page->status_text), status);
+}
+
+static void sieve_editor_set_status_icon(SieveEditorPage *page, const gchar *img_id)
+{
+ GtkImage *img = GTK_IMAGE(page->status_icon);
+ if (img_id)
+ gtk_image_set_from_stock(img, img_id, GTK_ICON_SIZE_BUTTON);
+ else
+ gtk_image_clear(img);
+}
+
+static void sieve_editor_append_status(SieveEditorPage *page,
+ const gchar *status)
+{
+ GtkLabel *label = GTK_LABEL(page->status_text);
+ const gchar *prev_status = gtk_label_get_text(label);
+ const gchar *sep = prev_status && prev_status[0] ? "\n" : "";
+ gtk_label_set_text(label, g_strconcat(prev_status, sep, status, NULL));
+}
+
+/* Update the status icon and text from a response. */
+static void sieve_editor_update_status(SieveEditorPage *page,
+ SieveResult *result)
+{
+ if (result->has_status) {
+ /* set status icon */
+ sieve_editor_set_status_icon(page,
+ result->success ? GTK_STOCK_DIALOG_INFO : GTK_STOCK_DIALOG_ERROR);
+ /* clear status text */
+ sieve_editor_set_status(page, "");
+ }
+ if (result->description) {
+ /* append to status */
+ sieve_editor_append_status(page, result->description);
+ }
+}
+
+/* Edit Menu */
+
+static void sieve_editor_undo_cb(GtkAction *action, SieveEditorPage *page)
+{
+ undo_undo(page->undostruct);
+}
+
+static void sieve_editor_redo_cb(GtkAction *action, SieveEditorPage *page)
+{
+ undo_redo(page->undostruct);
+}
+
+static void sieve_editor_cut_cb(GtkAction *action, SieveEditorPage *page)
+{
+ GtkTextBuffer *buf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(page->text));
+ GtkClipboard *clipboard = gtk_clipboard_get(GDK_SELECTION_CLIPBOARD);
+ gtk_text_buffer_cut_clipboard(buf, clipboard, TRUE);
+}
+
+static void sieve_editor_copy_cb(GtkAction *action, SieveEditorPage *page)
+{
+ GtkTextBuffer *buf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(page->text));
+ GtkClipboard *clipboard = gtk_clipboard_get(GDK_SELECTION_CLIPBOARD);
+ gtk_text_buffer_copy_clipboard(buf, clipboard);
+}
+
+static void sieve_editor_paste_cb(GtkAction *action, SieveEditorPage *page)
+{
+ if (!gtk_widget_has_focus(page->text))
+ return;
+
+ GtkTextBuffer *buf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(page->text));
+ GtkClipboard *clipboard = gtk_clipboard_get(GDK_SELECTION_CLIPBOARD);
+ gchar *contents = gtk_clipboard_wait_for_text(clipboard);
+ GtkTextMark *start_mark = gtk_text_buffer_get_insert(buf);
+ GtkTextIter start_iter;
+
+ undo_paste_clipboard(GTK_TEXT_VIEW(page->text), page->undostruct);
+ gtk_text_buffer_delete_selection(buf, FALSE, TRUE);
+
+ gtk_text_buffer_get_iter_at_mark(buf, &start_iter, start_mark);
+ gtk_text_buffer_insert(buf, &start_iter, contents, strlen(contents));
+}
+
+
+static void sieve_editor_allsel_cb(GtkAction *action, SieveEditorPage *page)
+{
+ GtkTextIter start, end;
+ GtkTextBuffer *buffer;
+
+ buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(page->text));
+ gtk_text_buffer_get_start_iter(buffer, &start);
+ gtk_text_buffer_get_end_iter(buffer, &end);
+ gtk_text_buffer_select_range(buffer, &start, &end);
+}
+
+/* Search */
+
+void sieve_editor_set_position(void *obj, gint pos)
+{
+ SieveEditorPage *page = (SieveEditorPage *)obj;
+ GtkTextView *text = GTK_TEXT_VIEW(page->text);
+
+ gtkut_text_view_set_position(text, pos);
+}
+
+gboolean sieve_editor_search_string(void *obj,
+ const gchar *str, gboolean case_sens)
+{
+ SieveEditorPage *page = (SieveEditorPage *)obj;
+ GtkTextView *text = GTK_TEXT_VIEW(page->text);
+
+ return gtkut_text_view_search_string(text, str, case_sens);
+}
+
+gboolean sieve_editor_search_string_backward(void *obj,
+ const gchar *str, gboolean case_sens)
+{
+ SieveEditorPage *page = (SieveEditorPage *)obj;
+ GtkTextView *text = GTK_TEXT_VIEW(page->text);
+
+ return gtkut_text_view_search_string_backward(text, str, case_sens);
+}
+
+static void sieve_editor_search(SieveEditorPage *page)
+{
+ message_search_other(&search_interface, page);
+}
+
+/* Actions */
+
+static void got_data_reverting(SieveSession *session, gchar *contents,
+ SieveEditorPage *page)
+{
+ if (contents == NULL) {
+ /* end of data */
+ undo_unblock(page->undostruct);
+ gtk_widget_set_sensitive(page->text, TRUE);
+ sieve_editor_set_status(page, "");
+ sieve_editor_set_modified(page, FALSE);
+ return;
+ }
+ if (contents == (void *)-1) {
+ /* error */
+ sieve_editor_set_status(page, _("Unable to get script contents"));
+ sieve_editor_set_status_icon(page, GTK_STOCK_DIALOG_ERROR);
+ return;
+ }
+
+ if (page->first_line) {
+ GtkTextIter start, end;
+ GtkTextBuffer *buffer;
+
+ page->first_line = FALSE;
+
+ /* delete previous data */
+ buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(page->text));
+ gtk_text_buffer_get_start_iter(buffer, &start);
+ gtk_text_buffer_get_end_iter(buffer, &end);
+ gtk_text_buffer_delete(buffer, &start, &end);
+
+ /* append data */
+ gtk_text_buffer_insert(buffer, &end, contents, strlen(contents));
+ } else {
+ sieve_editor_append_text(page, "\n", 1);
+ sieve_editor_append_text(page, contents, strlen(contents));
+ }
+}
+
+static void sieve_editor_revert(SieveEditorPage *page)
+{
+ undo_block(page->undostruct);
+ page->first_line = TRUE;
+ gtk_widget_set_sensitive(page->text, FALSE);
+ sieve_editor_set_status(page, _("Reverting..."));
+ sieve_editor_set_status_icon(page, NULL);
+ sieve_session_get_script(page->session, page->script_name,
+ (sieve_session_data_cb_fn)got_data_reverting, page);
+}
+
+static void sieve_editor_revert_cb(GtkAction *action, SieveEditorPage *page)
+{
+ if (!page->modified ||
+ alertpanel(_("Revert script"),
+ _("This script has been modified. Revert the unsaved changes?"),
+ _("_Revert"), NULL, GTK_STOCK_CANCEL) == G_ALERTDEFAULT)
+ sieve_editor_revert(page);
+}
+
+static void got_data_saved(SieveSession *session, SieveResult *result,
+ SieveEditorPage *page)
+{
+ if (result->has_status && result->success) {
+ sieve_editor_set_modified(page, FALSE);
+ if (page->closing) {
+ sieve_editor_close(page);
+ return;
+ }
+ /* use nice status message if there are no warnings */
+ if (result->code == SIEVE_CODE_NONE) {
+ result->description = _("Script saved successfully.");
+ }
+ }
+ sieve_editor_update_status(page, result);
+}
+
+static void sieve_editor_save(SieveEditorPage *page)
+{
+ gchar *text;
+ gint len = sieve_editor_get_text(page, &text);
+ sieve_editor_set_status(page, _("Saving..."));
+ sieve_editor_set_status_icon(page, NULL);
+ sieve_session_put_script(page->session, page->script_name, len, text,
+ (sieve_session_data_cb_fn)got_data_saved, page);
+ g_free(text);
+}
+
+static void sieve_editor_save_cb(GtkAction *action, SieveEditorPage *page)
+{
+ sieve_editor_save(page);
+}
+
+static void sieve_editor_find_cb(GtkAction *action, SieveEditorPage *page)
+{
+ sieve_editor_search(page);
+}
+
+static void got_data_checked(SieveSession *session, SieveResult *result,
+ SieveEditorPage *page)
+{
+ sieve_editor_update_status(page, result);
+}
+
+static void sieve_editor_check_cb(GtkAction *action, SieveEditorPage *page)
+{
+ gchar *text;
+ gint len = sieve_editor_get_text(page, &text);
+ sieve_editor_set_status(page, _("Checking syntax..."));
+ sieve_editor_set_status_icon(page, NULL);
+ sieve_session_check_script(page->session, len, text,
+ (sieve_session_data_cb_fn)got_data_checked, page);
+ g_free(text);
+}
+
+static void sieve_editor_changed_cb(GtkTextBuffer *textbuf,
+ SieveEditorPage *page)
+{
+ if (!page->modified) {
+ sieve_editor_set_modified(page, TRUE);
+ }
+}
+
+static void sieve_editor_destroy(SieveEditorPage *page)
+{
+ gtk_widget_destroy(page->window);
+ undo_destroy(page->undostruct);
+ g_free(page);
+}
+
+void sieve_editor_close(SieveEditorPage *page)
+{
+ editors = g_slist_remove(editors, (gconstpointer)page);
+ sieve_editor_destroy(page);
+ sieve_sessions_discard_callbacks(page);
+}
+
+static gboolean sieve_editor_confirm_close(SieveEditorPage *page)
+{
+ if (page->modified) {
+ switch (alertpanel(_("Save changes"),
+ _("This script has been modified. Save the latest changes?"),
+ _("_Discard"), _("+_Save"), GTK_STOCK_CANCEL)) {
+ case G_ALERTDEFAULT:
+ return TRUE;
+ case G_ALERTALTERNATE:
+ page->closing = TRUE;
+ sieve_editor_save(page);
+ return FALSE;
+ default:
+ return FALSE;
+ }
+ }
+ return TRUE;
+}
+
+static void sieve_editor_close_cb(GtkAction *action, SieveEditorPage *page)
+{
+ if (sieve_editor_confirm_close(page)) {
+ sieve_editor_close(page);
+ }
+}
+
+static gint sieve_editor_delete_cb(GtkWidget *widget, GdkEventAny *event,
+ SieveEditorPage *page)
+{
+ sieve_editor_close_cb(NULL, page);
+ return TRUE;
+}
+
+/**
+ * sieve_editor_undo_state_changed:
+ *
+ * Change the sensivity of the menuentries undo and redo
+ **/
+static void sieve_editor_undo_state_changed(UndoMain *undostruct,
+ gint undo_state, gint redo_state, gpointer data)
+{
+ SieveEditorPage *page = (SieveEditorPage *)data;
+
+ switch (undo_state) {
+ case UNDO_STATE_TRUE:
+ if (!undostruct->undo_state) {
+ undostruct->undo_state = TRUE;
+ cm_menu_set_sensitive_full(page->ui_manager, "Menu/Edit/Undo", TRUE);
+ }
+ break;
+ case UNDO_STATE_FALSE:
+ if (undostruct->undo_state) {
+ undostruct->undo_state = FALSE;
+ cm_menu_set_sensitive_full(page->ui_manager, "Menu/Edit/Undo", FALSE);
+ }
+ break;
+ case UNDO_STATE_UNCHANGED:
+ break;
+ case UNDO_STATE_REFRESH:
+ cm_menu_set_sensitive_full(page->ui_manager, "Menu/Edit/Undo", undostruct->undo_state);
+ break;
+ default:
+ g_warning("Undo state not recognized");
+ break;
+ }
+
+ switch (redo_state) {
+ case UNDO_STATE_TRUE:
+ if (!undostruct->redo_state) {
+ undostruct->redo_state = TRUE;
+ cm_menu_set_sensitive_full(page->ui_manager, "Menu/Edit/Redo", TRUE);
+ }
+ break;
+ case UNDO_STATE_FALSE:
+ if (undostruct->redo_state) {
+ undostruct->redo_state = FALSE;
+ cm_menu_set_sensitive_full(page->ui_manager, "Menu/Edit/Redo", FALSE);
+ }
+ break;
+ case UNDO_STATE_UNCHANGED:
+ break;
+ case UNDO_STATE_REFRESH:
+ cm_menu_set_sensitive_full(page->ui_manager, "Menu/Edit/Redo", undostruct->redo_state);
+ break;
+ default:
+ g_warning("Redo state not recognized");
+ break;
+ }
+}
+
+
+SieveEditorPage *sieve_editor_new(SieveSession *session, gchar *script_name)
+{
+ SieveEditorPage *page;
+ GtkUIManager *ui_manager;
+ UndoMain *undostruct;
+ GtkWidget *window;
+ GtkWidget *menubar;
+ GtkWidget *vbox, *hbox, *hbox1;
+ GtkWidget *scrolledwin;
+ GtkWidget *text;
+ GtkTextBuffer *buffer;
+ GtkWidget *check_btn, *save_btn, *close_btn;
+ GtkWidget *status_text;
+ GtkWidget *status_icon;
+
+ page = g_new0(SieveEditorPage, 1);
+
+ /* window */
+ window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "sieveeditor");
+ gtk_window_set_resizable(GTK_WINDOW(window), TRUE);
+ MANAGE_WINDOW_SIGNALS_CONNECT (window);
+ g_signal_connect(G_OBJECT(window), "delete_event",
+ G_CALLBACK(sieve_editor_delete_cb), page);
+
+ vbox = gtk_vbox_new(FALSE, 0);
+ gtk_container_add(GTK_CONTAINER(window), vbox);
+
+ ui_manager = gtk_ui_manager_new();
+ cm_menu_create_action_group_full(ui_manager,
+ "Menu", sieve_editor_entries, G_N_ELEMENTS(sieve_editor_entries),
+ page);
+
+ MENUITEM_ADDUI_MANAGER(ui_manager, "/", "Menu", NULL, GTK_UI_MANAGER_MENUBAR)
+
+ MENUITEM_ADDUI_MANAGER(ui_manager, "/Menu", "Filter", "Filter", GTK_UI_MANAGER_MENU)
+ MENUITEM_ADDUI_MANAGER(ui_manager, "/Menu", "Edit", "Edit", GTK_UI_MANAGER_MENU)
+
+/* Filter menu */
+ MENUITEM_ADDUI_MANAGER(ui_manager, "/Menu/Filter", "Save", "Filter/Save", GTK_UI_MANAGER_MENUITEM)
+MENUITEM_ADDUI_MANAGER(ui_manager, "/Menu/Filter", "CheckSyntax", "Filter/CheckSyntax", GTK_UI_MANAGER_MENUITEM)
+MENUITEM_ADDUI_MANAGER(ui_manager, "/Menu/Filter", "Revert", "Filter/Revert", GTK_UI_MANAGER_MENUITEM)
+ MENUITEM_ADDUI_MANAGER(ui_manager, "/Menu/Filter", "Close", "Filter/Close", GTK_UI_MANAGER_MENUITEM)
+
+/* Edit menu */
+ MENUITEM_ADDUI_MANAGER(ui_manager, "/Menu/Edit", "Undo", "Edit/Undo", GTK_UI_MANAGER_MENUITEM)
+ MENUITEM_ADDUI_MANAGER(ui_manager, "/Menu/Edit", "Redo", "Edit/Redo", GTK_UI_MANAGER_MENUITEM)
+ MENUITEM_ADDUI_MANAGER(ui_manager, "/Menu/Edit", "Separator1", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
+
+ MENUITEM_ADDUI_MANAGER(ui_manager, "/Menu/Edit", "Cut", "Edit/Cut", GTK_UI_MANAGER_MENUITEM)
+ MENUITEM_ADDUI_MANAGER(ui_manager, "/Menu/Edit", "Copy", "Edit/Copy", GTK_UI_MANAGER_MENUITEM)
+ MENUITEM_ADDUI_MANAGER(ui_manager, "/Menu/Edit", "Paste", "Edit/Paste", GTK_UI_MANAGER_MENUITEM)
+
+ MENUITEM_ADDUI_MANAGER(ui_manager, "/Menu/Edit", "SelectAll", "Edit/SelectAll", GTK_UI_MANAGER_MENUITEM)
+
+ MENUITEM_ADDUI_MANAGER(ui_manager, "/Menu/Edit", "Separator2", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
+
+ MENUITEM_ADDUI_MANAGER(ui_manager, "/Menu/Edit", "Find", "Edit/Find", GTK_UI_MANAGER_MENUITEM)
+
+ menubar = gtk_ui_manager_get_widget(ui_manager, "/Menu");
+
+ gtk_window_add_accel_group(GTK_WINDOW(window), gtk_ui_manager_get_accel_group(ui_manager));
+ gtk_box_pack_start(GTK_BOX(vbox), menubar, FALSE, TRUE, 0);
+
+ cm_menu_set_sensitive_full(ui_manager, "Menu/Edit/Undo", FALSE);
+ cm_menu_set_sensitive_full(ui_manager, "Menu/Edit/Redo", FALSE);
+
+ /* text */
+ scrolledwin = gtk_scrolled_window_new(NULL, NULL);
+ gtk_widget_set_size_request (scrolledwin, 660, 408);
+ gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwin),
+ GTK_POLICY_AUTOMATIC,
+ GTK_POLICY_AUTOMATIC);
+ gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolledwin),
+ GTK_SHADOW_IN);
+ gtk_box_pack_start(GTK_BOX(vbox), scrolledwin, TRUE, TRUE, 0);
+
+ text = gtk_text_view_new();
+ gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(text), GTK_WRAP_WORD_CHAR);
+ gtk_text_view_set_editable(GTK_TEXT_VIEW(text), TRUE);
+ gtk_container_add(GTK_CONTAINER(scrolledwin), text);
+
+ buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
+ g_signal_connect(G_OBJECT(buffer), "changed",
+ G_CALLBACK(sieve_editor_changed_cb), page);
+
+ /* set text font */
+ if (prefs_common.textfont) {
+ PangoFontDescription *font_desc;
+
+ font_desc = pango_font_description_from_string
+ (prefs_common.textfont);
+ if (font_desc) {
+ gtk_widget_modify_font(text, font_desc);
+ pango_font_description_free(font_desc);
+ }
+ }
+
+ hbox = gtk_hbox_new (FALSE, 8);
+ gtk_box_pack_end (GTK_BOX (vbox), hbox, FALSE, FALSE, 0);
+ gtk_container_set_border_width (GTK_CONTAINER (hbox), 8);
+
+ /* status */
+ status_icon = gtk_image_new ();
+ gtk_box_pack_start (GTK_BOX (hbox), status_icon, FALSE, FALSE, 0);
+ status_text = gtk_label_new ("");
+ gtk_box_pack_start (GTK_BOX (hbox), status_text, FALSE, FALSE, 0);
+ gtk_label_set_justify (GTK_LABEL (status_text), GTK_JUSTIFY_LEFT);
+
+ /* buttons */
+ gtkut_stock_with_text_button_set_create(&hbox1,
+ &close_btn, GTK_STOCK_CANCEL, _("_Close"),
+ &check_btn, GTK_STOCK_OK, _("Chec_k Syntax"),
+ &save_btn, GTK_STOCK_SAVE, _("_Save"));
+ gtk_box_pack_end (GTK_BOX (hbox), hbox1, FALSE, FALSE, 0);
+ gtk_widget_grab_default (save_btn);
+ g_signal_connect (G_OBJECT (close_btn), "clicked",
+ G_CALLBACK (sieve_editor_close_cb), page);
+ g_signal_connect (G_OBJECT (check_btn), "clicked",
+ G_CALLBACK (sieve_editor_check_cb), page);
+ g_signal_connect (G_OBJECT (save_btn), "clicked",
+ G_CALLBACK (sieve_editor_save_cb), page);
+
+ undostruct = undo_init(text);
+ undo_set_change_state_func(undostruct, &sieve_editor_undo_state_changed,
+ page);
+
+ gtk_widget_show_all(window);
+
+ page->window = window;
+ page->ui_manager = ui_manager;
+ page->text = text;
+ page->undostruct = undostruct;
+ page->session = session;
+ page->script_name = script_name;
+ page->status_text = status_text;
+ page->status_icon = status_icon;
+
+ editors = g_slist_prepend(editors, page);
+
+ sieve_editor_set_modified(page, FALSE);
+
+ return page;
+}
+
+SieveEditorPage *sieve_editor_get(SieveSession *session, gchar *script_name)
+{
+ GSList *item;
+ SieveEditorPage *page;
+ for (item = editors; item; item = item->next) {
+ page = (SieveEditorPage *)item->data;
+ if (page->session == session &&
+ strcmp(script_name, page->script_name) == 0)
+ return page;
+ }
+ return NULL;
+}
+
+void sieve_editor_present(SieveEditorPage *page)
+{
+ gtk_window_present(GTK_WINDOW(page->window));
+}
+
+static void sieve_editor_set_modified(SieveEditorPage *page,
+ gboolean modified)
+{
+ gchar *title;
+
+ page->modified = modified;
+ cm_menu_set_sensitive_full(page->ui_manager, "Menu/Filter/Revert",
+ modified);
+
+ title = g_strdup_printf(_("%s - Sieve Filter%s"), page->script_name,
+ modified ? " [Edited]" : "");
+ gtk_window_set_title (GTK_WINDOW (page->window), title);
+ g_free(title);
+
+ if (modified) {
+ sieve_editor_set_status(page, "");
+ sieve_editor_set_status_icon(page, NULL);
+ }
+}
diff --git a/src/plugins/managesieve/sieve_editor.h b/src/plugins/managesieve/sieve_editor.h
new file mode 100644
index 0000000..4925a54
--- /dev/null
+++ b/src/plugins/managesieve/sieve_editor.h
@@ -0,0 +1,50 @@
+/*
+ * Claws Mail -- a GTK+ based, lightweight, and fast e-mail client
+ * Copyright (C) 2004-2015 the Claws Mail team
+ * Copyright (C) 2014-2015 Charles Lehner
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef SIEVE_EDITOR_H
+#define SIEVE_EDITOR_H
+
+#include "undo.h"
+
+typedef struct SieveEditorPage SieveEditorPage;
+
+struct SieveEditorPage
+{
+ GtkWidget* window;
+ GtkWidget* status_text;
+ GtkWidget* status_icon;
+ GtkWidget* text;
+ GtkUIManager *ui_manager;
+ UndoMain *undostruct;
+ struct SieveSession *session;
+ gchar *script_name;
+ gboolean first_line;
+ gboolean modified;
+ gboolean closing;
+};
+
+SieveEditorPage *sieve_editor_new(SieveSession *session, gchar *script_name);
+SieveEditorPage *sieve_editor_get(SieveSession *session, gchar *script_name);
+void sieve_editor_append_text(SieveEditorPage *page, gchar *text, gint len);
+void sieve_editor_close(SieveEditorPage *page);
+void sieve_editor_present(SieveEditorPage *page);
+
+#endif /* SIEVE_EDITOR_H */
+
diff --git a/src/plugins/managesieve/sieve_manager.c b/src/plugins/managesieve/sieve_manager.c
new file mode 100644
index 0000000..212c30a
--- /dev/null
+++ b/src/plugins/managesieve/sieve_manager.c
@@ -0,0 +1,803 @@
+/*
+ * Claws Mail -- a GTK+ based, lightweight, and fast e-mail client
+ * Copyright (C) 2004-2015 the Claws Mail team
+ * Copyright (C) 2014-2015 Charles Lehner
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#include "claws-features.h"
+#endif
+
+#include <gtk/gtk.h>
+#include <glib.h>
+#include <glib/gi18n.h>
+
+#include "defs.h"
+#include "gtk/gtkutils.h"
+#include "gtk/combobox.h"
+#include "gtk/inputdialog.h"
+#include "gtk/manage_window.h"
+#include "alertpanel.h"
+#include "utils.h"
+#include "prefs.h"
+#include "account.h"
+#include "mainwindow.h"
+#include "managesieve.h"
+#include "sieve_editor.h"
+#include "sieve_prefs.h"
+#include "sieve_manager.h"
+
+enum {
+ FILTER_NAME,
+ FILTER_ACTIVE,
+ N_FILTER_COLUMNS
+};
+
+typedef struct {
+ SieveManagerPage *page;
+ gchar *name_old;
+ gchar *name_new;
+} CommandDataRename;
+
+typedef struct {
+ SieveManagerPage *page;
+ gchar *filter_name;
+} CommandDataName;
+
+typedef struct {
+ SieveManagerPage *page;
+ gchar *filter_name;
+ SieveEditorPage *editor_page;
+ gboolean first_line;
+} CommandDataGetScript;
+
+static void account_changed(GtkWidget *widget, SieveManagerPage *page);
+static void filter_activate(GtkWidget *widget, SieveManagerPage *page);
+void sieve_manager_close(GtkWidget *widget, SieveManagerPage *page);
+static void filter_set_active(SieveManagerPage *page, gchar *filter_name);
+gboolean filter_find_by_name (GtkTreeModel *model, GtkTreeIter *iter,
+ gchar *filter_name);
+static void got_session_error(SieveSession *session, const gchar *msg,
+ SieveManagerPage *page);
+
+static GSList *manager_pages = NULL;
+
+/*
+ * Perform a command on all manager pages for a given session
+ */
+#define manager_sessions_foreach(cur, session, page) \
+ for(cur = manager_pages; cur != NULL; cur = cur->next) \
+ if ((page = (SieveManagerPage *)cur->data) && \
+ page->active_session == session)
+
+static void filters_list_clear(SieveManagerPage *page)
+{
+ GtkListStore *list_store = GTK_LIST_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(page->filters_list)));
+ gtk_list_store_clear(list_store);
+ page->got_list = FALSE;
+}
+
+static void filters_list_delete_filter(SieveManagerPage *page, gchar *name)
+{
+ GtkTreeIter iter;
+ GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(page->filters_list));
+
+ if (!filter_find_by_name(model, &iter, name))
+ return;
+
+ gtk_list_store_remove(GTK_LIST_STORE(model), &iter);
+}
+
+
+static void filters_list_rename_filter(SieveManagerPage *page,
+ gchar *name_old, char *name_new)
+{
+ GtkTreeIter iter;
+ GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(page->filters_list));
+
+ if (!filter_find_by_name(model, &iter, name_old))
+ return;
+
+ gtk_list_store_set(GTK_LIST_STORE(model), &iter,
+ FILTER_NAME, name_new,
+ -1);
+}
+
+static void filters_list_insert_filter(SieveManagerPage *page,
+ SieveScript *filter)
+{
+ GtkListStore *list_store = GTK_LIST_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(page->filters_list)));
+ GtkTreeIter iter;
+
+ gtk_list_store_append(list_store, &iter);
+ gtk_list_store_set(list_store, &iter,
+ FILTER_NAME, filter->name,
+ FILTER_ACTIVE, filter->active,
+ -1);
+}
+
+static gchar *filters_list_get_selected_filter(GtkWidget *list_view)
+{
+ GtkTreeSelection *selector;
+ GtkTreeModel *model;
+ GtkTreeIter iter;
+ gchar *res = NULL;
+
+ selector = gtk_tree_view_get_selection(GTK_TREE_VIEW(list_view));
+
+ if (!gtk_tree_selection_get_selected(selector, &model, &iter))
+ return NULL;
+
+ gtk_tree_model_get(model, &iter, FILTER_NAME, &res, -1);
+
+ return res;
+}
+
+static void filter_add(GtkWidget *widget, SieveManagerPage *page)
+{
+ SieveSession *session = page->active_session;
+ if (!session)
+ return;
+ gchar *filter_name = input_dialog(_("Add Sieve script"),
+ _("Enter name for a new Sieve filter script."), "");
+ if (!filter_name || !filter_name[0])
+ return;
+
+ sieve_editor_new(session, filter_name);
+ /*
+ sieve_session_add_script(session, filter_name
+ (sieve_session_data_cb_fn)filter_added, (gpointer)page);
+ */
+}
+
+static void filter_got_data(SieveSession *session, gchar *contents,
+ CommandDataGetScript *cmd_data)
+{
+ SieveManagerPage *page = cmd_data->page;
+ SieveEditorPage *editor;
+
+ if (!contents) {
+ g_free(cmd_data);
+ return;
+ } else if (contents == (void *)-1) {
+ got_session_error(session, _("Unable to get script contents"), page);
+ return;
+ }
+
+ if (cmd_data->first_line) {
+ cmd_data->first_line = FALSE;
+ editor = sieve_editor_new(session, cmd_data->filter_name);
+ cmd_data->editor_page = editor;
+ } else {
+ editor = cmd_data->editor_page;
+ sieve_editor_append_text(editor, "\n", 1);
+ }
+ sieve_editor_append_text(editor, contents, strlen(contents));
+}
+
+static void filter_edit(GtkWidget *widget, SieveManagerPage *page)
+{
+ SieveEditorPage *editor;
+ CommandDataGetScript *cmd_data;
+ SieveSession *session = page->active_session;
+ if (!session)
+ return;
+ gchar *filter_name = filters_list_get_selected_filter(page->filters_list);
+ if (!filter_name)
+ return;
+
+ editor = sieve_editor_get(session, filter_name);
+ if (editor) {
+ sieve_editor_present(editor);
+ } else {
+ cmd_data = g_new0(CommandDataGetScript, 1);
+ cmd_data->first_line = TRUE;
+ cmd_data->filter_name = filter_name;
+ cmd_data->page = page;
+
+ sieve_session_get_script(session, filter_name,
+ (sieve_session_data_cb_fn)filter_got_data, cmd_data);
+ }
+}
+
+static void filter_renamed(SieveSession *session, gboolean success,
+ CommandDataRename *data)
+{
+ SieveManagerPage *page = data->page;
+ GSList *cur;
+
+ if (!success) {
+ got_session_error(session, "Unable to rename script", page);
+ return;
+ }
+
+ manager_sessions_foreach(cur, session, page) {
+ filters_list_rename_filter(page, data->name_old, data->name_new);
+ }
+ g_free(data);
+}
+
+static void filter_rename(GtkWidget *widget, SieveManagerPage *page)
+{
+ CommandDataRename *cmd_data;
+ gchar *name_old, *name_new;
+ SieveSession *session;
+
+ name_old = filters_list_get_selected_filter(page->filters_list);
+ if (!name_old)
+ return;
+
+ session = page->active_session;
+ if (!session)
+ return;
+
+ name_new = input_dialog(_("Add Sieve script"),
+ _("Enter new name for the script."), name_old);
+ if (!name_new)
+ return;
+
+ cmd_data = g_new(CommandDataRename, 1);
+ cmd_data->name_new = name_new;
+ cmd_data->name_old = name_old;
+ cmd_data->page = page;
+ sieve_session_rename_script(session, name_old, name_new,
+ (sieve_session_data_cb_fn)filter_renamed, (gpointer)cmd_data);
+}
+
+static void filter_activated(SieveSession *session, gboolean success,
+ CommandDataName *cmd_data)
+{
+ SieveManagerPage *page = cmd_data->page;
+ GSList *cur;
+
+ if (!success) {
+ got_session_error(session, "Unable to set active script", page);
+ return;
+ }
+
+ manager_sessions_foreach(cur, session, page) {
+ filter_set_active(page, cmd_data->filter_name);
+ }
+ g_free(cmd_data);
+}
+
+static void sieve_set_active_filter(SieveManagerPage *page, gchar *filter_name)
+{
+ SieveSession *session;
+ CommandDataName *cmd_data;
+
+ session = page->active_session;
+ cmd_data = g_new(CommandDataName, 1);
+ cmd_data->filter_name = filter_name;
+ cmd_data->page = page;
+
+ sieve_session_set_active_script(session, filter_name,
+ (sieve_session_data_cb_fn)filter_activated, cmd_data);
+}
+
+/*
+ * activate button clicked
+ */
+static void filter_activate(GtkWidget *widget, SieveManagerPage *page)
+{
+ gchar *filter_name = filters_list_get_selected_filter(page->filters_list);
+ if (!filter_name)
+ return;
+ sieve_set_active_filter(page, filter_name);
+}
+
+static void filter_deleted(SieveSession *session, const gchar *err_msg,
+ CommandDataName *cmd_data)
+{
+ SieveManagerPage *page = cmd_data->page;
+ GSList *cur;
+
+ if (err_msg) {
+ got_session_error(session, err_msg, page);
+ return;
+ }
+
+ manager_sessions_foreach(cur, session, page) {
+ filters_list_delete_filter(page, cmd_data->filter_name);
+ }
+ g_free(cmd_data);
+}
+
+
+static void filter_delete(GtkWidget *widget, SieveManagerPage *page)
+{
+ gchar buf[256];
+ gchar *filter_name;
+ SieveSession *session;
+ CommandDataName *cmd_data;
+
+ filter_name = filters_list_get_selected_filter(page->filters_list);
+ if (filter_name == NULL)
+ return;
+
+ session = page->active_session;
+ if (!session)
+ return;
+
+ g_snprintf(buf, sizeof(buf),
+ _("Do you really want to delete the filter '%s'?"), filter_name);
+ if (alertpanel_full(_("Delete filter"), buf,
+ GTK_STOCK_CANCEL, GTK_STOCK_DELETE, NULL, FALSE,
+ NULL, ALERT_WARNING, G_ALERTDEFAULT) != G_ALERTALTERNATE)
+ return;
+
+ cmd_data = g_new(CommandDataName, 1);
+ cmd_data->filter_name = filter_name;
+ cmd_data->page = page;
+
+ sieve_session_delete_script(session, filter_name,
+ (sieve_session_data_cb_fn)filter_deleted, cmd_data);
+}
+
+/*
+ * select a filter in the list
+ *
+ * return TRUE is successfully selected, FALSE otherwise
+ */
+
+static gboolean filter_select (GtkWidget *list_view, GtkTreeModel *model,
+ GtkTreeIter *iter)
+{
+ GtkTreeSelection *selection;
+ GtkTreePath* path;
+
+ selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(list_view));
+ gtk_tree_selection_select_iter(selection, iter);
+ path = gtk_tree_model_get_path(model, iter);
+ if (path == NULL) return FALSE;
+ gtk_tree_view_set_cursor(GTK_TREE_VIEW(list_view), path, NULL, FALSE);
+ gtk_tree_path_free(path);
+ return TRUE;
+}
+
+/*
+ * find matching filter. return FALSE on match
+ */
+static gboolean filter_search_equal_fn (GtkTreeModel *model, gint column,
+ const gchar *key, GtkTreeIter *iter, gpointer search_data)
+{
+ SieveManagerPage *page = (SieveManagerPage *)search_data;
+ gchar *filter_name;
+
+ if (!key) return TRUE;
+
+ gtk_tree_model_get (model, iter, FILTER_NAME, &filter_name, -1);
+
+ if (strncmp (key, filter_name, strlen(key)) != 0) return TRUE;
+ return !filter_select(page->filters_list, model, iter);
+}
+
+/*
+ * search for a filter row by its name. return true if found.
+ */
+gboolean filter_find_by_name (GtkTreeModel *model, GtkTreeIter *iter,
+ gchar *filter_name)
+{
+ gchar *name;
+
+ if (!gtk_tree_model_get_iter_first (model, iter))
+ return FALSE;
+
+ do {
+ gtk_tree_model_get (model, iter, FILTER_NAME, &name, -1);
+ if (strcmp(filter_name, name) == 0) {
+ return TRUE;
+ }
+ } while (gtk_tree_model_iter_next (model, iter));
+ return FALSE;
+}
+
+static gboolean filter_set_inactive(GtkTreeModel *model,
+ GtkTreePath *path, GtkTreeIter *iter, gpointer data)
+{
+ gtk_list_store_set(GTK_LIST_STORE(model), iter,
+ FILTER_ACTIVE, FALSE,
+ -1);
+ return FALSE;
+}
+
+/*
+ * Set the active filter in the table.
+ * @param filter_name The filter to make active (may be null)
+ */
+static void filter_set_active(SieveManagerPage *page, gchar *filter_name)
+{
+ GtkTreeIter iter;
+ GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(page->filters_list));
+
+ /* Deactivate all filters */
+ gtk_tree_model_foreach(model, filter_set_inactive, NULL);
+
+ /* Set active filter */
+ if (filter_name) {
+ if (!filter_find_by_name (model, &iter, filter_name))
+ return;
+
+ gtk_list_store_set(GTK_LIST_STORE(model), &iter,
+ FILTER_ACTIVE, TRUE,
+ -1);
+ }
+}
+
+static void filter_active_toggled(GtkCellRendererToggle *widget,
+ gchar *path,
+ SieveManagerPage *page)
+{
+ GtkTreeIter iter;
+ gchar *filter_name;
+ gboolean active;
+ GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(page->filters_list));
+
+ if (!gtk_tree_model_get_iter_from_string(model, &iter, path))
+ return;
+
+ /* get existing value */
+ gtk_tree_model_get(model, &iter,
+ FILTER_NAME, &filter_name,
+ FILTER_ACTIVE, &active,
+ -1);
+
+ sieve_set_active_filter(page, active ? NULL : filter_name);
+}
+
+static void filter_double_clicked(GtkTreeView *list_view,
+ GtkTreePath *path, GtkTreeViewColumn *column,
+ SieveManagerPage *page)
+{
+ filter_edit(GTK_WIDGET(list_view), page);
+}
+
+static void filters_create_list_view_columns(SieveManagerPage *page,
+ GtkWidget *list_view)
+{
+ GtkTreeViewColumn *column;
+ GtkCellRenderer *renderer;
+
+ /* Name */
+ renderer = gtk_cell_renderer_text_new();
+ column = gtk_tree_view_column_new_with_attributes
+ (_("Name"), renderer,
+ "text", FILTER_NAME,
+ NULL);
+ gtk_tree_view_append_column(GTK_TREE_VIEW(list_view), column);
+ gtk_tree_view_column_set_expand(column, TRUE);
+
+ /* Active */
+ renderer = gtk_cell_renderer_toggle_new();
+ g_object_set(renderer,
+ "radio", TRUE,
+ "activatable", TRUE,
+ NULL);
+ column = gtk_tree_view_column_new_with_attributes
+ (_("Active"), renderer,
+ "active", FILTER_ACTIVE,
+ NULL);
+ gtk_tree_view_append_column(GTK_TREE_VIEW(list_view), column);
+ gtk_tree_view_column_set_alignment (column, 0.5);
+ CLAWS_SET_TIP(gtk_tree_view_column_get_widget(column),
+ _("An account can only have one active script at a time."));
+ g_signal_connect(G_OBJECT(renderer), "toggled",
+ G_CALLBACK(filter_active_toggled), page);
+
+ gtk_tree_view_set_search_column(GTK_TREE_VIEW(list_view), FILTER_NAME);
+ gtk_tree_view_set_search_equal_func(GTK_TREE_VIEW(list_view),
+ filter_search_equal_fn, page, NULL);
+}
+
+
+static GtkListStore* filters_create_data_store(void)
+{
+ return gtk_list_store_new(N_FILTER_COLUMNS,
+ G_TYPE_STRING, /* FILTER_NAME */
+ G_TYPE_BOOLEAN, /* FILTER_ACTIVE */
+ -1);
+}
+
+static GtkWidget *filters_list_view_create(SieveManagerPage *page)
+{
+ GtkTreeView *list_view;
+ GtkTreeSelection *selector;
+ GtkListStore *store = filters_create_data_store();
+
+ list_view = GTK_TREE_VIEW(gtk_tree_view_new_with_model(GTK_TREE_MODEL(store)));
+ g_object_unref(G_OBJECT(store));
+
+ selector = gtk_tree_view_get_selection(list_view);
+ gtk_tree_selection_set_mode(selector, GTK_SELECTION_BROWSE);
+
+ /* create the columns */
+ filters_create_list_view_columns(page, GTK_WIDGET(list_view));
+
+ /* set a double click listener */
+ g_signal_connect(G_OBJECT(list_view), "row_activated",
+ G_CALLBACK(filter_double_clicked),
+ page);
+
+ return GTK_WIDGET(list_view);
+}
+
+static gboolean manager_key_pressed(GtkWidget *widget, GdkEventKey *event,
+ gpointer data)
+{
+ SieveManagerPage* page = (SieveManagerPage *) data;
+
+ if (event && event->keyval == GDK_KEY_Escape)
+ sieve_manager_done(page);
+
+ return FALSE;
+}
+
+static void got_session_error(SieveSession *session, const gchar *msg,
+ SieveManagerPage *page)
+{
+ if (page->active_session != session)
+ return;
+ gtk_label_set_text(GTK_LABEL(page->status_text), msg);
+}
+
+static void sieve_manager_on_error(SieveSession *session,
+ const gchar *msg, gpointer user_data)
+{
+ SieveManagerPage *page = (SieveManagerPage *)user_data;
+ got_session_error(session, msg, page);
+}
+
+static void sieve_manager_on_connected(SieveSession *session,
+ gboolean connected, gpointer user_data)
+{
+ SieveManagerPage *page = (SieveManagerPage *)user_data;
+ if (page->active_session != session)
+ return;
+ if (!connected) {
+ gtk_widget_set_sensitive(GTK_WIDGET(page->vbox_buttons), FALSE);
+ gtk_label_set_text(GTK_LABEL(page->status_text),
+ _("Unable to connect"));
+ }
+}
+
+static void got_filter_listed(SieveSession *session, SieveScript *script,
+ SieveManagerPage *page)
+{
+ if (!script) {
+ got_session_error(session, "Unable to list scripts", page);
+ return;
+ }
+ if (!script->name) {
+ /* done receiving list */
+ page->got_list = TRUE;
+ gtk_widget_set_sensitive(GTK_WIDGET(page->vbox_buttons), TRUE);
+ gtk_label_set_text(GTK_LABEL(page->status_text), "");
+ return;
+ }
+ filters_list_insert_filter(page, script);
+}
+
+/*
+ * An account was selected from the menu. Get its list of scripts.
+ */
+static void account_changed(GtkWidget *widget, SieveManagerPage *page)
+{
+ gint account_id;
+ PrefsAccount *account;
+ SieveSession *session;
+
+ account_id = combobox_get_active_data(GTK_COMBO_BOX(page->accounts_menu));
+ account = account_find_from_id(account_id);
+ if (!account)
+ return;
+ session = page->active_session = sieve_session_get_for_account(account);
+ sieve_session_handle_status(session,
+ sieve_manager_on_error,
+ sieve_manager_on_connected,
+ page);
+ filters_list_clear(page);
+ if (session_is_connected(SESSION(session))) {
+ gtk_label_set_text(GTK_LABEL(page->status_text),
+ _("Listing scripts..."));
+ } else {
+ gtk_label_set_text(GTK_LABEL(page->status_text),
+ _("Connecting..."));
+ }
+ sieve_session_list_scripts(session,
+ (sieve_session_data_cb_fn)got_filter_listed, (gpointer)page);
+}
+
+static SieveManagerPage *sieve_manager_page_new()
+{
+ SieveManagerPage *page;
+ GtkWidget *window;
+ GtkWidget *hbox, *vbox, *vbox_allbuttons, *vbox_buttons;
+ GtkWidget *accounts_menu;
+ GtkWidget *label;
+ GtkWidget *scrolledwin;
+ GtkWidget *list_view;
+ GtkWidget *btn;
+ GtkWidget *status_text;
+ GtkTreeIter iter;
+ GtkListStore *menu;
+ GList *account_list, *cur;
+ PrefsAccount *ap;
+ SieveAccountConfig *config;
+ PrefsAccount *default_account = NULL;
+
+ page = g_new0(SieveManagerPage, 1);
+
+ /* Manage Window */
+
+ window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "sievemanager");
+ gtk_container_set_border_width (GTK_CONTAINER (window), 8);
+ gtk_window_set_title (GTK_WINDOW (window), _("Manage Sieve Filters"));
+ gtk_widget_set_size_request (window, 480, 296);
+ MANAGE_WINDOW_SIGNALS_CONNECT (window);
+ g_signal_connect (G_OBJECT (window), "key_press_event",
+ G_CALLBACK (manager_key_pressed), page);
+
+ vbox = gtk_vbox_new (FALSE, 10);
+ gtk_container_add (GTK_CONTAINER (window), vbox);
+
+ hbox = gtk_hbox_new (FALSE, 8);
+ gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 0);
+
+ /* Accounts list */
+
+ label = gtk_label_new (_("Account"));
+ gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0);
+
+ accounts_menu = gtkut_sc_combobox_create(NULL, FALSE);
+ menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(accounts_menu)));
+ gtk_box_pack_start (GTK_BOX (hbox), accounts_menu, FALSE, FALSE, 0);
+ g_signal_connect (G_OBJECT(accounts_menu), "changed",
+ G_CALLBACK (account_changed), page);
+
+ account_list = account_get_list();
+ for (cur = account_list; cur != NULL; cur = cur->next) {
+ ap = (PrefsAccount *)cur->data;
+ config = sieve_prefs_account_get_config(ap);
+ if (config->enable) {
+ COMBOBOX_ADD (menu, ap->account_name, ap->account_id);
+ if (!default_account || ap->is_default)
+ default_account = ap;
+ }
+ }
+
+ if (!default_account) {
+ gtk_widget_destroy(label);
+ gtk_widget_destroy(accounts_menu);
+ }
+
+ /* status */
+ status_text = gtk_label_new ("");
+ gtk_box_pack_start (GTK_BOX (hbox), status_text, FALSE, FALSE, 0);
+ gtk_label_set_justify (GTK_LABEL (status_text), GTK_JUSTIFY_LEFT);
+
+ /* Filters list */
+
+ hbox = gtk_hbox_new (FALSE, 8);
+ gtk_box_pack_start (GTK_BOX (vbox), hbox, TRUE, TRUE, 0);
+ gtk_container_set_border_width (GTK_CONTAINER (hbox), 2);
+
+ /* Table */
+
+ scrolledwin = gtk_scrolled_window_new (NULL, NULL);
+ gtk_box_pack_start (GTK_BOX (hbox), scrolledwin, TRUE, TRUE, 0);
+ gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolledwin),
+ GTK_POLICY_AUTOMATIC,
+ GTK_POLICY_AUTOMATIC);
+
+ list_view = filters_list_view_create(page);
+ gtk_container_add(GTK_CONTAINER(scrolledwin), list_view);
+
+ /* Buttons */
+
+ vbox_allbuttons = gtk_vbox_new (FALSE, 0);
+ gtk_box_pack_start (GTK_BOX (hbox), vbox_allbuttons, FALSE, FALSE, 0);
+
+ vbox_buttons = gtk_vbox_new (FALSE, 0);
+ gtk_widget_set_sensitive(vbox_buttons, FALSE);
+ gtk_box_pack_start (GTK_BOX (vbox_allbuttons), vbox_buttons, FALSE, FALSE, 0);
+
+ /* new */
+ btn = gtk_button_new_from_stock(GTK_STOCK_NEW);
+ gtk_box_pack_start (GTK_BOX (vbox_buttons), btn, FALSE, FALSE, 4);
+ g_signal_connect (G_OBJECT(btn), "clicked",
+ G_CALLBACK (filter_add), page);
+
+ /* edit */
+ btn = gtk_button_new_from_stock (GTK_STOCK_EDIT);
+ gtk_box_pack_start (GTK_BOX (vbox_buttons), btn, FALSE, FALSE, 4);
+ g_signal_connect (G_OBJECT(btn), "clicked",
+ G_CALLBACK (filter_edit), page);
+
+ /* delete */
+ btn = gtk_button_new_from_stock(GTK_STOCK_DELETE);
+ gtk_box_pack_start (GTK_BOX (vbox_buttons), btn, FALSE, FALSE, 4);
+ g_signal_connect (G_OBJECT(btn), "clicked",
+ G_CALLBACK (filter_delete), page);
+
+ /* rename */
+ btn = gtk_button_new_with_label("Rename");
+ gtk_box_pack_start (GTK_BOX (vbox_buttons), btn, FALSE, FALSE, 4);
+ g_signal_connect (G_OBJECT(btn), "clicked",
+ G_CALLBACK (filter_rename), page);
+
+
+ /* activate */
+ btn = gtk_button_new_with_label("Activate");
+ gtk_box_pack_start (GTK_BOX (vbox_buttons), btn, FALSE, FALSE, 4);
+ g_signal_connect (G_OBJECT(btn), "clicked",
+ G_CALLBACK (filter_activate), page);
+
+ /* refresh */
+ btn = gtk_button_new_with_mnemonic("_Refresh");
+ gtk_box_pack_start (GTK_BOX (vbox_allbuttons), btn, FALSE, FALSE, 4);
+ g_signal_connect (G_OBJECT(btn), "clicked",
+ G_CALLBACK (account_changed), page);
+
+ /* bottom area stuff */
+
+ gtkut_stock_button_set_create(&hbox,
+ &btn, GTK_STOCK_CLOSE,
+ NULL, NULL, NULL, NULL);
+
+ /* close */
+ gtk_box_pack_end (GTK_BOX (vbox), hbox, FALSE, FALSE, 0);
+ gtk_widget_grab_default (btn);
+ g_signal_connect (G_OBJECT (btn), "clicked",
+ G_CALLBACK (sieve_manager_close), page);
+
+ page->window = window;
+ page->accounts_menu = accounts_menu;
+ page->filters_list = list_view;
+ page->status_text = status_text;
+ page->vbox_buttons = vbox_buttons;
+
+ /* select default (first) account */
+ if (default_account) {
+ combobox_select_by_data(GTK_COMBO_BOX(accounts_menu),
+ default_account->account_id);
+ } else {
+ gtk_label_set_text(GTK_LABEL(status_text),
+ _("To use Sieve, enable it in an account's preferences."));
+ }
+
+ return page;
+}
+
+void sieve_manager_close(GtkWidget *widget, SieveManagerPage *page)
+{
+ sieve_manager_done(page);
+}
+
+void sieve_manager_done(SieveManagerPage *page)
+{
+ manager_pages = g_slist_remove(manager_pages, page);
+ gtk_widget_destroy(page->window);
+ g_free(page);
+}
+
+void sieve_manager_show()
+{
+ SieveManagerPage *page = sieve_manager_page_new();
+ manager_pages = g_slist_prepend(manager_pages, page);
+ gtk_widget_show_all(page->window);
+}
diff --git a/src/plugins/managesieve/sieve_manager.h b/src/plugins/managesieve/sieve_manager.h
new file mode 100644
index 0000000..9665bb8
--- /dev/null
+++ b/src/plugins/managesieve/sieve_manager.h
@@ -0,0 +1,42 @@
+/*
+ * Claws Mail -- a GTK+ based, lightweight, and fast e-mail client
+ * Copyright (C) 2004-2015 the Claws Mail team
+ * Copyright (C) 2014-2015 Charles Lehner
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef SIEVE_MANAGER_H
+#define SIEVE_MANAGER_H
+
+#include "managesieve.h"
+
+typedef struct SieveManagerPage SieveManagerPage;
+
+struct SieveManagerPage
+{
+ GtkWidget* window;
+ GtkWidget* accounts_menu;
+ GtkWidget* status_text;
+ GtkWidget* filters_list;
+ GtkWidget* vbox_buttons;
+ SieveSession *active_session;
+ gboolean got_list;
+};
+
+void sieve_manager_show(void);
+void sieve_manager_done(SieveManagerPage *page);
+
+#endif /* SIEVE_MANAGER_H */
diff --git a/src/plugins/managesieve/sieve_plugin.c b/src/plugins/managesieve/sieve_plugin.c
new file mode 100644
index 0000000..0ed9114
--- /dev/null
+++ b/src/plugins/managesieve/sieve_plugin.c
@@ -0,0 +1,154 @@
+/*
+ * Claws Mail -- a GTK+ based, lightweight, and fast e-mail client
+ * Copyright (C) 1999-2015 the Claws Mail Team
+ * Copyright (C) 2014-2015 Charles Lehner
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <glib.h>
+#include <glib/gi18n.h>
+
+#include "version.h"
+#include "claws.h"
+#include "plugin.h"
+#include "utils.h"
+#include "hooks.h"
+#include "menu.h"
+#include "mainwindow.h"
+#include "log.h"
+#include "sieve_prefs.h"
+#include "sieve_manager.h"
+
+#define PLUGIN_NAME (_("ManageSieve"))
+
+static gint main_menu_id = 0;
+
+static void manage_cb(GtkAction *action, gpointer data) {
+ sieve_manager_show();
+}
+
+static GtkActionEntry sieve_main_menu[] = {{
+ "Tools/ManageSieveFilters",
+ NULL, N_("Manage Sieve Filters..."), NULL, NULL, G_CALLBACK(manage_cb)
+}};
+
+/**
+ * Initialize plugin.
+ *
+ * @param error For storing the returned error message.
+ *
+ * @return 0 if initialization succeeds, -1 on failure.
+ */
+gint plugin_init(gchar **error)
+{
+ MainWindow *mainwin = mainwindow_get_mainwindow();
+
+ if (!check_plugin_version(MAKE_NUMERIC_VERSION(2,9,2,72),
+ VERSION_NUMERIC, PLUGIN_NAME, error))
+ return -1;
+
+ gtk_action_group_add_actions(mainwin->action_group, sieve_main_menu, 1,
+ (gpointer)mainwin);
+ MENUITEM_ADDUI_ID_MANAGER(mainwin->ui_manager,
+ "/Menu/Tools", "ManageSieveFilters", "Tools/ManageSieveFilters",
+ GTK_UI_MANAGER_MENUITEM, main_menu_id)
+
+ sieve_prefs_init();
+
+ debug_print("ManageSieve plugin loaded\n");
+
+ return 0;
+}
+
+/**
+ * Destructor for the plugin.
+ * Unregister callback functions and free stuff.
+ *
+ * @return Always TRUE.
+ */
+gboolean plugin_done(void)
+{
+ MainWindow *mainwin = mainwindow_get_mainwindow();
+ if (mainwin == NULL)
+ return FALSE;
+
+ MENUITEM_REMUI_MANAGER(mainwin->ui_manager, mainwin->action_group, "Tools/ManageSieveFilters", main_menu_id);
+ main_menu_id = 0;
+
+ sieve_prefs_done();
+ sieve_sessions_close();
+
+ debug_print("ManageSieve plugin unloaded\n");
+ return TRUE;
+}
+
+const gchar *plugin_name(void)
+{
+ return PLUGIN_NAME;
+}
+
+/**
+ * Get the description of the plugin.
+ *
+ * @return The plugin's description, maybe translated.
+ */
+const gchar *plugin_desc(void)
+{
+ return _("Manage sieve filters on a server using the ManageSieve protocol.");
+}
+
+/**
+ * Get the kind of plugin.
+ *
+ * @return The "GTK2" constant.
+ */
+const gchar *plugin_type(void)
+{
+ return "GTK2";
+}
+/**
+ * Get the license acronym the plugin is released under.
+ *
+ * @return The "GPL3+" constant.
+ */
+const gchar *plugin_licence(void)
+{
+ return "GPL3+";
+}
+
+/**
+ * Get the version of the plugin.
+ *
+ * @return The current version string.
+ */
+const gchar *plugin_version(void)
+{
+ return VERSION;
+}
+
+/**
+ * Get the features implemented by the plugin.
+ *
+ * @return A constant PluginFeature structure with the features.
+ */
+struct PluginFeature *plugin_provides(void)
+{
+ static struct PluginFeature features[] =
+ { {PLUGIN_UTILITY, N_("ManageSieve")},
+ {PLUGIN_NOTHING, NULL}};
+
+ return features;
+}
diff --git a/src/plugins/managesieve/sieve_prefs.c b/src/plugins/managesieve/sieve_prefs.c
new file mode 100644
index 0000000..69d8aa4
--- /dev/null
+++ b/src/plugins/managesieve/sieve_prefs.c
@@ -0,0 +1,520 @@
+/*
+ * Claws Mail -- a GTK+ based, lightweight, and fast e-mail client
+ * Copyright (C) 2004-2015 the Claws Mail team
+ * Copyright (C) 2014-2015 Charles Lehner
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#include "claws-features.h"
+#endif
+
+#include <gtk/gtk.h>
+#include <glib.h>
+#include <glib/gi18n.h>
+
+#include "defs.h"
+#include "gtk/gtkutils.h"
+#include "gtk/combobox.h"
+#include "alertpanel.h"
+#include "passcrypt.h"
+#include "utils.h"
+#include "prefs.h"
+#include "prefs_gtk.h"
+#include "sieve_prefs.h"
+#include "managesieve.h"
+
+#define PACK_HBOX(hbox, vbox) \
+{ \
+ hbox = gtk_hbox_new (FALSE, 5); \
+ gtk_widget_show (hbox); \
+ gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 0); \
+}
+
+#define RADIO_ADD(radio, group, hbox, vbox, label) \
+{ \
+ PACK_HBOX(hbox, vbox); \
+ gtk_container_set_border_width(GTK_CONTAINER (hbox), 0); \
+ radio = gtk_radio_button_new_with_label(group, label); \
+ group = gtk_radio_button_get_group(GTK_RADIO_BUTTON(radio)); \
+ gtk_widget_show(radio); \
+ gtk_box_pack_start(GTK_BOX(hbox), radio, FALSE, FALSE, 0); \
+}
+
+struct SieveAccountPage
+{
+ PrefsPage page;
+
+ GtkWidget *enable_checkbtn;
+ GtkWidget *serv_frame;
+ GtkWidget *auth_frame;
+ GtkWidget *host_checkbtn, *host_entry;
+ GtkWidget *port_checkbtn, *port_spinbtn;
+ GtkWidget *tls_radio_no, *tls_radio_maybe, *tls_radio_yes;
+ GtkWidget *auth_radio_noauth, *auth_radio_reuse, *auth_radio_custom;
+ GtkWidget *auth_custom_vbox, *auth_method_hbox;
+ GtkWidget *uid_entry;
+ GtkWidget *pass_entry;
+ GtkWidget *auth_menu;
+
+ PrefsAccount *account;
+};
+
+static struct SieveAccountPage account_page;
+
+static void update_auth_sensitive(struct SieveAccountPage *page)
+{
+ gboolean use_auth, custom;
+
+ custom = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(page->auth_radio_custom));
+ use_auth = custom || gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(page->auth_radio_reuse));
+
+ gtk_widget_set_sensitive(GTK_WIDGET(page->auth_custom_vbox), custom);
+ gtk_widget_set_sensitive(GTK_WIDGET(page->auth_method_hbox), use_auth);
+}
+
+static void auth_toggled(GtkToggleButton *togglebutton,
+ gpointer user_data)
+{
+ struct SieveAccountPage *page = (struct SieveAccountPage *) user_data;
+ update_auth_sensitive(page);
+}
+
+static void sieve_prefs_account_create_widget_func(PrefsPage *_page,
+ GtkWindow *window,
+ gpointer data)
+{
+ struct SieveAccountPage *page = (struct SieveAccountPage *) _page;
+ PrefsAccount *account = (PrefsAccount *) data;
+ SieveAccountConfig *config;
+
+ GtkWidget *page_vbox, *sieve_vbox;
+ GtkWidget *hbox;
+ GtkWidget *hbox_spc;
+
+ GtkWidget *enable_checkbtn;
+ GtkWidget *serv_vbox, *tls_frame;
+ GtkWidget *tls_vbox, *serv_frame;
+ GtkWidget *auth_vbox, *auth_frame;
+ GtkWidget *auth_custom_vbox, *auth_method_hbox;
+ GtkSizeGroup *size_group;
+ GtkWidget *host_checkbtn, *host_entry;
+ GtkWidget *port_checkbtn, *port_spinbtn;
+ GSList *tls_group = NULL;
+ GSList *auth_group = NULL;
+ GtkWidget *tls_radio_no, *tls_radio_maybe, *tls_radio_yes;
+ GtkWidget *auth_radio_noauth, *auth_radio_reuse, *auth_radio_custom;
+ GtkWidget *label;
+ GtkWidget *uid_entry;
+ GtkWidget *pass_entry;
+ GtkWidget *auth_menu;
+ GtkListStore *menu;
+ GtkTreeIter iter;
+
+ page_vbox = gtk_vbox_new (FALSE, VSPACING);
+ gtk_widget_show (page_vbox);
+ gtk_container_set_border_width (GTK_CONTAINER (page_vbox), VBOX_BORDER);
+
+ /* Enable/disable */
+ PACK_CHECK_BUTTON (page_vbox, enable_checkbtn,
+ _("Enable Sieve"));
+
+ sieve_vbox = gtk_vbox_new (FALSE, VSPACING);
+ gtk_widget_show (sieve_vbox);
+ gtk_box_pack_start (GTK_BOX (page_vbox), sieve_vbox, FALSE, FALSE, 0);
+
+ /* Server info */
+ serv_vbox = gtkut_get_options_frame(sieve_vbox, &serv_frame, _("Server information"));
+ gtk_widget_show (serv_vbox);
+ gtk_box_pack_start (GTK_BOX (page_vbox), serv_vbox, FALSE, FALSE, 0);
+
+ SET_TOGGLE_SENSITIVITY (enable_checkbtn, sieve_vbox);
+ size_group = gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL);
+
+ /* Server name */
+ PACK_HBOX (hbox, serv_vbox);
+ PACK_CHECK_BUTTON (hbox, host_checkbtn, _("Server name"));
+ gtk_size_group_add_widget(size_group, host_checkbtn);
+
+ host_entry = gtk_entry_new();
+ gtk_widget_show (host_entry);
+ gtk_box_pack_start (GTK_BOX (hbox), host_entry, TRUE, TRUE, 0);
+ SET_TOGGLE_SENSITIVITY (host_checkbtn, host_entry);
+ CLAWS_SET_TIP(hbox,
+ _("Connect to this host instead of the host used for receiving mail"));
+
+ /* Port */
+ PACK_HBOX (hbox, serv_vbox);
+ PACK_CHECK_BUTTON (hbox, port_checkbtn, _("Server port"));
+ port_spinbtn = gtk_spin_button_new_with_range(1, 65535, 1);
+ gtk_widget_show (port_spinbtn);
+ gtk_box_pack_start (GTK_BOX (hbox), port_spinbtn, FALSE, FALSE, 0);
+ SET_TOGGLE_SENSITIVITY (port_checkbtn, port_spinbtn);
+ gtk_size_group_add_widget(size_group, port_checkbtn);
+ CLAWS_SET_TIP(hbox,
+ _("Connect to this port instead of the default"));
+
+ /* Encryption */
+
+ tls_vbox = gtkut_get_options_frame(sieve_vbox, &tls_frame, _("Encryption"));
+ gtk_widget_show (tls_vbox);
+ gtk_box_pack_start (GTK_BOX (page_vbox), tls_vbox, FALSE, FALSE, 0);
+
+ RADIO_ADD(tls_radio_no, tls_group, hbox, tls_vbox,
+ _("No TLS"));
+ RADIO_ADD(tls_radio_maybe, tls_group, hbox, tls_vbox,
+ _("Use TLS when available"));
+ RADIO_ADD(tls_radio_yes, tls_group, hbox, tls_vbox,
+ _("Require TLS"));
+
+ /* Authentication */
+
+ auth_vbox = gtkut_get_options_frame(sieve_vbox, &auth_frame,
+ _("Authentication"));
+
+ RADIO_ADD(auth_radio_noauth, auth_group, hbox, auth_vbox,
+ _("No authentication"));
+ RADIO_ADD(auth_radio_reuse, auth_group, hbox, auth_vbox,
+ _("Use same authentication as for receiving mail"));
+ RADIO_ADD(auth_radio_custom, auth_group, hbox, auth_vbox,
+ _("Specify authentication"));
+
+ g_signal_connect(G_OBJECT(auth_radio_custom), "toggled",
+ G_CALLBACK(auth_toggled), page);
+ g_signal_connect(G_OBJECT(auth_radio_reuse), "toggled",
+ G_CALLBACK(auth_toggled), page);
+
+ /* Custom Auth Settings */
+
+ hbox = gtk_hbox_new (FALSE, 0);
+ gtk_widget_show (hbox);
+ gtk_box_pack_start (GTK_BOX (auth_vbox), hbox, FALSE, FALSE, 0);
+
+ hbox_spc = gtk_hbox_new (FALSE, 0);
+ gtk_widget_show (hbox_spc);
+ gtk_box_pack_start (GTK_BOX (hbox), hbox_spc, FALSE, FALSE, 0);
+ gtk_widget_set_size_request (hbox_spc, 12, -1);
+
+ auth_custom_vbox = gtk_vbox_new (FALSE, VSPACING/2);
+ gtk_widget_show (auth_custom_vbox);
+ gtk_container_set_border_width (GTK_CONTAINER (auth_custom_vbox), 0);
+ gtk_box_pack_start (GTK_BOX (hbox), auth_custom_vbox, TRUE, TRUE, 0);
+
+ /* User ID + Password */
+
+ hbox = gtk_hbox_new (FALSE, 8);
+ gtk_widget_show (hbox);
+ gtk_box_pack_start (GTK_BOX (auth_custom_vbox), hbox, FALSE, FALSE, 0);
+
+ /* User ID*/
+ label = gtk_label_new (_("User ID"));
+ gtk_widget_show (label);
+ gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0);
+
+ uid_entry = gtk_entry_new ();
+ gtk_widget_show (uid_entry);
+ gtk_widget_set_size_request (uid_entry, DEFAULT_ENTRY_WIDTH, -1);
+ gtk_box_pack_start (GTK_BOX (hbox), uid_entry, TRUE, TRUE, 0);
+
+ /* Password */
+ label = gtk_label_new (_("Password"));
+ gtk_widget_show (label);
+ gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0);
+
+ pass_entry = gtk_entry_new ();
+ gtk_widget_show (pass_entry);
+ gtk_widget_set_size_request (pass_entry, DEFAULT_ENTRY_WIDTH, -1);
+ gtk_entry_set_visibility (GTK_ENTRY (pass_entry), FALSE);
+ gtk_box_pack_start (GTK_BOX (hbox), pass_entry, TRUE, TRUE, 0);
+
+ /* Authentication method */
+
+ auth_method_hbox = gtk_hbox_new (FALSE, 8);
+ gtk_widget_show (auth_method_hbox);
+ gtk_box_pack_start (GTK_BOX (auth_vbox), auth_method_hbox, FALSE, FALSE, 0);
+
+ label = gtk_label_new (_("Authentication method"));
+ gtk_widget_show (label);
+ gtk_box_pack_start (GTK_BOX (auth_method_hbox), label, FALSE, FALSE, 0);
+
+ auth_menu = gtkut_sc_combobox_create(NULL, FALSE);
+ menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(auth_menu)));
+ gtk_widget_show (auth_menu);
+ gtk_box_pack_start (GTK_BOX (auth_method_hbox), auth_menu, FALSE, FALSE, 0);
+
+ COMBOBOX_ADD (menu, _("Automatic"), SIEVEAUTH_AUTO);
+ COMBOBOX_ADD (menu, NULL, 0);
+ COMBOBOX_ADD (menu, "PLAIN", SIEVEAUTH_PLAIN);
+ COMBOBOX_ADD (menu, "LOGIN", SIEVEAUTH_LOGIN);
+ COMBOBOX_ADD (menu, "CRAM-MD5", SIEVEAUTH_CRAM_MD5);
+
+ /* Populate config */
+
+ config = sieve_prefs_account_get_config(account);
+
+ gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(enable_checkbtn), config->enable);
+ gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(host_checkbtn), config->use_host);
+ gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(port_checkbtn), config->use_port);
+ gtk_spin_button_set_value(GTK_SPIN_BUTTON(port_spinbtn), (float) config->port);
+
+ if (config->host != NULL)
+ gtk_entry_set_text(GTK_ENTRY(host_entry), config->host);
+ if (config->userid != NULL)
+ gtk_entry_set_text(GTK_ENTRY(uid_entry), config->userid);
+ if (config->passwd != NULL)
+ gtk_entry_set_text(GTK_ENTRY(pass_entry), config->passwd);
+
+ combobox_select_by_data(GTK_COMBO_BOX(auth_menu), config->auth_type);
+
+ /* Add items to page struct */
+ page->account = account;
+ page->enable_checkbtn = enable_checkbtn;
+ page->serv_frame = serv_frame;
+ page->auth_frame = auth_frame;
+ page->auth_custom_vbox = auth_custom_vbox;
+ page->auth_method_hbox = auth_method_hbox;
+ page->host_checkbtn = host_checkbtn;
+ page->host_entry = host_entry;
+ page->port_checkbtn = port_checkbtn;
+ page->port_spinbtn = port_spinbtn;
+ page->auth_radio_noauth = auth_radio_noauth;
+ page->auth_radio_reuse = auth_radio_reuse;
+ page->auth_radio_custom = auth_radio_custom;
+ page->tls_radio_no = tls_radio_no;
+ page->tls_radio_maybe = tls_radio_maybe;
+ page->tls_radio_yes = tls_radio_yes;
+ page->uid_entry = uid_entry;
+ page->pass_entry = pass_entry;
+ page->auth_menu = auth_menu;
+ page->page.widget = page_vbox;
+
+ /* Update things */
+
+ gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(
+ config->tls_type == SIEVE_TLS_NO ? tls_radio_no :
+ config->tls_type == SIEVE_TLS_MAYBE ? tls_radio_maybe :
+ tls_radio_yes), TRUE);
+
+ gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(
+ config->auth == SIEVEAUTH_REUSE ? auth_radio_reuse :
+ config->auth == SIEVEAUTH_CUSTOM ? auth_radio_custom :
+ auth_radio_noauth), TRUE);
+
+ update_auth_sensitive(page);
+
+ /* Free things */
+ g_object_unref(G_OBJECT(size_group));
+}
+
+static void sieve_prefs_account_destroy_widget_func(PrefsPage *_page)
+{
+}
+
+static gint sieve_prefs_account_apply(struct SieveAccountPage *page)
+{
+ SieveAccountConfig *config;
+
+ config = sieve_prefs_account_get_config(page->account);
+
+ config->enable = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(page->enable_checkbtn));
+ config->use_port = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(page->port_checkbtn));
+ config->use_host = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(page->host_checkbtn));
+ config->port = (gushort)gtk_spin_button_get_value_as_int
+ (GTK_SPIN_BUTTON(page->port_spinbtn));
+
+ if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(page->auth_radio_noauth)))
+ config->auth = SIEVEAUTH_NONE;
+ else if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(page->auth_radio_reuse)))
+ config->auth = SIEVEAUTH_REUSE;
+ else if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(page->auth_radio_custom)))
+ config->auth = SIEVEAUTH_CUSTOM;
+
+ config->tls_type =
+ gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(page->tls_radio_no)) ?
+ SIEVE_TLS_NO :
+ gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(page->tls_radio_maybe)) ?
+ SIEVE_TLS_MAYBE :
+ SIEVE_TLS_YES;
+
+ config->host = gtk_editable_get_chars(GTK_EDITABLE(page->host_entry), 0, -1);
+ config->userid = gtk_editable_get_chars(GTK_EDITABLE(page->uid_entry), 0, -1);
+ config->passwd = gtk_editable_get_chars(GTK_EDITABLE(page->pass_entry), 0, -1);
+ config->auth_type = combobox_get_active_data(GTK_COMBO_BOX(page->auth_menu));
+
+ sieve_prefs_account_set_config(page->account, config);
+ sieve_prefs_account_free_config(config);
+ return TRUE;
+}
+
+static gboolean sieve_prefs_account_check(struct SieveAccountPage *page)
+{
+ if (strchr(gtk_entry_get_text(GTK_ENTRY(page->host_entry)), ' ')) {
+ alertpanel_error(_("Sieve server must not contain a space."));
+ return FALSE;
+ }
+
+ if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(page->host_checkbtn)) &&
+ *gtk_entry_get_text(GTK_ENTRY(page->host_entry)) == '\0') {
+ alertpanel_error(_("Sieve server is not entered."));
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static void sieve_prefs_account_save_func(PrefsPage *_page)
+{
+ struct SieveAccountPage *page = (struct SieveAccountPage *) _page;
+ if (sieve_prefs_account_check(page)) {
+ sieve_prefs_account_apply(page);
+ }
+}
+
+static gboolean sieve_prefs_account_can_close(PrefsPage *_page)
+{
+ struct SieveAccountPage *page = (struct SieveAccountPage *) _page;
+ return sieve_prefs_account_check(page);
+}
+
+void sieve_prefs_init()
+{
+ static gchar *path[3];
+ path[0] = _("Plugins");
+ path[1] = _("Sieve");
+ path[2] = NULL;
+
+ account_page.page.path = path;
+ account_page.page.create_widget = sieve_prefs_account_create_widget_func;
+ account_page.page.destroy_widget = sieve_prefs_account_destroy_widget_func;
+ account_page.page.save_page = sieve_prefs_account_save_func;
+ account_page.page.can_close = sieve_prefs_account_can_close;
+ account_page.page.weight = 30.0;
+ prefs_account_register_page((PrefsPage *) &account_page);
+}
+
+void sieve_prefs_done(void)
+{
+ prefs_account_unregister_page((PrefsPage *) &account_page);
+}
+
+struct SieveAccountConfig *sieve_prefs_account_get_config(
+ PrefsAccount *account)
+{
+ SieveAccountConfig *config;
+ const gchar *confstr;
+ gchar enc_userid[256], enc_passwd[256];
+ gchar enable, use_host, use_port;
+ gsize len;
+
+ config = g_new0(SieveAccountConfig, 1);
+
+ config->enable = FALSE;
+ config->use_host = FALSE;
+ config->host = NULL;
+ config->use_port = FALSE;
+ config->port = 4190;
+ config->tls_type = SIEVE_TLS_YES;
+ config->auth = SIEVEAUTH_REUSE;
+ config->auth_type = SIEVEAUTH_AUTO;
+ config->userid = NULL;
+ config->passwd = NULL;
+
+ confstr = prefs_account_get_privacy_prefs(account, "sieve");
+ if (confstr == NULL)
+ return config;
+
+
+ sscanf(confstr, "%c%c %ms %c%hu %hhu %hhu %hhu %256s %256s",
+ &enable, &use_host,
+ &config->host,
+ &use_port, &config->port,
+ (char *)&config->tls_type,
+ (char *)&config->auth,
+ (char *)&config->auth_type,
+ enc_userid,
+ enc_passwd);
+
+ config->enable = enable == 'y';
+ config->use_host = use_host == 'y';
+ config->use_port = use_port == 'y';
+
+ if (config->host[0] == '!' && !config->host[1]) {
+ g_free(config->host);
+ config->host = NULL;
+ }
+
+ config->userid = g_base64_decode(enc_userid, &len);
+ config->passwd = g_base64_decode(enc_passwd, &len);
+ passcrypt_decrypt(config->passwd, len);
+
+ return config;
+}
+
+void sieve_prefs_account_set_config(
+ PrefsAccount *account, SieveAccountConfig *config)
+{
+ gchar *confstr = NULL;
+ gchar *enc_userid = NULL;
+ gchar *enc_passwd = NULL;
+ gchar *tmp;
+ gsize len;
+
+ if (config->userid) {
+ len = strlen(config->userid);
+ enc_userid = g_base64_encode(config->userid, len);
+ }
+
+ if (config->passwd) {
+ tmp = g_strdup(config->passwd);
+ len = strlen(tmp);
+ passcrypt_encrypt(tmp, len);
+ enc_passwd = g_base64_encode(tmp, len);
+ g_free(tmp);
+ }
+
+ confstr = g_strdup_printf("%c%c %s %c%hu %hhu %hhu %hhu %s %s",
+ config->enable ? 'y' : 'n',
+ config->use_host ? 'y' : 'n',
+ config->host && config->host[0] ? config->host : "!",
+ config->use_port ? 'y' : 'n',
+ config->port,
+ config->tls_type,
+ config->auth,
+ config->auth_type,
+ enc_userid ? enc_userid : "",
+ enc_passwd ? enc_passwd : "");
+
+ if (enc_userid)
+ g_free(enc_userid);
+ if (enc_passwd)
+ g_free(enc_passwd);
+
+ prefs_account_set_privacy_prefs(account, "sieve", confstr);
+
+ g_free(confstr);
+
+ sieve_account_prefs_updated(account);
+}
+
+void sieve_prefs_account_free_config(SieveAccountConfig *config)
+{
+ g_free(config->host);
+ g_free(config->userid);
+ g_free(config->passwd);
+ g_free(config);
+}
+
diff --git a/src/plugins/managesieve/sieve_prefs.h b/src/plugins/managesieve/sieve_prefs.h
new file mode 100644
index 0000000..c0c2417
--- /dev/null
+++ b/src/plugins/managesieve/sieve_prefs.h
@@ -0,0 +1,51 @@
+/*
+ * Claws Mail -- a GTK+ based, lightweight, and fast e-mail client
+ * Copyright (C) 2004-2015 the Claws Mail team
+ * Copyright (C) 2014-2015 Charles Lehner
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef SIEVE_PREFS_H
+#define SIEVE_PREFS_H
+
+typedef struct SieveAccountConfig SieveAccountConfig;
+
+#include "prefs_account.h"
+#include "managesieve.h"
+
+struct SieveAccountConfig
+{
+ gboolean enable;
+ gboolean use_host;
+ gchar *host;
+ gboolean use_port;
+ gushort port;
+ SieveAuth auth;
+ SieveAuthType auth_type;
+ SieveTLSType tls_type;
+ gchar *userid;
+ gchar *passwd;
+};
+
+void sieve_prefs_init(void);
+void sieve_prefs_done(void);
+struct SieveAccountConfig *sieve_prefs_account_get_config(
+ PrefsAccount *account);
+void sieve_prefs_account_set_config(
+ PrefsAccount *account, SieveAccountConfig *config);
+void sieve_prefs_account_free_config(SieveAccountConfig *config);
+
+#endif /* SIEVE_PREFS_H */
diff --git a/src/plugins/managesieve/version.rc b/src/plugins/managesieve/version.rc
new file mode 100644
index 0000000..0202262
--- /dev/null
+++ b/src/plugins/managesieve/version.rc
@@ -0,0 +1,36 @@
+1 VERSIONINFO
+ FILEVERSION 0, 0, 0, 0
+ PRODUCTVERSION 0, 0, 0, 0
+ FILEFLAGSMASK 0x3fL
+#ifdef _DEBUG
+ FILEFLAGS 0x1L
+#else
+ FILEFLAGS 0x0L
+#endif
+ FILEOS 0x40004L
+ FILETYPE 0x2L
+ FILESUBTYPE 0x0L
+BEGIN
+ BLOCK "StringFileInfo"
+ BEGIN
+ BLOCK "000004b0"
+ BEGIN
+ VALUE "FileDescription", "Claws Mail ManageSieve Plugin\0"
+ VALUE "FileVersion", "0.0.0.0\0"
+ VALUE "ProductVersion", "0.0.0.0 Win32\0"
+ VALUE "LegalCopyright", "GPL / © 1999-2014 Hiroyuki Yamamoto & The Claws Mail Team\0"
+ VALUE "CompanyName", "GNU / Free Software Foundation\0"
+ VALUE "ProductName", "Claws Mail\0"
+// VALUE "Comments", "\0"
+// VALUE "InternalName", "\0"
+// VALUE "LegalTrademarks", "\0"
+// VALUE "OriginalFilename", "\0"
+// VALUE "PrivateBuild", "\0"
+// VALUE "SpecialBuild", "\0"
+ END
+ END
+ BLOCK "VarFileInfo"
+ BEGIN
+ VALUE "Translation", 0x0, 1200
+ END
+END
commit 9b928dfc0e70dfd7010becf1801ccbcfd4ab7bfa
Author: Charles Lehner <cel at celehner.com>
Date: Sun Nov 16 17:14:14 2014 -0500
Make message_search interface-based
- Factor out compose/messageview checking.
- Make general so other code can use it
diff --git a/src/message_search.c b/src/message_search.c
index 9866d7c..33965bd 100644
--- a/src/message_search.c
+++ b/src/message_search.c
@@ -54,14 +54,25 @@ static struct MessageSearchWindow {
GtkWidget *close_btn;
GtkWidget *stop_btn;
- MessageView *messageview;
+ SearchInterface *interface;
+ void *interface_obj;
- Compose *compose;
- gboolean search_compose;
gboolean is_searching;
gboolean body_entry_has_focus;
} search_window;
+static SearchInterface compose_interface = {
+ .search_string_backward = (SearchStringFunc) compose_search_string_backward,
+ .set_position = (SetPositionFunc) compose_set_position,
+ .search_string = (SearchStringFunc) compose_search_string,
+};
+
+static SearchInterface messageview_interface = {
+ .set_position = (SetPositionFunc) messageview_set_position,
+ .search_string = (SearchStringFunc) messageview_search_string,
+ .search_string_backward = (SearchStringFunc) messageview_search_string_backward,
+};
+
static void message_search_create (void);
static void message_search_execute (gboolean backward);
@@ -103,34 +114,30 @@ static void message_hide_stop_button(void)
void message_search(MessageView *messageview)
{
- if (!search_window.window)
- message_search_create();
- else
- gtk_widget_hide(search_window.window);
-
- search_window.messageview = messageview;
- search_window.search_compose = FALSE;
-
- gtk_widget_grab_focus(search_window.next_btn);
- gtk_widget_grab_focus(search_window.body_entry);
- gtk_widget_show(search_window.window);
+ message_search_other(&messageview_interface, (void *)messageview);
}
void message_search_compose(Compose *compose)
{
+ message_search_other(&compose_interface, (void *)compose);
+}
+
+void message_search_other(SearchInterface *interface, void *obj)
+{
if (!search_window.window)
message_search_create();
else
gtk_widget_hide(search_window.window);
- search_window.compose = compose;
- search_window.search_compose = TRUE;
+ search_window.interface_obj = obj;
+ search_window.interface = interface;
gtk_widget_grab_focus(search_window.next_btn);
gtk_widget_grab_focus(search_window.body_entry);
gtk_widget_show(search_window.window);
}
+
static void message_search_create(void)
{
GtkWidget *window;
@@ -266,8 +273,7 @@ static void message_search_create(void)
static void message_search_execute(gboolean backward)
{
- MessageView *messageview = search_window.messageview;
- Compose *compose = search_window.compose;
+ void *interface_obj = search_window.interface_obj;
gboolean case_sens;
gboolean all_searched = FALSE;
gchar *body_str;
@@ -309,25 +315,13 @@ static void message_search_execute(gboolean backward)
AlertValue val;
if (backward) {
- if (search_window.search_compose) {
- if (compose_search_string_backward
- (compose, body_str, case_sens) == TRUE)
- break;
- } else {
- if (messageview_search_string_backward
- (messageview, body_str, case_sens) == TRUE)
- break;
- }
+ if (search_window.interface->search_string_backward
+ (interface_obj, body_str, case_sens) == TRUE)
+ break;
} else {
- if (search_window.search_compose) {
- if (compose_search_string
- (compose, body_str, case_sens) == TRUE)
- break;
- } else {
- if (messageview_search_string
- (messageview, body_str, case_sens) == TRUE)
- break;
- }
+ if (search_window.interface->search_string
+ (interface_obj, body_str, case_sens) == TRUE)
+ break;
}
if (all_searched) {
@@ -352,13 +346,8 @@ static void message_search_execute(gboolean backward)
if (G_ALERTALTERNATE == val) {
manage_window_focus_in(search_window.window,
NULL, NULL);
- if (search_window.search_compose) {
- compose_set_position(compose,
- backward ? -1 : 0);
- } else {
- messageview_set_position(messageview,
- backward ? -1 : 0);
- }
+ search_window.interface->set_position(interface_obj,
+ backward ? -1 : 0);
} else
break;
}
diff --git a/src/message_search.h b/src/message_search.h
index 625f0b8..e6ab436 100644
--- a/src/message_search.h
+++ b/src/message_search.h
@@ -25,7 +25,18 @@
#include "messageview.h"
#include "compose.h"
+typedef void (*SetPositionFunc)(void *obj, gint pos);
+typedef gboolean (*SearchStringFunc)(void *obj,
+ const gchar *str, gboolean case_sens);
+
+typedef struct {
+ SetPositionFunc set_position;
+ SearchStringFunc search_string;
+ SearchStringFunc search_string_backward;
+} SearchInterface;
+
void message_search (MessageView *messageview);
void message_search_compose (Compose *compose);
+void message_search_other (SearchInterface *source, void *obj);
#endif /* __MESSAGE_SEARCH_H__ */
commit b0978b8f43fa2da616a3171001ba75e7c89f9c34
Author: Charles Lehner <cel at celehner.com>
Date: Sat Nov 22 20:44:25 2014 -0500
Add callback for session connection result
diff --git a/src/common/session.c b/src/common/session.c
index 15d4fba..8569148 100644
--- a/src/common/session.c
+++ b/src/common/session.c
@@ -128,6 +128,8 @@ gint session_connect(Session *session, const gchar *server, gushort port)
if (session->conn_id < 0) {
g_warning("can't connect to server.");
session_close(session);
+ if (session->connect_finished)
+ session->connect_finished(session, FALSE);
return -1;
}
@@ -142,6 +144,8 @@ gint session_connect(Session *session, const gchar *server, gushort port)
if (sock == NULL) {
g_warning("can't connect to server.");
session_close(session);
+ if (session->connect_finished)
+ session->connect_finished(session, FALSE);
return -1;
}
sock->is_smtp = session->is_smtp;
@@ -159,6 +163,8 @@ static gint session_connect_cb(SockInfo *sock, gpointer data)
if (!sock) {
g_warning("can't connect to server.");
session->state = SESSION_ERROR;
+ if (session->connect_finished)
+ session->connect_finished(session, FALSE);
return -1;
}
@@ -176,6 +182,8 @@ static gint session_connect_cb(SockInfo *sock, gpointer data)
g_warning("can't initialize SSL.");
log_error(LOG_PROTOCOL, _("SSL handshake failed\n"));
session->state = SESSION_ERROR;
+ if (session->connect_finished)
+ session->connect_finished(session, FALSE);
return -1;
}
}
@@ -183,8 +191,11 @@ static gint session_connect_cb(SockInfo *sock, gpointer data)
/* we could have gotten a timeout while waiting for user input in
* an SSL certificate dialog */
- if (session->state == SESSION_TIMEOUT)
+ if (session->state == SESSION_TIMEOUT) {
+ if (session->connect_finished)
+ session->connect_finished(session, FALSE);
return -1;
+ }
sock_set_nonblocking_mode(sock, session->nonblocking);
@@ -195,6 +206,8 @@ static gint session_connect_cb(SockInfo *sock, gpointer data)
session_read_msg_cb,
session);
+ if (session->connect_finished)
+ session->connect_finished(session, TRUE);
return 0;
}
diff --git a/src/common/session.h b/src/common/session.h
index 98ae50e..cd4b774 100644
--- a/src/common/session.h
+++ b/src/common/session.h
@@ -130,6 +130,8 @@ struct _Session
gint (*recv_msg) (Session *session,
const gchar *msg);
+ void (*connect_finished) (Session *session,
+ gboolean success);
gint (*send_data_finished) (Session *session,
guint len);
gint (*recv_data_finished) (Session *session,
-----------------------------------------------------------------------
hooks/post-receive
--
Claws Mail
More information about the Commits
mailing list