Initial commit.

This commit is contained in:
Lefteris Chatzimparmpas 2011-03-06 12:58:58 +01:00
commit 5d4ed5ed3f
47 changed files with 12199 additions and 0 deletions

1
AUTHORS Normal file
View File

@ -0,0 +1 @@
Lefteris Chatzimparmpas <lefcha@fastmail.net>

19
LICENSE Normal file
View File

@ -0,0 +1,19 @@
Copyright (c) 2001-2011 Eleftherios Chatzimparmpas
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.

105
Makefile Normal file
View File

@ -0,0 +1,105 @@
DESTDIR =
BINDIR = /usr/local/bin
SHAREDIR = /usr/local/share/imapfilter
MANDIR = /usr/local/man
INCDIRS = -I/usr/local/include
LIBDIRS = -L/usr/local/lib
MYCFLAGS = -Wall -O
MYLDFLAGS =
DEFS = -DMAKEFILE_SHAREDIR='"$(SHAREDIR)"'
CFLAGS = $(MYCFLAGS) $(DEFS) $(INCDIRS)
LDFLAGS = $(MYLDFLAGS) $(LIBDIRS)
LIBS = -lm -llua -lpcre -lssl -lcrypto
MAN_BIN = imapfilter.1
MAN_CONFIG = imapfilter_config.5
COMMON_LUA = common.lua
SET_LUA = set.lua
REGEX_LUA = regex.lua
ACCOUNT_LUA = account.lua
MAILBOX_LUA = mailbox.lua
MESSAGE_LUA = message.lua
OPTIONS_LUA = options.lua
AUXILIARY_LUA = auxiliary.lua
DEPRECATED_LUA = deprecated.lua
BIN = imapfilter
OBJ = auth.o buffer.o cert.o core.o file.o imap.o imapfilter.o list.o log.o \
lua.o memory.o misc.o namespace.o pcre.o regexp.o request.o \
response.o session.o signal.o socket.o system.o
all: $(BIN)
$(BIN): $(OBJ)
$(CC) -o $(BIN) $(LDFLAGS) $(OBJ) $(LIBS)
$(OBJ): imapfilter.h
buffer.o imap.o imapfilter.o namespace.o request.o response.o: buffer.h
cert.o file.o imapfilter.o log.o lua.o: pathnames.h
imapfilter.o log.o session.o: list.h
imapfilter.o regexp.o response.o: regexp.h
auth.o cert.o imap.o imapfilter.o log.o request.o response.o session.o \
socket.o: session.h
imapfilter.o: version.h
install: $(BIN)
if test ! -d $(DESTDIR)$(BINDIR); then \
mkdir -p $(DESTDIR)$(BINDIR); fi
cp -f $(BIN) $(DESTDIR)$(BINDIR) && \
chmod 0755 $(DESTDIR)$(BINDIR)/$(BIN)
if test ! -d $(DESTDIR)$(SHAREDIR); then \
mkdir -p $(DESTDIR)$(SHAREDIR); fi
cp -f $(COMMON_LUA) $(DESTDIR)$(SHAREDIR) && \
chmod 0644 $(DESTDIR)$(SHAREDIR)/$(COMMON_LUA)
cp -f $(SET_LUA) $(DESTDIR)$(SHAREDIR) && \
chmod 0644 $(DESTDIR)$(SHAREDIR)/$(SET_LUA)
cp -f $(REGEX_LUA) $(DESTDIR)$(SHAREDIR) && \
chmod 0644 $(DESTDIR)$(SHAREDIR)/$(REGEX_LUA)
cp -f $(ACCOUNT_LUA) $(DESTDIR)$(SHAREDIR) && \
chmod 0644 $(DESTDIR)$(SHAREDIR)/$(ACCOUNT_LUA)
cp -f $(MAILBOX_LUA) $(DESTDIR)$(SHAREDIR) && \
chmod 0644 $(DESTDIR)$(SHAREDIR)/$(MAILBOX_LUA)
cp -f $(MESSAGE_LUA) $(DESTDIR)$(SHAREDIR) && \
chmod 0644 $(DESTDIR)$(SHAREDIR)/$(MESSAGE_LUA)
cp -f $(OPTIONS_LUA) $(DESTDIR)$(SHAREDIR) && \
chmod 0644 $(DESTDIR)$(SHAREDIR)/$(OPTIONS_LUA)
cp -f $(AUXILIARY_LUA) $(DESTDIR)$(SHAREDIR) && \
chmod 0644 $(DESTDIR)$(SHAREDIR)/$(AUXILIARY_LUA)
cp -f $(DEPRECATED_LUA) $(DESTDIR)$(SHAREDIR) && \
chmod 0644 $(DESTDIR)$(SHAREDIR)/$(DEPRECATED_LUA)
if test ! -d $(DESTDIR)$(MANDIR)/man1; then \
mkdir -p $(DESTDIR)$(MANDIR)/man1; fi
cp -f $(MAN_BIN) $(DESTDIR)$(MANDIR)/man1 && \
chmod 0644 $(DESTDIR)$(MANDIR)/man1/$(MAN_BIN)
if test ! -d $(DESTDIR)$(MANDIR)/man5; then \
mkdir -p $(DESTDIR)$(MANDIR)/man5; fi
cp -f $(MAN_CONFIG) $(DESTDIR)$(MANDIR)/man5 && \
chmod 0644 $(DESTDIR)$(MANDIR)/man5/$(MAN_CONFIG)
deinstall:
rm -f $(DESTDIR)$(BINDIR)/$(BIN) \
$(DESTDIR)$(SHAREDIR)/$(COMMON_LUA) \
$(DESTDIR)$(SHAREDIR)/$(SET_LUA) \
$(DESTDIR)$(SHAREDIR)/$(REGEX_LUA) \
$(DESTDIR)$(SHAREDIR)/$(ACCOUNT_LUA) \
$(DESTDIR)$(SHAREDIR)/$(MAILBOX_LUA) \
$(DESTDIR)$(SHAREDIR)/$(MESSAGE_LUA) \
$(DESTDIR)$(SHAREDIR)/$(OPTIONS_LUA) \
$(DESTDIR)$(SHAREDIR)/$(AUXILIARY_LUA) \
$(DESTDIR)$(SHAREDIR)/$(DEPRECATED_LUA) \
$(DESTDIR)$(MANDIR)/man1/$(MAN_BIN) \
$(DESTDIR)$(MANDIR)/man5/$(MAN_CONFIG)
uninstall: deinstall
clean:
rm -f $(OBJ) $(BIN) imapfilter.core core *.orig *.BAK *~
distclean: clean
@if test -f .Makefile; then mv -f .Makefile Makefile; fi

192
NEWS Normal file
View File

@ -0,0 +1,192 @@
IMAPFilter 2.2.3 - 6 Mar 2011
- Project moved to GitHub.
- The next UID is returned as an additional return value of check_status().
- All processing methods now return a boolean based on their success.
- Bug fix; a lost connection is now handled better by trying to reconnect.
- Bug fix; in some cases in IDLE a message had arrived but was ignored.
- Bug fix; in some servers the initial IDLE reply wasn't handled correctly.
- Bug fix; typo errors in the documentation.
IMAPFilter 2.2.2 - 23 Jan 2010
- Bug fix; a couple of errors in the extending examples file.
IMAPFilter 2.2.1 - 20 Jan 2010
- A global option for the IDLE refreshing interval was added.
- Bug fix; more detailed reporting when SSL socket errors occur.
IMAPFilter 2.2 - 30 Dec 2009
- Support for combining searching methods in multiple mailboxes at the same
or different accounts and processing of the results in bulk.
- Support for meta-searching that allows searching on the previous searching
results.
- The processing and fetching methods were enhanced to reflect the new
changes and the documentation was updated.
- Global options for the message cache and the certificates were added.
- Bug fix; questions for certificates are not asked while in daemon mode, but
instead an error is printed.
* A different format is used for the returned structures of the searching
methods, due to the introduction of multiple mailbox searching and
meta-searching, and thus any configuration files that rely on them should
be updated. Consequently, the processing and fetching methods have been
also enhanced and the relevant documentation updated, and while these
changes are backwards compatible, an update of the configuration file is
still recommended.
IMAPFilter 2.1.2 - 3 Dec 2009
- Bug fix; cache for message parts didn't work correctly.
- Bug fix; documentation error.
IMAPFilter 2.1.1 - 24 Nov 2009
- Bug fix; global option timeout and enter_idle() didn't play well together.
IMAPFilter 2.1 - 23 Nov 2009
- Support for the IMAP IDLE extension (RFC 2177) through the enter_idle()
method.
- Support for fetching of a message's body structure through the
fetch_structure() method, and of a message's specific body part through the
fetch_parts() method.
- Addition of a global option that controls the character set used for all
the searching methods.
- Bug fix; fetching of non-existent messages.
- Bug fix; no trailing end-of-line characters in the results of
fetch_fields().
IMAPFilter 2.0.11 - 20 Sep 2009
- Bug fix; fetching of messages with empty body.
- Workaround for problematic IMAP server sending non-compliant mailbox status
information.
IMAPFilter 2.0.10 - 16 Feb 2008
- Bug fix; failed a great number (tens of thousands) of commands were
exchanged with an IMAP server.
- Bug fix; failed to fetch the body of some messages in some extremely rare
occasions.
- Bug fix; the description for the contain_header() method was clarified.
IMAPFilter 2.0.9 - 26 Dec 2007
- Bug fix; the match_*() methods failed to match messages.
- Bug fix; the match_*() methods failed with an error when no messages
matched.
- Bug fix; note added in the documentation about the need to use double
backslashes inside of regular expression patterns.
IMAPFilter 2.0.8 - 23 Dec 2007
- Bug fix; on some platforms it is necessary to link against the math library.
IMAPFilter 2.0.7 - 22 Dec 2007
- Bug fix; the match_*() methods failed with an error message.
IMAPFilter 2.0.6 - 7 Oct 2007
- Bug fix; the search query that was sent with the select_all() method had an
incorrect format and this caused an error in some mail servers.
IMAPFilter 2.0.5 - 4 Oct 2007
- Bug fix; an error in the sample extensions file.
- Bug fix; typo errors in the manual page.
- The documentation was updated with details and examples on how to access
mailboxes inside folders.
- Examples were added on how to define composite filters that include
multiple searching rules.
IMAPFilter 2.0.4 - 27 Sep 2007
- Bug fix; the send_query() method didn't return the special form of table
that the rest of the searching methods did.
- An additional searching method has been added to search for keyword flags
set.
- A new variable that was added to the Makefile makes it possible to set an
alternative environment for the installation path.
IMAPFilter 2.0.3 - 27 Jul 2007
- Bug fix; part of the program's functionality didn't seem to work at all.
(did nothing), due to problem when providing the results from searching
methods to processing methods.
IMAPFilter 2.0.2 - 30 Jun 2007
- Bug fix; message cache problem due to non-use of message UIDs.
IMAPFilter 2.0.1 - 29 Jun 2007
- Bug fix; character set problem with 1.x configuration files.
- Bug fix; typo errors in the documentation.
IMAPFilter 2.0 - 27 Jun 2007
- New, more powerful, feature rich and yet simpler configuration file.
- Easier object oriented view of accounts and mailboxes.
- Simpler approach to filters, with infix logical or/and/not operators.
- No more need to mess with server search queries.
- More and simpler functions instead of few and complicated ones.
- More feature complete interface that can now even manipulate mailboxes.
- Regular expressions integrated into the searching interface.
- Effective caching subsystem when fetching message parts.
- Can still read old version 1.x configuration files for compatibility.
- Lua 5.1 and the PCRE library are now requirements.
* The configuration file format has changed. The new format is not backwards
compatible, and thus it should not be mixed with the old format.
Nevertheless, configuration files that employ the old, and now deprecated,
format can still be read and executed as before.
IMAPFilter 1.3 - 13 Feb 2007
- Perl Compatible Regular Expression (PCRE) support.
- Compile against Lua 5.1 by default.
- Bug fix; program fault in some cases and when namespace prefix was empty.
- Bug fix; program fault on some platforms when running in verbose mode.
IMAPFilter 1.2.2 - 1 Aug 2006
- Bug fix; a mix up of connections could happen in certain circumstances,
when a hostname and/or username was a prefix of another hostname and/or
username respectively, or when the same hostname and username was used to
connect to a different port.
- Bug fix; the list()/lsub() functions parsed mailboxes/folders whose names
contained spaces incorrectly.
- The list() function now does not return the folder itself, when listing
mailboxes inside a specific folder.
- It is now possible to define new user keywords for messages inside a
mailbox, apart from the standard system flags.
IMAPFilter 1.2.1 - 9 Mar 2006
- Buf fix; program fault when using the fetch*() family of functions.
IMAPFilter 1.2 - 2 Mar 2006
- IPv6 support.
- Lua 5.1 compatibility.
- Bug fix; handle messages containing binary data.
- Bug fix; problems with CPU utilisation when the inactivity timeout timer
was set.
IMAPFilter 1.1.1 - 11 Nov 2005
- Bug fix; minor memory leak.
- Bug fix; on some systems, failure resulted while disconnecting from all the
servers, during the shutdown phase just before exiting.
IMAPFilter 1.1 - 24 Aug 2005
- Addition of the list() and lsub() commands, that make it possible to get a
list of the available mailboxes or only of those that are subscribed.
Implementation of the IMAP LIST/LSUB commands, with additional support for
the IMAP CHILDREN (RFC 3348) and IMAP NAMESPACE (RFC 2342) extensions.
- New program option to execute a string from the command line, without
loading a configuration file.
- New program option to enter interactive mode after executing the
configuration file or the command line.
- Servers that reply with multiple SEARCH responses are taken into
consideration.
- Bug fix; failure to parse the response to fetchfast() that some mail
servers sent.
- Bug fix; in some systems and when in debug mode, an empty namespace caused
program fault.
IMAPFilter 1.0.1 - 22 Aug 2004
- Bug fix; in some cases processing of messages with an empty body caused
failure.
- Bug fix; an invalid namespace prefix was inserted in mailbox names of some
mail servers.
- Unique message identifiers are now used by default, instead of message
sequence numbers, when accessing messages in a mailbox.
- Sequence set ranges are generated and sent to the mail server instead of
enumerations, when this is possible.
- The client now limits the length of the command lines it generates to
approximately 1000 octets, by splitting the request into multiple commands.
- Systems that have no limit on the number of bytes in a pathname are now
considered.
- Debug files are now written in $HOME/.imapfilter/ instead of /tmp/.
IMAPFilter 1.0 - 23 May 2004
- Initial release of IMAPFilter with extension language Lua.

60
README Normal file
View File

@ -0,0 +1,60 @@
IMAPFilter
Description
IMAPFilter is a mail filtering utility. It connects to remote mail servers
using the Internet Message Access Protocol (IMAP), sends searching queries to
the server and processes mailboxes based on the results. It can be used to
delete, copy, move, flag, etc. messages residing in mailboxes at the same or
different mail servers. The 4rev1 and 4 versions of the IMAP protocol are
supported.
IMAPFilter uses the Lua programming language as a configuration and extension
language.
Website
http://github.com/lefcha/imapfilter
Changes
All the changes in each new release up to the latest are in the NEWS file.
Installation
Compile time requirements are Lua (5.1 or later), the PCRE library, and
optionally the OpenSSL library (for SSL/TLS and CRAM-MD5 support).
Optionally, configure installation options using the supplied shell script.
To see usage details:
./configure -h
Compile and install the program:
make
make install
Documentation
There is detailed description of the command line options in the
imapfilter(1) manual page, and of the configuration file format in the
imapfilter_config(5) manual page.
There are also more configuration examples in the sample.config.lua file, and
some examples of extensions through Lua in the sample.extend.lua file.
License
Released under the terms and conditions of the MIT/X11 license, included in
the LICENSE file.
Authors
See AUTHORS file.

239
account.lua Normal file
View File

@ -0,0 +1,239 @@
-- The Account class represents an IMAP account.
Account = {}
IMAP = Account
imap = Account
Account._mt = {}
setmetatable(Account, Account._mt)
Account._mt.__call = function (self, arg)
_check_required(arg.server, 'string')
_check_required(arg.username, 'string')
_check_optional(arg.password, 'string')
_check_optional(arg.port, 'number')
_check_optional(arg.ssl, 'string')
local object = {}
object._type = 'account'
for key, value in pairs(Account) do
if (type(value) == 'function') then
object[key] = value
end
end
object._mt = {}
object._mt.__index = object._attach_mailbox
setmetatable(object, object._mt)
object._imap = arg
return object
end
function Account._login_user(self)
if (self._imap.password == nil) then
self._imap.password = get_password('Enter password for ' ..
self._imap.username .. '@' .. self._imap.server .. ': ')
end
local r = ifcore.login(self._imap)
if (r == nil) then
return true
elseif (r == true) then
self._mailbox = nil
return true
elseif (r == false) then
return false
end
end
function Account._attach_mailbox(self, mailbox)
self[mailbox] = Mailbox(self, mailbox)
return self[mailbox]
end
function Account._detach_mailbox(self, mailbox)
self[mailbox] = nil
end
function Account.list_all(self, folder, mbox)
_check_optional(folder, 'string')
_check_optional(mbox, 'string')
if (folder == nil) then
folder = ''
else
if (type(options) == 'table' and options.namespace == true) then
if (folder == '/') then
folder = ''
end
if (folder ~= '') then
folder = folder .. '/'
end
end
end
if (mbox == nil) then
mbox = '%'
end
if (self._login_user(self) ~= true) then
return
end
local _, mailboxes, folders = ifcore.list(self._imap, '', folder .. mbox)
local m = {}
for s in string.gmatch(mailboxes, '%C+') do
table.insert(m, s)
end
local f = {}
for s in string.gmatch(folders, '%C+') do
if s ~= folder and s ~= folder .. '/' then
table.insert(f, s)
end
end
return m, f
end
function Account.list_subscribed(self, folder, mbox)
_check_optional(folder, 'string')
_check_optional(mbox, 'string')
if (folder == nil) then
folder = ''
else
if (type(options) == 'table' and options.namespace == true) then
if (folder == '/') then
folder = ''
end
if (folder ~= '') then
folder = folder .. '/'
end
end
end
if (mbox == nil) then
mbox = '*'
end
if (self._login_user(self) ~= true) then
return
end
local _, mailboxes, folders = ifcore.lsub(self._imap, '', folder .. mbox)
local m = {}
for s in string.gmatch(mailboxes, '%C+') do
table.insert(m, s)
end
local f = {}
for s in string.gmatch(folders, '%C+') do
if s ~= folder and s ~= folder .. '/' then
table.insert(f, s)
end
end
return m, f
end
function Account.create_mailbox(self, name)
_check_required(name, 'string')
if (self._login_user(self) ~= true) then
return
end
local r = ifcore.create(self._imap, name)
if (type(options) == 'table' and options.info == true) then
print(string.format("Created mailbox %s@%s/%s.",
self._imap.username, self._imap.server, name))
end
return r
end
function Account.delete_mailbox(self, name)
_check_required(name, 'string')
if (self._login_user(self) ~= true) then
return
end
local r = ifcore.delete(self._imap, name)
if (type(options) == 'table' and options.info == true) then
print(string.format("Deleted mailbox %s@%s/%s.",
self._imap.username, self._imap.server, name))
end
return r
end
function Account.rename_mailbox(self, oldname, newname)
_check_required(oldname, 'string')
_check_required(newname, 'string')
if (self._login_user(self) ~= true) then
return
end
local r = ifcore.rename(self._imap, oldname, newname)
if (type(options) == 'table' and options.info == true) then
print(string.format("Renamed mailbox %s@%s/%s to %s@%s/%s.",
self._imap.username, self._imap.server, oldname,
self._imap.username, self._imap.server, newname))
end
return r
end
function Account.subscribe_mailbox(self, name)
_check_required(name, 'string')
if (self._login_user(self) ~= true) then
return
end
local r = ifcore.subscribe(self._imap, name)
if (type(options) == 'table' and options.info == true) then
print(string.format("Subscribed mailbox %s@%s/%s.",
self._imap.username, self._imap.server, name))
end
return r
end
function Account.unsubscribe_mailbox(self, name)
_check_required(name, 'string')
if (self._login_user(self) ~= true) then
return
end
local r = ifcore.unsubscribe(self._imap, name)
if (type(options) == 'table' and options.info == true) then
print(string.format("Unsubscribed mailbox %s@%s/%s.",
self._imap.username, self._imap.server, name))
end
return r
end
Account._mt.__index = function () end
Account._mt.__newindex = function () end

73
auth.c Normal file
View File

@ -0,0 +1,73 @@
#include <stdio.h>
#include <string.h>
#include "imapfilter.h"
#include "session.h"
#ifndef NO_CRAMMD5
#include <openssl/hmac.h>
#include <openssl/evp.h>
/*
* Authenticate to the server with the Challenge-Response Authentication
* Mechanism (CRAM). The authentication type associated with CRAM is
* "CRAM-MD5".
*/
int
auth_cram_md5(session *ssn, const char *user, const char *pass)
{
int t;
size_t n;
unsigned int i;
unsigned char *chal, *resp, *out, *buf;
unsigned char md[EVP_MAX_MD_SIZE], mdhex[EVP_MAX_MD_SIZE * 2 + 1];
unsigned int mdlen;
HMAC_CTX hmac;
if ((t = imap_authenticate(ssn, "CRAM-MD5")) == -1)
return -1;
if (response_authenticate(ssn, t, &chal) ==
STATUS_RESPONSE_CONTINUE) {
n = strlen((char *)(chal)) * 3 / 4 + 1;
resp = (unsigned char *)xmalloc(n * sizeof(char));
memset(resp, 0, n);
EVP_DecodeBlock(resp, chal, strlen((char *)(chal)));
HMAC_Init(&hmac, (const unsigned char *)pass, strlen(pass),
EVP_md5());
HMAC_Update(&hmac, resp, strlen((char *)(resp)));
HMAC_Final(&hmac, md, &mdlen);
xfree(chal);
xfree(resp);
for (i = 0; i < mdlen; i++)
snprintf((char *)(mdhex) + i * 2, mdlen * 2 - i * 2 + 1,
"%02x", md[i]);
mdhex[mdlen * 2] = '\0';
n = strlen(user) + 1 + strlen((char *)(mdhex)) + 1;
buf = (unsigned char *)xmalloc(n * sizeof(unsigned char));
memset(buf, 0, n);
snprintf((char *)(buf), n, "%s %s", user, mdhex);
n = (strlen((char *)(buf)) + 3) * 4 / 3 + 1;
out = (unsigned char *)xmalloc(n * sizeof(unsigned char));
memset(out, 0, n);
EVP_EncodeBlock(out, buf, strlen((char *)(buf)));
imap_continuation(ssn, (char *)(out), strlen((char *)(out)));
xfree(buf);
xfree(out);
} else
return -1;
return response_authenticate(ssn, t, NULL);
}
#endif /* NO_CRAMMD5 */

66
auxiliary.lua Normal file
View File

@ -0,0 +1,66 @@
-- Miscellaneous auxiliary functions.
function form_date(days)
_check_required(days, 'number')
return os.date("%d-%b-%Y", os.time() - days * 60 * 60 * 24)
end
function get_password(prompt)
_check_optional(prompt, 'string')
if (prompt ~= nil) then
io.write(prompt)
else
io.write('Enter password: ')
end
ifsys.noecho()
local p = io.read()
ifsys.echo()
return p
end
function pipe_to(command, data)
_check_required(command, 'string')
_check_required(data, 'string')
f = ifsys.popen(command, "w")
ifsys.write(f, data)
return ifsys.pclose(f)
end
function pipe_from(command)
_check_required(command, 'string')
f = ifsys.popen(command, "r")
local string = ''
while (true) do
s = ifsys.read(f)
if (s ~= nil) then
string = string .. s
else
break
end
end
return ifsys.pclose(f), string
end
function become_daemon(interval, commands)
_check_required(interval, 'number')
_check_required(commands, 'function')
ifsys.daemon()
repeat
pcall(commands)
until (ifsys.sleep(interval) ~= 0)
end

60
buffer.c Normal file
View File

@ -0,0 +1,60 @@
#include <stdio.h>
#include "imapfilter.h"
#include "buffer.h"
/*
* Initialize buffer.
*/
void
buffer_init(buffer *buf, size_t n)
{
buf->data = (char *)xmalloc((n + 1) * sizeof(char));
*buf->data = '\0';
buf->len = 0;
buf->size = n;
}
/*
* Free allocated memory of buffer.
*/
void
buffer_free(buffer *buf)
{
if (!buf->data)
return;
xfree(buf->data);
buf->data = NULL;
}
/*
* Reset buffer.
*/
void
buffer_reset(buffer *buf)
{
*buf->data = '\0';
buf->len = 0;
}
/*
* Check if the buffer has enough space to store data and reallocate memory if
* needed.
*/
void
buffer_check(buffer *buf, size_t n)
{
while (n > buf->size) {
buf->size *= 2;
buf->data = (char *)xrealloc(buf->data, buf->size + 1);
}
}

23
buffer.h Normal file
View File

@ -0,0 +1,23 @@
#ifndef BUFFER_H
#define BUFFER_H
#include <stdio.h>
/* Temporary buffer. */
typedef struct buffer {
char *data; /* Text or binary data. */
size_t len; /* Length of text or binary data. */
size_t size; /* Maximum size of data. */
} buffer;
/* buffer.c */
void buffer_init(buffer *buf, size_t n);
void buffer_free(buffer *buf);
void buffer_reset(buffer *buf);
void buffer_check(buffer *buf, size_t n);
#endif /* BUFFER_H */

235
cert.c Normal file
View File

