site/api/compose.lua (98 lines of code) (raw):
--[[
 Licensed to the Apache Software Foundation (ASF) under one or more
 contributor license agreements.  See the NOTICE file distributed with
 this work for additional information regarding copyright ownership.
 The ASF licenses this file to You under the Apache License, Version 2.0
 (the "License"); you may not use this file except in compliance with
 the License.  You may obtain a copy of the License at
     http://www.apache.org/licenses/LICENSE-2.0
 Unless required by applicable law or agreed to in writing, software
 distributed under the License is distributed on an "AS IS" BASIS,
 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 See the License for the specific language governing permissions and
 limitations under the License.
]]--
-- This is compose.lua - a script for sending replies or new topics to lists
local JSON = require 'cjson'
local elastic = require 'lib/elastic'
local user = require 'lib/user'
local config = require 'lib/config'
local smtp = require 'socket.smtp'
local cross = require 'lib/cross'
function handle(r)
    local account = user.get(r)
    cross.contentType(r, "application/json")
    
    -- make sure the user is logged in
    if account and account.cid then
        -- parse response, up to 1MB of it. if >1MB, we're gonna pretend we never saw anything ;)
        local post = r:parsebody(1024*1024)
        
        -- check that recipient, subject and body exists
        if post.to and post.subject and post.body then
            -- validate recipient
            local to = ("<%s>"):format(post.to)
            local fp, lp = post.to:match("([^@]+)@([^@]+)")
            local domainIsOkay = false
            
            -- check that recipient is whitelisted in config.lua
            if type(config.accepted_domains) == "string" then
                if r.strcmp_match(lp, config.accepted_domains) or config.accepted_domains == "*" then
                    domainIsOkay = true
                end
            elseif type(config.accepted_domains) == "table" then
                for k, ad in pairs(config.accepted_domains) do
                    if r.strcmp_match(lp, ad) or ad == "*" then
                        domainIsOkay = true
                        break
                    end
                end
            end
            
            -- if we can send, then...
            if domainIsOkay then
                -- find user's full name
                local fname = nil
                if account.preferences then
                    fname = account.preferences.fullname
                end
                -- construct sender name+address
                local fr = ([[%s<%s>]]):format(fname or account.credentials.fullname, account.credentials.email)
                
                -- Using alt email??
                if account.credentials.altemail and post.alt then
                    for k, v in pairs(account.credentials.altemail) do
                        if v.email == post.alt then
                            fr = ([[%s<%s>]]):format(fname or account.credentials.fullname, v.email)
                            break
                        end
                    end
                end
                
                -- standard headers + headers we need ourselves for parsing in the archiver (notifications etc)
                local headers = {
                    ['Content-Type'] = 'text/plain; charset=utf-8',
                    ['X-PonyMail-Sender'] = r:sha1(account.cid),
                    ['X-PonyMail-Agent'] = "PonyMail Composer/0.2",
                    ['message-id'] = ("<pony-%s-%s@%s>"):format(r:sha1(account.cid), r:sha1(r:clock() .. os.time() .. r.useragent_ip), post.to:gsub("@", ".")),
                    to = to,
                    subject = post.subject,
                    from = fr,
                }
                
                -- set references and IRT if need be
                if post['references'] then
                    headers['references'] = post['references']
                end
                if post['in-reply-to'] then
                    headers['in-reply-to'] = post['in-reply-to']
                end
                local msgbody = post.body
                
                -- set an email footer if specified in config.lua
                if config.email_footer then
                    local subs = {
                        list = to:gsub("[<>]", ""),
                        domain = r.hostname,
                        port = r.port,
                        msgid = headers['message-id']
                    }
                    msgbody = msgbody .. "\n" .. config.email_footer:gsub("$([a-z]+)", function(a) return subs[a] or a end)
                end
                
                -- construct the smtp object
                local source = smtp.message{
                        headers = headers,
                        body = msgbody
                    }
                
                -- send email!
                local rv, er = smtp.send{
                    from = fr:gsub(".-<", "<"), -- MTAs only need the email address in MAIL FROM, cut out the name.
                    rcpt = to,
                    source = source,
                    server = config.mailserver
                }
                
                -- let the user know what happened
                r:puts(JSON.encode{
                    result = rv,
                    error = er,
                    src = headers
                })
            else
                r:puts(JSON.encode{
                    error = "Invalid recipient specified."
                })
            end
        else
            r:puts(JSON.encode{
                    error = "Invalid or missing headers",
                    headers = post
                })
        end
    else
        r:puts[[{"error": "You need to be logged in before you can send emails"}]]
    end
    return cross.OK
end
cross.start(handle)