Created
September 13, 2017 07:43
-
-
Save a0x/b28b06ddc35fdbd6376e3ad37fa8d0f3 to your computer and use it in GitHub Desktop.
Revisions
-
a0x created this gist
Sep 13, 2017 .There are no files selected for viewing
This file contains hidden or 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 charactersOriginal 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