@ -0,0 +1,235 @@
#ifndef NO_SSLTLS
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <limits.h>
#include <sys/stat.h>
#include <unistd.h>
#include "imapfilter.h"
#include "session.h"
#include "pathnames.h"
#include <openssl/x509.h>
#include <openssl/ssl.h>
#include <openssl/pem.h>
#include <openssl/evp.h>
extern environment env;
int check_cert(X509 *pcert, unsigned char *pmd, unsigned int *pmdlen);
void print_cert(X509 *cert, unsigned char *md, unsigned int *mdlen);
int write_cert(X509 *cert);
int mismatch_cert(void);
/*
* Get SSL/TLS certificate check it, maybe ask user about it and act
* accordingly.
*/
int
get_cert(session *ssn)
{
X509 *cert;
unsigned char md[EVP_MAX_MD_SIZE];
unsigned int mdlen;
mdlen = 0;
if (!(cert = SSL_get_peer_certificate(ssn->ssl)))
return -1;
if (!(X509_digest(cert, EVP_md5(), md, &mdlen)))
return -1;
switch (check_cert(cert, md, &mdlen)) {
case 0:
if (isatty(STDIN_FILENO) == 0)
fatal(ERROR_CERTIFICATE, "%s\n",
"can't accept certificate in non-interactive mode");
print_cert(cert, md, &mdlen);
if (write_cert(cert) == -1)
goto fail;
break;
case -1:
if (isatty(STDIN_FILENO) == 0)
fatal(ERROR_CERTIFICATE, "%s\n",
"certificate mismatch in non-interactive mode");
print_cert(cert, md, &mdlen);
if (mismatch_cert() == -1)
goto fail;
break;
}
X509_free(cert);
return 0;
fail:
X509_free(cert);
return -1;
}
/*
* Check if the SSL/TLS certificate exists in the certificates file.
*/
int
check_cert(X509 *pcert, unsigned char *pmd, unsigned int *pmdlen)
{
int n, r;
FILE *fd;
char b;
char *certf;
X509 *cert;
unsigned char md[EVP_MAX_MD_SIZE];
unsigned int mdlen;
r = 0;
cert = NULL;
n = snprintf(&b, 1, "%s/%s", env.home, PATHNAME_CERTS);
if (env.pathmax != -1 && n > env.pathmax)
fatal(ERROR_PATHNAME,
"pathname limit %ld exceeded: %d\n", env.pathmax, n);
certf = (char *)xmalloc((n + 1) * sizeof(char));
snprintf(certf, n + 1, "%s/%s", env.home, PATHNAME_CERTS);
if (!exists_file(certf)) {
xfree(certf);
return 0;
}
fd = fopen(certf, "r");
xfree(certf);
if (fd == NULL)
return -1;
while ((cert = PEM_read_X509(fd, &cert, NULL, NULL)) != NULL) {
if (X509_subject_name_cmp(cert, pcert) != 0 ||
X509_issuer_name_cmp(cert, pcert) != 0)
continue;
if (!X509_digest(cert, EVP_md5(), md, &mdlen) ||
*pmdlen != mdlen)
continue;
if (memcmp(pmd, md, mdlen) != 0) {
r = -1;
break;
}
r = 1;
break;
}
fclose(fd);
X509_free(cert);
return r;
}
/*
* Print information about the SSL/TLS certificate.
*/
void
print_cert(X509 *cert, unsigned char *md, unsigned int *mdlen)
{
unsigned int i;
char *c;
c = X509_NAME_oneline(X509_get_subject_name(cert), NULL, 0);
printf("Server certificate subject: %s\n", c);
xfree(c);
c = X509_NAME_oneline(X509_get_issuer_name(cert), NULL, 0);
printf("Server certificate issuer: %s\n", c);
xfree(c);
printf("Server key fingerprint: ");
for (i = 0; i < *mdlen; i++)
printf(i != *mdlen - 1 ? "%02X:" : "%02X\n", md[i]);
}
/*
* Write the SSL/TLS certificate after asking the user to accept/reject it.
*/
int
write_cert(X509 *cert)
{
int n;
FILE *fd;
char b, c, buf[64];
char *certf;
do {
printf("(R)eject, accept (t)emporarily or "
"accept (p)ermanently? ");
if (fgets(buf, sizeof(buf), stdin) == NULL)
return -1;
c = tolower((int)(*buf));
} while (c != 'r' && c != 't' && c != 'p');
if (c == 'r')
return -1;
else if (c == 't')
return 0;
n = snprintf(&b, 1, "%s/%s", env.home, PATHNAME_CERTS);
if (env.pathmax != -1 && n > env.pathmax)
fatal(ERROR_PATHNAME,
"pathname limit %ld exceeded: %d\n", env.pathmax, n);
certf = (char *)xmalloc((n + 1) * sizeof(char));
snprintf(certf, n + 1, "%s/%s", env.home, PATHNAME_CERTS);
create_file(certf, S_IRUSR | S_IWUSR);
fd = fopen(certf, "a");
xfree(certf);
if (fd == NULL)
return -1;
PEM_write_X509(fd, cert);
fclose(fd);
return 0;
}
/*
* Ask user to proceed, while a fingerprint mismatch in the SSL/TLS certificate
* was found.
*/
int
mismatch_cert(void)
{
char c, buf[64];
do {
printf("ATTENTION: SSL/TLS certificate fingerprint mismatch.\n"
"Proceed with the connection (y/n)? ");
if (fgets(buf, sizeof(buf), stdin) == NULL)
return -1;
c = tolower((int)(*buf));
} while (c != 'y' && c != 'n');
if (c == 'y')
return 0;
else
return -1;
}
#endif /* NO_SSLTLS */

510
common.lua Normal file
View File

@ -0,0 +1,510 @@
-- Common functions for all classes.
function _check_required(arg, argtype)
if (type(arg) == 'nil') then
error('required argument left out', 3)
else
_check_optional(arg, argtype)
end
end
function _check_optional(arg, argtype)
if (type(arg) ~= 'nil') then
if (type(argtype) == 'string') then
if (type(arg) ~= argtype) then
error(argtype .. ' argument expected, got ' .. type(arg), 3)
end
elseif (type(argtype) == 'table') then
local b = false
for _, t in ipairs(argtype) do
if (type(arg) == t) then
b = true
end
end
if (b == false) then
error(argtype .. ' argument expected, got ' .. type(arg), 3)
end
end
end
end
function _extract_mailboxes(messages)
local t = {}
for _, v in ipairs(messages) do
b, _ = unpack(v)
t[b] = true
end
return t
end
function _extract_messages(mailbox, messages)
local t = {}
for _, v in ipairs(messages) do
b, m = unpack(v)
if mailbox == b then
table.insert(t, m)
end
end
return t
end
function _make_range(messages)
for _, m in ipairs(messages) do
if type(m) ~= 'number' then
return messages
end
end
table.sort(messages)
local t = {}
local a, z
for _, m in ipairs(messages) do
if a == nil or z == nil then
a = m
z = m
else
if m == z + 1 then
z = m
else
if a == z then
table.insert(t, tostring(a))
else
table.insert(t, a .. ':' .. z)
end
a = m
z = m
end
end
end
if a == z then
table.insert(t, tostring(a))
else
table.insert(t, a .. ':' .. z)
end
return t
end
function _make_query(criteria)
local s = 'ALL '
if (criteria.invert ~= true) then
for ka, va in ipairs(criteria) do
if (type(va) == 'string') then
s = s .. '' .. '(' .. va .. ')' .. ' '
elseif (type(va) == 'table') then
for i = 1, #va - 1 do
s = s .. 'OR '
end
for ko, vo in ipairs(va) do
if (type(vo) ~= 'string') then
error('filter rule not a string', 2)
end
s = s .. '(' .. vo .. ') '
end
else
error('filter element not a string or table', 2)
end
end
else
for i = 1, #criteria - 1 do
s = s .. 'OR '
end
for ko, vo in ipairs(criteria) do
if (type(vo) == 'string') then
s = s .. '' .. '(' .. vo .. ')' .. ' '
elseif (type(vo) == 'table') then
s = s .. '('
for ka, va in ipairs(vo) do
if (type(va) ~= 'string') then
error('filter rule not a string', 2)
end
s = s .. va .. ' '
end
s = string.gsub(s, '(.+) ', '%1')
s = s .. ') '
else
error('filter rule not a string or table', 2)
end
end
end
s = string.gsub(s, '(.+) ', '%1')
return s
end
function _parse_structure(b)
local bs = _parse_body(b)
if not bs then error(b.i .. ':' .. b.s) end
return _parse_normalize(bs)
end
function _parse_normalize(bs, key, val)
if not key or not val then
if #bs == 0 then
return { ['1'] = bs }
else
for k, v in pairs(bs) do
if type(k) ~= 'number' then bs[k] = nil end
end
for k, v in ipairs(bs) do
_parse_normalize(bs, k, v)
bs[tostring(k)] = v
bs[k] = nil
end
end
return bs
else
for k, v in ipairs(val) do
local new = tostring(key) .. '.' .. tostring(k)
bs[new] = v
_parse_normalize(bs, new, v)
val[k] = nil
end
end
end
function _parse_body(b)
if not _parse_lpar(b) then return end
local bp
if _parse_lpar(b, true) then
bp = _parse_mpart(b)
else
bp = _parse_1part(b)
end
if not _parse_rpar(b) then return end
return bp
end
function _parse_1part(b)
local i = b.i
local t = _parse_string(b)
_parse_space(b)
local s = _parse_string(b)
if t and t:lower() == 'message' and s and s:lower() == 'rfc822' then
return _parse_message(b)
else
b.i = i
return _parse_basic(b)
end
end
function _parse_basic(b)
local bp = {}
local s
s = _parse_string(b)
if not s then return end
bp['type'] = s
_parse_space(b)
s = _parse_string(b)
if not s then return end
bp['type'] = bp['type'] .. '/' .. s
_parse_space(b)
s = _parse_param(b, 'name')
if s then bp['name'] = s end
_parse_space(b)
_parse_nstring(b)
_parse_space(b)
_parse_nstring(b)
_parse_space(b)
_parse_string(b)
_parse_space(b)
bp['size'] = _parse_number(b)
if bp['type']:sub(1, 5):lower() == 'text/' then
_parse_space(b)
_parse_number(b)
end
if _parse_space(b) then _parse_nstring(b) end
if _parse_space(b) then
s = _parse_dsp(b)
if s then bp['name'] = s end
end
if _parse_space(b) then _parse_lang(b) end
if _parse_space(b) then _parse_nstring(b) end
while _parse_space(b) and b.i <= #b.s do _parse_extension(b) end
return bp
end
function _parse_mpart(b)
local bp = {}
local i = 1
local s
bp['type'] = 'multipart'
while _parse_lpar(b, true) and b.i <= #b.s do
bp[i] = _parse_body(b)
i = i + 1
end
_parse_space(b)
s = _parse_string(b)
if not s then return end
bp['type'] = bp['type'] .. '/' .. s
if _parse_space(b) then
s = _parse_param(b, 'name')
if s then bp['name'] = s end
end
if _parse_space(b) then
s = _parse_dsp(b)
if s then bp['name'] = s end
end
if _parse_space(b) then _parse_lang(b) end
if _parse_space(b) then _parse_nstring(b) end
while _parse_space(b) and b.i < #b.s do _parse_extension(b) end
return bp
end
function _parse_message(b)
local bp = {}
local s
bp['type'] = 'message/rfc822'
_parse_space(b)
s = _parse_param(b, 'name')
if s then bp['name'] = s end
_parse_space(b)
_parse_nstring(b)
_parse_space(b)
_parse_nstring(b)
_parse_space(b)
_parse_string(b)
_parse_space(b)
bp['size'] = _parse_number(b)
_parse_space(b)
_parse_envelope(b)
_parse_space(b)
local p = _parse_body(b)
if not p then return end
if #p == 0 then
bp[1] = p
else
for k, v in pairs(p) do
if type(k) == 'number' then
bp[k] = v
end
end
end
_parse_space(b)
_parse_number(b)
if _parse_space(b) then _parse_nstring(b) end
if _parse_space(b) then
s = _parse_dsp(b)
if s then bp['name'] = s end
end
if _parse_space(b) then _parse_lang(b) end
if _parse_space(b) then _parse_nstring(b) end
while _parse_space(b) and b.i <= #b.s do _parse_extension(b) end
return bp
end
function _parse_envelope(b)
_parse_lpar(b)
_parse_nstring(b)
_parse_space(b)
_parse_nstring(b)
_parse_space(b)
_parse_address(b)
_parse_space(b)
_parse_address(b)
_parse_space(b)
_parse_address(b)
_parse_space(b)
_parse_address(b)
_parse_space(b)
_parse_address(b)
_parse_space(b)
_parse_address(b)
_parse_space(b)
_parse_nstring(b)
_parse_space(b)
_parse_nstring(b)
_parse_rpar(b)
end
function _parse_address(b)
if _parse_lpar(b) then
while _parse_lpar(b) and b.i <= #b.s do
_parse_nstring(b)
_parse_space(b)
_parse_nstring(b)
_parse_space(b)
_parse_nstring(b)
_parse_space(b)
_parse_nstring(b)
_parse_rpar(b)
end
_parse_rpar(b)
elseif _parse_nil(b) then
end
end
function _parse_lang(b)
if _parse_lpar(b) then
local lang = {}
repeat
table.insert(lang, _parse_string(b))
until not _parse_space(b) or b.i > #b.s
_parse_rpar(b)
return lang
else
return _parse_nstring(b)
end
end
function _parse_dsp(b)
local r
if _parse_lpar(b) then
_parse_string(b)
_parse_space(b)
r = _parse_param(b, 'filename')
_parse_rpar(b)
elseif _parse_nil(b) then
end
return r
end
function _parse_param(b, key)
local r
if _parse_lpar(b) then
repeat
local s = _parse_string(b)
_parse_space(b)
if s and s:lower() == key then
r = _parse_string(b)
else
_parse_string(b)
end
until not _parse_space(b) or b.i > #b.s
_parse_rpar(b)
elseif _parse_nil(b) then
end
return r
end
function _parse_extension(b)
if _parse_nstring(b) then
elseif _parse_number(b) then
elseif _parse_lpar(b) then
_parse_extension(b)
while _parse_space(b) and b.i <= #b.s do
_parse_extension(b)
end
_parse_rpar(b)
else
end
end
function _parse_space(b, peek)
if b.s:sub(b.i, b.i) == ' ' then
if not peek then b.i = b.i + 1 end
return true
else
return false
end
end
function _parse_lpar(b, peek)
if b.s:sub(b.i, b.i) == '(' then
if not peek then b.i = b.i + 1 end
return true
else
return false
end
end
function _parse_rpar(b, peek)
if b.s:sub(b.i, b.i) == ')' then
if not peek then b.i = b.i + 1 end
return true
else
return false
end
end
function _parse_nil(b)
if b.s:sub(b.i, b.i + 2):upper() == 'NIL' then
b.i = b.i + 3
return true
else
return false
end
end
function _parse_string(b)
local i = b.i
if b.s:sub(i, i) == '"' then
i = i + 1
else return end
local j = i
local n = 0
while true do
n = b.s:find('"', i + n)
if not n then return end
if b.s:sub(n - 1, n - 1) ~= '\\' then
i = n + 1
b.i = i
return b.s:sub(j, n - 1)
else return end
end
end
function _parse_nstring(b)
local i = b.i
if b.s:sub(i, i) == '"' then
i = i + 1
elseif _parse_nil(b) then
return 'NIL'
else return end
local j = i
local n = 0
while true do
n = b.s:find('"', i + n)
if not n then return end
if b.s:sub(n - 1, n - 1) ~= '\\' then
i = n + 1
b.i = i
return b.s:sub(j, n - 1)
else return end
end
end
function _parse_number(b)
local j = b.i
local n = b.s:find('[^0-9]', b.i)
if not n then return end
b.i = n
return tonumber(b.s:sub(j, n - 1))
end

251
configure vendored Executable file
View File

@ -0,0 +1,251 @@
#!/bin/sh
# Default values
prefix="/usr/local"
bindir="$prefix/bin"
sharedir="$prefix/share/imapfilter"
mandir="$prefix/man"
ssltls="yes"
crammd5="yes"
incdirs="-I/usr/local/include"
libdirs="-L/usr/local/lib"
mycflags="$CFLAGS -Wall -O"
myldflags="$LDFLAGS"
libs="-lm -llua -lpcre"
libssl="-lssl"
libcrypto="-lcrypto"
defs="-DMAKEFILE_SHAREDIR='\"\$(SHAREDIR)\"'"
bin="imapfilter"
# Get options and arguments
while getopts "d:p:b:s:m:o:h" opt
do
case $opt in
d | p)
prefix=$OPTARG
bindir=$prefix/bin
sharedir=$prefix/share/imapfilter
mandir=$prefix/man
;;
b)
bindir=$OPTARG
;;
s)
sharedir=$OPTARG
;;
m)
mandir=$OPTARG
;;
o)
head=`echo $OPTARG | cut -d= -f1`
body=`echo $OPTARG | cut -d= -f2`
if [ $head = "ssltls" ]
then
if [ $body = "yes" ]; then ssltls="yes"
elif [ $body = "no" ]; then ssltls="no"
fi
elif [ $head = "crammd5" ]
then
if [ $body = "yes" ]; then crammd5="yes"
elif [ $body = "no" ]; then crammd5="no"
fi
fi
;;
h | *)
cat << EOF
Usage:
configure [-h] [-p prefix] [-b bindir] [-s sharedir] [-m mandir]
[-o option=argument]
Description:
-h This brief usage and description message.
-p prefix Installation path for program's files [$prefix]
-b bindir Installation path for binaries [$bindir]
-s sharedir Installation path for libraries [$sharedir]
-m mandir Installation path for manual pages [$mandir]
-o option=argument Enabling/disabling of program's compilation options.
Options:
ssltls Secure Socket Layer and Transport Layer Security \
[$ssltls]
crammd5 Challenge-Response Authentication Mechanism [$crammd5]
EOF
exit 1
;;
esac
done
# Print values
cat << EOF
Installation directory: $prefix
Binaries directory: $bindir
Architecture independent libraries: $sharedir
Manual pages directory: $mandir
Secure Socket Layer and Transport Layer Security: $ssltls
Challenge-Response Authentication Mechanism: $crammd5
EOF
# Defines
if [ $ssltls = "no" ]
then
defs="$defs -DNO_SSLTLS"
fi
if [ $crammd5 = "no" ]
then
defs="$defs -DNO_CRAMMD5"
fi
# Libraries
if [ $ssltls = "yes" ]
then
libs="$libs $libssl $libcrypto"
elif [ $crammd5 = "yes" ]
then
libs="$libs $libcrypto"
fi
# Binary name
uname -a | grep -qi cygwin
if [ $? = 0 ]
then
bin="imapfilter.exe"
fi
# Backup of original Makefile
if [ ! -f .Makefile ]; then cp -f Makefile .Makefile; fi
# Write Makefile
mv -f Makefile Makefile~
cat > Makefile << EOF
DESTDIR =
BINDIR = $bindir
SHAREDIR = $sharedir
MANDIR = $mandir
INCDIRS = $incdirs
LIBDIRS = $libdirs
MYCFLAGS = $mycflags
MYLDFLAGS = $myldflags
DEFS = $defs
CFLAGS = \$(MYCFLAGS) \$(DEFS) \$(INCDIRS)
LDFLAGS = \$(MYLDFLAGS) \$(LIBDIRS)
LIBS = $libs
MAN_BIN = imapfilter.1
MAN_CONFIG = imapfilter_config.5
COMMON_LUA = common.lua
SET_LUA = set.lua
REGEX_LUA = regex.lua
ACCOUNT_LUA = account.lua
MAILBOX_LUA = mailbox.lua
MESSAGE_LUA = message.lua
OPTIONS_LUA = options.lua
AUXILIARY_LUA = auxiliary.lua
DEPRECATED_LUA = deprecated.lua
BIN = $bin
OBJ = auth.o buffer.o cert.o core.o file.o imap.o imapfilter.o list.o log.o \\
lua.o memory.o misc.o namespace.o pcre.o regexp.o request.o \\
response.o session.o signal.o socket.o system.o
all: \$(BIN)
\$(BIN): \$(OBJ)
\$(CC) -o \$(BIN) \$(LDFLAGS) \$(OBJ) \$(LIBS)
\$(OBJ): imapfilter.h
buffer.o imap.o imapfilter.o namespace.o request.o response.o: buffer.h
cert.o file.o imapfilter.o log.o lua.o: pathnames.h
imapfilter.o log.o session.o: list.h
imapfilter.o regexp.o response.o: regexp.h
auth.o cert.o imap.o imapfilter.o log.o request.o response.o session.o \\
socket.o: session.h
imapfilter.o: version.h
install: \$(BIN)
if test ! -d \$(DESTDIR)\$(BINDIR); then \\
mkdir -p \$(DESTDIR)\$(BINDIR); fi
cp -f \$(BIN) \$(DESTDIR)\$(BINDIR) && \\
chmod 0755 \$(DESTDIR)\$(BINDIR)/\$(BIN)
if test ! -d \$(DESTDIR)\$(SHAREDIR); then \\
mkdir -p \$(DESTDIR)\$(SHAREDIR); fi
cp -f \$(COMMON_LUA) \$(DESTDIR)\$(SHAREDIR) && \\
chmod 0644 \$(DESTDIR)\$(SHAREDIR)/\$(COMMON_LUA)
cp -f \$(SET_LUA) \$(DESTDIR)\$(SHAREDIR) && \\
chmod 0644 \$(DESTDIR)\$(SHAREDIR)/\$(SET_LUA)
cp -f \$(REGEX_LUA) \$(DESTDIR)\$(SHAREDIR) && \\
chmod 0644 \$(DESTDIR)\$(SHAREDIR)/\$(REGEX_LUA)
cp -f \$(ACCOUNT_LUA) \$(DESTDIR)\$(SHAREDIR) && \\
chmod 0644 \$(DESTDIR)\$(SHAREDIR)/\$(ACCOUNT_LUA)
cp -f \$(MAILBOX_LUA) \$(DESTDIR)\$(SHAREDIR) && \\
chmod 0644 \$(DESTDIR)\$(SHAREDIR)/\$(MAILBOX_LUA)
cp -f \$(MESSAGE_LUA) \$(DESTDIR)\$(SHAREDIR) && \\
chmod 0644 \$(DESTDIR)\$(SHAREDIR)/\$(MESSAGE_LUA)
cp -f \$(OPTIONS_LUA) \$(DESTDIR)\$(SHAREDIR) && \\
chmod 0644 \$(DESTDIR)\$(SHAREDIR)/\$(OPTIONS_LUA)
cp -f \$(AUXILIARY_LUA) \$(DESTDIR)\$(SHAREDIR) && \\
chmod 0644 \$(DESTDIR)\$(SHAREDIR)/\$(AUXILIARY_LUA)
cp -f \$(DEPRECATED_LUA) \$(DESTDIR)\$(SHAREDIR) && \\
chmod 0644 \$(DESTDIR)\$(SHAREDIR)/\$(DEPRECATED_LUA)
if test ! -d \$(DESTDIR)\$(MANDIR)/man1; then \\
mkdir -p \$(DESTDIR)\$(MANDIR)/man1; fi
cp -f \$(MAN_BIN) \$(DESTDIR)\$(MANDIR)/man1 && \\
chmod 0644 \$(DESTDIR)\$(MANDIR)/man1/\$(MAN_BIN)
if test ! -d \$(DESTDIR)\$(MANDIR)/man5; then \\
mkdir -p \$(DESTDIR)\$(MANDIR)/man5; fi
cp -f \$(MAN_CONFIG) \$(DESTDIR)\$(MANDIR)/man5 && \\
chmod 0644 \$(DESTDIR)\$(MANDIR)/man5/\$(MAN_CONFIG)
deinstall:
rm -f \$(DESTDIR)\$(BINDIR)/\$(BIN) \\
\$(DESTDIR)\$(SHAREDIR)/\$(COMMON_LUA) \\
\$(DESTDIR)\$(SHAREDIR)/\$(SET_LUA) \\
\$(DESTDIR)\$(SHAREDIR)/\$(REGEX_LUA) \\
\$(DESTDIR)\$(SHAREDIR)/\$(ACCOUNT_LUA) \\
\$(DESTDIR)\$(SHAREDIR)/\$(MAILBOX_LUA) \\
\$(DESTDIR)\$(SHAREDIR)/\$(MESSAGE_LUA) \\
\$(DESTDIR)\$(SHAREDIR)/\$(OPTIONS_LUA) \\
\$(DESTDIR)\$(SHAREDIR)/\$(AUXILIARY_LUA) \\
\$(DESTDIR)\$(SHAREDIR)/\$(DEPRECATED_LUA) \\
\$(DESTDIR)\$(MANDIR)/man1/\$(MAN_BIN) \\
\$(DESTDIR)\$(MANDIR)/man5/\$(MAN_CONFIG)
uninstall: deinstall
clean:
rm -f \$(OBJ) \$(BIN) imapfilter.core core *.orig *.BAK *~
distclean: clean
@if test -f .Makefile; then mv -f .Makefile Makefile; fi
EOF
exit 0

1169
core.c Normal file

File diff suppressed because it is too large Load Diff

609
deprecated.lua Normal file
View File

