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