Changeset 11153 in ntrip for trunk/BNC/tests


Ignore:
Timestamp:
Oct 31, 2026, 11:50:15 PM (6 days ago)
Author:
stuerze
Message:

Credentials files are implemented, with existing configs keep working unchanged

Location:
trunk/BNC/tests
Files:
1 added
2 edited

Legend:

Unmodified
Added
Removed
  • trunk/BNC/tests/README.md

    r11152 r11153  
    3232| `tools/ssl_check.py` | TLS checks of the Ntrip 2s client and upload (not part of `run_tests.py`) |
    3333| `tools/net_check.py` | UDP/RTP sender filter and upload status checks on loopback (not part of `run_tests.py`) |
     34| `tools/cred_check.py` | credentials files (`~/.netrc`, BNC format) for download and upload (not part of `run_tests.py`) |
    3435| `work/` | scratch output, not versioned |
    3536
    … …  
    179180as broken. Takes about 2 min.
    180181
     182## Credentials check
     183
     184```
     185tests/tools/cred_check.py [--bnc PATH]
     186```
     187
     188BNC against `tools/fakecaster.py --auth user:password` (other credentials
     189rejected), each scenario with its own `HOME`: password in the configuration
     190file (hint logged), `~/.netrc` with two users for the host, `$NETRC` with
     191`machine host:port` preferred, BNC format file (`credentialsFile`) with exact
     192port preferred over `*` and a quoted password, URL without user (no
     193credentials sent), user without entry, `~/.netrc` readable for others and the
     194netrc `default` entry (not used), Ntrip 1 download, Ntrip 2 upload and
     195Ntrip 1 upload without user. Takes about 2 min.
     196
    181197## Adding a replay case
    182198
  • trunk/BNC/tests/tools/fakecaster.py

    r11130 r11153  
    1616a GET request (BNC as Ntrip client) is answered with the RTCM bytes of the
    1717given raw file. With --reply an upload is answered with this text instead
    18 of the 200 status (e.g. a rejection), and the connection is closed. Failed
     18of the 200 status (e.g. a rejection), and the connection is closed. With
     19--auth user:password (repeatable, ':password' for an Ntrip 1 upload) only
     20these credentials are accepted, others get 401 / 'ERROR - Bad Password'.
     21The user received ("-" without user, "none" without credentials) and failed
    1922TLS handshakes are reported on stdout.
    2023"""
    2124
    2225import argparse
     26import base64
    2327import os
    2428import socket
    … …  
    7074
    7175
    72 def serve(conn, store, timeout, tls=None, served=None, reply=None):
     76def credentials(header):
     77    """(user, password) of a request header, None without credentials."""
     78    lines = header.split(b"\r\n")
     79    if lines[0].startswith(b"SOURCE"):
     80        parts = lines[0].split(b" ")
     81        return "", parts[1].decode("latin-1") if len(parts) > 1 else ""
     82    for line in lines[1:]:
     83        key, _, value = line.partition(b":")
     84        value = value.strip()
     85        if key.strip().lower() == b"authorization" and value.lower().startswith(b"basic "):
     86            user, _, password = base64.b64decode(value[6:]).decode("latin-1").partition(":")
     87            return user, password
     88    return None
     89
     90
     91def serve(conn, store, timeout, tls=None, served=None, reply=None, auth=None):
    7392    conn.settimeout(timeout)
    7493    buf = b""
    … …  
    88107        header, _, pending = buf.partition(b"\r\n\r\n")
    89108        chunked = b"chunked" in header.lower()
     109        cred = credentials(header)
     110        ok = auth is None or cred in auth
     111        print("auth %s %s" % ("none" if cred is None else (cred[0] or "-"),
     112                              "accepted" if ok else "rejected"), flush=True)
     113        if not ok:
     114            conn.sendall(b"ERROR - Bad Password\r\n" if header.startswith(b"SOURCE")
     115                         else b"HTTP/1.1 401 Unauthorized\r\n\r\n")
     116            store.rejected += 1
     117            return
    90118        if header.startswith(b"GET") and served is not None:
    91             conn.sendall(b"HTTP/1.1 200 OK\r\nNtrip-Version: Ntrip/2.0\r\n"
    92                          b"Content-Type: gnss/data\r\nConnection: close\r\n\r\n" + served)
     119            if b"ntrip-version" in header.lower():
     120                conn.sendall(b"HTTP/1.1 200 OK\r\nNtrip-Version: Ntrip/2.0\r\n"
     121                             b"Content-Type: gnss/data\r\nConnection: close\r\n\r\n" + served)
     122            else:  # Ntrip 1
     123                conn.sendall(b"ICY 200 OK\r\n\r\n" + served)
    93124            store.served += len(served)
    94125            return
    … …  
    134165    ap.add_argument("--tls-client-ca", help="require a client certificate of this CA")
    135166    ap.add_argument("--serve", help="raw file whose RTCM bytes are sent on GET")
     167    ap.add_argument("--auth", action="append",
     168                    help="accepted user:password (repeatable), others rejected")
    136169    ap.add_argument("--reply", help="answer to uploads instead of 200 ('\\r\\n' escapes allowed)")
    137170    args = ap.parse_args()
    … …  
    147180    if args.reply is not None:
    148181        reply = args.reply.encode("latin-1").decode("unicode_escape").encode("latin-1")
     182    auth = None
     183    if args.auth:
     184        auth = [tuple(a.split(":", 1)) for a in args.auth]
    149185    served = None
    150186    if args.serve:
    … …  
    166202        except socket.timeout:
    167203            break
    168         t = threading.Thread(target=serve, args=(conn, store, args.timeout, tls, served, reply),
     204        t = threading.Thread(target=serve, args=(conn, store, args.timeout, tls, served, reply, auth),
    169205                             daemon=True)
    170206        t.start()
    … …  
    177213    if served is not None:
    178214        print("served %d bytes" % store.served, flush=True)
    179     if reply is not None:
    180         print("%d upload(s) answered with --reply" % store.rejected, flush=True)
     215    if reply is not None or auth is not None:
     216        print("%d connection(s) rejected" % store.rejected, flush=True)
    181217    return 0
    182218
Note: See TracChangeset for help on using the changeset viewer.