@ -0,0 +1,609 @@
-- The old and deprecated interface, provided for compatibility.
function check(account, mbox)
_check_required(account, 'table')
_check_required(mbox, 'string')
_check_required(account.server, 'string')
_check_required(account.username, 'string')
_check_required(account.password, 'string')
if (ifcore.login(account) ~= true) then
return
end
local _, exist, recent, unseen = ifcore.status(account, mbox)
if (type(options) == 'table' and options.info == true) then
print(string.format("%d messages, %d recent, %d unseen, in %s@%s/%s.",
exist, recent, unseen, account.username,
account.server, mbox))
end
return exist, recent, unseen
end
function match(account, mbox, criteria)
_check_required(account, 'table')
_check_required(mbox, 'string')
_check_required(criteria, 'table')
_check_required(account.server, 'string')
_check_required(account.username, 'string')
_check_required(account.password, 'string')
if (ifcore.login(account) ~= true) then
return
end
if (_cached_select(account, mbox) ~= true) then
return {}
end
local charset = ''
if (type(options) == 'table' and type(options.charset) == 'string') then
charset = options.charset
end
local _, results = ifcore.search(account, _make_query(criteria), charset)
if (type(options) == 'table' and options.close == true) then
_cached_close(account)
end
if (results == nil) then
return {}
end
local t = {}
for n in string.gmatch(results, '%d+') do
table.insert(t, tonumber(n))
end
return t
end
function flag(account, mbox, mode, flags, messages)
_check_required(account, 'table')
_check_required(mbox, 'string')
_check_required(mode, 'string')
_check_required(flags, 'table')
_check_required(messages, 'table')
_check_required(account.server, 'string')
_check_required(account.username, 'string')
_check_required(account.password, 'string')
local r = flag_aux(account, mbox, mode, flags, messages)
if (type(options) == 'table' and options.info == true and
messages ~= nil and r == true) then
print(string.format("%d messages flagged in %s@%s/%s.",
#messages, account.username,
account.server, mbox))
end
return r
end
function flag_aux(account, mbox, mode, flags, messages)
if (#messages == 0) then
return
end
if (mode ~= 'add' and mode ~= 'remove' and mode ~= 'replace') then
error('"add", "remove" or "replace" expected for mode', 3)
end
if (ifcore.login(account) ~= true) then
return
end
if (_cached_select(account, mbox) ~= true) then
return
end
local f = ''
if (#flags ~= 0) then
if (flags.keywords ~= true) then
for k, v in ipairs(flags) do
if (string.lower(v) == 'answered' or
string.lower(v) == 'deleted' or
string.lower(v) == 'draft' or
string.lower(v) == 'flagged' or
string.lower(v) == 'seen') then
f = f .. '\\' .. v .. ' '
end
end
f = string.gsub(f, '(.+) ', '%1')
else
f = table.concat(flags, ' ')
end
end
local m = _make_range(messages)
n = #m
local r = false
for i = 1, n, 50 do
j = i + 49
if (n < j) then
j = n
end
r = ifcore.store(account, table.concat(m, ',', i, j), mode, f)
if r == false then
break
end
end
if (type(options) == 'table' and options.close == true) then
_cached_close(account)
end
return r
end
function copy(srcaccount, srcmbox, dstaccount, dstmbox, messages)
_check_required(srcaccount, 'table')
_check_required(srcmbox, 'string')
_check_required(dstaccount, 'table')
_check_required(dstmbox, 'string')
_check_required(messages, 'table')
_check_required(srcaccount.server, 'string')
_check_required(srcaccount.username, 'string')
_check_required(srcaccount.password, 'string')
_check_required(dstaccount.server, 'string')
_check_required(dstaccount.username, 'string')
_check_required(dstaccount.password, 'string')
local r = copy_aux(srcaccount, srcmbox, dstaccount, dstmbox, messages)
if (type(options) == 'table' and options.info == true and
messages ~= nil and r == true) then
print(string.format("%d messages copied from %s@%s/%s to %s@%s/%s.",
#messages, srcaccount.username,
srcaccount.server, srcmbox, dstaccount.username,
dstaccount.server, dstmbox))
end
return r
end
function copy_aux(srcaccount, srcmbox, dstaccount, dstmbox, messages)
if (#messages == 0) then
return
end
if (ifcore.login(srcaccount) ~= true) then
return
end
local r = false
if (srcaccount == dstaccount) then
if (_cached_select(srcaccount, srcmbox) ~= true) then
return
end
local m = _make_range(messages)
n = #m
for i = 1, n, 50 do
j = i + 49
if (n < j) then
j = n
end
r = ifcore.copy(srcaccount, table.concat(m, ',', i, j), dstmbox)
if r == false then
break
end
end
if (type(options) == 'table' and options.close == true) then
_cached_close(srcaccount)
end
else
local fast = fetchfast(srcaccount, srcmbox, messages)
local msgs = fetchmessage(srcaccount, srcmbox, messages)
if (ifcore.login(dstaccount) ~= true) then
return
end
for i in pairs(fast) do
for k, v in ipairs(fast[i]['flags']) do
if (string.lower(v) == '\\recent') then
table.remove(fast[i]['flags'], k)
end
end
r = ifcore.append(dstaccount, dstmbox, msgs[i],
table.concat(fast[i]['flags'], ' '), fast[i]['date'])
end
end
return r
end
function delete(account, mbox, messages)
_check_required(account, 'table')
_check_required(mbox, 'string')
_check_required(messages, 'table')
_check_required(account.server, 'string')
_check_required(account.username, 'string')
_check_required(account.password, 'string')
local r = flag_aux(account, mbox, 'add', { 'Deleted' }, messages)
if (type(options) == 'table' and options.info == true and
messages ~= nil and r == true) then
print(string.format("%d messages deleted in %s@%s/%s.",
#messages, account.username,
account.server, mbox))
end
return r
end
function move(srcaccount, srcmbox, dstaccount, dstmbox, messages)
_check_required(srcaccount, 'table')
_check_required(srcmbox, 'string')
_check_required(dstaccount, 'table')
_check_required(dstmbox, 'string')
_check_required(messages, 'table')
_check_required(srcaccount.server, 'string')
_check_required(srcaccount.username, 'string')
_check_required(srcaccount.password, 'string')
_check_required(dstaccount.server, 'string')
_check_required(dstaccount.username, 'string')
_check_required(dstaccount.password, 'string')
local rc = copy_aux(srcaccount, srcmbox, dstaccount, dstmbox, messages)
local rf = false
if (rc == true) then
rf = flag_aux(srcaccount, srcmbox, 'add', { 'Deleted' }, messages)
end
if (type(options) == 'table' and options.info == true and
messages ~= nil and rc == true and rf == true) then
print(string.format("%d messages moved from %s@%s/%s to %s@%s/%s.",
#messages, srcaccount.username,
srcaccount.server, srcmbox, dstaccount.username,
dstaccount.server, dstmbox))
end
return rc == true and rf == true
end
function fetchheader(account, mbox, messages)
_check_required(account, 'table')
_check_required(mbox, 'string')
_check_required(messages, 'table')
_check_required(account.server, 'string')
_check_required(account.username, 'string')
_check_required(account.password, 'string')
if (#messages == 0) then
return
end
if (ifcore.login(account) ~= true) then
return
end
if (_cached_select(account, mbox) ~= true) then
return
end
local results = {}
for i, v in ipairs(messages) do
local _, header = ifcore.fetchheader(account, tostring(v))
if (header ~= nil) then
results[tonumber(v)] = header
end
end
if (type(options) == 'table' and options.close == true) then
_cached_close(account)
end
return results
end
function fetchbody(account, mbox, messages)
_check_required(account, 'table')
_check_required(mbox, 'string')
_check_required(messages, 'table')
_check_required(account.server, 'string')
_check_required(account.username, 'string')
_check_required(account.password, 'string')
if (#messages == 0) then
return
end
if (ifcore.login(account) ~= true) then
return
end
if (_cached_select(account, mbox) ~= true) then
return
end
local results = {}
for i, v in ipairs(messages) do
local _, body = ifcore.fetchbody(account, tostring(v))
if (body ~= nil) then
results[tonumber(v)] = body
end
end
if (type(options) == 'table' and options.close == true) then
_cached_close(account)
end
return results
end
function fetchmessage(account, mbox, messages)
_check_required(account, 'table')
_check_required(mbox, 'string')
_check_required(messages, 'table')
_check_required(account.server, 'string')
_check_required(account.username, 'string')
_check_required(account.password, 'string')
if (#messages == 0) then
return
end
if (ifcore.login(account) ~= true) then
return
end
if (_cached_select(account, mbox) ~= true) then
return
end
local results = {}
for i, v in ipairs(messages) do
local _, header = ifcore.fetchheader(account, tostring(v))
local _, body = ifcore.fetchbody(account, tostring(v))
if (header ~= nil and body ~= nil) then
results[tonumber(v)] = header .. body
end
end
if (type(options) == 'table' and options.close == true) then
_cached_close(account)
end
return results
end
fetchtext = fetchmessage
function fetchfields(account, mbox, fields, messages)
_check_required(account, 'table')
_check_required(mbox, 'string')
_check_required(fields, 'table')
_check_required(messages, 'table')
_check_required(account.server, 'string')
_check_required(account.username, 'string')
_check_required(account.password, 'string')
if (#messages == 0) then
return
end
if (ifcore.login(account) ~= true) then
return
end
if (_cached_select(account, mbox) ~= true) then
return
end
local results = {}
for i, v in ipairs(messages) do
local _, headerfields = ifcore.fetchfields(account, tostring(v),
table.concat(fields, ' '))
if (headerfields ~= nil) then
results[tonumber(v)] = headerfields
end
end
if (type(options) == 'table' and options.close == true) then
_cached_close(account)
end
return results
end
fetchheaders = fetchfields
function fetchfast(account, mbox, messages)
_check_required(account, 'table')
_check_required(mbox, 'string')
_check_required(messages, 'table')
_check_required(account.server, 'string')
_check_required(account.username, 'string')
_check_required(account.password, 'string')
if (#messages == 0) then
return
end
if (ifcore.login(account) ~= true) then
return
end
if (_cached_select(account, mbox) ~= true) then
return
end
local results = {}
for i, v in ipairs(messages) do
local _, flags, date, size = ifcore.fetchfast(account, tostring(v))
if (flags ~= nil and date ~= nil and size ~= nil ) then
local f = {}
for s in string.gmatch(flags, '%S+') do
table.insert(f, s)
end
results[tonumber(v)] = {}
results[tonumber(v)]['flags'] = f
results[tonumber(v)]['date'] = date
results[tonumber(v)]['size'] = size
end
end
if (type(options) == 'table' and options.close == true) then
_cached_close(account)
end
return results
end
function list(account, name)
_check_required(account, 'table')
_check_optional(name, 'string')
_check_required(account.server, 'string')
_check_required(account.username, 'string')
_check_required(account.password, 'string')
if (name == nil) then
name = ''
else
if (type(options) == 'table' and options.namespace == true) then
if (name == '/') then
name = ''
end
if (name ~= '') then
name = name .. '/'
end
end
end
if (ifcore.login(account) ~= true) then
return
end
local _, mboxs, folders = ifcore.list(account, '', name .. '%')
local m = {}
for s in string.gmatch(mboxs, '%C+') do
table.insert(m, s)
end
local f = {}
for s in string.gmatch(folders, '%C+') do
if s ~= name and s ~= name .. '/' then
table.insert(f, s)
end
end
return m, f
end
function lsub(account, name)
_check_required(account, 'table')
_check_optional(name, 'string')
_check_required(account.server, 'string')
_check_required(account.username, 'string')
_check_required(account.password, 'string')
if (name == nil) then
name = ''
else
if (type(options) == 'table' and options.namespace == true) then
if (name == '/') then
name = ''
end
if (name ~= '') then
name = name .. '/'
end
end
end
if (ifcore.login(account) ~= true) then
return
end
local _, mboxs, folders = ifcore.lsub(account, '', name .. '%')
local m = {}
for s in string.gmatch(mboxs, '%C+') do
table.insert(m, s)
end
local f = {}
for s in string.gmatch(folders, '%C+') do
if s ~= name and s ~= name .. '/' then
table.insert(f, s)
end
end
return m, f
end
function ping(account)
_check_required(account, 'table')
_check_required(account.server, 'string')
_check_required(account.username, 'string')
_check_required(account.password, 'string')
if (ifcore.login(account) ~= true) then
return
end
local r = ifcore.noop(account)
return r
end
function _cached_select(account, mbox)
if (account.mailbox == nil or account.mailbox ~= mbox) then
if (ifcore.select(account, mbox) == true) then
account.mailbox = mbox
return true
else
return false
end
else
return true
end
end
function _cached_close(account)
account.mailbox = nil
return ifcore.close(account)
end
date_before = form_date
get_pass = get_password
daemon_mode = become_daemon

132
file.c Normal file
View File

@ -0,0 +1,132 @@
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <limits.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include "imapfilter.h"
#include "pathnames.h"
extern environment env;
/*
* Create imapfilter's home directory.
*/
int
create_homedir(void)
{
int n;
char b;
char *hd;
n = snprintf(&b, 1, "%s/%s", env.home, PATHNAME_HOME);
if (env.pathmax != -1 && n > env.pathmax)
fatal(ERROR_PATHNAME,
"pathname limit %ld exceeded: %d\n", env.pathmax, n);
hd = (char *)xmalloc((n + 1) * sizeof(char));
snprintf(hd, n + 1, "%s/%s", env.home, PATHNAME_HOME);
if (!exists_dir(hd)) {
if (mkdir(hd, S_IRUSR | S_IWUSR | S_IXUSR))
error("could not create directory %s; %s\n", hd,
strerror(errno));
}
xfree(hd);
return 0;
}
/*
* Check if a file exists.
*/
int
exists_file(char *fname)
{
struct stat fs;
if (access(fname, F_OK))
return 0;
stat(fname, &fs);
if (!S_ISREG(fs.st_mode)) {
error("file %s not a regular file\n", fname);
return -1;
}
return 1;
}
/*
* Check if a directory exists.
*/
int
exists_dir(char *dname)
{
struct stat ds;
if (access(dname, F_OK))
return 0;
stat(dname, &ds);
if (!S_ISDIR(ds.st_mode)) {
error("file %s not a directory\n", dname);
return -1;
}
return 1;
}
/*
* Create a file with the specified permissions.
*/
int
create_file(char *fname, mode_t mode)
{
int fd;
fd = 0;
if (!exists_file(fname)) {
fd = open(fname, O_CREAT | O_WRONLY | O_TRUNC, mode);
if (fd == -1) {
error("could not create file %s; %s\n", fname,
strerror(errno));
return -1;
}
close(fd);
}
return 0;
}
/*
* Get the system's maximum number of bytes in a pathname.
*/
int
get_pathmax(void)
{
int n;
errno = 0;
n = pathconf("/", _PC_PATH_MAX);
if (n == -1 && errno != 0) {
error("getting PATH_MAX limit; %s\n", strerror(errno));
return -1;
}
env.pathmax = n;
return 0;
}

484
imap.c Normal file
View File

@ -0,0 +1,484 @@
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <strings.h>
#include <stdarg.h>
#include "imapfilter.h"
#include "session.h"
#include "buffer.h"
extern options opts;
buffer obuf; /* Output buffer. */
static int tag = 0x1000; /* Every IMAP command is prefixed with a
* unique [:alnum:] string. */
int send_command(session *ssn, char *cmd, char *alt);
void prepare_command(const char *fmt,...);
/*
* Sends to server data; a command.
*/
int
send_command(session *ssn, char *cmd, char *alt)
{
int t = tag;
if (ssn->socket == -1)
return -1;
debug("sending command (%d):\n\n%s\n", ssn->socket,
(opts.debug == 1 && alt ? alt : cmd));
verbose("C (%d): %s", ssn->socket, (alt ? alt : cmd));
if (socket_write(ssn, cmd, strlen(cmd)) == -1)
return -1;
if (tag == 0xFFFF) /* Tag always between 0x1000 and 0xFFFF. */
tag = 0x0FFF;
tag++;
return t;
}
/*
* Prepares data for sending and check that the output buffer size is
* sufficient.
*/
void
prepare_command(const char *fmt,...)
{
int n;
va_list args;
va_start(args, fmt);
buffer_reset(&obuf);
n = vsnprintf(obuf.data, obuf.size + 1, fmt, args);
if (n > (int)obuf.size) {
buffer_check(&obuf, n);
vsnprintf(obuf.data, obuf.size + 1, fmt, args);
}
va_end(args);
}
/*
* Sends a response to a command continuation request.
*/
int
imap_continuation(session *ssn, const char *cont, size_t len)
{
if (ssn->socket == -1)
return -1;
if (socket_write(ssn, cont, len) == -1 ||
socket_write(ssn, "\r\n", strlen("\r\n")) == -1)
return -1;
if (opts.debug > 0) {
unsigned int i;
debug("sending continuation data (%d):\n\n", ssn->socket);
for (i = 0; i < len; i++)
debugc(cont[i]);
debug("\r\n\n");
}
return 0;
}
/*
* IMAP CAPABILITY: requests listing of capabilities that the server supports.
*/
int
imap_capability(session *ssn)
{
prepare_command("%04X CAPABILITY\r\n", tag);
return send_command(ssn, obuf.data, NULL);
}
/*
* IMAP NOOP: does nothing always succeeds.
*/
int
imap_noop(session *ssn)
{
prepare_command("%04X NOOP\r\n", tag);
return send_command(ssn, obuf.data, NULL);
}
/*
* IMAP LOGOUT: informs server that client is done.
*/
int
imap_logout(session *ssn)
{
prepare_command("%04X LOGOUT\r\n", tag);
return send_command(ssn, obuf.data, NULL);
}
/*
* IMAP STARTTLS: begins TLS negotiation.
*/
int
imap_starttls(session *ssn)
{
prepare_command("%04X STARTTLS\r\n", tag);
return send_command(ssn, obuf.data, NULL);
}
#ifndef NO_CRAMMD5
/*
* IMAP AUTHENTICATE: indicates authentication mechanism and performs an
* authentication protocol exchange.
*/
int
imap_authenticate(session *ssn, const char *auth)
{
prepare_command("%04X AUTHENTICATE %s\r\n", tag, auth);
return send_command(ssn, obuf.data, NULL);
}
#endif
/*
* IMAP LOGIN: identifies client to server.
*/
int
imap_login(session *ssn, const char *user, const char *pass)
{
int n, r;
char c;
char *s;
/* Command to send to server. */
prepare_command("%04X LOGIN \"%s\" \"%s\"\r\n", tag, user, pass);
/* Alternate command with password shrouded for safe printing. */
n = snprintf(&c, 1, "%04X LOGIN \"%s\" *\r\n", tag, user);
s = (char *)xmalloc((n + 1) * sizeof(char));
snprintf(s, n + 1, "%04X LOGIN \"%s\" *\r\n", tag, user);
r = send_command(ssn, obuf.data, s);
xfree(s);
return r;
}
/*
* IMAP SELECT: accesses a mailbox in READ-WRITE mode.
*/
int
imap_select(session *ssn, const char *mbox)
{
prepare_command("%04X SELECT \"%s\"\r\n", tag, mbox);
return send_command(ssn, obuf.data, NULL);
}
/*
* IMAP EXAMINE: accesses a mailbox in READ-ONLY mode.
*/
int
imap_examine(session *ssn, const char *mbox)
{
prepare_command("%04X EXAMINE \"%s\"\r\n", tag, mbox);
return send_command(ssn, obuf.data, NULL);
}
/*
* IMAP CREATE: creates mailbox.
*/
int
imap_create(session *ssn, const char *mbox)
{
prepare_command("%04X CREATE \"%s\"\r\n", tag, mbox);
return send_command(ssn, obuf.data, NULL);
}
/*
* IMAP DELETE: deletes mailbox.
*/
int
imap_delete(session *ssn, const char *mbox)
{
prepare_command("%04X DELETE \"%s\"\r\n", tag, mbox);
return send_command(ssn, obuf.data, NULL);
}
/*
* IMAP RENAME: renames mailbox.
*/
int
imap_rename(session *ssn, const char *oldmbox, const char *newmbox)
{
prepare_command("%04X RENAME \"%s\" \"%s\"\r\n", tag, oldmbox, newmbox);
return send_command(ssn, obuf.data, NULL);
}
/*
* IMAP SUBSCRIBE: adds the specified mailbox name to the server's set of
* "active" or "subscribed" mailboxes.
*/
int
imap_subscribe(session *ssn, const char *mbox)
{
prepare_command("%04X SUBSCRIBE \"%s\"\r\n", tag, mbox);
return send_command(ssn, obuf.data, NULL);
}
/*
* IMAP UNSUBSCRIBE: removes the specified mailbox name to the server's set of
* "active" or "subscribed" mailboxes.
*/
int
imap_unsubscribe(session *ssn, const char *mbox)
{
prepare_command("%04X UNSUBSCRIBE \"%s\"\r\n", tag, mbox);
return send_command(ssn, obuf.data, NULL);
}
/*
* IMAP LIST: returns a subset of names from the complete set of all names
* available.
*/
int
imap_list(session *ssn, const char *refer, const char *name)
{
prepare_command("%04X LIST \"%s\" \"%s\"\r\n", tag, refer, name);
return send_command(ssn, obuf.data, NULL);
}
/*
* IMAP LSUB: returns a subset of names from the set of names that the user has
* declared as being "active" or "subscribed".
*/
int
imap_lsub(session *ssn, const char *refer, const char *name)
{
prepare_command("%04X LSUB \"%s\" \"%s\"\r\n", tag, refer, name);
return send_command(ssn, obuf.data, NULL);
}
/*
* IMAP STATUS: requests status of the indicated mailbox.
*/
int
imap_status(session *ssn, const char *mbox, const char *items)
{
prepare_command("%04X STATUS \"%s\" (%s)\r\n", tag, mbox, items);
return send_command(ssn, obuf.data, NULL);
}
/*
* IMAP APPEND: append message to the end of a mailbox.
*/
int
imap_append(session *ssn, const char *mbox, const char *flags,
const char *date, unsigned int size)
{
prepare_command("%04X APPEND \"%s\"%s%s%s%s%s%s {%d}\r\n", tag, mbox,
(flags ? " (" : ""), (flags ? flags : ""), (flags ? ")" : ""),
(date ? " \"" : ""), (date ? date : ""), (date ? "\"" : ""), size);
return send_command(ssn, obuf.data, NULL);
}
/*
* IMAP CHECK: requests a checkpoint of the currently selected mailbox.
*/
int
imap_check(session *ssn)
{
prepare_command("%04X CHECK\r\n", tag);
return send_command(ssn, obuf.data, NULL);
}
/*
* IMAP CLOSE: deletes messages and returns to authenticated state.
*/
int
imap_close(session *ssn)
{
prepare_command("%04X CLOSE\r\n", tag);
return send_command(ssn, obuf.data, NULL);
}
/*
* IMAP EXPUNGE: permanently removes any messages with the \Deleted flag set.
*/
int
imap_expunge(session *ssn)
{
prepare_command("%04X EXPUNGE\r\n", tag);
return send_command(ssn, obuf.data, NULL);
}
/*
* IMAP SEARCH: searches the mailbox for messages that match certain criteria.
*/
int
imap_search(session *ssn, const char *charset, const char *criteria)
{
if (charset != NULL && *charset != '\0')
prepare_command("%04X UID SEARCH CHARSET \"%s\" %s\r\n", tag,
charset, criteria);
else
prepare_command("%04X UID SEARCH %s\r\n", tag,
criteria);
return send_command(ssn, obuf.data, NULL);
}
/*
* IMAP FETCH: retrieves data associated with a message.
*/
int
imap_fetch(session *ssn, const char *mesg, const char *items)
{
prepare_command("%04X UID FETCH %s %s\r\n", tag, mesg, items);
return send_command(ssn, obuf.data, NULL);
}
/*
* IMAP STORE: alters data associated with a message.
*/
int
imap_store(session *ssn, const char *mesg, const char *mode,
const char *flags)
{
prepare_command("%04X UID STORE %s %sFLAGS.SILENT (%s)\r\n", tag,
mesg, (!strncasecmp(mode, "add", 3) ? "+" :
!strncasecmp(mode, "remove", 6) ? "-" : ""), flags);
return send_command(ssn, obuf.data, NULL);
}
/*
* IMAP COPY: copy messages to mailbox.
*/
int
imap_copy(session *ssn, const char *mesg, const char *mbox)
{
prepare_command("%04X UID COPY %s \"%s\"\r\n", tag, mesg, mbox);
return send_command(ssn, obuf.data, NULL);
}
/*
* IMAP NAMESPACE: discovers the prefix and delimeter of namespaces used by the
* server for mailboxes (RFC 2342).
*/
int
imap_namespace(session *ssn)
{
prepare_command("%04X NAMESPACE\r\n", tag);
return send_command(ssn, obuf.data, NULL);
}
/*
* IMAP IDLE: enter the idle state and waits for mailbox updates, as specified
* in the IMAP IDLE extension (RFC 2177).
*/
int
imap_idle(session *ssn)
{
prepare_command("%04X IDLE\r\n", tag);
return send_command(ssn, obuf.data, NULL);
}
/*
* IMAP DONE: ends the idle state entered through the IMAP IDLE command
* (RFC 2177).
*/
int
imap_done(session *ssn)
{
return imap_continuation(ssn, "DONE", strlen("DONE"));
}

74
imapfilter.1 Normal file
View File

@ -0,0 +1,74 @@
.Dd February 28, 2011
.Dt IMAPFILTER 1
.Os
.Sh NAME
.Nm imapfilter
.Nd mail filter
.Sh SYNOPSIS
.Nm
.Op Fl Vdiv
.Op Fl c Ar configfile
.Op Fl e Ar 'command'
.Op Fl l Ar logfile
.Sh DESCRIPTION
.Nm
is a mail filtering utility. It connects to remote mail servers using the
Internet Message Access Protocol (IMAP), sends searching queries to the server
and processes mailboxes based on the results. It can be used to delete, copy,
move, flag, etc. messages residing in mailboxes at the same or different mail
servers. The 4rev1 and 4 versions of the IMAP protocol are supported.
.Pp
The command line options of
.Xr imapfilter 1
are as follows:
.Bl -tag -width Ds
.It Fl V
Displays program's version.
.It Fl c Ar configfile
Path to the configuration file. The default is
.Pa $HOME/.imapfilter/config.lua .
.It Fl d
Debug mode; creates a temporary file name in
.Pa $HOME/.imapfilter/debug.XXXXXX ,
where debugging messages about the program's progress are printed.
.It Fl e Ar 'command'
May be used to enter one line of program (configuration).
When this options is used,
.Nm
will not look for any configuration file.
.It Fl i
Enters interactive mode after executing the configuration file or the command
line.
.It Fl l Ar logfile
File that contains logs of error messages
.Nm
produces.
.It Fl v
Verbose mode; prints detailed information about the program's actions.
.El
.Sh ENVIRONMENT
.Bl -tag -width Ds
.It Ev HOME
User's home directory.
.El
.Sh FILES
.Bl -tag -width Ds
.It Pa $HOME/.imapfilter/config.lua
Default configuration file. Because this file may contain sensitive data such
as user passwords, the recommended permissions are read/write for the user, and
not accessible by others.
.It Pa $HOME/.imapfilter/certificates
File where the SSL/TLS certificates are stored.
.It Pa $HOME/.imapfilter/debug.XXXXXX
Debug files.
.El
.Sh SEE ALSO
.Xr imapfilter_config 5
.Sh CONFORMING TO
.Bl -tag -width Ds
.It IMAP4rev1:
RFC 3501, RFC 3348, RFC 2683, RFC 2595, RFC 2342, RFC 2195,
RFC 2177
.It IMAP4:
RFC 1730
.El

186
imapfilter.c Normal file
View File

@ -0,0 +1,186 @@
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <limits.h>
#include <sys/stat.h>
#include <locale.h>
#include "imapfilter.h"
#include "session.h"
#include "list.h"
#include "version.h"
#include "buffer.h"
#include "pathnames.h"
#include "regexp.h"
#ifndef NO_SSLTLS
#include <openssl/ssl.h>
#include <openssl/err.h>
#endif
extern buffer ibuf, obuf, nbuf;
extern regexp responses[];
options opts; /* Program options. */
environment env; /* Environment variables. */
list *sessions = NULL; /* Active IMAP sessions. */
void usage(void);
void version(void);
/*
* IMAPFilter: an IMAP mail filtering utility.
*/
int
main(int argc, char *argv[])
{
int c;
setlocale(LC_CTYPE, "");
opts.debug = 0;
opts.verbose = 0;
opts.interactive = 0;
opts.log = NULL;
opts.config = NULL;
opts.oneline = NULL;
env.home = getenv("HOME");
env.pathmax = -1;
while ((c = getopt(argc, argv, "Vc:de:il:v?")) != -1) {
switch (c) {
case 'V':
version();
/* NOTREACHED */
break;
case 'c':
opts.config = optarg;
break;
case 'd':
if (opts.debug < 2)
opts.debug++;
break;
case 'e':
opts.oneline = optarg;
break;
case 'i':
opts.interactive = 1;
break;
case 'l':
opts.log = optarg;
break;
case 'v':
opts.verbose = 1;
break;
case '?':
default:
usage();
/* NOTREACHED */
break;
}
}
get_pathmax();
open_debug();
create_homedir();
catch_signals();
open_log();
buffer_init(&ibuf, INPUT_BUF);
buffer_init(&obuf, OUTPUT_BUF);
buffer_init(&nbuf, NAMESPACE_BUF);
if (opts.config == NULL) {
int n;
char b;
n = snprintf(&b, 1, "%s/%s", env.home, PATHNAME_CONFIG);
if (env.pathmax != -1 && n > env.pathmax)
fatal(ERROR_PATHNAME,
"pathname limit %ld exceeded: %d\n", env.pathmax,
n);
opts.config = (char *)xmalloc((n + 1) * sizeof(char));
snprintf(opts.config, n + 1, "%s/%s", env.home,
PATHNAME_CONFIG);
}
regexp_compile(responses);
#ifndef NO_SSLTLS
SSL_library_init();
SSL_load_error_strings();
#endif
start_lua();
{
list *l;
session *s;
l = sessions;
while (l != NULL) {
s = l->data;
l = l->next;
response_generic(s, imap_logout(s));
close_connection(s);
session_destroy(s);
}
}
stop_lua();
#ifndef NO_SSLTLS
ERR_free_strings();
#endif
regexp_free(responses);
buffer_free(&ibuf);
buffer_free(&obuf);
buffer_free(&nbuf);
close_log();
close_debug();
exit(0);
}
/*
* Print a very brief usage message.
*/
void
usage(void)
{
fprintf(stderr, "usage: imapfilter [-Vdiv] [-c configfile] "
"[-e 'command'] [-l logfile]\n");
exit(0);
}
/*
* Print program's version and copyright.
*/
void
version(void)
{
fprintf(stderr, "IMAPFilter %s %s\n", VERSION, COPYRIGHT);
exit(0);
}

285
imapfilter.h Normal file
View File

@ -0,0 +1,285 @@
#ifndef IMAPFILTER_H
#define IMAPFILTER_H
#include <stdio.h>
#include <sys/types.h>
#include <limits.h>
#include <lua.h>
#include <lualib.h>
#include "session.h"
#ifndef NO_SSLTLS
#include <openssl/ssl.h>
#endif
/* Fatal error exit codes. */
#define ERROR_SIGNAL 1
#define ERROR_CONFIG 2
#define ERROR_MEMALLOC 3
#define ERROR_PATHNAME 4
#define ERROR_CERTIFICATE 5
/* IMAP protocol supported by the server. */
#define PROTOCOL_NONE 0
#define PROTOCOL_IMAP4REV1 1
#define PROTOCOL_IMAP4 2
/* Capabilities of mail server. */
#define CAPABILITY_NONE 0x00
#define CAPABILITY_NAMESPACE 0x01
#define CAPABILITY_CRAMMD5 0x02
#define CAPABILITY_STARTTLS 0x04
#define CAPABILITY_CHILDREN 0x08
#define CAPABILITY_IDLE 0x10
/* Status responses and response codes. */
#define STATUS_RESPONSE_NONE 0
#define STATUS_RESPONSE_OK 1
#define STATUS_RESPONSE_NO 2
#define STATUS_RESPONSE_BAD 3
#define STATUS_RESPONSE_UNTAGGED 4
#define STATUS_RESPONSE_CONTINUE 5
#define STATUS_RESPONSE_BYE 6
#define STATUS_RESPONSE_PREAUTH 7
#define STATUS_RESPONSE_READONLY 8
#define STATUS_RESPONSE_TRYCREATE 9
#define STATUS_RESPONSE_TIMEOUT 10
/* Initial buffer size for input, output and namespace buffers. */
#define INPUT_BUF 4096
#define OUTPUT_BUF 1024
#define NAMESPACE_BUF 128
/* Maximum length, in bytes, of a utility's input line. */
#ifndef LINE_MAX
#define LINE_MAX 2048
#endif
/* Program's options. */
typedef struct options {
int debug; /* Debugging level (0..2). */
int verbose; /* Verbose mode. */
int interactive; /* Act as an interpreter. */
char *log; /* Log file for error messages. */
char *config; /* Configuration file. */
char *oneline; /* One line of program/configuration. */
} options;
/* Environment variables. */
typedef struct environment {
char *home; /* User's home directory. */
long pathmax; /* Maximum pathname. */
} environment;
/* auth.c */
#ifndef NO_CRAMMD5
int auth_cram_md5(session *ssn, const char *user, const char *pass);
#endif
/* cert.c */
#ifndef NO_SSLTLS
int get_cert(session *ssn);
#endif
/* core.c */
LUALIB_API int luaopen_ifcore(lua_State *lua);
/* file.c */
int create_homedir(void);
int exists_file(char *fname);
int exists_dir(char *fname);
int create_file(char *fname, mode_t mode);
int get_pathmax(void);
/* imap.c */
int imap_continuation(session *ssn, const char *cont, size_t len);
int imap_capability(session *ssn);
int imap_noop(session *ssn);
int imap_logout(session *ssn);
#ifndef NO_SSLTLS
int imap_starttls(session *ssn);
#endif
int imap_authenticate(session *ssn, const char *auth);
int imap_login(session *ssn, const char *user, const char *pass);
int imap_select(session *ssn, const char *mbox);
int imap_examine(session *ssn, const char *mbox);
int imap_create(session *ssn, const char *mbox);
int imap_delete(session *ssn, const char *mbox);
int imap_rename(session *ssn, const char *oldmbox, const char *newmbox);
int imap_subscribe(session *ssn, const char *mbox);
int imap_unsubscribe(session *ssn, const char *mbox);
int imap_list(session *ssn, const char *refer, const char *name);
int imap_lsub(session *ssn, const char *refer, const char *name);
int imap_status(session *ssn, const char *mbox, const char *items);
int imap_append(session *ssn, const char *mbox, const char *flags,
const char *date, unsigned int size);
int imap_check(session *ssn);
int imap_close(session *ssn);
int imap_expunge(session *ssn);
int imap_search(session *ssn, const char *charset, const char *criteria);
int imap_fetch(session *ssn, const char *mesg, const char *items);
int imap_store(session *ssn, const char *mesg, const char *mode,
const char *flags);
int imap_copy(session *ssn, const char *mesg, const char *mbox);
int imap_namespace(session *ssn);
int imap_idle(session *ssn);
int imap_done(session *ssn);
/* log.c */
void verbose(const char *info,...);
void debug(const char *debug,...);
void debugc(char c);
void error(const char *errmsg,...);
void fatal(unsigned int errnum, const char *fatal,...);
int open_debug(void);
int close_debug(void);
int open_log(void);
int close_log(void);
/* lua.c */
void start_lua(void);
void stop_lua(void);
int get_option_boolean(const char *opt);
lua_Number get_option_number(const char *opt);
const char *get_option_string(const char *opt);
int get_table_type(const char *key);
lua_Number get_table_number(const char *key);
const char *get_table_string(const char *key);
int set_table_nil(const char *key);
int set_table_boolean(const char *key, int value);
int set_table_number(const char *key, lua_Number value);
int set_table_string(const char *key, const char *value);
/* memory.c */
void *xmalloc(size_t size);
void *xrealloc(void *ptr, size_t size);
void xfree(void *ptr);
char *xstrdup(const char *str);
char *xstrndup(const char *str, size_t len);
/* misc.c */
const char *xstrcasestr(const char *haystack, const char *needle);
char *xstrncpy(char *dest, const char *src, size_t size);
/* namespace.c */
const char *apply_namespace(const char *mbox, char *prefix, char delim);
const char *reverse_namespace(const char *mbox, char *prefix, char delim);
/* pcre.c */
LUALIB_API int luaopen_ifre(lua_State *lua);
/* request.c */
int request_noop(const char *server, const char *port, const char *user);
int request_login(const char *server, const char *port, const char *ssl,
const char *user, const char *pass);
int request_logout(const char *server, const char *port, const char *user);
int request_status(const char *server, const char *port, const char *user,
const char *mbox, unsigned int *exist, unsigned int *recent,
unsigned int *unseen, unsigned int *uidnext);
int request_select(const char *server, const char *port, const char *user,
const char *mbox);
int request_close(const char *server, const char *port, const char *user);
int request_expunge(const char *server, const char *port, const char *user);
int request_list(const char *server, const char *port, const char *user,
const char *refer, const char *name, char **mboxs, char **folders);
int request_lsub(const char *server, const char *port, const char *user,
const char *refer, const char *name, char **mboxs, char **folders);
int request_search(const char *server, const char *port, const char *user,
const char *criteria, const char *charset, char **mesgs);
int request_fetchfast(const char *server, const char *port, const char *user,
const char *mesg, char **flags, char **date, char **size);
int request_fetchflags(const char *server, const char *port, const char *user,
const char *mesg, char **flags);
int request_fetchdate(const char *server, const char *port, const char *user,
const char *mesg, char **date);
int request_fetchsize(const char *server, const char *port, const char *user,
const char *mesg, char **size);
int request_fetchstructure(const char *server, const char *port,
const char *user, const char *mesg, char **structure);
int request_fetchheader(const char *server, const char *port, const char *user,
const char *mesg, char **header, size_t *len);
int request_fetchtext(const char *server, const char *port, const char *user,
const char *mesg, char **text, size_t *len);
int request_fetchfields(const char *server, const char *port, const char *user,
const char *mesg, const char *headerfields, char **fields, size_t *len);
int request_fetchpart(const char *server, const char *port, const char *user,
const char *mesg, const char *bodypart, char **part, size_t *len);
int request_store(const char *server, const char *port, const char *user,
const char *mesg, const char *mode, const char *flags);
int request_copy(const char *server, const char *port, const char *user,
const char *mesg, const char *mbox);
int request_append(const char *server, const char *port, const char *user,
const char *mbox, const char *mesg, size_t mesglen, const char *flags,
const char *date);
int request_create(const char *server, const char *port, const char *user,
const char *mbox);
int request_delete(const char *server, const char *port, const char *user,
const char *mbox);
int request_rename(const char *server, const char *port, const char *user,
const char *oldmbox, const char *newmbox);
int request_subscribe(const char *server, const char *port, const char *user,
const char *mbox);
int request_unsubscribe(const char *server, const char *port, const char *user,
const char *mbox);
int request_idle(const char *server, const char *port, const char *user);
/* response.c */
int response_generic(session *ssn, int tag);
int response_continuation(session *ssn);
int response_greeting(session *ssn);
int response_capability(session *ssn, int tag);
int response_authenticate(session *ssn, int tag, unsigned char **cont);
int response_namespace(session *ssn, int tag);
int response_status(session *ssn, int tag, unsigned int *exist,
unsigned int *recent, unsigned int *unseen, unsigned int *uidnext);
int response_examine(session *ssn, int tag, unsigned int *exist,
unsigned int *recent);
int response_select(session *ssn, int tag);
int response_list(session *ssn, int tag, char **mboxs, char **folders);
int response_search(session *ssn, int tag, char **mesgs);
int response_fetchfast(session *ssn, int tag, char **flags, char **date,
char **size);
int response_fetchflags(session *ssn, int tag, char **flags);
int response_fetchdate(session *ssn, int tag, char **date);
int response_fetchsize(session *ssn, int tag, char **size);
int response_fetchstructure(session *ssn, int tag, char **structure);
int response_fetchbody(session *ssn, int tag, char **body, size_t *len);
int response_idle(session *ssn, int tag);
/* signal.c */
void catch_signals(void);
void release_signals(void);
/* socket.c */
int open_connection(session *ssn, const char *server, const char *port,
const char *protocol);
int close_connection(session *ssn);
ssize_t socket_read(session *ssn, char *buf, size_t len, long timeout,
int timeoutfail);
ssize_t socket_write(session *ssn, const char *buf, size_t len);
#ifndef NO_SSLTLS
int open_secure_connection(session *ssn, const char *server, const char *port,
const char *protocol);
int close_secure_connection(session *ssn);
ssize_t socket_secure_read(session *ssn, char *buf, size_t len);
ssize_t socket_secure_write(session *ssn, const char *buf, size_t len);
#endif
/* system.c */
LUALIB_API int luaopen_ifsys(lua_State *lua);
#endif /* IMAPFILTER_H */

1112
imapfilter_config.5 Normal file

File diff suppressed because it is too large Load Diff

59
list.c Normal file
View File

@ -0,0 +1,59 @@
#include <stdio.h>
#include "imapfilter.h"
#include "list.h"
/*
* Add a new element at the end of the list.
*/
list *
list_append(list *lst, void *data)
{
list *l, *nl;
nl = (list *)xmalloc(sizeof(list));
nl->data = data;
nl->prev = nl->next = NULL;
if (lst != NULL) {
for (l = lst; l->next != NULL; l = l->next);
l->next = nl;
nl->prev = l;
return lst;
} else {
return nl;
}
}
/*
* Remove an element from the list.
*/
list *
list_remove(list *lst, void *data)
{
list *l;
l = lst;
while (l != NULL) {
if (l->data != data)
l = l->next;
else {
if (l->prev)
l->prev->next = l->next;
if (l->next)
l->next->prev = l->prev;
if (lst == l)
lst = lst->next;
xfree(l);
break;
}
}
return lst;
}

16
list.h Normal file
View File

@ -0,0 +1,16 @@
#ifndef LIST_H
#define LIST_H
typedef struct list {
void *data;
struct list *next, *prev;
} list;
/* list.h */
list *list_append(list *lst, void *data);
list *list_remove(list *lst, void *data);
#endif /* LIST_H */

240
log.c Normal file
View File

@ -0,0 +1,240 @@
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <stdarg.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <time.h>
#include "imapfilter.h"
#include "session.h"
#include "list.h"
#include "pathnames.h"
extern options opts;
extern environment env;
extern unsigned int flags;
extern list *sessions;
static FILE *debugfp = NULL; /* Pointer to debug file. */
static FILE *logfp = NULL; /* Pointer to log file. */
char *log_time(void);
/*
* Print message if in verbose mode.
*/
void
verbose(const char *fmt,...)
{
va_list args;
if (!opts.verbose)
return;
va_start(args, fmt);
vprintf(fmt, args);
va_end(args);
}
/*
* Write message to debug file.
*/
void
debug(const char *fmt,...)
{
va_list args;
if (opts.debug <= 0 || !debugfp)
return;
va_start(args, fmt);
vfprintf(debugfp, fmt, args);
fflush(debugfp);
va_end(args);
}
/*
* Write character to debug file.
*/
void
debugc(char c)
{
if (opts.debug <= 0 || !debugfp)
return;
fputc(c, debugfp);
}
/*
* Print error message and write it into log file.
*/
void
error(const char *fmt,...)
{
va_list args;
va_start(args, fmt);
fprintf(stderr, "imapfilter: ");
vfprintf(stderr, fmt, args);
va_end(args);
if (logfp) {
va_start(args, fmt);
fprintf(logfp, "%s: ", log_time());
vfprintf(logfp, fmt, args);
fflush(logfp);
va_end(args);
}
}
/*
* Print error message and exit program.
*/
void
fatal(unsigned int errnum, const char *fmt,...)
{
va_list args;
list *l;
session *s;
va_start(args, fmt);
fprintf(stderr, "imapfilter: ");
vfprintf(stderr, fmt, args);
va_end(args);
if (logfp) {
va_start(args, fmt);
fprintf(logfp, "%s: ", log_time());
vfprintf(logfp, fmt, args);
fflush(logfp);
va_end(args);
}
for (l = sessions; l; l = l->next) {
s = l->data;
close_connection(s);
}
close_log();
close_debug();
exit(errnum);
}
/*
* Open temporary debug file and associate a stream with the returned file
* descriptor.
*/
int
open_debug(void)
{
int n;
char b;
char *dt;
int fd;
if (!opts.debug)
return 0;
n = snprintf(&b, 1, "%s/%s", env.home, PATHNAME_DEBUG);
if (env.pathmax != -1 && n > env.pathmax)
fatal(ERROR_PATHNAME,
"pathname limit %ld exceeded: %d\n", env.pathmax, n);
dt = (char *)xmalloc((n + 1) * sizeof(char));
snprintf(dt, n + 1, "%s/%s", env.home, PATHNAME_DEBUG);
fd = mkstemp(dt);
if (fd != -1) {
debugfp = fdopen(fd, "w");
if (debugfp == NULL) {
error("opening debug file %s: %s\n", dt,
strerror(errno));
return -1;
}
}
return 0;
}
/*
* Close temporary debug file.
*/
int
close_debug(void)
{
if (debugfp == NULL)
return 0;
else
return fclose(debugfp);
}
/*
* Open the file for saving of logging information.
*/
int
open_log(void)
{
if (opts.log == NULL)
return 0;
debug("log file: '%s'\n", opts.log);
if (create_file(opts.log, S_IRUSR | S_IWUSR))
return 1;
logfp = fopen(opts.log, "a");
if (logfp == NULL) {
error("opening log file %s: %s\n", opts.log, strerror(errno));
return 1;
}
return 0;
}
/*
* Close the log file.
*/
int
close_log(void)
{
if (logfp == NULL)
return 0;
else
return fclose(logfp);
}
/*
* Return current local time and date.
*/
char *
log_time(void)
{
char *ct;
time_t t;
t = time(NULL);
ct = ctime(&t);
*(strchr(ct, '\n')) = '\0';
return ct;
}

343
lua.c Normal file
View File

@ -0,0 +1,343 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <lua.h>
#include <lauxlib.h>
#include <lualib.h>
#include "imapfilter.h"
#include "pathnames.h"
extern options opts;
extern struct sessionhead sessions;
static lua_State *lua; /* Lua interpreter state. */
void init_options(void);
void interactive_mode(void);
/*
* Start the Lua interpreter, export IMAP core and system functions, load the
* Lua interface functions, load and execute imapfilter's configuration file.
*/
void
start_lua()
{
lua = luaL_newstate();
luaL_openlibs(lua);
luaopen_ifcore(lua);
luaopen_ifsys(lua);
luaopen_ifre(lua);
lua_settop(lua, 0);
init_options();
if (luaL_loadfile(lua, PATHNAME_COMMON) ||
lua_pcall(lua, 0, LUA_MULTRET, 0))
fatal(ERROR_CONFIG, "%s\n", lua_tostring(lua, -1));
if (luaL_loadfile(lua, PATHNAME_SET) ||
lua_pcall(lua, 0, LUA_MULTRET, 0))
fatal(ERROR_CONFIG, "%s\n", lua_tostring(lua, -1));
if (luaL_loadfile(lua, PATHNAME_REGEX) ||
lua_pcall(lua, 0, LUA_MULTRET, 0))
fatal(ERROR_CONFIG, "%s\n", lua_tostring(lua, -1));
if (luaL_loadfile(lua, PATHNAME_ACCOUNT) ||
lua_pcall(lua, 0, LUA_MULTRET, 0))
fatal(ERROR_CONFIG, "%s\n", lua_tostring(lua, -1));
if (luaL_loadfile(lua, PATHNAME_MAILBOX) ||
lua_pcall(lua, 0, LUA_MULTRET, 0))
fatal(ERROR_CONFIG, "%s\n", lua_tostring(lua, -1));
if (luaL_loadfile(lua, PATHNAME_MESSAGE) ||
lua_pcall(lua, 0, LUA_MULTRET, 0))
fatal(ERROR_CONFIG, "%s\n", lua_tostring(lua, -1));
if (luaL_loadfile(lua, PATHNAME_OPTIONS) ||
lua_pcall(lua, 0, LUA_MULTRET, 0))
fatal(ERROR_CONFIG, "%s\n", lua_tostring(lua, -1));
if (luaL_loadfile(lua, PATHNAME_AUXILIARY) ||
lua_pcall(lua, 0, LUA_MULTRET, 0))
fatal(ERROR_CONFIG, "%s\n", lua_tostring(lua, -1));
if (luaL_loadfile(lua, PATHNAME_DEPRECATED) ||
lua_pcall(lua, 0, LUA_MULTRET, 0))
fatal(ERROR_CONFIG, "%s\n", lua_tostring(lua, -1));
if (opts.oneline != NULL) {
if (luaL_loadbuffer(lua, opts.oneline, strlen(opts.oneline),
"=<command line>") || lua_pcall(lua, 0, LUA_MULTRET, 0))
fatal(ERROR_CONFIG, "%s\n", lua_tostring(lua, -1));
} else {
if (luaL_loadfile(lua, opts.config) ||
lua_pcall(lua, 0, LUA_MULTRET, 0))
fatal(ERROR_CONFIG, "%s\n", lua_tostring(lua, -1));
}
if (opts.interactive)
interactive_mode();
}
/*
* Stop the Lua interpreter.
*/
void
stop_lua(void)
{
lua_close(lua);
}
/*
* Set default values to program's options.
*/
void
init_options(void)
{
lua_newtable(lua);
set_table_boolean("certificates", 1);
set_table_boolean("crammd5", 1);
set_table_boolean("create", 0);
set_table_boolean("expunge", 1);
set_table_number("keepalive", 29);
set_table_boolean("namespace", 1);
set_table_boolean("starttls", 1);
set_table_boolean("subscribe", 0);
set_table_number("timeout", 0);
lua_setglobal(lua, "options");
}
/*
* Interactive mode.
*/
void
interactive_mode(void)
{
char buf[LINE_MAX];
for (;;) {
printf("> ");
fflush(stdout);
if (fgets(buf, sizeof(buf), stdin) == NULL) {
printf("\n");
break;
}
if (luaL_loadbuffer(lua, buf, strlen(buf), "=<line>") ||
lua_pcall(lua, 0, LUA_MULTRET, 0)) {
error("%s\n", lua_tostring(lua, -1));
lua_pop(lua, 1);
}
}
}
/*
* Get from the configuration file the value of a boolean option variable.
*/
int
get_option_boolean(const char *opt)
{
int b;
lua_pushstring(lua, "options");
lua_gettable(lua, LUA_GLOBALSINDEX);
if (!lua_istable(lua, -1)) {
lua_pop(lua, 1);
return 0;
}
lua_pushstring(lua, opt);
lua_gettable(lua, -2);
b = lua_toboolean(lua, -1);
lua_pop(lua, 2);
return b;
}
/*
* Get from the configuration file the value of a number option variable.
*/
lua_Number
get_option_number(const char *opt)
{
lua_Number n;
lua_pushstring(lua, "options");
lua_gettable(lua, LUA_GLOBALSINDEX);
if (!lua_istable(lua, -1)) {
lua_pop(lua, 1);
return 0;
}
lua_pushstring(lua, opt);
lua_gettable(lua, -2);
n = lua_tonumber(lua, -1);
lua_pop(lua, 2);
return n;
}
/*
* Get from the configuration file the value of a string option variable.
*/
const char *
get_option_string(const char *opt)
{
const char *s;
lua_pushstring(lua, "options");
lua_gettable(lua, LUA_GLOBALSINDEX);
if (!lua_istable(lua, -1)) {
lua_pop(lua, 1);
return NULL;
}
lua_pushstring(lua, opt);
lua_gettable(lua, -2);
s = lua_tostring(lua, -1);
lua_pop(lua, 2);
return s;
}
/*
* Get the type of a table's element.
*/
int
get_table_type(const char *key)
{
int t;
lua_pushstring(lua, key);
lua_gettable(lua, -2);
t = lua_type(lua, -1);
lua_pop(lua, 1);
return t;
}
/*
* Get the value of a table's element of type number.
*/
lua_Number
get_table_number(const char *key)
{
lua_Number n;
lua_pushstring(lua, key);
lua_gettable(lua, -2);
n = lua_tonumber(lua, -1);
lua_pop(lua, 1);
return n;
}
/*
* Get the value of a table's element of type string.
*/
const char *
get_table_string(const char *key)
{
const char *s;
lua_pushstring(lua, key);
lua_gettable(lua, -2);
s = lua_tostring(lua, -1);
lua_pop(lua, 1);
return s;
}
/*
* Set a table's element value to nil.
*/
int
set_table_nil(const char *key)
{
lua_pushstring(lua, key);
lua_pushnil(lua);
lua_settable(lua, -3);
return 0;
}
/*
* Set a table's element value to the specified boolean.
*/
int
set_table_boolean(const char *key, int value)
{
lua_pushstring(lua, key);
lua_pushboolean(lua, value);
lua_settable(lua, -3);
return 0;
}
/*
* Set a table's element value to the specified number.
*/
int
set_table_number(const char *key, lua_Number value)
{
lua_pushstring(lua, key);
lua_pushnumber(lua, value);
lua_settable(lua, -3);
return 0;
}
/*
* Set a table's element value to the specified string.
*/
int
set_table_string(const char *key, const char *value)
{
lua_pushstring(lua, key);
lua_pushstring(lua, value);
lua_settable(lua, -3);
return 0;
}

1243
mailbox.lua Normal file

File diff suppressed because it is too large Load Diff

92
memory.c Normal file
View File

@ -0,0 +1,92 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include "imapfilter.h"
/*
* A malloc() that checks the results and dies in case of error.
*/
void *
xmalloc(size_t size)
{
void *ptr;
ptr = (void *)malloc(size);
if (ptr == NULL)
fatal(ERROR_MEMALLOC,
"allocating memory; %s\n", strerror(errno));
return ptr;
}
/*
* A realloc() that checks the results and dies in case of error.
*/
void *
xrealloc(void *ptr, size_t size)
{
ptr = (void *)realloc(ptr, size);
if (ptr == NULL)
fatal(ERROR_MEMALLOC,
"allocating memory; %s\n", strerror(errno));
return ptr;
}
/*
* A free() that dies if fed with NULL pointer.
*/
void
xfree(void *ptr)
{
if (ptr == NULL)
fatal(ERROR_MEMALLOC,
"NULL pointer given as argument\n");
free(ptr);
}
/*
* A strdup() that checks the results and dies in case of error.
*/
char *
xstrdup(const char *str)
{
char *dup;
dup = strdup(str);
if (dup == NULL)
fatal(ERROR_MEMALLOC, "allocating memory; %s\n",
strerror(errno));
return dup;
}
/*
* A strndup() implementation that also checks the results and dies in case of
* error.
*/
char *
xstrndup(const char *str, size_t len)
{
char *dup;
dup = (char *)xmalloc((len + 1) * sizeof(char));
memcpy(dup, str, len);
dup[len] = '\0';
return dup;
}

93
message.lua Normal file
View File

@ -0,0 +1,93 @@
-- The Message class that represents messages inside a mailbox.
Message = {}
Message._mt = {}
setmetatable(Message, Message._mt)
Message._mt.__call = function (self, account, mailbox, uid)
local object = {}
object._type = 'message'
for key, value in pairs(Message) do
if (type(value) == 'function') then
object[key] = value
end
end
object._mt = {}
setmetatable(object, object._mt)
object._account = account
object._mailbox = mailbox
object._uid = uid
object._structure = nil
object._header = nil
object._body = nil
object._fields = {}
object._parts = {}
object._size = nil
object._date = nil
return object
end
function Message.fetch_structure(self)
local r = self._mailbox._fetch_structure(self._mailbox, { self._uid })
return r[self._uid]
end
function Message.fetch_header(self)
local r = self._mailbox._fetch_header(self._mailbox, { self._uid })
return r[self._uid]
end
function Message.fetch_body(self)
local r = self._mailbox._fetch_body(self._mailbox, { self._uid })
return r[self._uid]
end
function Message.fetch_message(self)
local r = self._mailbox._fetch_message(self._mailbox, { self._uid })
return r[self._uid]
end
function Message.fetch_field(self, field)
local r = self._mailbox._fetch_fields(self._mailbox, { field },
{ self._uid })
return r[self._uid]
end
function Message.fetch_fields(self, fields)
local r = self._mailbox._fetch_fields(self._mailbox, fields, { self._uid })
return r[self._uid]
end
function Message.fetch_part(self, part)
local r = self._mailbox._fetch_parts(self._mailbox, { part }, self._uid)
return r[part]
end
function Message.fetch_size(self)
local r = self._mailbox._fetch_size(self._mailbox, { self._uid })
return r[self._uid]
end
function Message.fetch_date(self)
local r = self._mailbox._fetch_date(self._mailbox, { self._uid })
return r[self._uid]
end
function Message.fetch_flags(self)
local r = self._mailbox._fetch_flags(self._mailbox, { self._uid })
return r[self._uid]
end
Message._mt.__index = function () end
Message._mt.__newindex = function () end

74
misc.c Normal file
View File

@ -0,0 +1,74 @@
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include "imapfilter.h"
/*
* An implementation of strstr() with case-insensitivity.
*/
const char *
xstrcasestr(const char *haystack, const char *needle)
{
const char *h, *n, *c;
size_t hl, nl;
c = haystack;
n = needle;
hl = strlen(haystack);
nl = strlen(needle);
while (hl >= nl) {
while (tolower((int)(*c)) != tolower((int)(*needle))) {
c++;
hl--;
if (hl < nl)
return NULL;
}
h = c;
n = needle;
while (tolower((int)(*h)) == tolower((int)(*n))) {
h++;
n++;
if (*n == '\0')
return c;
}
c++;
hl--;
}
return NULL;
}
/*
* Copies at most size characters from the string pointed by src to the array
* pointed by dest, always NULL terminating (unless size == 0). Returns
* pointer to dest.
*/
char *
xstrncpy(char *dst, const char *src, size_t len)
{
char *d;
const char *s;
size_t n;
d = dst;
s = src;
n = len;
while (n != 0) {
if ((*d++ = *s++) == '\0')
break;
n--;
}
if (n == 0 && len != 0)
*d = '\0';
return dst;
}

78
namespace.c Normal file
View File

@ -0,0 +1,78 @@
#include <stdio.h>
#include <string.h>
#include "imapfilter.h"
#include "buffer.h"
buffer nbuf; /* Namespace buffer. */
/*
* Convert the names of personal mailboxes, using the namespace specified
* by the mail server, from internal to mail server format.
*/
const char *
apply_namespace(const char *mbox, char *prefix, char delim)
{
int n;
char *c;
if ((prefix == NULL && delim == '\0') ||
(prefix == NULL && delim == '/') ||
!strcasecmp(mbox, "INBOX"))
return mbox;
buffer_reset(&nbuf);
n = snprintf(nbuf.data, nbuf.size + 1, "%s%s", (prefix ? prefix : ""),
mbox);
if (n > (int)nbuf.size) {
buffer_check(&nbuf, n);
snprintf(nbuf.data, nbuf.size + 1, "%s%s",
(prefix ? prefix : ""), mbox);
}
c = nbuf.data;
while ((c = strchr(c, '/')))
*(c++) = delim;
debug("namespace: '%s' -> '%s'\n", mbox, nbuf.data);
return nbuf.data;
}
/*
* Convert the names of personal mailboxes, using the namespace specified by
* the mail server, from mail server format to internal format.
*/
const char *
reverse_namespace(const char *mbox, char *prefix, char delim)
{
int n, o;
char *c;
if ((prefix == NULL && delim == '\0') ||
(prefix == NULL && delim == '/') ||
!strcasecmp(mbox, "INBOX"))
return mbox;
buffer_reset(&nbuf);
o = strlen(prefix ? prefix : "");
if (strncasecmp(mbox, (prefix ? prefix : ""), o))
o = 0;
n = snprintf(nbuf.data, nbuf.size + 1, "%s", mbox + o);
if (n > (int)nbuf.size) {
buffer_check(&nbuf, n);
snprintf(nbuf.data, nbuf.size + 1, "%s", mbox + o);
}
c = nbuf.data;
while ((c = strchr(c, delim)))
*(c++) = '/';
debug("namespace: '%s' <- '%s'\n", mbox, nbuf.data);
return nbuf.data;
}

5
options.lua Normal file
View File

@ -0,0 +1,5 @@
-- Options related to the interface implementation.
options.cache = true
options.close = false
options.info = true

45
pathnames.h Normal file
View File

@ -0,0 +1,45 @@
#ifndef PATHNAMES_H
#define PATHNAMES_H
/* Program's home directory. */
#define PATHNAME_HOME ".imapfilter"
/* Program's configuration file. */
#define PATHNAME_CONFIG PATHNAME_HOME "/config.lua"
/* Lua imapfilter set functions file. */
#define PATHNAME_COMMON MAKEFILE_SHAREDIR "/common.lua"
/* Lua imapfilter set functions file. */
#define PATHNAME_SET MAKEFILE_SHAREDIR "/set.lua"
/* Lua imapfilter account functions file. */
#define PATHNAME_ACCOUNT MAKEFILE_SHAREDIR "/account.lua"
/* Lua imapfilter mailbox functions file. */
#define PATHNAME_MAILBOX MAKEFILE_SHAREDIR "/mailbox.lua"
/* Lua imapfilter message functions file. */
#define PATHNAME_MESSAGE MAKEFILE_SHAREDIR "/message.lua"
/* Lua imapfilter message functions file. */
#define PATHNAME_OPTIONS MAKEFILE_SHAREDIR "/options.lua"
/* Lua imapfilter regex functions file. */
#define PATHNAME_REGEX MAKEFILE_SHAREDIR "/regex.lua"
/* Lua imapfilter auxiliary functions file. */
#define PATHNAME_AUXILIARY MAKEFILE_SHAREDIR "/auxiliary.lua"
/* Lua imapfilter old interface functions file. */
#define PATHNAME_DEPRECATED MAKEFILE_SHAREDIR "/deprecated.lua"
/* SSL/TLS certificates file. */
#define PATHNAME_CERTS PATHNAME_HOME "/certificates"
/* Debug temporary file template. */
#define PATHNAME_DEBUG PATHNAME_HOME "/debug.XXXXXX"
#endif /* PATHNAMES_H */

268
pcre.c Normal file
View File

@ -0,0 +1,268 @@
#include <stdio.h>
#include <lua.h>
#include <lauxlib.h>
#include <lualib.h>
#include <pcre.h>
#include "imapfilter.h"
static int ifre_flags(lua_State *lua);
static int ifre_compile(lua_State *lua);
static int ifre_exec(lua_State *lua);
static int ifre_free(lua_State *lua);
/* Lua imapfilter library of PCRE related functions. */
static const luaL_reg ifrelib[] = {
{ "flags", ifre_flags },
{ "compile", ifre_compile },
{ "exec", ifre_exec },
{ "free", ifre_free },
{ NULL, NULL }
};
/*
* Return PCRE available compile and exec flags.
*/
static int
ifre_flags(lua_State *lua)
{
if (lua_gettop(lua) != 0)
luaL_error(lua, "wrong number of arguments");
lua_newtable(lua);
#ifdef PCRE_CASELESS
set_table_number("CASELESS", PCRE_CASELESS);
#endif
#ifdef PCRE_MULTILINE
set_table_number("MULTILINE", PCRE_MULTILINE);
#endif
#ifdef PCRE_DOTALL
set_table_number("DOTALL", PCRE_DOTALL);
#endif
#ifdef PCRE_EXTENDED
set_table_number("EXTENDED", PCRE_EXTENDED);
#endif
#ifdef PCRE_ANCHORED
set_table_number("ANCHORED", PCRE_ANCHORED);
#endif
#ifdef PCRE_DOLLAR_ENDONLY
set_table_number("DOLLAR_ENDONLY", PCRE_DOLLAR_ENDONLY);
#endif
#ifdef PCRE_EXTRA
set_table_number("EXTRA", PCRE_EXTRA);
#endif
#ifdef PCRE_NOTBOL
set_table_number("NOTBOL", PCRE_NOTBOL);
#endif
#ifdef PCRE_NOTEOL
set_table_number("NOTEOL", PCRE_NOTEOL);
#endif
#ifdef PCRE_UNGREEDY
set_table_number("UNGREEDY", PCRE_UNGREEDY);
#endif
#ifdef PCRE_NOTEMPTY
set_table_number("NOTEMPTY", PCRE_NOTEMPTY);
#endif
#ifdef PCRE_UTF8
set_table_number("UTF8", PCRE_UTF8);
#endif
#ifdef PCRE_NO_AUTO_CAPTURE
set_table_number("NO_AUTO_CAPTURE", PCRE_NO_AUTO_CAPTURE);
#endif
#ifdef PCRE_NO_UTF8_CHECK
set_table_number("NO_UTF8_CHECK", PCRE_NO_UTF8_CHECK);
#endif
#ifdef PCRE_FIRSTLINE
set_table_number("FIRSTLINE", PCRE_FIRSTLINE);
#endif
#ifdef PCRE_AUTO_CALLOUT
set_table_number("AUTO_CALLOUT", PCRE_AUTO_CALLOUT);
#endif
#ifdef PCRE_PARTIAL
set_table_number("PARTIAL", PCRE_PARTIAL);
#endif
#ifdef PCRE_DFA_SHORTEST
set_table_number("DFA_SHORTEST", PCRE_DFA_SHORTEST);
#endif
#ifdef PCRE_DFA_RESTART
set_table_number("DFA_RESTART", PCRE_DFA_RESTART);
#endif
#ifdef PCRE_FIRSTLINE
set_table_number("FIRSTLINE", PCRE_FIRSTLINE);
#endif
#ifdef PCRE_DUPNAMES
set_table_number("DUPNAMES", PCRE_DUPNAMES);
#endif
#ifdef PCRE_NEWLINE_CR
set_table_number("NEWLINE_CR)", PCRE_NEWLINE_CR);
#endif
#ifdef PCRE_NEWLINE_LF
set_table_number("NEWLINE_LF", PCRE_NEWLINE_LF);
#endif
#ifdef PCRE_NEWLINE_CRLF
set_table_number("NEWLINE_CRLF", PCRE_NEWLINE_CRLF);
#endif
#ifdef PCRE_NEWLINE_ANY
set_table_number("NEWLINE_ANY", PCRE_NEWLINE_ANY);
#endif
#ifdef PCRE_NEWLINE_ANYCRLF
set_table_number("NEWLINE_ANYCRLF", PCRE_NEWLINE_ANYCRLF);
#endif
#ifdef PCRE_BSR_ANYCRLF
set_table_number("PCRE_BSR_ANYCRLF", PCRE_BSR_ANYCRLF);
#endif
#ifdef PCRE_BSR_UNICODE
set_table_number("PCRE_BSR_UNICODE", PCRE_BSR_UNICODE);
#endif
#ifdef PCRE_JAVASCRIPT_COMPAT
set_table_number("PCRE_JAVASCRIPT_COMPAT", PCRE_JAVASCRIPT_COMPAT);
#endif
#ifdef PCRE_NO_START_OPTIMIZE
set_table_number("PCRE_NO_START_OPTIMIZE", PCRE_NO_START_OPTIMIZE);
#endif
#ifdef PCRE_NO_START_OPTIMISE
set_table_number("PCRE_NO_START_OPTIMISE", PCRE_NO_START_OPTIMISE);
#endif
#ifdef PCRE_PARTIAL_HARD
set_table_number("PCRE_PARTIAL_HARD", PCRE_PARTIAL_HARD);
#endif
#ifdef PCRE_NOTEMPTY_ATSTART
set_table_number("PCRE_NOTEMPTY_ATSTART", PCRE_NOTEMPTY_ATSTART);
#endif
#ifdef PCRE_UCP
set_table_number("PCRE_UCP", PCRE_UCP);
#endif
return 1;
}
/*
* Lua implementation of the PCRE compile function.
*/
static int
ifre_compile(lua_State *lua)
{
pcre **re;
const char *error;
int erroffset;
if (lua_gettop(lua) != 2)
luaL_error(lua, "wrong number of arguments");
luaL_checktype(lua, 1, LUA_TSTRING);
luaL_checktype(lua, 2, LUA_TNUMBER);
re = (pcre **)(lua_newuserdata(lua, sizeof(pcre *)));
*re = pcre_compile(lua_tostring(lua, 1), lua_tonumber(lua, 2), &error,
&erroffset, NULL);
if (*re == NULL) {
fprintf(stderr, "RE failed at offset %d: %s\n", erroffset,
error);
lua_pop(lua, 1);
}
lua_remove(lua, 1);
lua_remove(lua, 1);
lua_pushboolean(lua, (*re != NULL));
lua_insert(lua, 1);
return (*re != NULL ? 2 : 1);
}
/*
* Lua implementation of the PCRE exec function.
*/
static int
ifre_exec(lua_State *lua)
{
int i, n;
pcre *re;
int ovecsize;
int *ovector;
if (lua_gettop(lua) != 3)
luaL_error(lua, "wrong number of arguments");
luaL_checktype(lua, 1, LUA_TUSERDATA);
luaL_checktype(lua, 2, LUA_TSTRING);
luaL_checktype(lua, 3, LUA_TNUMBER);
re = *(pcre **)(lua_touserdata(lua, 1));
pcre_fullinfo(re, NULL, PCRE_INFO_CAPTURECOUNT, &ovecsize);
ovector = (int *)xmalloc(sizeof(int) * (ovecsize + 1) * 3);
for (i = 0; i <= ovecsize; i++)
ovector[2 * i] = ovector[2 * i + 1] = -1;
n = pcre_exec(re, NULL, lua_tostring(lua, 2), lua_strlen(lua, 2), 0,
lua_tonumber(lua, 3), ovector, (ovecsize + 1) * 3);
if (n > 0)
for (i = 1; i < n; i++)
if (ovector[2 * i] != -1 && ovector[2 * i + 1] != -1)
lua_pushlstring(lua, lua_tostring(lua, 2) +
ovector[2 * i], ovector[2 * i + 1] -
ovector[2 * i]);
else
lua_pushnil(lua);
xfree(ovector);
lua_remove(lua, 1);
lua_remove(lua, 1);
lua_remove(lua, 1);
lua_pushboolean(lua, (n > 0));
lua_insert(lua, 1);
return (n > 0 ? n : 1);
}
/*
* Lua implementation of a PCRE free function.
*/
static int
ifre_free(lua_State *lua)
{
pcre *re;
if (lua_gettop(lua) != 1)
luaL_error(lua, "wrong number of arguments");
luaL_checktype(lua, 1, LUA_TUSERDATA);
re = *(pcre **)(lua_touserdata(lua, 1));
xfree(re);
lua_remove(lua, 1);
return 0;
}
/*
* Open imapfilter library of PCRE related functions.
*/
LUALIB_API int
luaopen_ifre(lua_State *lua)
{
luaL_register(lua, "ifre", ifrelib);
return 1;
}

53
regex.lua Normal file
View File

@ -0,0 +1,53 @@
-- A simple wrapper for PCRE that uses a cache for compiled expressions.
_regex_cache = {}
_regex_cache.mt = {}
setmetatable(_regex_cache, _regex_cache.mt)
_regex_cache.mt.__index = function (self, key)
local _, _, pattern, cflags = string.find(key, '^(.*)%z(.*)$')
local _, compiled = ifre.compile(pattern, tonumber(cflags))
self[key] = compiled
return compiled
end
function regex_search(pattern, subject, cflags, eflags)
_check_required(pattern, 'string')
_check_required(subject, 'string')
_check_optional(cflags, 'table')
_check_optional(eflags, 'table')
if (cflags == nil) then
cflags = {}
end
if (eflags == nil) then
eflags = {}
end
local flags = ifre.flags()
cf = 0
for _, f in ipairs(cflags) do
cf = cf + flags[f]
end
ef = 0
for _, f in pairs(eflags) do
ef = cf + flags[f]
end
local compiled = _regex_cache[pattern .. '\0' .. cf]
if (compiled == nil) then
return nil
end
return ifre.exec(compiled, subject, ef)
end

42
regexp.c Normal file
View File

@ -0,0 +1,42 @@
#include <stdio.h>
#include <sys/types.h>
#include <regex.h>
#include "imapfilter.h"
#include "regexp.h"
/*
* Compile all the patterns and allocate the necessary space for the substring
* matching.
*/
void
regexp_compile(regexp *reg)
{
regexp *re;
for (re = reg; re->pattern != NULL; re++) {
re->preg = (regex_t *)xmalloc(sizeof(regex_t));
regcomp(re->preg, re->pattern, REG_EXTENDED | REG_ICASE);
re->nmatch = re->preg->re_nsub + 1;
re->pmatch = (regmatch_t *)xmalloc(sizeof(regmatch_t) *
re->nmatch);
}
}
/*
* Free the compiled regular expressions and the space allocated for the
* substring matching.
*/
void
regexp_free(regexp *reg)
{
regexp *re;
for (re = reg; re->pattern != NULL; re++) {
regfree(re->preg);
xfree(re->preg);
xfree(re->pmatch);
}
}

24
regexp.h Normal file
View File

@ -0,0 +1,24 @@
#ifndef REGEXP_H
#define REGEXP_H
#include <stdio.h>
#include <sys/types.h>
#include <regex.h>
/* Regular expression convenience structure. */
typedef struct regexp {
const char *pattern; /* Regular expression pattern string. */
regex_t *preg; /* Compiled regular expression. */
size_t nmatch; /* Number of subexpressions in pattern. */
regmatch_t *pmatch; /* Structure for substrings that matched. */
} regexp;
/* regexp.c */
void regexp_compile(regexp *reg);
void regexp_free(regexp *reg);
#endif /* REGEXP_H */

918
request.c Normal file
View File

@ -0,0 +1,918 @@
#include <stdio.h>
#include <string.h>
#include <strings.h>
#include "imapfilter.h"
#include "session.h"
#include "buffer.h"
extern options opts;
int create_mailbox(session *ssn, const char *mbox);
/*
* Reset any inactivity autologout timer on the server.
*/
int
request_noop(const char *server, const char *port, const char *user)
{
int r;
session *s;
if (!(s = session_find(server, port, user)))
return -1;
if ((r = response_generic(s, imap_noop(s))) == -1)
goto fail;
return r;
fail:
close_connection(s);
session_destroy(s);
return -1;
}
/*
* Connect to the server, login to the IMAP server, get it's capabilities, get
* the namespace of the mailboxes.
*/
int
request_login(const char *server, const char *port, const char *ssl,
const char *user, const char *pass)
{
int r = -1, rg = -1;
session *s;
if ((s = session_find(server, port, user)))
return STATUS_RESPONSE_NONE;
s = session_new();
s->server = xstrdup(server);
s->port = xstrdup(port);
s->username = xstrdup(user);
if (ssl && strncasecmp(ssl, "tls1", 4) &&
strncasecmp(ssl, "ssl3", 4) && strncasecmp(ssl, "ssl2", 4))
ssl = NULL;
if (open_connection(s, server, port, ssl) == -1)
goto fail;
if ((rg = response_greeting(s)) == -1)
goto fail;
if (opts.debug > 0)
if (response_generic(s, imap_noop(s)) == -1)
goto fail;
if (response_capability(s, imap_capability(s)) == -1)
goto fail;
#ifndef NO_SSLTLS
if (!ssl && s->capabilities & CAPABILITY_STARTTLS &&
get_option_boolean("starttls"))
switch (response_generic(s, imap_starttls(s))) {
case STATUS_RESPONSE_OK:
if (open_secure_connection(s, server, port, "tls1")
== -1)
goto fail;
if (response_capability(s, imap_capability(s)) == -1)
goto fail;
break;
case -1:
goto fail;
break;
}
#endif
if (rg != STATUS_RESPONSE_PREAUTH) {
#ifndef NO_CRAMMD5
if (s->capabilities & CAPABILITY_CRAMMD5 &&
get_option_boolean("crammd5")) {
if ((r = auth_cram_md5(s, user, pass)) == -1)
goto fail;
}
#endif
if (r != STATUS_RESPONSE_OK &&
(r = response_generic(s, imap_login(s, user, pass))) == -1)
goto fail;
if (r == STATUS_RESPONSE_NO) {
error("username %s or password rejected at %s\n",
user, server);
goto fail;
}
} else {
r = STATUS_RESPONSE_PREAUTH;
}
if (s->capabilities & CAPABILITY_NAMESPACE &&
get_option_boolean("namespace")) {
if (response_namespace(s, imap_namespace(s)) == -1)
goto fail;
}
return r;
fail:
close_connection(s);
session_destroy(s);
return -1;
}
/*
* Logout from the IMAP server and disconnect from the server.
*/
int
request_logout(const char *server, const char *port, const char *user)
{
int r;
session *s;
if (!(s = session_find(server, port, user)))
return -1;
r = response_generic(s, imap_logout(s));
close_connection(s);
session_destroy(s);
return r;
}
/*
* Get mailbox's status.
*/
int
request_status(const char *server, const char *port, const char *user,
const char *mbox, unsigned int *exists, unsigned int *recent,
unsigned int *unseen, unsigned int *uidnext)
{
int t, r;
session *s;
const char *m;
if (!(s = session_find(server, port, user)))
return -1;
m = apply_namespace(mbox, s->ns.prefix, s->ns.delim);
if (s->protocol == PROTOCOL_IMAP4REV1) {
t = imap_status(s, m, "MESSAGES RECENT UNSEEN UIDNEXT");
if ((r = response_status(s, t, exists, recent, unseen, uidnext)) == -1)
goto fail;
} else {
t = imap_examine(s, m);
if ((r = response_examine(s, t, exists, recent)) == -1)
goto fail;
}
return r;
fail:
close_connection(s);
session_destroy(s);
return -1;
}
/*
* Open mailbox in read-write mode.
*/
int
request_select(const char *server, const char *port, const char *user,
const char *mbox)
{
int r;
session *s;
const char *m;
if (!(s = session_find(server, port, user)))
return -1;
m = apply_namespace(mbox, s->ns.prefix, s->ns.delim);
if ((r = response_select(s, imap_select(s, m))) == -1)
goto fail;
return r;
fail:
close_connection(s);
session_destroy(s);
return -1;
}
/*
* Close examined/selected mailbox.
*/
int
request_close(const char *server, const char *port, const char *user)
{
int r;
session *s;
if (!(s = session_find(server, port, user)))
return -1;
if ((r = response_generic(s, imap_close(s))) == -1)
goto fail;
return r;
fail:
close_connection(s);
session_destroy(s);
return -1;
}
/*
* Remove all messages marked for deletion from selected mailbox.
*/
int
request_expunge(const char *server, const char *port, const char *user)
{
int r;
session *s;
if (!(s = session_find(server, port, user)))
return -1;
if ((r = response_generic(s, imap_expunge(s))) == -1)
goto fail;
return r;
fail:
close_connection(s);
session_destroy(s);
return -1;
}
/*
* List available mailboxes.
*/
int
request_list(const char *server, const char *port, const char *user,
const char *refer, const char *name, char **mboxs, char **folders)
{
int t, r;
session *s;
const char *n;
if (!(s = session_find(server, port, user)))
return -1;
n = apply_namespace(name, s->ns.prefix, s->ns.delim);
t = imap_list(s, refer, n);
if ((r = response_list(s, t, mboxs, folders)) == -1)
goto fail;
return r;
fail:
close_connection(s);
session_destroy(s);
return -1;
}
/*
* List subscribed mailboxes.
*/
int
request_lsub(const char *server, const char *port, const char *user,
const char *refer, const char *name, char **mboxs, char **folders)
{
int t, r;
session *s;
const char *n;
if (!(s = session_find(server, port, user)))
return -1;
n = apply_namespace(name, s->ns.prefix, s->ns.delim);
t = imap_lsub(s, refer, n);
if ((r = response_list(s, t, mboxs, folders)) == -1)
goto fail;
return r;
fail:
close_connection(s);
session_destroy(s);
return -1;
}
/*
* Search selected mailbox according to the supplied search criteria.
*/
int
request_search(const char *server, const char *port, const char *user,
const char *criteria, const char *charset, char **mesgs)
{
int t, r;
session *s;
if (!(s = session_find(server, port, user)))
return -1;
t = imap_search(s, charset, criteria);
if ((r = response_search(s, t, mesgs)) == -1)
goto fail;
return r;
fail:
close_connection(s);
session_destroy(s);
return -1;
}
/*
* Fetch the FLAGS, INTERNALDATE and RFC822.SIZE of the messages.
*/
int
request_fetchfast(const char *server, const char *port, const char *user,
const char *mesg, char **flags, char **date, char **size)
{
int t, r;
session *s;
if (!(s = session_find(server, port, user)))
return -1;
t = imap_fetch(s, mesg, "FAST");
if ((r = response_fetchfast(s, t, flags, date, size)) == -1)
goto fail;
return r;
fail:
close_connection(s);
session_destroy(s);
return -1;
}
/*
* Fetch the FLAGS of the messages.
*/
int
request_fetchflags(const char *server, const char *port, const char *user,
const char *mesg, char **flags)
{
int t, r;
session *s;
if (!(s = session_find(server, port, user)))
return -1;
t = imap_fetch(s, mesg, "FLAGS");
if ((r = response_fetchflags(s, t, flags)) == -1)
goto fail;
return r;
fail:
close_connection(s);
session_destroy(s);
return -1;
}
/*
* Fetch the INTERNALDATE of the messages.
*/
int
request_fetchdate(const char *server, const char *port, const char *user,
const char *mesg, char **date)
{
int t, r;
session *s;
if (!(s = session_find(server, port, user)))
return -1;
t = imap_fetch(s, mesg, "INTERNALDATE");
if ((r = response_fetchdate(s, t, date)) == -1)
goto fail;
return r;
fail:
close_connection(s);
session_destroy(s);
return -1;
}
/*
* Fetch the RFC822.SIZE of the messages.
*/
int
request_fetchsize(const char *server, const char *port, const char *user,
const char *mesg, char **size)
{
int t, r;
session *s;
if (!(s = session_find(server, port, user)))
return -1;
t = imap_fetch(s, mesg, "RFC822.SIZE");
if ((r = response_fetchsize(s, t, size)) == -1)
goto fail;
return r;
fail:
close_connection(s);
session_destroy(s);
return -1;
}
/*
* Fetch the body structure, ie. BODYSTRUCTURE, of the messages.
*/
int
request_fetchstructure(const char *server, const char *port, const char *user,
const char *mesg, char **structure)
{
int t, r;
session *s;
if (!(s = session_find(server, port, user)))
return -1;
t = imap_fetch(s, mesg, "BODYSTRUCTURE");
if ((r = response_fetchstructure(s, t, structure)) == -1)
goto fail;
return r;
fail:
close_connection(s);
session_destroy(s);
return -1;
}
/*
* Fetch the header, ie. BODY[HEADER], of the messages.
*/
int
request_fetchheader(const char *server, const char *port, const char *user,
const char *mesg, char **header, size_t *len)
{
int t, r;
session *s;
if (!(s = session_find(server, port, user)))
return -1;
t = imap_fetch(s, mesg, "BODY.PEEK[HEADER]");
if ((r = response_fetchbody(s, t, header, len)) == -1)
goto fail;
return r;
fail:
close_connection(s);
session_destroy(s);
return -1;
}
/*
* Fetch the text, ie. BODY[TEXT], of the messages.
*/
int
request_fetchtext(const char *server, const char *port, const char *user,
const char *mesg, char **text, size_t *len)
{
int t, r;
session *s;
if (!(s = session_find(server, port, user)))
return -1;
t = imap_fetch(s, mesg, "BODY.PEEK[TEXT]");
if ((r = response_fetchbody(s, t, text, len)) == -1)
goto fail;
return r;
fail:
close_connection(s);
session_destroy(s);
return -1;
}
/*
* Fetch the specified header fields, ie. BODY[HEADER.FIELDS (<fields>)], of
* the messages.
*/
int
request_fetchfields(const char *server, const char *port, const char *user,
const char *mesg, const char *headerfields, char **fields, size_t *len)
{
int t, r, n;
session *s;
char *f;
n = strlen("BODY.PEEK[HEADER.FIELDS ()]") + strlen(headerfields) + 1;
f = (char *)xmalloc(n * sizeof(char));
snprintf(f, n, "%s%s%s", "BODY.PEEK[HEADER.FIELDS (", headerfields, ")]");
if (!(s = session_find(server, port, user)))
return -1;
t = imap_fetch(s, mesg, f);
if ((r = response_fetchbody(s, t, fields, len)) == -1)
goto fail;
xfree(f);
return r;
fail:
close_connection(s);
session_destroy(s);
return -1;
}
/*
* Fetch the specified message part, ie. BODY[<part>], of the
* messages.
*/
int
request_fetchpart(const char *server, const char *port, const char *user,
const char *mesg, const char *part, char **bodypart, size_t *len)
{
int t, r, n;
session *s;
char *f;
n = strlen("BODY.PEEK[]") + strlen(part) + 1;
f = (char *)xmalloc(n * sizeof(char));
snprintf(f, n, "%s%s%s", "BODY.PEEK[", part, "]");
if (!(s = session_find(server, port, user)))
return -1;
t = imap_fetch(s, mesg, f);
if ((r = response_fetchbody(s, t, bodypart, len)) == -1)
goto fail;
xfree(f);
return r;
fail:
close_connection(s);
session_destroy(s);
return -1;
}
/*
* Add, remove or replace the specified flags of the messages.
*/
int
request_store(const char *server, const char *port, const char *user,
const char *mesg, const char *mode, const char *flags)
{
int t, r;
session *s;
if (!(s = session_find(server, port, user)))
return -1;
t = imap_store(s, mesg, mode, flags);
if ((r = response_generic(s, t)) == -1)
goto fail;
if (xstrcasestr(flags, "\\Deleted") && get_option_boolean("expunge"))
if (response_generic(s, imap_expunge(s)) == -1)
goto fail;
return r;
fail:
close_connection(s);
session_destroy(s);
return -1;
}
/*
* Copy the specified messages to another mailbox.
*/
int
request_copy(const char *server, const char *port, const char *user,
const char *mesg, const char *mbox)
{
int t, r;
session *s;
const char *m;
if (!(s = session_find(server, port, user)))
return -1;
m = apply_namespace(mbox, s->ns.prefix, s->ns.delim);
do {
t = imap_copy(s, mesg, m);
switch (r = response_generic(s, t)) {
case STATUS_RESPONSE_TRYCREATE:
if (create_mailbox(s, mbox) == -1)
goto fail;
break;
case -1:
goto fail;
break;
}
} while (r == STATUS_RESPONSE_TRYCREATE);
return r;
fail:
close_connection(s);
session_destroy(s);
return -1;
}
/*
* Append supplied message to the specified mailbox.
*/
int
request_append(const char *server, const char *port, const char *user,
const char *mbox, const char *mesg, size_t mesglen, const char *flags,
const char *date)
{
int t, r;
session *s;
const char *m;
if (!(s = session_find(server, port, user)))
return -1;
m = apply_namespace(mbox, s->ns.prefix, s->ns.delim);
do {
if ((t = imap_append(s, m, flags, date, mesglen)) == -1)
goto fail;
if ((r = response_continuation(s)) == -1)
goto fail;
switch (r) {
case STATUS_RESPONSE_CONTINUE:
if (imap_continuation(s, mesg, mesglen) == -1)
goto fail;
if ((r = response_generic(s, t)) == -1)
goto fail;
break;
case STATUS_RESPONSE_TRYCREATE:
if (create_mailbox(s, mbox) == -1)
goto fail;
break;
case -1:
goto fail;
break;
}
} while (r == STATUS_RESPONSE_TRYCREATE);
return r;
fail:
close_connection(s);
session_destroy(s);
return -1;
}
/*
* Create the specified mailbox.
*/
int
request_create(const char *server, const char *port, const char *user,
const char *mbox)
{
int r;
session *s;
const char *m;
if (!(s = session_find(server, port, user)))
return -1;
m = apply_namespace(mbox, s->ns.prefix, s->ns.delim);
if ((r = response_generic(s, imap_create(s, m))) == -1)
goto fail;
return r;
fail:
close_connection(s);
session_destroy(s);
return -1;
}
/*
* Delete the specified mailbox.
*/
int
request_delete(const char *server, const char *port, const char *user,
const char *mbox)
{
int r;
session *s;
const char *m;
if (!(s = session_find(server, port, user)))
return -1;
m = apply_namespace(mbox, s->ns.prefix, s->ns.delim);
if ((r = response_generic(s, imap_delete(s, m))) == -1)
goto fail;
return r;
fail:
close_connection(s);
session_destroy(s);
return -1;
}
/*
* Rename a mailbox.
*/
int
request_rename(const char *server, const char *port, const char *user,
const char *oldmbox, const char *newmbox)
{
int r;
session *s;
char *o, *n;
if (!(s = session_find(server, port, user)))
return -1;
o = xstrdup(apply_namespace(oldmbox, s->ns.prefix, s->ns.delim));
n = xstrdup(apply_namespace(newmbox, s->ns.prefix, s->ns.delim));
r = response_generic(s, imap_rename(s, o, n));
xfree(o);
xfree(n);
if (r == -1)
goto fail;
return r;
fail:
close_connection(s);
session_destroy(s);
return -1;
}
/*
* Subscribe the specified mailbox.
*/
int
request_subscribe(const char *server, const char *port, const char *user,
const char *mbox)
{
int r;
session *s;
const char *m;
if (!(s = session_find(server, port, user)))
return -1;
m = apply_namespace(mbox, s->ns.prefix, s->ns.delim);
if ((r = response_generic(s, imap_subscribe(s, m))) == -1)
goto fail;
return r;
fail:
close_connection(s);
session_destroy(s);
return -1;
}
/*
* Unsubscribe the specified mailbox.
*/
int
request_unsubscribe(const char *server, const char *port, const char *user,
const char *mbox)
{
int r;
session *s;
const char *m;
if (!(s = session_find(server, port, user)))
return -1;
m = apply_namespace(mbox, s->ns.prefix, s->ns.delim);
if ((r = response_generic(s, imap_unsubscribe(s, m))) == -1)
goto fail;
return r;
fail:
close_connection(s);
session_destroy(s);
return -1;
}
int
request_idle(const char *server, const char *port, const char *user)
{
int t, rg, ri;
session *s;
if (!(s = session_find(server, port, user)))
return -1;
if (!(s->capabilities & CAPABILITY_IDLE))
return -1;
do {
ri = 0;
t = imap_idle(s);
if ((rg = response_continuation(s)) == -1)
goto fail;
if (rg == STATUS_RESPONSE_CONTINUE) {
if ((ri = response_idle(s, t)) == -1)
goto fail;
imap_done(s);
if ((rg = response_generic(s, t)) == -1)
goto fail;
}
} while (ri == STATUS_RESPONSE_TIMEOUT);
return rg;
fail:
close_connection(s);
session_destroy(s);
return -1;
}
/*
* Auxiliary function to create a mailbox.
*/
int
create_mailbox(session *ssn, const char *mbox)
{
int r;
const char *m;
m = apply_namespace(mbox, ssn->ns.prefix, ssn->ns.delim);
if ((r = response_generic(ssn, imap_create(ssn, m))) == -1)
return -1;
if (get_option_boolean("subscribe"))
if (response_generic(ssn, imap_subscribe(ssn, m)) == -1)
return -1;
return r;
}

846
response.c Normal file
View File

@ -0,0 +1,846 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <ctype.h>
#include <regex.h>
#include "imapfilter.h"
#include "session.h"
#include "buffer.h"
#include "regexp.h"
extern options opts;
buffer ibuf; /* Input buffer. */
enum { /* Server data responses to be parsed;
* regular expressions index. */
DATA_RESPONSE_TAGGED,
DATA_RESPONSE_CAPABILITY,
DATA_RESPONSE_AUTHENTICATE,
DATA_RESPONSE_NAMESPACE,
DATA_RESPONSE_STATUS,
DATA_RESPONSE_STATUS_MESSAGES,
DATA_RESPONSE_STATUS_RECENT,
DATA_RESPONSE_STATUS_UNSEEN,
DATA_RESPONSE_STATUS_UIDNEXT,
DATA_RESPONSE_EXAMINE_EXISTS,
DATA_RESPONSE_EXAMINE_RECENT,
DATA_RESPONSE_LIST,
DATA_RESPONSE_SEARCH,
DATA_RESPONSE_FETCH,
DATA_RESPONSE_FETCH_FLAGS,
DATA_RESPONSE_FETCH_DATE,
DATA_RESPONSE_FETCH_SIZE,
DATA_RESPONSE_FETCH_STRUCTURE,
DATA_RESPONSE_FETCH_BODY,
DATA_RESPONSE_IDLE,
};
regexp responses[] = { /* Server data responses to be parsed;
* regular expressions patterns. */
{ "([[:xdigit:]]{4,4}) (OK|NO|BAD) [[:print:]]*\r\n", NULL, 0, NULL },
{ "\\* CAPABILITY ([[:print:]]*)\r\n", NULL, 0, NULL },
{ "\\+ ([[:graph:]]*)\r\n", NULL, 0, NULL },
{ "\\* NAMESPACE (NIL|\\(\\(\"([[:graph:]]*)\" \"([[:print:]])\"\\)"
"[[:print:]]*\\)) (NIL|\\([[:print:]]*\\)) (NIL|\\([[:print:]]*\\))"
"\r\n", NULL, 0, NULL },
{ "\\* STATUS [[:print:]]* \\(([[:alnum:] ]*)\\) *\r\n", NULL, 0, NULL },
{ "MESSAGES ([[:digit:]]+)", NULL, 0, NULL },
{ "RECENT ([[:digit:]]+)", NULL, 0, NULL },
{ "UNSEEN ([[:digit:]]+)", NULL, 0, NULL },
{ "UIDNEXT ([[:digit:]]+)", NULL, 0, NULL },
{ "\\* ([[:digit:]]+) EXISTS\r\n", NULL, 0, NULL },
{ "\\* ([[:digit:]]+) RECENT\r\n", NULL, 0, NULL },
{ "\\* (LIST|LSUB) \\(([[:print:]]*)\\) (\"[[:print:]]\"|NIL) "
"(\"([[:print:]]+)\"|([[:print:]]+)|\\{([[:digit:]]+)\\}\r\n"
"([[:print:]]*))\r\n", NULL, 0, NULL },
{ "\\* SEARCH ?([[:digit:] ]*)\r\n", NULL, 0, NULL },
{ "\\* [[:digit:]]+ FETCH \\(([[:print:]]*)\\)\r\n", NULL, 0, NULL },
{ "FLAGS \\(([[:print:]]*)\\)", NULL, 0, NULL },
{ "INTERNALDATE \"([[:print:]]*)\"", NULL, 0, NULL },
{ "RFC822.SIZE ([[:digit:]]+)", NULL, 0, NULL },
{ "BODYSTRUCTURE (\\([[:print:]]+\\))", NULL, 0, NULL },
{ "\\* [[:digit:]]+ FETCH \\([[:print:]]*BODY\\[[[:print:]]*\\] "
"(\\{([[:digit:]]+)\\}\r\n|\"([[:print:]]*)\")", NULL, 0, NULL },
{ "\\* [[:digit:]]+ (RECENT|EXISTS)\r\n", NULL, 0, NULL },
{ NULL, NULL, 0, NULL }
};
int receive_response(session *ssn, char *buf, long timeout, int timeoutfail);
int check_tag(char *buf, session *ssn, int tag);
int check_bye(char *buf);
int check_continuation(char *buf);
int check_trycreate(char *buf);
/*
* Read data the server sent.
*/
int
receive_response(session *ssn, char *buf, long timeout, int timeoutfail)
{
ssize_t n;
if ((n = socket_read(ssn, buf, INPUT_BUF, timeout ? timeout :
(long)(get_option_number("timeout")), timeoutfail)) == -1)
return -1;
if (opts.debug > 0) {
int i;
debug("getting response (%d):\n\n", ssn->socket);
for (i = 0; i < n; i++)
debugc(buf[i]);
debug("\n");
}
return n;
}
/*
* Search for tagged response in the data that the server sent.
*/
int
check_tag(char *buf, session *ssn, int tag)
{
int r;
char t[4 + 1];
regexp *re;
r = STATUS_RESPONSE_NONE;
snprintf(t, sizeof(t), "%04X", tag);
re = &responses[DATA_RESPONSE_TAGGED];
if (!regexec(re->preg, buf, re->nmatch, re->pmatch, 0)) {
if (!strncasecmp(buf + re->pmatch[1].rm_so, t,
strlen(t))) {
if (!strncasecmp(buf + re->pmatch[2].rm_so,
"OK", strlen("OK")))
r = STATUS_RESPONSE_OK;
else if (!strncasecmp(buf + re->pmatch[2].rm_so,
"NO", strlen("NO")))
r = STATUS_RESPONSE_NO;
else if (!strncasecmp(buf + re->pmatch[2].rm_so,
"BAD", strlen("BAD")))
r = STATUS_RESPONSE_BAD;
}
}
if (r != STATUS_RESPONSE_NONE)
verbose("S (%d): %s", ssn->socket, buf + re->pmatch[0].rm_so);
if (r == STATUS_RESPONSE_NO || r == STATUS_RESPONSE_BAD)
error("IMAP (%d): %s", ssn->socket, buf + re->pmatch[0].rm_so);
return r;
}
/*
* Check if server sent a BYE response (connection is closed immediately).
*/
int
check_bye(char *buf)
{
if (xstrcasestr(buf, "* BYE") &&
!xstrcasestr(buf, " LOGOUT "))
return 1;
else
return 0;
}
/*
* Check if server sent a PREAUTH response (connection already authenticated
* by external means).
*/
int
check_preauth(char *buf)
{
if (xstrcasestr(ibuf.data, "* PREAUTH"))
return 1;
else
return 0;
}
/*
* Check if the server sent a continuation request.
*/
int
check_continuation(char *buf)
{
if ((buf[0] == '+' && buf[1] == ' ') || xstrcasestr(buf, "\r\n+ "))
return 1;
else
return 0;
}
/*
* Check if the server sent a TRYCREATE response.
*/
int
check_trycreate(char *buf)
{
if (xstrcasestr(buf, "[TRYCREATE]"))
return 1;
else
return 0;
}
/*
* Get server data and make sure there is a tagged response inside them.
*/
int
response_generic(session *ssn, int tag)
{
int r;
ssize_t n;
if (tag == -1)
return -1;
buffer_reset(&ibuf);
do {
buffer_check(&ibuf, ibuf.len + INPUT_BUF);
if ((n = receive_response(ssn, ibuf.data + ibuf.len, 0, 1)) == -1)
return -1;
ibuf.len += n;
if (check_bye(ibuf.data))
return -1;
} while ((r = check_tag(ibuf.data, ssn, tag)) == STATUS_RESPONSE_NONE);
if (r == STATUS_RESPONSE_NO &&
(check_trycreate(ibuf.data) || get_option_boolean("create")))
return STATUS_RESPONSE_TRYCREATE;
return r;
}
/*
* Get server data and make sure there is a continuation response inside them.
*/
int
response_continuation(session *ssn)
{
ssize_t n;
buffer_reset(&ibuf);
do {
buffer_check(&ibuf, ibuf.len + INPUT_BUF);
if ((n = receive_response(ssn, ibuf.data + ibuf.len, 0, 1)) == -1)
return -1;
ibuf.len += n;
if (check_bye(ibuf.data))
return -1;
} while (!check_continuation(ibuf.data));
return STATUS_RESPONSE_CONTINUE;
}
/*
* Process the greeting that server sends during connection.
*/
int
response_greeting(session *ssn)
{
buffer_reset(&ibuf);
if (receive_response(ssn, ibuf.data, 0, 1) == -1)
return -1;
verbose("S (%d): %s", ssn->socket, ibuf.data);
if (check_bye(ibuf.data))
return -1;
if (check_preauth(ibuf.data))
return STATUS_RESPONSE_PREAUTH;
return STATUS_RESPONSE_NONE;
}
/*
* Process the data that server sent due to IMAP CAPABILITY client request.
*/
int
response_capability(session *ssn, int tag)
{
int r;
char *s;
regexp *re;
if ((r = response_generic(ssn, tag)) == -1)
return -1;
ssn->protocol = PROTOCOL_NONE;
re = &responses[DATA_RESPONSE_CAPABILITY];
if (!regexec(re->preg, ibuf.data, re->nmatch, re->pmatch, 0)) {
s = xstrndup(ibuf.data + re->pmatch[1].rm_so,
re->pmatch[1].rm_eo - re->pmatch[1].rm_so);
if (xstrcasestr(s, "IMAP4rev1"))
ssn->protocol = PROTOCOL_IMAP4REV1;
else if (xstrcasestr(s, "IMAP4"))
ssn->protocol = PROTOCOL_IMAP4;
else {
error("server supports neither the IMAP4rev1 nor the "
"IMAP4 protocol\n");
return -1;
}
ssn->capabilities = CAPABILITY_NONE;
if (xstrcasestr(s, "NAMESPACE"))
ssn->capabilities |= CAPABILITY_NAMESPACE;
#ifndef NO_CRAMMD5
if (xstrcasestr(s, "AUTH=CRAM-MD5"))
ssn->capabilities |= CAPABILITY_CRAMMD5;
#endif
#ifndef NO_SSLTLS
if (xstrcasestr(s, "STARTTLS"))
ssn->capabilities |= CAPABILITY_STARTTLS;
#endif
if (xstrcasestr(s, "CHILDREN"))
ssn->capabilities |= CAPABILITY_CHILDREN;
if (xstrcasestr(s, "IDLE"))
ssn->capabilities |= CAPABILITY_IDLE;
xfree(s);
}
return r;
}
#ifndef NO_CRAMMD5
/*
* Process the data that server sent due to IMAP AUTHENTICATE client request.
*/
int
response_authenticate(session *ssn, int tag, unsigned char **cont)
{
int r;
regexp *re;
re = &responses[DATA_RESPONSE_AUTHENTICATE];
if ((r = response_continuation(ssn)) == STATUS_RESPONSE_CONTINUE &&
!regexec(re->preg, ibuf.data, re->nmatch, re->pmatch, 0))
*cont = (unsigned char *)xstrndup(ibuf.data + re->pmatch[1].rm_so,
re->pmatch[1].rm_eo - re->pmatch[1].rm_so);
return r;
}
#endif
/*
* Process the data that server sent due to IMAP NAMESPACE client request.
*/
int
response_namespace(session *ssn, int tag)
{
int r, n;
regexp *re;
if ((r = response_generic(ssn, tag)) == -1)
return -1;
ssn->ns.prefix = NULL;
ssn->ns.delim = '\0';
re = &responses[DATA_RESPONSE_NAMESPACE];
if (!regexec(re->preg, ibuf.data, re->nmatch, re->pmatch, 0)) {
n = re->pmatch[2].rm_eo - re->pmatch[2].rm_so;
if (n > 0)
ssn->ns.prefix = xstrndup(ibuf.data +
re->pmatch[2].rm_so, n);
ssn->ns.delim = *(ibuf.data + re->pmatch[3].rm_so);
}
debug("namespace (%d): '%s' '%c'\n", ssn->socket,
(ssn->ns.prefix ? ssn->ns.prefix : ""), ssn->ns.delim);
return r;
}
/*
* Process the data that server sent due to IMAP STATUS client request.
*/
int
response_status(session *ssn, int tag, unsigned int *exist,
unsigned int *recent, unsigned int *unseen, unsigned int *uidnext)
{
int r;
char *s;
regexp *re;
if ((r = response_generic(ssn, tag)) == -1)
return -1;
re = &responses[DATA_RESPONSE_STATUS];
if (!regexec(re->preg, ibuf.data, re->nmatch, re->pmatch, 0)) {
s = xstrndup(ibuf.data + re->pmatch[1].rm_so,
re->pmatch[1].rm_eo - re->pmatch[1].rm_so);
re = &responses[DATA_RESPONSE_STATUS_MESSAGES];
if (!regexec(re->preg, s, re->nmatch, re->pmatch, 0))
*exist = strtol(s + re->pmatch[1].rm_so, NULL, 10);
re = &responses[DATA_RESPONSE_STATUS_RECENT];
if (!regexec(re->preg, s, re->nmatch, re->pmatch, 0))
*recent = strtol(s + re->pmatch[1].rm_so, NULL, 10);
re = &responses[DATA_RESPONSE_STATUS_UNSEEN];
if (!regexec(re->preg, s, re->nmatch, re->pmatch, 0))
*unseen = strtol(s + re->pmatch[1].rm_so, NULL, 10);
re = &responses[DATA_RESPONSE_STATUS_UIDNEXT];
if (!regexec(re->preg, s, re->nmatch, re->pmatch, 0))
*uidnext = strtol(s + re->pmatch[1].rm_so, NULL, 10);
xfree(s);
}
return r;
}
/*
* Process the data that server sent due to IMAP EXAMINE client request.
*/
int
response_examine(session *ssn, int tag, unsigned int *exist,
unsigned int *recent)
{
int r;
regexp *re;
if ((r = response_generic(ssn, tag)) == -1)
return -1;
re = &responses[DATA_RESPONSE_EXAMINE_EXISTS];
if (!regexec(re->preg, ibuf.data, re->nmatch, re->pmatch, 0))
*exist = strtol(ibuf.data + re->pmatch[1].rm_so, NULL, 10);
re = &responses[DATA_RESPONSE_EXAMINE_RECENT];
if (!regexec(re->preg, ibuf.data, re->nmatch, re->pmatch, 0))
*recent = strtol(ibuf.data + re->pmatch[1].rm_so, NULL, 10);
return r;
}
/*
* Process the data that server sent due to IMAP SELECT client request.
*/
int
response_select(session *ssn, int tag)
{
int r;
if ((r = response_generic(ssn, tag)) == -1)
return -1;
if (xstrcasestr(ibuf.data, "[READ-ONLY]"))
return STATUS_RESPONSE_READONLY;
return r;
}
/*
* Process the data that server sent due to IMAP LIST or IMAP LSUB client
* request.
*/
int
response_list(session *ssn, int tag, char **mboxs, char **folders)
{
int r, n;
char *b, *a, *s, *m, *f;
const char *v;
regexp *re;
if ((r = response_generic(ssn, tag)) == -1)
return -1;
m = *mboxs = (char *)xmalloc((ibuf.len + 1) * sizeof(char));
f = *folders = (char *)xmalloc((ibuf.len + 1) * sizeof(char));
*m = *f = '\0';
re = &responses[DATA_RESPONSE_LIST];
b = ibuf.data;
while (!regexec(re->preg, b, re->nmatch, re->pmatch, 0)) {
a = xstrndup(b + re->pmatch[2].rm_so,
re->pmatch[2].rm_eo - re->pmatch[2].rm_so);
if (re->pmatch[5].rm_so != -1 && re->pmatch[5].rm_so != -1)
s = xstrndup(b + re->pmatch[5].rm_so,
re->pmatch[5].rm_eo - re->pmatch[5].rm_so);
else if (re->pmatch[6].rm_so != -1 &&
re->pmatch[6].rm_so != -1)
s = xstrndup(b + re->pmatch[6].rm_so,
re->pmatch[6].rm_eo - re->pmatch[6].rm_so);
else
s = xstrndup(b + re->pmatch[8].rm_so, strtoul(b +
re->pmatch[7].rm_so, NULL, 10));
v = reverse_namespace(s, ssn->ns.prefix, ssn->ns.delim);
n = strlen(v);
if (!xstrcasestr(a, "\\NoSelect")) {
xstrncpy(m, v, ibuf.len - (m - *mboxs));
m += n;
xstrncpy(m, "\n", ibuf.len - (m - *mboxs));
m += strlen("\n");
}
if (!xstrcasestr(a, "\\NoInferiors") &&
(!(ssn->capabilities & CAPABILITY_CHILDREN) ||
((ssn->capabilities & CAPABILITY_CHILDREN) &&
(xstrcasestr(a, "\\HasChildren")) &&
!xstrcasestr(a, "\\HasNoChildren")))) {
xstrncpy(f, v, ibuf.len - (f - *folders));
f += n;
xstrncpy(f, "\n", ibuf.len - (f - *folders));
f += strlen("\n");
}
b += re->pmatch[0].rm_eo;
xfree(a);
xfree(s);
}
return r;
}
/*
* Process the data that server sent due to IMAP SEARCH client request.
*/
int
response_search(session *ssn, int tag, char **mesgs)
{
int r;
unsigned int min;
regexp *re;
char *b, *m;
if ((r = response_generic(ssn, tag)) == -1)
return -1;
re = &responses[DATA_RESPONSE_SEARCH];
b = ibuf.data;
m = NULL;
while (!regexec(re->preg, b, re->nmatch, re->pmatch, 0)) {
if (!*mesgs) {
m = *mesgs = (char *)xmalloc((ibuf.len + 1) *
sizeof(char));
*m = '\0';
}
min = (unsigned int)(re->pmatch[1].rm_eo - re->pmatch[1].rm_so) < ibuf.len ?
(unsigned int)(re->pmatch[1].rm_eo - re->pmatch[1].rm_so) :
ibuf.len;
xstrncpy(m, b + re->pmatch[1].rm_so, min);
m += min;
xstrncpy(m++, " ", ibuf.len - min);
b += re->pmatch[0].rm_eo;
}
return r;
}
/*
* Process the data that server sent due to IMAP FETCH FAST client request.
*/
int
response_fetchfast(session *ssn, int tag, char **flags, char **date,
char **size)
{
int r;
char *s;
regexp *re;
if ((r = response_generic(ssn, tag)) == -1)
return -1;
re = &responses[DATA_RESPONSE_FETCH];
if (!regexec(re->preg, ibuf.data, re->nmatch, re->pmatch, 0)) {
s = xstrndup(ibuf.data + re->pmatch[1].rm_so,
re->pmatch[1].rm_eo - re->pmatch[1].rm_so);
re = &responses[DATA_RESPONSE_FETCH_FLAGS];
if (!regexec(re->preg, s, re->nmatch, re->pmatch, 0))
*flags = xstrndup(s + re->pmatch[1].rm_so,
re->pmatch[1].rm_eo - re->pmatch[1].rm_so);
re = &responses[DATA_RESPONSE_FETCH_DATE];
if (!regexec(re->preg, s, re->nmatch, re->pmatch, 0))
*date = xstrndup(s + re->pmatch[1].rm_so,
re->pmatch[1].rm_eo - re->pmatch[1].rm_so);
re = &responses[DATA_RESPONSE_FETCH_SIZE];
if (!regexec(re->preg, s, re->nmatch, re->pmatch, 0))
*size = xstrndup(s + re->pmatch[1].rm_so,
re->pmatch[1].rm_eo - re->pmatch[1].rm_so);
xfree(s);
}
return r;
}
/*
* Process the data that server sent due to IMAP FETCH FLAGS client request.
*/
int
response_fetchflags(session *ssn, int tag, char **flags)
{
int r;
char *s;
regexp *re;
if ((r = response_generic(ssn, tag)) == -1)
return -1;
re = &responses[DATA_RESPONSE_FETCH];
if (!regexec(re->preg, ibuf.data, re->nmatch, re->pmatch, 0)) {
s = xstrndup(ibuf.data + re->pmatch[1].rm_so,
re->pmatch[1].rm_eo - re->pmatch[1].rm_so);
re = &responses[DATA_RESPONSE_FETCH_FLAGS];
if (!regexec(re->preg, s, re->nmatch, re->pmatch, 0))
*flags = xstrndup(s + re->pmatch[1].rm_so,
re->pmatch[1].rm_eo - re->pmatch[1].rm_so);
xfree(s);
}
return r;
}
/*
* Process the data that server sent due to IMAP FETCH INTERNALDATE client
* request.
*/
int
response_fetchdate(session *ssn, int tag, char **date)
{
int r;
char *s;
regexp *re;
if ((r = response_generic(ssn, tag)) == -1)
return -1;
re = &responses[DATA_RESPONSE_FETCH];
if (!regexec(re->preg, ibuf.data, re->nmatch, re->pmatch, 0)) {
s = xstrndup(ibuf.data + re->pmatch[1].rm_so,
re->pmatch[1].rm_eo - re->pmatch[1].rm_so);
re = &responses[DATA_RESPONSE_FETCH_DATE];
if (!regexec(re->preg, s, re->nmatch, re->pmatch, 0))
*date = xstrndup(s + re->pmatch[1].rm_so,
re->pmatch[1].rm_eo - re->pmatch[1].rm_so);
xfree(s);
}
return r;
}
/*
* Process the data that server sent due to IMAP FETCH RFC822.SIZE client
* request.
*/
int
response_fetchsize(session *ssn, int tag, char **size)
{
int r;
char *s;
regexp *re;
if ((r = response_generic(ssn, tag)) == -1)
return -1;
re = &responses[DATA_RESPONSE_FETCH];
if (!regexec(re->preg, ibuf.data, re->nmatch, re->pmatch, 0)) {
s = xstrndup(ibuf.data + re->pmatch[1].rm_so,
re->pmatch[1].rm_eo - re->pmatch[1].rm_so);
re = &responses[DATA_RESPONSE_FETCH_SIZE];
if (!regexec(re->preg, s, re->nmatch, re->pmatch, 0))
*size = xstrndup(s + re->pmatch[1].rm_so,
re->pmatch[1].rm_eo - re->pmatch[1].rm_so);
xfree(s);
}
return r;
}
/*
* Process the data that server sent due to IMAP FETCH BODYSTRUCTURE client
* request.
*/
int
response_fetchstructure(session *ssn, int tag, char **structure)
{
int r;
char *s;
regexp *re;
if ((r = response_generic(ssn, tag)) == -1)
return -1;
re = &responses[DATA_RESPONSE_FETCH];
if (!regexec(re->preg, ibuf.data, re->nmatch, re->pmatch, 0)) {
s = xstrndup(ibuf.data + re->pmatch[1].rm_so,
re->pmatch[1].rm_eo - re->pmatch[1].rm_so);
re = &responses[DATA_RESPONSE_FETCH_STRUCTURE];
if (!regexec(re->preg, s, re->nmatch, re->pmatch, 0)) {
*structure = xstrndup(s + re->pmatch[1].rm_so,
re->pmatch[1].rm_eo - re->pmatch[1].rm_so);
}
xfree(s);
}
return r;
}
/*
* Process the data that server sent due to IMAP FETCH BODY[] client request,
* ie. FETCH BODY[HEADER], FETCH BODY[TEXT], FETCH BODY[HEADER.FIELDS
* (<fields>)], FETCH BODY[<part>].
*/
int
response_fetchbody(session *ssn, int tag, char **body, size_t *len)
{
int r, match;
unsigned int offset;
ssize_t n;
regexp *re;
if (tag == -1)
return -1;
buffer_reset(&ibuf);
match = -1;
offset = 0;
re = &responses[DATA_RESPONSE_FETCH_BODY];
do {
buffer_check(&ibuf, ibuf.len + INPUT_BUF);
if ((n = receive_response(ssn, ibuf.data + ibuf.len, 0, 1)) == -1)
return -1;
ibuf.len += n;
if (match != 0) {
match = regexec(re->preg, ibuf.data, re->nmatch,
re->pmatch, 0);
if (match == 0 && re->pmatch[2].rm_so != -1 &&
re->pmatch[2].rm_eo != -1) {
*len = strtoul(ibuf.data + re->pmatch[2].rm_so,
NULL, 10);
offset = re->pmatch[0].rm_eo + *len;
}
}
if (offset != 0 && ibuf.len >= offset) {
if (check_bye(ibuf.data + offset))
return -1;
}
} while (ibuf.len < offset || (r = check_tag(ibuf.data + offset, ssn,
tag)) == STATUS_RESPONSE_NONE);
if (match == 0) {
if (re->pmatch[2].rm_so != -1 &&
re->pmatch[2].rm_eo != -1) {
*body = ibuf.data + re->pmatch[0].rm_eo;
} else {
*body = ibuf.data + re->pmatch[3].rm_so;
*len = re->pmatch[3].rm_eo - re->pmatch[3].rm_so;
}
}
return r;
}
/*
* Process the data that server sent due to IMAP IDLE client request.
*/
int
response_idle(session *ssn, int tag)
{
regexp *re;
re = &responses[DATA_RESPONSE_IDLE];
do {
buffer_reset(&ibuf);
switch (receive_response(ssn, ibuf.data,
get_option_number("keepalive") * 60, 0)) {
case -1:
return -1;
break; /* NOTREACHED */
case 0:
return STATUS_RESPONSE_TIMEOUT;
break; /* NOTREACHED */
}
verbose("S (%d): %s", ssn->socket, ibuf.data);
if (check_bye(ibuf.data))
return -1;
} while (regexec(re->preg, ibuf.data, re->nmatch, re->pmatch, 0));
return STATUS_RESPONSE_UNTAGGED;
}

129
sample.config.lua Normal file
View File

@ -0,0 +1,129 @@
---------------
-- Options --
---------------
options.timeout = 120
options.subscribe = true
----------------
-- Accounts --
----------------
-- Connects to "imap1.mail.server", as user "user1" with "secret1" as
-- password.
account1 = IMAP {
server = 'imap1.mail.server',
username = 'user1',
password = 'secret1',
}
-- Another account which connects to the mail server using the SSLv3
-- protocol.
account2 = IMAP {
server = 'imap2.mail.server',
username = 'user2',
password = 'secret2',
ssl = 'ssl3',
}
-- Get a list of the available mailboxes and folders
mailboxes, folders = account1:list_all()
-- Get a list of the subscribed mailboxes and folders
mailboxes, folders = account1:list_subscribed()
-- Create a mailbox
account1:create_mailbox('Friends')
-- Subscribe a mailbox
account1:subscribe_mailbox('Friends')
-----------------
-- Mailboxes --
-----------------
-- Get the status of a mailbox
account1.INBOX:check_status()
-- Get all the messages in the mailbox.
results = account1.INBOX:select_all()
-- Get newly arrived, unread messages
results = account1.INBOX:is_new()
-- Get unseen messages with the specified "From" header.
results = account1.INBOX:is_unseen() *
account1.INBOX:contain_from('weekly-news@news.letter')
-- Copy messages between mailboxes at the same account.
results:copy_messages(account1.news)
-- Get messages with the specified "From" header but without the
-- specified "Subject" header.
results = account1.INBOX:contain_from('announce@my.unix.os') -
account1.INBOX:contain_subject('security advisory')
-- Copy messages between mailboxes at a different account.
results:copy_messages(account2.security)
-- Get messages with any of the specified headers.
results = account1.INBOX:contain_from('marketing@company.junk') +
account1.INBOX:contain_from('advertising@annoying.promotion') +
account1.INBOX:contain_subject('new great products')
-- Delete messages.
results:delete_messages()
-- Get messages with the specified "Sender" header, which are older than
-- 30 days.
results = account1.INBOX:contain_field('sender', 'owner@announce-list') *
account1.INBOX:is_older(30)
-- Move messages to the "announce" mailbox inside the "lists" folder.
results:move_messages(account1['lists/announce'])
-- Get messages, in the "devel" mailbox inside the "lists" folder, with the
-- specified "Subject" header and a size less than 50000 octets (bytes).
results = account1['lists/devel']:contain_subject('[patch]') *
account1['lists/devel']:is_smaller(50000)
-- Move messages to the "patch" mailbox.
results:move_messages(account2.patch)
-- Get recent, unseen messages, that have either one of the specified
-- "From" headers, but do not have the specified pattern in the body of
-- the message.
results = ( account1.INBOX:is_recent() *
account1.INBOX:is_unseen() *
( account1.INBOX:contain_from('tux@penguin.land') +
account1.INBOX:contain_from('beastie@daemon.land') ) ) -
account1.INBOX:match_body('.*all.work.and.no.play.*')
-- Mark messages as important.
results:mark_flagged()
-- Get all messages in two mailboxes residing in the same server.
results = account1.news:select_all() +
account1.security:select_all()
-- Mark messages as seen.
results:mark_seen()
-- Get recent messages in two mailboxes residing in different servers.
results = account1.INBOX:is_recent() +
account2.INBOX:is_recent()
-- Flag messages as seen and important.
results:add_flags({ '\\Seen', '\\Flagged' })
-- Get unseen messages.
results = account1.INBOX:is_unseen()
-- From the messages that were unseen, match only those with the specified
-- regular expression in the header.
newresults = results:match_header('^.+MailScanner.*Check: [Ss]pam$')
-- Delete those messages.
newresults:delete_messages()

103
sample.extend.lua Normal file
View File

@ -0,0 +1,103 @@
--
-- This file contains examples on how IMAPFilter can be extended using
-- the Lua programming language.
--
-- IMAPFilter can be detached from the controlling terminal and run in
-- the background as a system daemon.
--
-- The auxiliary function daemon_mode() is supplied for conveniency.
-- The following example puts imapfilter in the background and runs
-- endlessly, executing the commands in the forever() function and
-- sleeping for 600 seconds between intervals:
function forever()
results = myaccount.mymailbox:is_old()
results:move_messages(myaccount.archive)
end
become_daemon(600, forever)
-- IMAPFilter can take advantage of all those filtering utilities that
-- are available and use a wide range of heuristic tests, text analysis,
-- internet-based realtime blacklists, advanced learning algorithms,
-- etc. to classify mail. IMAPFilter can pipe a message to a program
-- and act on the message based on the program's exit status.
--
-- The auxiliary function pipe_to() is supplied for conveniency. For
-- example if there was a utility named "bayesian-spam-filter", which
-- returned 1 when it considered the message "spam" and 0 otherwise:
all = myaccount.mymailbox:select_all()
results = Set {}
for _, mesg in ipairs(all) do
mbox, uid = unpack(mesg)
text = mbox[uid]:fetch_message()
if (pipe_to('bayesian-spam-filter', text) == 1) then
table.insert(results, mesg)
end
end
results:delete_messages()
-- One might want to run the bayesian filter only in those parts (attachments)
-- of the message that are of type text/plain and smaller than 1024 bytes.
-- This is possible using the fetch_structure() and fetch_part() functions:
all = myaccount.mymailbox:select_all()
results = Set {}
for _, mesg in ipairs(all) do
mbox, uid = unpack(mesg)
structure = mbox[uid]:fetch_structure()
for partid, partinf in pairs(structure) do
if partinf.type:lower() == 'text/plain' and partinf.size < 1024 then
part = mbox[uid]:fetch_part(partid)
if (pipe_to('bayesian-spam-filter', part) == 1) then
table.insert(results, mesg)
break
end
end
end
end
results:delete_messages()
-- Passwords could be extracted during execution time from an encrypted
-- file.
--
-- The file is encrypted using the openssl(1) command line tool. For
-- example the "passwords.txt" file:
--
-- secret1 secret2
--
-- ... is encrypted and saved to a file named "passwords.enc" with the
-- command:
--
-- $ openssl bf -salt -in passwords.txt -out passwords.enc
--
-- The auxiliary function pipe_from() is supplied for conveniency. The
-- user is prompted to enter the decryption password, the file is
-- decrypted and the account passwords are set accordingly:
status, output = pipe_from('openssl bf -d -salt -in ~/passwords.enc')
_, _, password1, password2 = string.find(output, '([%w%p]+)\n([%w%p]+)\n')
account1 = IMAP {
server = 'imap1.mail.server',
username = 'user1',
password = password1
}
account2 = IMAP {
server = 'imap2.mail.server',
username = 'user2',
password = password2
}

101
session.c Normal file
View File

@ -0,0 +1,101 @@
#include <stdio.h>
#include <string.h>
#include "imapfilter.h"
#include "session.h"
#include "list.h"
extern list *sessions;
/*
* Allocate memory for a new session and add it to the sessions linked list.
*/
session *
session_new(void)
{
session *s;
s = (session *)xmalloc(sizeof(session));
session_init(s);
sessions = list_append(sessions, s);
return s;
}
/*
* Set session variables to safe values.
*/
void
session_init(session *ssn)
{
ssn->server = NULL;
ssn->port = NULL;
ssn->username = NULL;
ssn->socket = -1;
#ifndef NO_SSLTLS
ssn->ssl = NULL;
#endif
ssn->protocol = PROTOCOL_NONE;
ssn->capabilities = CAPABILITY_NONE;
ssn->ns.prefix = NULL;
ssn->ns.delim = '\0';
}
/*
* Remove session from sessions linked list.
*/
void
session_destroy(session *ssn)
{
sessions = list_remove(sessions, ssn);
session_free(ssn);
}
/*
* Free session allocated memory.
*/
void
session_free(session *ssn)
{
if (ssn->server)
xfree(ssn->server);
if (ssn->port)
xfree(ssn->port);
if (ssn->username)
xfree(ssn->username);
if (ssn->ns.prefix)
xfree(ssn->ns.prefix);
xfree(ssn);
}
/*
* Based on the specified socket, find an active IMAP session.
*/
session *
session_find(const char *serv, const char *port, const char *user)
{
list *l;
session *s;
for (l = sessions; l; l = l->next) {
s = l->data;
if (!strcmp(s->server, serv) &&
!strcmp(s->port, port) &&
!strcmp(s->username, user))
return s;
}
return NULL;
}

37
session.h Normal file
View File

@ -0,0 +1,37 @@
#ifndef SESSION_H
#define SESSION_H
#ifndef NO_SSLTLS
#include <openssl/ssl.h>
#endif
/* IMAP session. */
typedef struct session {
char *server; /* Server hostname. */
char *port; /* Server port. */
char *username; /* User name. */
int socket; /* Socket. */
#ifndef NO_SSLTLS
SSL *ssl; /* SSL socket. */
#endif
unsigned int protocol; /* IMAP protocol. Currently IMAP4rev1 and
* IMAP4 are supported. */
unsigned int capabilities; /* Capabilities of the mail server. */
struct { /* Namespace of the mail server's mailboxes. */
char *prefix; /* Namespace prefix. */
char delim; /* Namespace delimiter. */
} ns;
} session;
/* session.c */
session *session_new(void);
void session_init(session *ssn);
void session_destroy(session *ssn);
void session_free(session *ssn);
session *session_find(const char *server, const char *port, const char *user);
#endif /* SESSION_H */

700
set.lua Normal file
View File

@ -0,0 +1,700 @@
-- A simple implementation of sets.
Set = {}
Set._mt = {}
setmetatable(Set, Set._mt)
function Set._new(self, values)
local object
object = values or {}
for key, value in pairs(Set) do
if (type(value) == 'function') then
object[key] = value
end
end
object._type = 'set'
object._mt = {}
object._mt.__add = object._union
object._mt.__mul = object._intersection
object._mt.__sub = object._difference
setmetatable(object, object._mt)
return object
end
function Set._union(seta, setb)
local set = Set()
local t = {}
for _, v in ipairs(seta) do
b, m = unpack(v)
if not t[b] then t[b] = {} end
t[b][m] = true
end
for _, v in ipairs(setb) do
b, m = unpack(v)
if not t[b] then t[b] = {} end
t[b][m] = true
end
for b in pairs(t) do
for m in pairs(t[b]) do
table.insert(set, { b, m })
end
end
return set
end
function Set._intersection(seta, setb)
local set = Set()
local ta = {}
local tb = {}
for _, v in ipairs(seta) do
b, m = unpack(v)
if not ta[b] then ta[b] = {} end
ta[b][m] = true
end
for _, v in ipairs(setb) do
b, m = unpack(v)
if not tb[b] then tb[b] = {} end
tb[b][m] = true
end
for b in pairs(ta) do
if tb[b] then
for m in pairs(ta[b]) do
if tb[b][m] then
table.insert(set, { b, m })
end
end
end
end
return set
end
function Set._difference(seta, setb)
local set = Set()
local t = {}
for _, v in ipairs(seta) do
b, m = unpack(v)
if not t[b] then t[b] = {} end
t[b][m] = true
end
for _, v in ipairs(setb) do
b, m = unpack(v)
if t[b] then
t[b][m] = nil
end
end
for b in pairs(t) do
for m in pairs(t[b]) do
table.insert(set, { b, m })
end
end
return set
end
function Set.add_flags(self, flags)
_check_required(flags, 'table')
local r = true
for mbox in pairs(_extract_mailboxes(self)) do
if not mbox.add_flags(mbox, flags, self) then
r = false
end
end
return r
end
function Set.remove_flags(self, flags)
_check_required(flags, 'table')
local r = true
for mbox in pairs(_extract_mailboxes(self)) do
if not mbox.remove_flags(mbox, flags, self) then
r = false
end
end
return r
end
function Set.replace_flags(self, flags)
_check_required(flags, 'table')
local r = true
for mbox in pairs(_extract_mailboxes(self)) do
if not mbox.replace_flags(mbox, flags, self) then
r = false
end
end
return r
end
function Set.mark_answered(self)
local r = true
for mbox in pairs(_extract_mailboxes(self)) do
if not mbox.mark_answered(mbox, self) then
r = false
end
end
return r
end
function Set.mark_deleted(self)
local r = true
for mbox in pairs(_extract_mailboxes(self)) do
if not mbox.mark_deleted(mbox, self) then
r = false
end
end
return r
end
function Set.mark_draft(self)
local r = true
for mbox in pairs(_extract_mailboxes(self)) do
if not mbox.mark_draft(mbox, self) then
r = false
end
end
return r
end
function Set.mark_flagged(self)
local r = true
for mbox in pairs(_extract_mailboxes(self)) do
if not mbox.mark_flagged(mbox, self) then
r = false
end
end
return r
end
function Set.mark_seen(self)
local r = true
for mbox in pairs(_extract_mailboxes(self)) do
if not mbox.mark_seen(mbox, self) then
r = false
end
end
return r
end
function Set.unmark_answered(self)
local r = true
for mbox in pairs(_extract_mailboxes(self)) do
if not mbox.unmark_answered(mbox, self) then
r = false
end
end
return r
end
function Set.unmark_deleted(self)
local r = true
for mbox in pairs(_extract_mailboxes(self)) do
if not mbox.unmark_deleted(mbox, self) then
r = false
end
end
return r
end
function Set.unmark_draft(self)
local r = true
for mbox in pairs(_extract_mailboxes(self)) do
if not mbox.unmark_draft(mbox, self) then
r = false
end
end
return r
end
function Set.unmark_flagged(self)
local r = true
for mbox in pairs(_extract_mailboxes(self)) do
if not mbox.unmark_flagged(mbox, self) then
r = false
end
end
return r
end
function Set.unmark_seen(self)
local r = true
for mbox in pairs(_extract_mailboxes(self)) do
if not mbox.unmark_seen(mbox, self) then
r = false
end
end
return r
end
function Set.delete_messages(self)
local r = true
for mbox in pairs(_extract_mailboxes(self)) do
if not mbox.delete_messages(mbox, self) then
r = false
end
end
return r
end
function Set.copy_messages(self, dest)
_check_required(dest, 'table')
local r = true
for mbox in pairs(_extract_mailboxes(self)) do
if not mbox.copy_messages(mbox, dest, self) then
r = false
end
end
return r
end
function Set.move_messages(self, dest)
_check_required(dest, 'table')
local r = true
for mbox in pairs(_extract_mailboxes(self)) do
if not mbox.move_messages(mbox, dest, self) then
r = false
end
end
return r
end
function Set.select_all(self)
local set = Set()
for mbox in pairs(_extract_mailboxes(self)) do
set = set + mbox.select_all(mbox)
end
return self * set
end
function Set.send_query(self, criteria)
local set = Set()
for mbox in pairs(_extract_mailboxes(self)) do
set = set + mbox.send_query(mbox, criteria)
end
return self * set
end
function Set.is_answered(self)
local set = Set()
for mbox in pairs(_extract_mailboxes(self)) do
set = set + mbox.is_answered(mbox)
end
return self * set
end
function Set.is_deleted(self)
local set = Set()
for mbox in pairs(_extract_mailboxes(self)) do
set = set + mbox.is_deleted(mbox)
end
return self * set
end
function Set.is_draft(self)
local set = Set()
for mbox in pairs(_extract_mailboxes(self)) do
set = set + mbox.is_draft(mbox)
end
return self * set
end
function Set.is_flagged(self)
local set = Set()
for mbox in pairs(_extract_mailboxes(self)) do
set = set + mbox.is_flagged(mbox)
end
return self * set
end
function Set.is_new(self)
local set = Set()
for mbox in pairs(_extract_mailboxes(self)) do
set = set + mbox.is_new(mbox)
end
return self * set
end
function Set.is_old(self)
local set = Set()
for mbox in pairs(_extract_mailboxes(self)) do
set = set + mbox.is_old(mbox)
end
return self * set
end
function Set.is_recent(self)
local set = Set()
for mbox in pairs(_extract_mailboxes(self)) do
set = set + mbox.is_recent(mbox)
end
return self * set
end
function Set.is_seen(self)
local set = Set()
for mbox in pairs(_extract_mailboxes(self)) do
set = set + mbox.is_seen(mbox)
end
return self * set
end
function Set.is_unanswered(self)
local set = Set()
for mbox in pairs(_extract_mailboxes(self)) do
set = set + mbox.is_unanswered(mbox)
end
return self * set
end
function Set.is_undeleted(self)
local set = Set()
for mbox in pairs(_extract_mailboxes(self)) do
set = set + mbox.is_undeleted(mbox)
end
return self * set
end
function Set.is_undraft(self)
local set = Set()
for mbox in pairs(_extract_mailboxes(self)) do
set = set + mbox.is_undraft(mbox)
end
return self * set
end
function Set.is_unflagged(self)
local set = Set()
for mbox in pairs(_extract_mailboxes(self)) do
set = set + mbox.is_unflagged(mbox)
end
return self * set
end
function Set.is_unseen(self)
local set = Set()
for mbox in pairs(_extract_mailboxes(self)) do
set = set + mbox.is_unseen(mbox)
end
return self * set
end
function Set.is_larger(self, size)
_check_required(size, 'number')
local set = Set()
for mbox in pairs(_extract_mailboxes(self)) do
set = set + mbox.is_larger(mbox, size)
end
return self * set
end
function Set.is_smaller(self, size)
_check_required(size, 'number')
local set = Set()
for mbox in pairs(_extract_mailboxes(self)) do
set = set + mbox.is_smaller(mbox, size)
end
return self * set
end
function Set.arrived_on(self, date)
_check_required(date, 'string')
local set = Set()
for mbox in pairs(_extract_mailboxes(self)) do
set = set + mbox.arrived_on(mbox, date)
end
return self * set
end
function Set.arrived_before(self, date)
_check_required(date, 'string')
local set = Set()
for mbox in pairs(_extract_mailboxes(self)) do
set = set + mbox.arrived_before(mbox, date)
end
return self * set
end
function Set.arrived_since(self, date)
_check_required(date, 'string')
local set = Set()
for mbox in pairs(_extract_mailboxes(self)) do
set = set + mbox.arrived_since(mbox, date)
end
return self * set
end
function Set.sent_on(self, date)
_check_required(date, 'string')
local set = Set()
for mbox in pairs(_extract_mailboxes(self)) do
set = set + mbox.sent_on(mbox, date)
end
return self * set
end
function Set.sent_before(self, date)
_check_required(date, 'string')
local set = Set()
for mbox in pairs(_extract_mailboxes(self)) do
set = set + mbox.sent_before(mbox, date)
end
return self * set
end
function Set.sent_since(self, date)
_check_required(date, 'string')
local set = Set()
for mbox in pairs(_extract_mailboxes(self)) do
set = set + mbox.sent_since(mbox, date)
end
return self * set
end
function Set.is_newer(self, days)
_check_required(days, 'number')
local set = Set()
for mbox in pairs(_extract_mailboxes(self)) do
set = set + mbox.is_newer(mbox, days)
end
return self * set
end
function Set.is_older(self, days)
_check_required(days, 'number')
local set = Set()
for mbox in pairs(_extract_mailboxes(self)) do
set = set + mbox.is_older(mbox, days)
end
return self * set
end
function Set.has_flag(self, flag)
_check_required(flag, 'string')
local set = Set()
for mbox in pairs(_extract_mailboxes(self)) do
set = set + mbox.has_flag(mbox, flag)
end
return self * set
end
function Set.contain_field(self, field, string)
_check_required(field, 'string')
_check_required(string, 'string')
local set = Set()
for mbox in pairs(_extract_mailboxes(self)) do
set = set + mbox.contain_field(mbox, field, string)
end
return self * set
end
function Set.contain_bcc(self, string)
_check_required(string, 'string')
local set = Set()
for mbox in pairs(_extract_mailboxes(self)) do
set = set + mbox.contain_bcc(mbox, string)
end
return self * set
end
function Set.contain_cc(self, string)
_check_required(string, 'string')
local set = Set()
for mbox in pairs(_extract_mailboxes(self)) do
set = set + mbox.contain_cc(mbox, string)
end
return self * set
end
function Set.contain_from(self, string)
_check_required(string, 'string')
local set = Set()
for mbox in pairs(_extract_mailboxes(self)) do
set = set + mbox.contain_from(mbox, string)
end
return self * set
end
function Set.contain_subject(self, string)
_check_required(string, 'string')
local set = Set()
for mbox in pairs(_extract_mailboxes(self)) do
set = set + mbox.contain_subject(mbox, string)
end
return self * set
end
function Set.contain_to(self, string)
_check_required(string, 'string')
local set = Set()
for mbox in pairs(_extract_mailboxes(self)) do
set = set + mbox.contain_to(mbox, string)
end
return self * set
end
function Set.contain_header(self, string)
_check_required(string, 'string')
local set = Set()
for mbox in pairs(_extract_mailboxes(self)) do
set = set + mbox.contain_header(mbox, string)
end
return self * set
end
function Set.contain_body(self, string)
_check_required(string, 'string')
local set = Set()
for mbox in pairs(_extract_mailboxes(self)) do
set = set + mbox.contain_body(mbox, string)
end
return self * set
end
function Set.contain_message(self, string)
_check_required(string, 'string')
local set = Set()
for mbox in pairs(_extract_mailboxes(self)) do
set = set + mbox.contain_message(mbox, string)
end
return self * set
end
function Set.match_bcc(self, pattern)
_check_required(pattern, 'string')
local set = Set()
for mbox in pairs(_extract_mailboxes(self)) do
set = set + mbox.match_bcc(mbox, pattern, self)
end
return self * set
end
function Set.match_cc(self, pattern)
_check_required(pattern, 'string')
local set = Set()
for mbox in pairs(_extract_mailboxes(self)) do
set = set + mbox.match_cc(mbox, pattern, self)
end
return self * set
end
function Set.match_from(self, pattern)
_check_required(pattern, 'string')
local set = Set()
for mbox in pairs(_extract_mailboxes(self)) do
set = set + mbox.match_from(mbox, pattern, self)
end
return self * set
end
function Set.match_subject(self, pattern)
_check_required(pattern, 'string')
local set = Set()
for mbox in pairs(_extract_mailboxes(self)) do
set = set + mbox.match_subject(mbox, pattern, self)
end
return self * set
end
function Set.match_to(self, pattern)
_check_required(pattern, 'string')
local set = Set()
for mbox in pairs(_extract_mailboxes(self)) do
set = set + mbox.match_to(mbox, pattern, self)
end
return self * set
end
function Set.match_field(self, field, pattern)
_check_required(field, 'string')
_check_required(pattern, 'string')
local set = Set()
for mbox in pairs(_extract_mailboxes(self)) do
set = set + mbox.match_field(mbox, field, pattern, self)
end
return self * set
end
function Set.match_header(self, pattern)
_check_required(pattern, 'string')
local set = Set()
for mbox in pairs(_extract_mailboxes(self)) do
set = set + mbox.match_header(mbox, pattern, self)
end
return self * set
end
function Set.match_body(self, pattern)
_check_required(pattern, 'string')
local set = Set()
for mbox in pairs(_extract_mailboxes(self)) do
set = set + mbox.match_body(mbox, pattern, self)
end
return self * set
end
function Set.match_message(self, pattern)
_check_required(pattern, 'string')
local set = Set()
for mbox in pairs(_extract_mailboxes(self)) do
set = set + mbox.match_message(mbox, pattern, self)
end
return self * set
end
Set._mt.__call = Set._new

45
signal.c Normal file
View File

@ -0,0 +1,45 @@
#include <signal.h>
#include "imapfilter.h"
void signal_handler(int sig);
/*
* Catch signals that cause program's termination.
*/
void
catch_signals(void)
{
signal(SIGINT, signal_handler);
signal(SIGQUIT, signal_handler);
signal(SIGTERM, signal_handler);
}
/*
* Release signals and reset them to default action.
*/
void
release_signals(void)
{
signal(SIGINT, SIG_DFL);
signal(SIGQUIT, SIG_DFL);
signal(SIGTERM, SIG_DFL);
}
/*
* Signal handler for signals that cause termination of program.
*/
void
signal_handler(int sig)
{
release_signals();
fatal(ERROR_SIGNAL, "killed by signal %d\n", sig);
}

440
socket.c Normal file
View File

@ -0,0 +1,440 @@
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <strings.h>
#include <errno.h>
#include <netinet/in.h>
#include <netdb.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/time.h>
#include <sys/select.h>
#include "imapfilter.h"
#include "session.h"
#ifndef NO_SSLTLS
#include <openssl/ssl.h>
#include <openssl/err.h>
#endif
/*
* Connect to mail server.
*/
int
open_connection(session *ssn, const char *server, const char *port,
const char *protocol)
{
struct addrinfo hints, *res, *ressave;
int n, sockfd;
#ifdef NO_SSLTLS
if (protocol) {
error("SSL not supported by this build\n");
return -1;
}
#endif
memset(&hints, 0, sizeof(struct addrinfo));
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
n = getaddrinfo(server, port, &hints, &res);
if (n < 0) {
error("gettaddrinfo; %s\n", gai_strerror(n));
return -1;
}
ressave = res;
sockfd = -1;
while (res) {
sockfd = socket(res->ai_family, res->ai_socktype,
res->ai_protocol);
if (sockfd >= 0) {
if (connect(sockfd, res->ai_addr, res->ai_addrlen) == 0)
break;
sockfd = -1;
}
res = res->ai_next;
}
if (ressave)
freeaddrinfo(ressave);
if (sockfd == -1) {
error("error while initiating connection to %s at port %s\n",
server, port);
return -1;
}
ssn->socket = sockfd;
#ifndef NO_SSLTLS
if (protocol) {
if (open_secure_connection(ssn, server, port, protocol) == -1) {
close_connection(ssn);
return -1;
}
}
#endif
return ssn->socket;
}
#ifndef NO_SSLTLS
/*
* Initialize SSL/TLS connection.
*/
int
open_secure_connection(session *ssn, const char *server, const char *port,
const char *protocol)
{
int r, e;
SSL_CTX *ctx;
SSL_METHOD *method;
method = NULL;
if (!strncasecmp(protocol, "tls1", 4))
method = TLSv1_client_method();
else if (!strncasecmp(protocol, "ssl3", 4) ||
!strncasecmp(protocol, "ssl2", 4))
method = SSLv23_client_method();
if (!(ctx = SSL_CTX_new(method)))
goto fail;
if (!(ssn->ssl = SSL_new(ctx)))
goto fail;
SSL_set_fd(ssn->ssl, ssn->socket);
for (;;) {
if ((r = SSL_connect(ssn->ssl)) > 0)
break;
switch (SSL_get_error(ssn->ssl, r)) {
case SSL_ERROR_ZERO_RETURN:
error("initiating SSL connection to %s; the "
"connection has been closed cleanly\n", server);
goto fail;
case SSL_ERROR_WANT_CONNECT:
case SSL_ERROR_WANT_ACCEPT:
case SSL_ERROR_WANT_X509_LOOKUP:
case SSL_ERROR_WANT_READ:
case SSL_ERROR_WANT_WRITE:
break;
case SSL_ERROR_SYSCALL:
e = ERR_get_error();
if (e == 0)
error("initiating SSL connection to %s; EOF "
"in violation of the protocol\n", server);
else if (e == -1)
error("initiating SSL connection to %s; %s\n",
server, strerror(errno));
goto fail;
case SSL_ERROR_SSL:
error("initiating SSL connection to %s; %s\n", server,
ERR_error_string(ERR_get_error(), NULL));
goto fail;
default:
goto fail;
}
}
if (get_option_boolean("certificates") && get_cert(ssn) == -1)
goto fail;
SSL_CTX_free(ctx);
return 0;
fail:
ssn->ssl = NULL;
SSL_CTX_free(ctx);
return -1;
}
#endif /* NO_SSLTLS */
/*
* Disconnect from mail server.
*/
int
close_connection(session *ssn)
{
int r;
r = 0;
#ifndef NO_SSLTLS
close_secure_connection(ssn);
#endif
if (ssn->socket != -1) {
r = close(ssn->socket);
ssn->socket = -1;
if (r == -1)
error("closing socket; %s\n", strerror(errno));
}
return r;
}
#ifndef NO_SSLTLS
/*
* Shutdown SSL/TLS connection.
*/
int
close_secure_connection(session *ssn)
{
if (ssn->ssl) {
SSL_shutdown(ssn->ssl);
SSL_free(ssn->ssl);
ssn->ssl = NULL;
}
return 0;
}
#endif
/*
* Read data from socket.
*/
ssize_t
socket_read(session *ssn, char *buf, size_t len, long timeout, int timeoutfail)
{
int s;
ssize_t r;
fd_set fds;
struct timeval tv;
struct timeval *tvp;
r = 0;
s = 1;
tvp = NULL;
memset(buf, 0, len + 1);
if (timeout > 0) {
tv.tv_sec = timeout;
tv.tv_usec = 0;
tvp = &tv;
}
FD_ZERO(&fds);
FD_SET(ssn->socket, &fds);
#ifndef NO_SSLTLS
if (ssn->ssl) {
if (SSL_pending(ssn->ssl) > 0 ||
((s = select(ssn->socket + 1, &fds, NULL, NULL, tvp)) > 0 &&
FD_ISSET(ssn->socket, &fds))) {
r = socket_secure_read(ssn, buf, len);
if (r <= 0)
goto fail;
}
} else
#endif
{
if ((s = select(ssn->socket + 1, &fds, NULL, NULL, tvp)) > 0 &&
FD_ISSET(ssn->socket, &fds)) {
r = read(ssn->socket, buf, len);
if (r == -1) {
error("reading data; %s\n", strerror(errno));
goto fail;
} else if (r == 0) {
goto fail;
}
}
}
if (s == -1) {
error("waiting to read from socket; %s\n", strerror(errno));
goto fail;
} else if (s == 0 && timeoutfail) {
error("timeout period expired while waiting to read data\n");
goto fail;
}
return r;
fail:
close_connection(ssn);
return -1;
}
#ifndef NO_SSLTLS
/*
* Read data from a TLS/SSL connection.
*/
ssize_t
socket_secure_read(session *ssn, char *buf, size_t len)
{
int r, e;
for (;;) {
r = (ssize_t) SSL_read(ssn->ssl, buf, len);
if (r > 0)
break;
switch (SSL_get_error(ssn->ssl, r)) {
case SSL_ERROR_ZERO_RETURN:
error("reading data; the connection has been closed "
"cleanly\n");
return -1;
case SSL_ERROR_WANT_READ:
case SSL_ERROR_WANT_WRITE:
case SSL_ERROR_WANT_CONNECT:
case SSL_ERROR_WANT_ACCEPT:
case SSL_ERROR_WANT_X509_LOOKUP:
break;
case SSL_ERROR_SYSCALL:
e = ERR_get_error();
if (e == 0)
error("reading data; EOF in violation of the "
"protocol\n");
else if (e == -1)
error("reading data; %s\n", strerror(errno));
return -1;
case SSL_ERROR_SSL:
error("reading data; %s\n",
ERR_error_string(ERR_get_error(), NULL));
return -1;
default:
return -1;
}
}
return r;
}
#endif
/*
* Write data to socket.
*/
ssize_t
socket_write(session *ssn, const char *buf, size_t len)
{
int s;
ssize_t w, wt;
fd_set fds;
w = wt = 0;
s = 1;
FD_ZERO(&fds);
FD_SET(ssn->socket, &fds);
while (len) {
if ((s = select(ssn->socket + 1, NULL, &fds, NULL, NULL) > 0 &&
FD_ISSET(ssn->socket, &fds))) {
#ifndef NO_SSLTLS
if (ssn->ssl) {
w = socket_secure_write(ssn, buf, len);
if (w <= 0)
goto fail;
} else
#endif
{
w = write(ssn->socket, buf, len);
if (w == -1) {
error("writing data; %s\n",
strerror(errno));
goto fail;
} else if (w == 0) {
goto fail;
}
}
if (w > 0) {
len -= w;
buf += w;
wt += w;
}
}
}
if (s == -1) {
error("waiting to write to socket; %s\n", strerror(errno));
goto fail;
} else if (s == 0) {
error("timeout period expired while waiting to write data\n");
goto fail;
}
return wt;
fail:
close_connection(ssn);
return -1;
}
#ifndef NO_SSLTLS
/*
* Write data to a TLS/SSL connection.
*/
ssize_t
socket_secure_write(session *ssn, const char *buf, size_t len)
{
int w, e;
for (;;) {
w = (ssize_t) SSL_write(ssn->ssl, buf, len);
if (w > 0)
break;
switch (SSL_get_error(ssn->ssl, w)) {
case SSL_ERROR_ZERO_RETURN:
error("writing data; the connection has been closed "
"cleanly\n");
return -1;
case SSL_ERROR_WANT_READ:
case SSL_ERROR_WANT_WRITE:
case SSL_ERROR_WANT_CONNECT:
case SSL_ERROR_WANT_ACCEPT:
case SSL_ERROR_WANT_X509_LOOKUP:
break;
case SSL_ERROR_SYSCALL:
e = ERR_get_error();
if (e == 0)
error("writing data; EOF in violation of the "
"protocol\n");
else if (e == -1)
error("writing data; %s\n", strerror(errno));
return -1;
case SSL_ERROR_SSL:
error("writing data; %s\n",
ERR_error_string(ERR_get_error(), NULL));
return -1;
default:
return -1;
}
}
return w;
}
#endif

308
system.c Normal file
View File

@ -0,0 +1,308 @@
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/types.h>
#include <termios.h>
#include <lua.h>
#include <lauxlib.h>
#include <lualib.h>
#include "imapfilter.h"
static int ifsys_echo(lua_State *lua);
static int ifsys_noecho(lua_State *lua);
static int ifsys_popen(lua_State *lua);
static int ifsys_pclose(lua_State *lua);
static int ifsys_read(lua_State *lua);
static int ifsys_write(lua_State *lua);
static int ifsys_sleep(lua_State *lua);
static int ifsys_daemon(lua_State *lua);
/* Lua imapfilter library of system's functions. */
static const luaL_reg ifsyslib[] = {
{ "echo", ifsys_echo },
{ "noecho", ifsys_noecho },
{ "popen", ifsys_popen },
{ "pclose", ifsys_pclose },
{ "read", ifsys_read },
{ "write", ifsys_write },
{ "sleep", ifsys_sleep },
{ "daemon", ifsys_daemon },
{ NULL, NULL }
};
/*
* Enable character echoing.
*/
static int
ifsys_echo(lua_State *lua)
{
struct termios t;
if (lua_gettop(lua) != 0)
luaL_error(lua, "wrong number of arguments");
if (tcgetattr(fileno(stdin), &t)) {
fprintf(stderr, "getting term attributs; %s\n",
strerror(errno));
return 1;
}
t.c_lflag |= (ECHO);
t.c_lflag &= ~(ECHONL);
if (tcsetattr(fileno(stdin), TCSAFLUSH, &t)) {
fprintf(stderr, "setting term attributes; %s\n",
strerror(errno));
return 1;
}
return 0;
}
/*
* Disable character echoing.
*/
static int
ifsys_noecho(lua_State *lua)
{
struct termios t;
if (lua_gettop(lua) != 0)
luaL_error(lua, "wrong number of arguments");
if (tcgetattr(fileno(stdin), &t)) {
fprintf(stderr, "getting term attributs; %s\n",
strerror(errno));
return 1;
}
t.c_lflag &= ~(ECHO);
t.c_lflag |= (ECHONL);
if (tcsetattr(fileno(stdin), TCSAFLUSH, &t)) {
fprintf(stderr, "setting term attributes; %s\n",
strerror(errno));
return 1;
}
return 0;
}
/*
* Lua implementation of the POSIX popen() function.
*/
static int
ifsys_popen(lua_State *lua)
{
FILE **fp;
if (lua_gettop(lua) != 2)
luaL_error(lua, "wrong number of arguments");
luaL_checktype(lua, 1, LUA_TSTRING);
luaL_checktype(lua, 2, LUA_TSTRING);
fp = (FILE **) lua_newuserdata(lua, sizeof(FILE *));
*fp = NULL;
*fp = popen(lua_tostring(lua, 1), lua_tostring(lua, 2));
lua_remove(lua, 1);
lua_remove(lua, 1);
return (*fp == NULL ? 0 : 1);
}
/*
* Lua implementation of the POSIX pclose() function.
*/
static int
ifsys_pclose(lua_State *lua)
{
lua_Number r;
FILE *fp;
if (lua_gettop(lua) != 1)
luaL_error(lua, "wrong number of arguments");
luaL_checktype(lua, 1, LUA_TUSERDATA);
fp = *(FILE **) (lua_touserdata(lua, 1));
r = (lua_Number) (pclose(fp) >> 8);
lua_pop(lua, 1);
if (r == -1)
return 0;
fp = NULL;
lua_pushnumber(lua, r);
return 1;
}
/*
* Reads a line from a file stream.
*/
static int
ifsys_read(lua_State *lua)
{
FILE *fp;
luaL_Buffer b;
char *c;
size_t n;
if (lua_gettop(lua) != 1)
luaL_error(lua, "wrong number of arguments");
luaL_checktype(lua, 1, LUA_TUSERDATA);
fp = *(FILE **) (lua_touserdata(lua, 1));
lua_pop(lua, 1);
luaL_buffinit(lua, &b);
for (;;) {
c = luaL_prepbuffer(&b);
if (fgets(c, LUAL_BUFFERSIZE, fp) == NULL && feof(fp)) {
luaL_pushresult(&b);
return (lua_strlen(lua, -1) > 0);
}
n = strlen(c);
if (c[n - 1] != '\n')
luaL_addsize(&b, n);
else {
luaL_addsize(&b, n);
luaL_pushresult(&b);
return 1;
}
}
}
/*
* Writes a string to a file stream.
*/
static int
ifsys_write(lua_State *lua)
{
size_t n;
if (lua_gettop(lua) != 2)
luaL_error(lua, "wrong number of arguments");
luaL_checktype(lua, 1, LUA_TUSERDATA);
luaL_checktype(lua, 2, LUA_TSTRING);
n = fwrite(lua_tostring(lua, 2), sizeof(char), strlen(lua_tostring(lua,
2)), *(FILE **) (lua_touserdata(lua, 1)));
lua_pop(lua, 2);
lua_pushboolean(lua, (n != 0));
return 1;
}
/*
* Lua implementation of the POSIX sleep() function.
*/
static int
ifsys_sleep(lua_State *lua)
{
if (lua_gettop(lua) != 1)
luaL_error(lua, "wrong number of arguments");
luaL_checktype(lua, 1, LUA_TNUMBER);
lua_pushnumber(lua,
(lua_Number) (sleep) ((unsigned int)(lua_tonumber(lua, 1))));
lua_remove(lua, 1);
return 1;
}
/*
* Lua implementation of the BSD daemon() function.
*/
static int
ifsys_daemon(lua_State *lua)
{
if (lua_gettop(lua) != 0)
luaL_error(lua, "wrong number of arguments");
switch (fork()) {
case -1:
fprintf(stderr, "forking; %s\n", strerror(errno));
exit(1);
break;
case 0:
break;
default:
exit(0);
break;
}
if (setsid() == -1) {
fprintf(stderr, "creating session; %s\n", strerror(errno));
exit(1);
}
switch (fork()) {
case -1:
fprintf(stderr, "creating session; %s\n", strerror(errno));
exit(1);
break;
case 0:
break;
default:
exit(0);
break;
}
close(STDIN_FILENO);
close(STDOUT_FILENO);
close(STDERR_FILENO);
if (open("/dev/null", O_RDWR) == -1 ||
dup(STDIN_FILENO) == -1 ||
dup(STDIN_FILENO) == -1)
fprintf(stderr, "creating session; %s\n", strerror(errno));
return 0;
}
/*
* Open imapfilter library of system's functions.
*/
LUALIB_API int
luaopen_ifsys(lua_State *lua)
{
luaL_register(lua, "ifsys", ifsyslib);
return 1;
}

12
version.h Normal file
View File

@ -0,0 +1,12 @@
#ifndef VERSION_H
#define VERSION_H
/* Program's version number. */
#define VERSION "2.2.3"
/* Program's copyright. */
#define COPYRIGHT "Copyright (c) 2001-2011 Eleftherios Chatzimparmpas"
#endif /* VERSION_H */