Created
September 13, 2017 07:43
-
-
Save a0x/b28b06ddc35fdbd6376e3ad37fa8d0f3 to your computer and use it in GitHub Desktop.
Simple Auth Service for Nginx Mail Proxy
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# This is a basic HTTP server, confirming to the authentication protocol | |
# required by Nginx's mail module. | |
require 'logger' | |
require 'rack' | |
module MailAuth | |
# setup a protocol-to-port mapping | |
Port = { | |
'smtp' => '25', | |
'pop3' => '110', | |
'imap' => '143' | |
} | |
class Handler | |
def initialize | |
# setup logging, as a mail server | |
@log = Logger.new("| logger -p mail.info") | |
# replacing the normal timestamp by the service name and pid | |
@log.datetime_format = "nginx_mail_proxy_auth pid: " | |
# the "Auth-Server" header must be an IP address | |
@mailhost = '127.0.0.1' | |
# set a maximum number of login attempts | |
@max_attempts = 3 | |
# our authentication 'database' will just be a fixed hash for | |
# this example | |
# it should be replaced by a method to connect to LDAP or a | |
# database | |
@auths = {"test:1234" => "127.0.0.1"} | |
end | |
def call(env) | |
# our headers are contained in the environment | |
@env = env | |
# set up the request and response objects | |
@req = Rack::Request.new(env) | |
@res = Rack::Response.new | |
# pass control to the method named after the Http verb | |
# with which we're called | |
self.send(@req.request_method.downcase) | |
# come back here to finish the response when done | |
@res.finish | |
end | |
def get | |
# the authentication mechanism | |
meth = @env['Http_AUTH_METHOD'] | |
# the username (login) | |
user = @env['Http_AUTH_USER'] | |
# the password, either in the clear or encrypted, depending on | |
# the authentication mechanism used | |
pass = @env['Http_AUTH_PASS'] | |
# need the salt to encrypt the cleartext password, used for some | |
# authentication mechanisms, not in our example | |
salt = @env['Http_AUTH_SALT'] | |
# this is the protocol being proxied | |
proto = @env['Http_AUTH_PROTOCOL'] | |
# the number of attempts needs to be an integer | |
attempt = @env['Http_AUTH_LOGIN_ATTEMPT'].to_i | |
# not used in our implementation, but these are here for reference | |
client = @env['Http_CLIENT_IP'] | |
host = @env['Http_CLIENT_HOST'] | |
# fail if more than the maximum login attempts are tried | |
if attempt > @max_attempts | |
@res["Auth-Status"] = "Maximum login atempts exceeded" | |
return | |
end | |
# for the special case where no authentication is done | |
# on smtp transactions, the following is in nginx.conf: | |
# smtp_auth none; | |
# may want to setup a lookup table to steer certain senders | |
# to particular SMTP servers | |
if meth == 'none' && proto == 'smtp' | |
helo = @env[Http_AUTH_SMTP_HELO] | |
# want to get just the address from these two here | |
from = @env['Http_AUTH_SMTP_FROM'].split(/: /)[1] | |
to = @env['Http_AUTH_SMTP_TO'].split(/: /)[1] | |
@res["Auth-Status"] = "OK" | |
@res["Auth-Server"] = @mailhost | |
# return the correct port for this protocol | |
@res["Auth-Port"] = MailAuth::Port[proto] | |
@log.info("a main from #{from} on #{helo} for #{to}") | |
# try to authenticate using the headers provided | |
elsif auth(user, pass) | |
@res["Auth-Status"] = "OK" | |
@res["Auth-Server"] = @mailhost | |
# return the correct port for this protocol | |
@res["Auth-Port"] = MailAuth::Port[proto] | |
# if we're using APOP. we need to return the password in cleartext | |
if meth == 'apop' && proto == 'pop3' | |
@res["Auth-User"] = user | |
@res["Auth-Pass"] = pass | |
end | |
@log.info("+ #{user} from #{client}") | |
# the authentication attempts has fafiled | |
else | |
# if authentication was unsuccessful, we return an appropriate response | |
@res["Auth-Status"] = "Invalid login or password" | |
# and set the wait time in seconds before the client may make | |
# another authentication attempt | |
@res["Auth-Wait"] = "3" | |
# we can also set the error code to be returned to the SMTP client | |
@res["Auth-Error-Code"] = "535 5.7.8" | |
@log.info("! #{user} from #{client}") | |
end | |
end | |
private | |
# our authentication method, adapt to fit your environment | |
def auth(user, pass) | |
# this simply returns the calue looked-up by the 'user:pass' key | |
if @auths.key?("#{user}:#{pass}") | |
@mailhost = @auths["#{user}:#{pass}"] | |
return @mailhost | |
else | |
return false | |
end | |
end | |
# just in case some other process tries to access the service | |
# and sends something other than a GET | |
def method_missing(env) | |
@res.status = 404 | |
end | |
end # class MailAuthHandler | |
end # module MailAuth | |
# setup Rack middleware | |
use Rack::ShowStatus | |
# map the /auth URI to our authentication handler | |
map "/auth" do | |
run MailAuth::Handler.new | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment