Changeset 11153 in ntrip for trunk/BNC


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
Files:
3 added
13 edited

Legend:

Unmodified
Added
Removed
  • trunk/BNC/CHANGELOG.md

    r11148 r11153  
    8282- CHANGED: Plot scripts (cmbPlot.pl, pppPlot.pl, compareSP3Plot.pl) delete only the images they created (cmbPlot.pl deleted all PNG files of the input directory)
    8383- CHANGED: Linux build with stack protector, bounds-checked std containers (_GLIBCXX_ASSERTIONS) and read-only relocations, independent of the distribution's compiler defaults
     84- ADDED: Option 'Credentials files' (key credentialsFile): passwords of Ntrip Broadcasters can be kept in credentials files instead of the configuration file, in netrc format ('machine host[:port] login user password secret', default $NETRC or ~/.netrc) or BNC format ('host port|* user|- password'); several users per host, URLs without user stay anonymous, passwords in the configuration file are used as before
    8485
    8586## 2.13.7 (2026-07-31)
  • trunk/BNC/src/bncgetthread.cpp

    r11066 r11153  
    6565#include "bncnetqueryudp.h"
    6666#include "bncnetqueryudp0.h"
     67#include "bnccredentials.h"
    6768#include "bncnetquerys.h"
    6869#include "bncsettings.h"
    … …  
    9596  _rawFile = 0;
    9697  _mountPoint   = mountPoint;
     98  _requestUrl   = mountPoint;
     99  bncCredentials::complete(_requestUrl); // password from a credentials file
    97100  _staID        = mountPoint.path().mid(1).toLatin1();
    98101  _format       = format;
    … …  
    356359  msleep(100); //sleep 0.1 sec
    357360
    358   _decoder->initRinex(_staID, _mountPoint, _latitude, _longitude, _nmea,
     361  _decoder->initRinex(_staID, _requestUrl, _latitude, _longitude, _nmea,
    359362      _ntripVersion);
    360363
    … …  
    768771    if (_nmea == "yes") {
    769772      if (_serialNMEA == MANUAL_NMEA) {
    770         _query->startRequest(_mountPoint, _manualNMEAString);
     773        _query->startRequest(_requestUrl, _manualNMEAString);
    771774        _lastNMEA = QDateTime::currentDateTime();
    772775      } else if (_serialNMEA == AUTO_NMEA) {
    … …  
    783786              if (i2 != -1 && data.size() > i2 + 1) {
    784787                QByteArray gga = data.mid(i1, i2 - i1 + 3);
    785                 _query->startRequest(_mountPoint, gga);
     788                _query->startRequest(_requestUrl, gga);
    786789                _lastNMEA = QDateTime::currentDateTime();
    787790              }
    … …  
    791794      }
    792795    } else {
    793       _query->startRequest(_mountPoint, "");
     796      _query->startRequest(_requestUrl, "");
    794797    }
    795798
  • trunk/BNC/src/bncgetthread.h

    r11021 r11153  
    109109   GPSDecoder*                _decoder;
    110110   bncNetQuery*               _query;
    111    QUrl                       _mountPoint;
     111   QUrl                       _mountPoint;   // as configured (identifies the stream)
     112   QUrl                       _requestUrl;   // with password from a credentials file
    112113   QByteArray                 _staID;
    113114   QByteArray                 _format;
  • trunk/BNC/src/bnchelp.html

    r11148 r11153  
    26922692    and feed in manipulated data. The ignored errors are logged. Better save the CA certificate of the Ntrip
    26932693    Broadcaster in the 'Path to SSL certificates' directory.
     2694  </p>
     2695  <p>
     2696    Passwords of Ntrip Broadcasters can be kept in 'Credentials files' instead of BNC's configuration file, which
     2697    then contains no secrets and can be passed on. Give the user name without password in the stream's URL, e.g.
     2698    '//user@products.igs-ip.net:443/SSRA00BKG1', or in the upload settings (empty password). BNC takes the password
     2699    from the first matching entry of the credentials files. Streams without user name are retrieved without
     2700    authentication, even if a file contains an entry for that host. A password still given in the configuration
     2701    file is used as before (with a hint in the logfile), so configurations can be changed step by step.
     2702  </p>
     2703  <p>
     2704    Two file formats are accepted and detected from the content:
     2705  <ul>
     2706    <li>netrc, as used by curl, wget or git: 'machine products.igs-ip.net login user password secret'. The machine
     2707      may also be given as 'host:port' for an account that applies to one port only. The netrc 'default' entry is
     2708      not used.</li>
     2709    <li>BNC: one entry per line '&lt;host&gt; &lt;port&gt; &lt;user&gt; &lt;password&gt;', port '*' for any port,
     2710      user '-' for an entry without user (Ntrip Version 1 upload).</li>
     2711  </ul>
     2712    Several users per host are distinguished by the user name. An entry for the exact port is preferred over one
     2713    for any port. Words containing blanks are quoted: "my secret". A '#' starts a comment. Several files can be
     2714    given, separated by commas; default is the file named in the environment variable NETRC, otherwise ~/.netrc.
     2715    A file readable for other users is not used (chmod 600). Note that a password containing '%' followed by two
     2716    hexadecimal digits is not supported, in the configuration file neither.
    26942717  </p>
    26952718  <p><img src="IMG/Figure07.png" width=800 /></p>
  • trunk/BNC/src/bncmain.cpp

    r11140 r11153  
    136136        "   sslClientCertPath {Full path to client SSL certificates [character string]}\n"
    137137        "   sslIgnoreErrors   {Ignore SSL authorization errors [integer number: 0=no,2=yes]}\n"
     138        "   credentialsFile   {Credentials files, netrc or BNC format, default $NETRC or ~/.netrc [character string, comma separated list]}\n"
    138139        "\n"
    139140        "General Panel keys:\n"
  • trunk/BNC/src/bncsettings.cpp

    r11140 r11153  
    7575        setValue_p("sslClientCertPath", "");
    7676        setValue_p("sslIgnoreErrors", "0");
     77        setValue_p("credentialsFile", "");
    7778        // General
    7879        setValue_p("logFile", "");
  • trunk/BNC/src/bnctabledlg.cpp

    r11116 r11153  
    4747
    4848#include "bnctabledlg.h"
     49#include "bnccredentials.h"
    4950#include "bncgetthread.h"
    5051#include "bncnetqueryv1.h"
    … …  
    238239  }
    239240  url.setUserName(QUrl::toPercentEncoding(casterUser));
    240   url.setPassword(QUrl::toPercentEncoding(casterPassword));
     241  QString password = casterPassword; // empty: from a credentials file
     242  bncCredentials::complete(casterHost, casterPort, casterUser, password, false, false);
     243  url.setPassword(QUrl::toPercentEncoding(password));
    241244  QByteArray outData;
    242245  query->waitForRequestResult(url, outData);
  • trunk/BNC/src/bncwindow.cpp

    r11140 r11153  
    194194  _sslIgnoreErrorsCheckBox->setCheckState(Qt::CheckState(
    195195                                                         settings.value("sslIgnoreErrors").toInt()));
     196  _credentialsFileLineEdit = new QLineEdit(settings.value("credentialsFile").toString());
    196197
    197198  // General Options
    … …  
    828829  pLayout->addWidget(new QLabel("Ignore SSL authorization errors"), 5, 0);
    829830  pLayout->addWidget(_sslIgnoreErrorsCheckBox, 5, 1, 1, 10);
    830   pLayout->addWidget(new QLabel(""), 6, 1);
    831   pLayout->setRowStretch(6, 999);
     831  pLayout->addWidget(new QLabel("Credentials files"), 6, 0);
     832  pLayout->addWidget(_credentialsFileLineEdit, 6, 1, 1, 10);
     833  pLayout->addWidget(new QLabel("Default:  $NETRC or ~/.netrc"), 6, 11, 1, 20);
     834  pLayout->addWidget(new QLabel(""), 7, 1);
     835  pLayout->setRowStretch(7, 999);
    832836
    833837  pgroup->setLayout(pLayout);
    … …  
    15581562  _sslCaCertPathLineEdit->setWhatsThis(tr("<p>Communication with an Ntrip Broadcaster over SSL requires the exchange of server certificates. Specify the path to a directory where you save CA certificates on your system. </p><p>BNC creates from *.crt and *.pem files a CA certificate database, which is used by the socket during the handshake phase to validate the peer's certificate. </p><p>Note that SSL communication is usually done over port 443. <i>[key: sslCaCertPath]</i></p>"));
    15591563  _sslClientCertPathLineEdit->setWhatsThis(tr("<p>Two-sided communication with an Ntrip Broadcaster over SSL requires in addition the exchange of client certificates. Specify the full path to the client certificates on your system.</p><p></p><p>The file naming convention for client certificates in BNC is as follows: &lt;hostname&gt;.&lt;port&gt;.crt for the certificate and &lt;hostname&gt;.&lt;port&gt;.key for the private key, where &lt;hostname&gt; is without https://. </p><p> If available, the client or personal authentication certificate is presented to the peer during the SSL handshake process. Password protected key files are not supported. </p><p>Don't try communication via two sided SSL if you are not sure whether this is supported by the involved Ntrip Broadcaster. </p><p>Note that SSL communication is usually done over port 443. <i>[key: sslClientCertPath]</i></p>"));
     1564  _credentialsFileLineEdit->setWhatsThis(tr("<p>Passwords of Ntrip Broadcasters can be kept in credentials files instead of the configuration file: give the user name without password in the stream's URL (e.g. '//user@products.igs-ip.net:443/SSRA00BKG1') or in the upload settings (empty password). BNC then takes the password from the first matching entry of these files. Streams without user name are retrieved without authentication.</p><p>Two formats are accepted, detected from the file content:<br>netrc: 'machine products.igs-ip.net login user password secret', where the machine may also be given as 'host:port';<br>BNC: one entry per line '&lt;host&gt; &lt;port&gt; &lt;user&gt; &lt;password&gt;', port '*' for any port, user '-' for an entry without user (Ntrip Version 1 upload).</p><p>An entry for the exact port is preferred. Several files can be given, separated by commas; default is the file named in the environment variable NETRC or ~/.netrc. A file readable for other users is not used. A password still given in the configuration file is used as before. <i>[key: credentialsFile]</i></p>"));
    15601565  _sslIgnoreErrorsCheckBox->setWhatsThis(tr("<p>Tick 'Ignore SSL authorization errors' to accept a server certificate that cannot be verified, e.g. a self-signed certificate or one issued for another host name. The connection is then still encrypted, but the server is not authenticated: anybody able to intercept the connection may receive your user name and password and feed in manipulated data. The ignored errors are logged. Better save the CA certificate of the Ntrip Broadcaster in the 'Path to SSL certificates' directory. <i>[key: sslIgnoreErrors]</i></p>"));
    15611566
    … …  
    18741879  delete _sslClientCertPathLineEdit;
    18751880  delete _sslIgnoreErrorsCheckBox;
     1881  delete _credentialsFileLineEdit;
    18761882  delete _logFileLineEdit;
    18771883  delete _rawOutFileLineEdit;
    … …  
    20882094  settings.setValue("sslClientCertPath", _sslClientCertPathLineEdit->text());
    20892095  settings.setValue("sslIgnoreErrors", _sslIgnoreErrorsCheckBox->checkState());
     2096  settings.setValue("credentialsFile", _credentialsFileLineEdit->text());
    20902097
    20912098  QMessageBox msgBox;
    … …  
    24042411  settings.setValue("sslClientCertPath", _sslClientCertPathLineEdit->text());
    24052412  settings.setValue("sslIgnoreErrors", _sslIgnoreErrorsCheckBox->checkState());
     2413  settings.setValue("credentialsFile", _credentialsFileLineEdit->text());
    24062414  // General
    24072415  settings.setValue("logFile", _logFileLineEdit->text());
  • trunk/BNC/src/bncwindow.h

    r11140 r11153  
    141141    QLineEdit* _sslClientCertPathLineEdit;
    142142    QCheckBox* _sslIgnoreErrorsCheckBox;
     143    QLineEdit* _credentialsFileLineEdit;
    143144    QLineEdit* _outFileLineEdit;
    144145    QLineEdit* _outPortLineEdit;
  • trunk/BNC/src/src.pri

    r11130 r11153  
    6767          bncoutf.h bncclockrinex.h bncsp3.h bncsinextro.h            \
    6868          bncbiassinex.h                                              \
    69           bncbytescounter.h bncsslconfig.h reqcdlg.h                  \
     69          bncbytescounter.h bncsslconfig.h bnccredentials.h reqcdlg.h                  \
    7070          ephemeris.h t_prn.h satObs.h                                \
    7171          upload/bncrtnetdecoder.h upload/bncuploadcaster.h           \
    … …  
    110110          bncbiassinex.cpp                                            \
    111111          ephemeris.cpp t_prn.cpp satObs.cpp                          \
    112           bncbytescounter.cpp bncsslconfig.cpp reqcdlg.cpp            \
     112          bncbytescounter.cpp bncsslconfig.cpp bnccredentials.cpp reqcdlg.cpp            \
    113113          upload/bncrtnetdecoder.cpp upload/bncuploadcaster.cpp       \
    114114          upload/bncrawuploadcaster.cpp                               \
  • trunk/BNC/src/upload/bncuploadcaster.cpp

    r11128 r11153  
    2222#include "bncsettings.h"
    2323#include "bncsslconfig.h"
     24#include "bnccredentials.h"
    2425
    2526using namespace std;
    … …  
    4647  _userName = userName;
    4748  _password = password;
     49  if (!_mountpoint.isEmpty() && !_casterOutHost.isEmpty()) {
     50    // empty password: from a credentials file, also without user (Ntrip Version 1)
     51    bncCredentials::complete(_casterOutHost, _casterOutPort, _userName, _password, true, true);
     52  }
    4853  _outSocket = 0;
    4954  _sOpenTrial = 0;
  • 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.