mirror of
https://github.com/moparisthebest/imapfilter
synced 2024-12-21 07:08:49 -05:00
Initial commit.
This commit is contained in:
commit
5d4ed5ed3f
19
LICENSE
Normal file
19
LICENSE
Normal 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
105
Makefile
Normal 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
192
NEWS
Normal 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
60
README
Normal 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
239
account.lua
Normal 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
73
auth.c
Normal 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
66
auxiliary.lua
Normal 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
60
buffer.c
Normal 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
23
buffer.h
Normal 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
235
cert.c
Normal 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
510
common.lua
Normal 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
251
configure
vendored
Executable 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
|
609
deprecated.lua
Normal file
609
deprecated.lua
Normal 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
132
file.c
Normal 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
484
imap.c
Normal 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
74
imapfilter.1
Normal 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
186
imapfilter.c
Normal 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
285
imapfilter.h
Normal 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
1112
imapfilter_config.5
Normal file
File diff suppressed because it is too large
Load Diff
59
list.c
Normal file
59
list.c
Normal 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
16
list.h
Normal 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
240
log.c
Normal 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
343
lua.c
Normal 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
1243
mailbox.lua
Normal file
File diff suppressed because it is too large
Load Diff
92
memory.c
Normal file
92
memory.c
Normal 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
93
message.lua
Normal 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
74
misc.c
Normal 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
78
namespace.c
Normal 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
5
options.lua
Normal 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
45
pathnames.h
Normal 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
268
pcre.c
Normal 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
53
regex.lua
Normal 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
42
regexp.c
Normal 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
24
regexp.h
Normal 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
918
request.c
Normal 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
846
response.c
Normal 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
129
sample.config.lua
Normal 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
103
sample.extend.lua
Normal 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
101
session.c
Normal 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
37
session.h
Normal 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
700
set.lua
Normal 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
45
signal.c
Normal 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
440
socket.c
Normal 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
308
system.c
Normal 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;
|
||||
}
|
Loading…
Reference in New Issue
Block a user