diff --git a/nselib/pop3.lua b/nselib/pop3.lua
index 179bd5266e..a6ca73da5f 100644
--- a/nselib/pop3.lua
+++ b/nselib/pop3.lua
@@ -1,235 +1,194 @@
---
--- POP3 functions.
+-- POP3 helper functions for NSE scripts.
+--
+-- @copyright Same as Nmap
+-- See https://nmap.org/book/man-legal.html
--
--- @copyright Same as Nmap--See https://nmap.org/book/man-legal.html
-local base64 = require "base64"
-local comm = require "comm"
-local match = require "match"
-local stdnse = require "stdnse"
-local string = require "string"
+local base64 = require "base64"
+local comm = require "comm"
+local match = require "match"
+local stdnse = require "stdnse"
local stringaux = require "stringaux"
-local table = require "table"
-_ENV = stdnse.module("pop3", stdnse.seeall)
-local HAVE_SSL, openssl = pcall(require,'openssl')
+local string = string
+local table = table
+_ENV = stdnse.module("pop3", stdnse.seeall)
-err = {
- none = 0,
- userError = 1,
- pwError = 2,
- informationMissing = 3,
- OpenSSLMissing = 4,
+local HAVE_SSL, openssl = pcall(require, "openssl")
+
+-- Error codes returned by login helpers
+local err = {
+ none = 0,
+ userError = 1,
+ pwError = 2,
+ informationMissing = 3,
+ OpenSSLMissing = 4,
}
---
--- Check a POP3 response for "+OK".
--- @param line First line returned from an POP3 request.
--- @return The string "+OK" if found or nil otherwise.
-function stat(line)
- return string.match(line, "+OK")
+-- Check whether a POP3 response indicates success.
+-- @param line POP3 response line
+-- @return true if response starts with "+OK"
+local function stat(line)
+ return type(line) == "string" and line:match("^%+OK")
end
-
-
---
--- Try to log in using the USER/PASS commands.
--- @param socket Socket connected to POP3 server.
--- @param user User string.
--- @param pw Password string.
--- @return Status (true or false).
--- @return Error code if status is false.
+-- USER / PASS authentication
function login_user(socket, user, pw)
socket:send("USER " .. user .. "\r\n")
- local status, line = socket:receive_lines(1)
- if not stat(line) then return false, err.userError end
- socket:send("PASS " .. pw .. "\r\n")
+ local _, line = socket:receive_lines(1)
+ if not stat(line) then
+ return false, err.userError
+ end
- status, line = socket:receive_lines(1)
+ socket:send("PASS " .. pw .. "\r\n")
+ _, line = socket:receive_lines(1)
- if stat(line) then return true, err.none
- else return false, err.pwError
+ if stat(line) then
+ return true, err.none
end
-end
+ return false, err.pwError
+end
---
--- Try to login using the AUTH command using SASL/Plain method.
--- @param socket Socket connected to POP3 server.
--- @param user User string.
--- @param pw Password string.
--- @return Status (true or false).
--- @return Error code if status is false.
+-- SASL PLAIN authentication
function login_sasl_plain(socket, user, pw)
-
local auth64 = base64.enc(user .. "\0" .. user .. "\0" .. pw)
socket:send("AUTH PLAIN " .. auth64 .. "\r\n")
- local status, line = socket:receive_lines(1)
-
+ local _, line = socket:receive_lines(1)
if stat(line) then
return true, err.none
- else
- return false, err.pwError
end
+
+ return false, err.pwError
end
---
--- Try to login using the AUTH command using SASL/Login method.
--- @param user User string.
--- @param pw Password string.
--- @param pw String containing password to login.
--- @return Status (true or false).
--- @return Error code if status is false.
+-- SASL LOGIN authentication
function login_sasl_login(socket, user, pw)
+ socket:send("AUTH LOGIN\r\n")
- local user64 = base64.enc(user)
+ local _, line = socket:receive_lines(1)
+ local prompt = base64.dec(string.sub(line or "", 3)):lower()
- local pw64 = base64.enc(pw)
+ if not prompt:find("user") then
+ return false, err.userError
+ end
- socket:send("AUTH LOGIN\r\n")
+ socket:send(base64.enc(user) .. "\r\n")
+ _, line = socket:receive_lines(1)
- local status, line = socket:receive_lines(1)
- if not base64.dec(string.sub(line, 3)) == "User Name:" then
+ prompt = base64.dec(string.sub(line or "", 3)):lower()
+ if not prompt:find("pass") then
return false, err.userError
end
- socket:send(user64)
+ socket:send(base64.enc(pw) .. "\r\n")
+ _, line = socket:receive_lines(1)
- local status, line = socket:receive_lines(1)
+ if stat(line) then
+ return true, err.none
+ end
- if not base64.dec(string.sub(line, 3)) == "Password:" then
- return false, err.userError
+ return false, err.pwError
+end
+
+---
+-- APOP authentication (RFC 1939)
+function login_apop(socket, user, pw, challenge)
+ if not HAVE_SSL then
+ return false, err.OpenSSLMissing
end
- socket:send(pw64)
+ if type(challenge) ~= "string" then
+ return false, err.informationMissing
+ end
- local status, line = socket:receive_lines(1)
+ local digest = stdnse.tohex(openssl.md5(challenge .. pw))
+ socket:send(("APOP %s %s\r\n"):format(user, digest))
+ local _, line = socket:receive_lines(1)
if stat(line) then
return true, err.none
- else
- return false, err.pwError
end
+
+ return false, err.pwError
end
---
--- Try to login using the APOP command.
--- @param socket Socket connected to POP3 server.
--- @param user User string.
--- @param pw Password string.
--- @param challenge String containing challenge from POP3 server greeting.
--- @return Status (true or false).
--- @return Error code if status is false.
-function login_apop(socket, user, pw, challenge)
- if type(challenge) ~= "string" then return false, err.informationMissing end
+-- SASL CRAM-MD5 authentication
+function login_sasl_crammd5(socket, user, pw)
+ if not HAVE_SSL then
+ return false, err.OpenSSLMissing
+ end
- local apStr = stdnse.tohex(openssl.md5(challenge .. pw))
- socket:send(("APOP %s %s\r\n"):format(user, apStr))
+ socket:send("AUTH CRAM-MD5\r\n")
+ local _, line = socket:receive_lines(1)
- local status, line = socket:receive_lines(1)
+ local challenge = base64.dec(string.sub(line or "", 3))
+ local digest = stdnse.tohex(openssl.hmac("md5", pw, challenge))
+ local auth = base64.enc(user .. " " .. digest)
- if (stat(line)) then
+ socket:send(auth .. "\r\n")
+ _, line = socket:receive_lines(1)
+
+ if stat(line) then
return true, err.none
- else
- return false, err.pwError
end
+
+ return false, err.pwError
end
---
--- Asks a POP3 server for capabilities.
---
--- See RFC 2449.
--- @param host Host to be queried.
--- @param port Port to connect to.
--- @return Table containing capabilities or nil on error.
--- @return nil or String error message.
+-- Query POP3 server capabilities (RFC 2449)
function capabilities(host, port)
+ local socket, _, _, greeting =
+ comm.tryssl(host, port, "", { recv_before = true })
- local socket, line, bopt, first_line = comm.tryssl(host, port, "" , {request_timeout=10000, recv_before=true})
if not socket then
- return nil, "Could Not Connect"
+ return nil, "Could not connect"
end
- if not stat(first_line) then
- return nil, "No Response"
+
+ if not stat(greeting) then
+ socket:close()
+ return nil, "Invalid POP3 greeting"
end
local capas = {}
- if string.find(first_line, "<[%p%w]+>") then
+
+ -- APOP challenge present in greeting
+ if greeting:find("<[^>]+>") then
capas.APOP = {}
end
- local status = socket:send("CAPA\r\n")
- if( not(status) ) then
- return nil, "Failed to send"
- end
+ socket:send("CAPA\r\n")
+ local status, response =
+ socket:receive_buf(match.pattern_limit("%.\r?\n", 4096), false)
- status, line = socket:receive_buf(match.pattern_limit("%.", 2048), false)
- if( not(status) ) then
- return nil, "Failed to receive"
- end
socket:close()
+ if not status then
+ return nil, "Failed to receive CAPA response"
+ end
- local lines = stringaux.strsplit("\r\n",line)
- if not stat(table.remove(lines,1)) then
+ local lines = stringaux.strsplit("\r\n", response)
+ if not stat(table.remove(lines, 1)) then
capas.capa = false
return capas
end
for _, line in ipairs(lines) do
- if ( line and #line>0 ) then
- local capability = line:sub(line:find("[%w-]+"))
- line = line:sub(#capability + 2)
- if ( line ~= "" ) then
- capas[capability] = stringaux.strsplit(" ", line)
- else
- capas[capability] = {}
- end
+ if line and #line > 0 then
+ local name, args = line:match("^(%S+)%s*(.*)")
+ capas[name] = args ~= "" and stringaux.strsplit(" ", args) or {}
end
end
return capas
end
----
--- Try to login using the AUTH command using SASL/CRAM-MD5 method.
--- @param socket Socket connected to POP3 server.
--- @param user User string.
--- @param pw Password string.
--- @return Status (true or false).
--- @return Error code if status is false.
-function login_sasl_crammd5(socket, user, pw)
-
- socket:send("AUTH CRAM-MD5\r\n")
-
- local status, line = socket:receive_lines(1)
-
- local challenge = base64.dec(string.sub(line, 3))
-
- local digest = stdnse.tohex(openssl.hmac('md5', pw, challenge))
- local authStr = base64.enc(user .. " " .. digest)
- socket:send(authStr .. "\r\n")
-
- local status, line = socket:receive_lines(1)
-
- if stat(line) then
- return true, err.none
- else
- return false, err.pwError
- end
-end
-
--- Overwrite functions requiring OpenSSL if we got no OpenSSL.
-if not HAVE_SSL then
-
- local no_ssl = function()
- return false, err.OpenSSLMissing
- end
-
- login_apop = no_ssl
- login_sasl_crammd5 = no_ssl
-end
-
-
-return _ENV;
+return _ENV