Skip to content

Instantly share code, notes, and snippets.

@a0x
Created September 13, 2017 07:43
Show Gist options
  • Save a0x/b28b06ddc35fdbd6376e3ad37fa8d0f3 to your computer and use it in GitHub Desktop.
Save a0x/b28b06ddc35fdbd6376e3ad37fa8d0f3 to your computer and use it in GitHub Desktop.

Revisions

  1. a0x created this gist Sep 13, 2017.
    139 changes: 139 additions & 0 deletions nginx_mail_proxy_auth.ru
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,139 @@


    # 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