Files
sqlmap/extra/dbwire/__init__.py
T

127 lines
4.0 KiB
Python

#!/usr/bin/env python
"""
Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org)
See the file 'LICENSE' for copying permission
"""
"""
dbwire - minimal, dependency-free (stdlib-only) database wire-protocol clients used as a fallback for
sqlmap's direct ('-d') connection when no native driver (and no SQLAlchemy) is installed.
Design note: connectors speak a *wire protocol*, not a product, so a single client covers the whole
compatible family - e.g. the PostgreSQL client also serves CockroachDB, CrateDB, Redshift and Greenplum;
a MySQL client serves MariaDB/TiDB/Aurora. Where a family split the protocol, so does the client: tds.py
speaks Microsoft's TDS 7.x and sybase.py the TDS 5.0 that ASE kept. Each module exposes a small PEP 249
(DB-API 2.0) subset (connect(), Connection.cursor()/commit()/close(), Cursor.execute()/fetchall()).
"""
import socket
__version__ = "0.1"
apilevel = "2.0"
threadsafety = 1
paramstyle = "pyformat"
# PEP 249 exception hierarchy (shared by every wire module)
class Error(Exception):
pass
class InterfaceError(Error):
pass
class DatabaseError(Error):
pass
class OperationalError(DatabaseError):
pass
class DataError(DatabaseError):
pass
class IntegrityError(DatabaseError):
pass
class ProgrammingError(DatabaseError):
pass
class InternalError(DatabaseError):
pass
class NotSupportedError(DatabaseError):
pass
def http_origin(host, port):
"""
'http://host:port', with a literal IPv6 address bracketed as RFC 3986 requires. Without the brackets
the colons in the address are parsed as the port separator and the URL is simply wrong.
"""
host = host or "localhost"
if ":" in host and not host.startswith("["):
host = "[%s]" % host
return "http://%s:%d" % (host, int(port))
def connection_lost(ex):
"""
Turn a raw socket/OS failure into the DB-API hierarchy above.
Callers of a PEP 249 driver only ever catch Error and its subclasses, so a bare socket.error escaping
from a send/recv leaves them with an unhandled traceback instead of a handled connection failure.
"""
return OperationalError("connection lost (%s)" % ex)
def handshake_done(sock):
"""
Drop the connect deadline, once the login exchange is over.
connect_timeout has to stay armed THROUGH the handshake, not just the TCP connect: a peer that
accepts the connection and then says nothing (a wrong port, a silent proxy, a dropping firewall) is
perfectly alive as far as keepalive() below is concerned, so an unbounded login read waits forever.
A query is the opposite case - see keepalive().
"""
try:
sock.settimeout(None)
except Exception:
pass
def keepalive(sock):
"""
Ask the kernel to probe an idle connection, so a peer that dies without a FIN is eventually detected.
Deliberately NOT a read timeout: a legitimate query can take minutes on a big table, and a fixed
deadline would kill it. Keepalive distinguishes a dead peer from a slow one, which is the actual
failure being guarded against. Best-effort - the options are not portable everywhere.
"""
try:
sock.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)
for name, value in (("TCP_KEEPIDLE", 60), ("TCP_KEEPINTVL", 10), ("TCP_KEEPCNT", 5)):
if hasattr(socket, name):
sock.setsockopt(socket.IPPROTO_TCP, getattr(socket, name), value)
except Exception:
pass
def recvn(sock, n):
"""
Read exactly n bytes off `sock`, or raise - every wire protocol here is framed, so a short read is a
desynchronized stream, not a smaller message.
Shared because it was five identical copies: a fix to the recv loop has to land once, not per module.
"""
buf = b""
while len(buf) < n:
try:
chunk = sock.recv(n - len(buf))
except (socket.error, OSError) as ex:
raise connection_lost(ex)
if not chunk:
raise InterfaceError("connection closed by server")
buf += chunk
return buf