Add ManageSieve plugin
authorCharles Lehner <cel@celehner.com>
Sun, 23 Nov 2014 03:32:35 +0000 (22:32 -0500)
committerAndrej Kacian <ticho@claws-mail.org>
Sat, 25 Apr 2015 11:26:06 +0000 (13:26 +0200)
17 files changed:
configure.ac
doc/src/readme.txt
doc/src/rfc5804.txt [new file with mode: 0644]
src/plugins/Makefile.am
src/plugins/managesieve/Makefile.am [new file with mode: 0644]
src/plugins/managesieve/claws.def [new file with mode: 0644]
src/plugins/managesieve/managesieve.c [new file with mode: 0644]
src/plugins/managesieve/managesieve.h [new file with mode: 0644]
src/plugins/managesieve/plugin.def [new file with mode: 0644]
src/plugins/managesieve/sieve_editor.c [new file with mode: 0644]
src/plugins/managesieve/sieve_editor.h [new file with mode: 0644]
src/plugins/managesieve/sieve_manager.c [new file with mode: 0644]
src/plugins/managesieve/sieve_manager.h [new file with mode: 0644]
src/plugins/managesieve/sieve_plugin.c [new file with mode: 0644]
src/plugins/managesieve/sieve_prefs.c [new file with mode: 0644]
src/plugins/managesieve/sieve_prefs.h [new file with mode: 0644]
src/plugins/managesieve/version.rc [new file with mode: 0644]

index 943998003bfb01ce124ce8a763df484a5db21080..60cca19c8fd9e9b654e5514106d4ef41ce40e914 100644 (file)
@@ -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
index a2c86947f4bcd495d853dd558c718570b24a4bd0..9e2e1b739e6989eff3991c9ba1e09bacd06085d4 100644 (file)
@@ -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 (file)
index 0000000..d6deaa8
--- /dev/null
@@ -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]
+\f
+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]
+\f
+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]
+\f
+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]
+\f
+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]
+\f
+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]
+\f
+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]
+\f
+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]
+\f
+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@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]
+\f
+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]
+\f
+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]
+\f
+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]
+\f
+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]
+\f
+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]
+\f
+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]
+\f
+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]
+\f
+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]
+\f
+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]
+\f
+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]
+\f
+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]
+\f
+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]
+\f
+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]
+\f
+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@example.net";
+       C:
+       C: if size :under 10k {
+       C:     redirect "mobile@cell.example.com";
+       C: }
+       C:
+       C: if envelope :contains "to" "tmartin+lists" {
+       C:     redirect "lists@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]
+\f
+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]
+\f
+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]
+\f
+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]
+\f
+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]
+\f
+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]
+\f
+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]
+\f
+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]
+\f
+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@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]
+\f
+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]
+\f
+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]
+\f
+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]
+\f
+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]
+\f
+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]
+\f
+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]
+\f
+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]
+\f
+RFC 5804                       ManageSieve                     July 2010
+
+
+6.1.  ManageSieve Capability Registration Template
+
+   To: iana@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@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@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@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@isode.com>
+   Author/Change controller:  IESG.
+
+
+
+
+
+
+
+
+Melnikov & Martin            Standards Track                   [Page 39]
+\f
+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@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@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@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@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@isode.com>
+   Author/Change controller:  IESG.
+
+
+
+
+
+
+
+
+
+Melnikov & Martin            Standards Track                   [Page 40]
+\f
+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@isode.com>
+   Author/Change controller:  IESG.
+
+6.3.  ManageSieve Response Code Registration Template
+
+   To: iana@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@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@isode.com>
+   Author/Change controller:  IESG.
+
+
+
+
+
+
+
+
+
+Melnikov & Martin            Standards Track                   [Page 41]
+\f
+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@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@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@isode.com>
+   Author/Change controller:  IESG.
+
+
+
+
+
+
+
+
+Melnikov & Martin            Standards Track                   [Page 42]
+\f
+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@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@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@isode.com>
+   Author/Change controller:  IESG.
+
+
+
+
+
+
+
+
+Melnikov & Martin            Standards Track                   [Page 43]
+\f
+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@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@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@isode.com>
+   Author/Change controller:  IESG.
+
+
+
+
+
+
+
+
+
+
+Melnikov & Martin            Standards Track                   [Page 44]
+\f
+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@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@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@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@isode.com>
+   Author/Change controller:  IESG.
+
+
+
+
+
+
+Melnikov & Martin            Standards Track                   [Page 45]
+\f
+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]
+\f
+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]
+\f
+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]
+\f
+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@isode.com
+
+
+   Tim Martin
+   BeThereBeSquare, Inc.
+   672 Haight st.
+   San Francisco, CA  94117
+   USA
+
+   Phone: +1 510 260-4175
+   EMail: timmartin@alumni.cmu.edu
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Melnikov & Martin            Standards Track                   [Page 49]
+\f
index e87e4bddd5d0c4a811b1f651192aae1c13432576..53abd257a4e1a68ac86ebcf074d40c3188338891 100644 (file)
@@ -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 (file)
index 0000000..5758aaf
--- /dev/null
@@ -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 (file)
index 0000000..cd46de6
--- /dev/null
@@ -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 (file)
index 0000000..7dc23b1
--- /dev/null
@@ -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 (file)
index 0000000..b775f90
--- /dev/null
@@ -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 (file)
index 0000000..8471df1
--- /dev/null
@@ -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 (file)
index 0000000..28f75f5
--- /dev/null
@@ -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 (file)
index 0000000..4925a54
--- /dev/null
@@ -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 (file)
index 0000000..212c30a
--- /dev/null
@@ -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 (file)
index 0000000..9665bb8
--- /dev/null
@@ -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 (file)
index 0000000..0ed9114
--- /dev/null
@@ -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 (file)
index 0000000..69d8aa4
--- /dev/null
@@ -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 (file)
index 0000000..c0c2417
--- /dev/null
@@ -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 (file)
index 0000000..0202262
--- /dev/null
@@ -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