-- ************************************************************************** -- -- FreePOPs pop3 forwarding plugin -- -- $Id: popforward.lua,v 1.18 2006/01/26 10:41:37 gareuselesinge Exp $ -- -- Released under the GNU/GPL license -- Written by Enrico Tassi -- ************************************************************************** -- -- ************************************************************************** -- -- WARNING the bindings for psock in lua are really poor WARNING -- -- ************************************************************************** -- PLUGIN_VERSION = "0.0.3" PLUGIN_NAME = "POPforward" PLUGIN_REQUIRE_VERSION = "0.0.97" PLUGIN_LICENSE = "GNU/GPL" PLUGIN_URL = "http://www.freepops.org/download.php?file=popforward.lua" PLUGIN_HOMEPAGE = "http://www.freepops.org/" PLUGIN_AUTHORS_NAMES = {"Enrico Tassi"} PLUGIN_AUTHORS_CONTACTS = {"gareuselesinge (at) users (.) sourceforge (.) net"} PLUGIN_DOMAINS = {"@..."} PLUGIN_PARAMETERS = { {name = "realusername", description = { it = [[Se lanci il plugin con username foo@popforward.lua hai bisogno di questo per scegliere lo username reale]], en = [[If you start the plugin with the username foo@popforward.lua then you need this option to set the real username]],} }, {name = "host", description = { it = [[L'hostname del server POP3 a cui connetterti, puoi anche specificare la porta separandola con :. Esempio: 'in.virgilio.it:110'.]], en = [[The POP3 server hostname. You can specify the port in the hostname:portnumber way.]],} }, {name = "port", description = { it = [[Per specificare la porta dell'host a cui connettersi, se non gia' specificato in host con i :]], en = [[To choose to server port to connect to. Use this if you have not specifyed the port in the host parameter.]],} }, {name = "pipe", description = { en = [[Pipe the messages to the specified command before passing them to the mail client. Example: '/usr/bin/spamc -t 10']], it = [[Filtra il messaggio con il comando specificato prima di passarlo al client. Esempio: '/usr/bin/spamc -t 10']],} }, {name = "pipe_limit", description = { it = [[Limita i messaggi filtrati a quelli la cui dimensione e' minore di quelle specificata. Con 0 li filtra tutti. Default: 0. ]], en = [[Limit the maximum size of piped messages, the default (0) is to pipe all of them.]],} }, } PLUGIN_DESCRIPTIONS = { it=[[Questo e' un proxy POP3. Leggi i parametri per conoscere le feature di cui dispone]], en=[[This is a POP3 proxy. Read the parameters to know the additional features]] } -- ************************************************************************** -- -- State -- ************************************************************************** -- pf_state = { socket = nil, pipe = nil, pipe_limit = 0, listed = false, stat_done = false, } -- ************************************************************************** -- -- helpers -- ************************************************************************** -- -- ask for command and call f on the result function single_line(cmd,f) local l = pf_state.socket:send(cmd) if l < 0 then log.error_print("Short send of "..l.. " instead of "..string.len(cmd).."\n") return POPSERVER_ERR_NETWORK end local l = pf_state.socket:recv() or "-ERR network error" if not (string.find(l,"^+OK")) then log.error_print(l) return POPSERVER_ERR_UNKNOWN end if f then return f(l) else return POPSERVER_ERR_OK end end -- pass all to the client until the ".\r\n" is reached function do_pipe(pdata) return function(s) local l = nil while l ~= "." do l = pf_state.socket:recv() if not l then log.error_print("ERROR??") return POPSERVER_ERR_NETWORK end if l ~= "." then popserver_callback(l.."\r\n",pdata) end end return POPSERVER_ERR_OK end end -- generates a function that repeats f on each line until "." ir reached function do_repeat(f) return function(s) local l = nil while l ~= "." do l = pf_state.socket:recv() if not l then log.error_print("ERROR?") return POPSERVER_ERR_NETWORK end if l ~= "." then f(l) end end return POPSERVER_ERR_OK end end -- spluits a string "aaa bb cccc" in a table {"aaa","bb","cccc"} -- usefull for the piping feature local function pipe_split(s) if type(s) == "table" then return s elseif type(s) == "nil" then return nil else local t = {} for x in string.gfind(s,"([^ ]+)") do table.insert(t,x) end return t end end -- ensure that stat is called local function ensure_stat(pstate) if pf_state.stat_done then return POPSERVER_ERR_OK end return stat(pstate) end -- ensure we know all the messages size local function ensure_list_all(pstate) if pf_state.listed then return POPSERVER_ERR_OK end return list_all(pstate) end -- ************************************************************************** -- -- here we are! -- ************************************************************************** -- -- Must save the mailbox name function user(pstate,username) pf_state.pipe = pipe_split(freepops.MODULE_ARGS.pipe) pf_state.pipe_limit = freepops.MODULE_ARGS.pipe_limit or 0 pf_state.pipe_limit = tonumber(pf_state.pipe_limit) -- sanity checks if freepops.MODULE_ARGS.host == nil then log.error_print("host must be non null") return POPSERVER_ERR_AUTH end local _,_,host,port=string.find(freepops.MODULE_ARGS.host,"(.*):(%d+)") if host == nil then host = freepops.MODULE_ARGS.host end if port ~= nil and freepops.MODULE_ARGS.port ~= nil then log.error_print("you should use host:port or set ".. "explicity the port, but not both") return POPSERVER_ERR_AUTH end if port == nil and freepops.MODULE_ARGS.port == nil then log.error_print("you should use host:port or set ".. "explicity the port") return POPSERVER_ERR_AUTH end port = port or freepops.MODULE_ARGS.port --here we are pf_state.socket = psock.connect(host,port,psock.NONE) --for debugging --pf_state.socket = psock.connect(host,port,psock.ALL) if not pf_state.socket then log.error_print("unable to connect") return POPSERVER_ERR_NETWORK end local l = nil l = pf_state.socket:recv() if not l then log.error_print("Error receiving the welcome") return POPSERVER_ERR_NETWORK end return single_line("USER ".. (freepops.MODULE_ARGS.realusername or username),nil) end -- Must login function pass(pstate,password) return single_line("PASS "..password,nil) end -- Must quit without updating function quit(pstate) local rc = single_line("QUIT",nil) return rc end -- Update the mailbox status and quit function quit_update(pstate) return quit(pstate) end -- Fill the number of messages and their size function stat(pstate) local f = function(l) for n,s in string.gfind(l,"+OK (%d+) (%d+)") do set_popstate_nummesg(pstate,n) set_popstate_boxsize(pstate,s) return POPSERVER_ERR_OK end log.dbg("Ubale to find +OK in "..l.."\n") return POPSERVER_ERR_UNKNOWN end pf_state.stat_done = true return single_line("STAT",f) end -- Fill msg uidl field function uidl(pstate,msg) local rc = ensure_stat(pstate) if rc ~= POPSERVER_ERR_OK then return rc end local f = function(l) for n,u in string.gfind(l,"+OK (%d+) (%d+)") do set_mailmessage_uidl(pstate,n,u) return POPSERVER_ERR_OK end return POPSERVER_ERR_UNKNOWN end return single_line("UIDL "..msg,f) end -- Fill all messages uidl field function uidl_all(pstate) local rc = ensure_stat(pstate) if rc ~= POPSERVER_ERR_OK then return rc end local f = do_repeat(function(l) for n,u in string.gfind(l,"(%d+) (%d+)") do set_mailmessage_uidl(pstate,n,u) print(l) end end) return single_line("UIDL",f) end -- Fill msg size function list(pstate,msg) local rc = ensure_stat(pstate) if rc ~= POPSERVER_ERR_OK then return rc end local f = function(l) for n,u in string.gfind(l,"+OK (%d+) (%d+)") do set_mailmessage_size(pstate,n,u) return POPSERVER_ERR_OK end return POPSERVER_ERR_UNKNOWN end return single_line("LIST "..msg,f) end -- Fill all messages size function list_all(pstate) local rc = ensure_stat(pstate) if rc ~= POPSERVER_ERR_OK then return rc end local f = do_repeat(function(l) for n,u in string.gfind(l,"(%d+) (%d+)") do set_mailmessage_size(pstate,n,u) end end) pf_state.listed = true return single_line("LIST",f) end -- Unflag each message merked for deletion function rset(pstate) local rc = ensure_stat(pstate) if rc ~= POPSERVER_ERR_OK then return rc end return single_line("RSET",nil) end -- Mark msg for deletion function dele(pstate,msg) return single_line("DELE "..msg,nil) end -- Do nothing function noop(pstate) return single_line("NOOP",nil) end -- Get first lines message msg lines, must call -- popserver_callback to send the data function top(pstate,msg,lines,pdata) return single_line("TOP "..msg.." "..lines,do_pipe(pdata)) end -- Get message msg, must call -- popserver_callback to send the data function retr(pstate,msg,pdata) if pf_state.pipe ~= nil then if pf_state.pipe_limit ~= 0 then ensure_list_all(pstate) end local size = get_mailmessage_size(pstate,msg) if pf_state.pipe_limit == 0 or size < pf_state.pipe_limit then -- fetch the message local m = {} local f = do_repeat(function(l) table.insert(m,l) end) local rc = single_line("RETR "..msg,f) if rc ~= POPSERVER_ERR_OK then -- fixme end m = table.concat(m,"\r\n") .. "\r\n" -- pipe it local r,w = io.dpopen(unpack(pf_state.pipe)) if r == nil or w == nil then --fixme end w:write(m) w:close() -- read it back m = r:read("*a") r:close() -- send it popserver_callback(m,pdata) return POPSERVER_ERR_OK else -- too big return single_line("RETR "..msg,do_pipe(pdata)) end else -- no pipe asked return single_line("RETR "..msg,do_pipe(pdata)) end end -- Is called to initialize the module function init(pstate) log.dbg("FreePOPs plugin '".. PLUGIN_NAME.."' version '"..PLUGIN_VERSION.."' started!\n") freepops.export(pop3server) -- checks on globals freepops.set_sanity_checks() return POPSERVER_ERR_OK end -- EOF -- ************************************************************************** --