Friday, October 30, 2026

holy fuck

#!/usr/bin/env python3
"""Log in from the shell.

Usage:
    ./_login.py -u YOUR_USERNAME_OR_EMAIL
    PO_PASSWORD='secret' ./_login.py -u you@example.com --cookies po.txt

The password is read from the PO_PASSWORD environment variable or, if that is
unset, prompted for without echo. On success the session cookies are saved in
Netscape format (default: ~/._cookies.txt, mode 600), so you can
reuse them with curl:
    curl -b ~/._cookies.txt https://.org/account

Only the Python standard library is needed.
"""
import argparse
import getpass
import http.cookiejar
import os
import re
import sys
import urllib.error
import urllib.parse
import urllib.request
from html.parser import HTMLParser

UA = "Mozilla/5.0 (X11; Linux x86_64) -login-script/1.0"


class FormParser(HTMLParser):
    """Collects every <form> with its action, method and <input> fields."""

    def __init__(self):
        super().__init__()
        self.forms = []
        self._cur = None

    def handle_starttag(self, tag, attrs):
        a = dict(attrs)
        if tag == "form":
            self._cur = {"action": a.get("action", ""),
                         "method": a.get("method", "get").lower(),
                         "inputs": []}
            self.forms.append(self._cur)
        elif tag == "input" and self._cur is not None:
            self._cur["inputs"].append(a)

    def handle_endtag(self, tag):
        if tag == "form":
            self._cur = None


class ErrorParser(HTMLParser):
    """Pulls out error messages (class contains 'error')."""

    def __init__(self):
        super().__init__()
        self.messages = []
        self._depth = 0
        self._buf = []

    def handle_starttag(self, tag, attrs):
        cls = dict(attrs).get("class") or ""
        if self._depth:
            self._depth += 1
        elif "error" in cls:
            self._depth = 1
            self._buf = []

    def handle_endtag(self, tag):
        if self._depth:
            self._depth -= 1
            if self._depth == 0:
                text = " ".join("".join(self._buf).split())
                if text:
                    self.messages.append(text)

    def handle_data(self, data):
        if self._depth:
            self._buf.append(data)


def find_login_form(html):
    parser = FormParser()
    parser.feed(html)
    for form in parser.forms:
        names = {i.get("name") for i in form["inputs"]}
        if "emailhandle" in names and "password" in names:
            return form
    return None


def main():
    ap = argparse.ArgumentParser(description="Log in ")
    ap.add_argument("-u", "--user", required=True, help="username or email")
    ap.add_argument("--base", default="https://.org",
                    help="site root (default: %(default)s; cookies differ "
                         "between the www and non-www hosts)")
    ap.add_argument("--cookies",
                    default=os.path.expanduser("~/._cookies.txt"),
                    help="where to save session cookies (default: %(default)s)")
    ap.add_argument("--no-remember", action="store_true",
                    help="don't tick 'remember me'")
    args = ap.parse_args()

    password = os.environ.get("PO_PASSWORD") or getpass.getpass("Password: ")

    jar = http.cookiejar.MozillaCookieJar(args.cookies)
    opener = urllib.request.build_opener(urllib.request.HTTPCookieProcessor(jar))
    opener.addheaders = [("User-Agent", UA)]

    login_url = args.base.rstrip("/") + "/login"

    # 1. Fetch the login page: gets the session cookie and the hidden CSRF "code".
    try:
        with opener.open(login_url, timeout=30) as r:
            page_url = r.geturl()
            html = r.read().decode("utf-8", "replace")
    except (urllib.error.URLError, OSError) as e:
        sys.exit(f"Could not reach {login_url}: {e}")

    form = find_login_form(html)
    if form is None:
        sys.exit("Could not find the login form (already logged in, or the "
                 "site layout changed).")

    # 2. Build the POST body from the form's own fields, then fill in ours.
    data = {}
    for inp in form["inputs"]:
        name = inp.get("name")
        if not name or inp.get("type") in ("submit", "button", "image"):
            continue
        if inp.get("type") == "checkbox" and "checked" not in inp:
            continue
        data[name] = inp.get("value", "")
    data["emailhandle"] = args.user
    data["password"] = password
    data["dologin"] = "1"
    if not args.no_remember:
        data["remember"] = "1"

    action = urllib.parse.urljoin(page_url, form["action"] or login_url)

    # 3. Submit.
    body = urllib.parse.urlencode(data).encode()
    req = urllib.request.Request(action, data=body,
                                 headers={"Referer": page_url})
    try:
        with opener.open(req, timeout=30) as r:
            result = r.read().decode("utf-8", "replace")
            final_url = r.geturl()
    except (urllib.error.URLError, OSError) as e:
        sys.exit(f"Login request failed: {e}")

    # 4. Success = the page no longer shows a login form and offers a logout link.
    still_login = find_login_form(result) is not None
    has_logout = bool(re.search(r'href="[^"]*(?:qa=logout|/logout)', result))

    if has_logout and not still_login:
        jar.save(ignore_discard=True, ignore_expires=True)
        os.chmod(args.cookies, 0o600)
        print(f"Logged in as {args.user} ({final_url})")
        print(f"Cookies saved to {args.cookies}")
        return 0

    errors = ErrorParser()
    errors.feed(result)
    print("Login failed.", file=sys.stderr)
    for m in dict.fromkeys(errors.messages):
        print(f"  site says: {m}", file=sys.stderr)
    if re.search(r"anti-spam|captcha", result, re.I):
        print("  The site is asking for an anti-spam check, which this "
              "script cannot solve; log in once in a browser.", file=sys.stderr)
    return 1


if __name__ == "__main__":
    sys.exit(main())

No comments:

Post a Comment

no thanks

Note: Only a member of this blog may post a comment.

this no one will understand i promise

  #!/usr/bin/env bash # pyreg.sh: registry-style store in pure bash. # # Here the directory tree IS the registry: keys are directories under...