From 1c024fee73c43c4a1925961e2c302f504734aee5 Mon Sep 17 00:00:00 2001 From: igricrbx Date: Sun, 1 Jun 2025 04:29:39 +0200 Subject: [PATCH 1/4] Switched from Ubuntu to Alpine --- Dockerfile | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/Dockerfile b/Dockerfile index b9f0f17..2b9b872 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,10 +1,11 @@ -FROM ubuntu:18.04 as builder +FROM alpine:3.18 as builder COPY ntripcaster /ntripcaster WORKDIR /ntripcaster -RUN apt-get update && apt-get install build-essential --assume-yes +# Install build tools (Alpine uses apk instead of apt-get) +RUN apk add --no-cache build-base RUN ./configure @@ -12,9 +13,9 @@ RUN make install # The builder image is dumped and a fresh image is used # just with the built binary, config and logs made from 'make install' -FROM ubuntu:18.04 +FROM alpine:3.18 COPY --from=builder /usr/local/ntripcaster/ /usr/local/ntripcaster/ EXPOSE 2101 WORKDIR /usr/local/ntripcaster/logs -CMD /usr/local/ntripcaster/bin/ntripcaster +CMD /usr/local/ntripcaster/bin/ntripcaster \ No newline at end of file From a3088a17c9acc175322e5f114a6f899a5b6b2514 Mon Sep 17 00:00:00 2001 From: igricrbx Date: Sun, 1 Jun 2025 04:29:56 +0200 Subject: [PATCH 2/4] Simple docker build script --- rebuild_docker.sh | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100755 rebuild_docker.sh diff --git a/rebuild_docker.sh b/rebuild_docker.sh new file mode 100755 index 0000000..7f7e34b --- /dev/null +++ b/rebuild_docker.sh @@ -0,0 +1,17 @@ +#!/bin/bash +set -xe + +# Require script to be run as sudo +if [[ $EUID -ne 0 ]]; then + echo "This script must be run as root (sudo)" >&2 + exit 1 +fi + +# Check if container exists +if docker ps -a --format '{{.Names}}' | grep -q '^ntripcaster$'; then + docker stop ntripcaster + docker rm ntripcaster +fi + +docker build -t ntripcaster . +docker run -d --name ntripcaster -p 2101:2101 ntripcaster \ No newline at end of file From e215ca1404fbd5cb5e4fce1be59cfa2fd4bfd4ef Mon Sep 17 00:00:00 2001 From: igricrbx Date: Sun, 1 Jun 2025 04:40:25 +0200 Subject: [PATCH 3/4] Added HTML display for the SOURCETABLE when accessing the server via a web browser --- .gitignore | 3 + ntripcaster/src/client.c | 374 ++++++++++++++++++++++++++++++++- ntripcaster/src/ntrip_string.c | 32 +++ ntripcaster/src/ntrip_string.h | 1 + 4 files changed, 404 insertions(+), 6 deletions(-) diff --git a/.gitignore b/.gitignore index c6127b3..0b487b8 100644 --- a/.gitignore +++ b/.gitignore @@ -50,3 +50,6 @@ modules.order Module.symvers Mkfile.old dkms.conf + +ntripcaster.conf +sourcetable.dat \ No newline at end of file diff --git a/ntripcaster/src/client.c b/ntripcaster/src/client.c index 8629bb2..26c4fad 100644 --- a/ntripcaster/src/client.c +++ b/ntripcaster/src/client.c @@ -363,6 +363,327 @@ client_errors (const client_t *client) return (CHUNKLEN - (client->cid - client->source->cid)) % CHUNKLEN; } +/* Check if the user agent indicates a web browser */ +static int is_browser(const char *user_agent) { + if (!user_agent || ice_strcmp(user_agent, "(null)") == 0) + return 0; + + /* Check for common browser user agent strings */ + if (ice_strcasestr(user_agent, "Mozilla") || + ice_strcasestr(user_agent, "Chrome") || + ice_strcasestr(user_agent, "Safari") || + ice_strcasestr(user_agent, "Firefox") || + ice_strcasestr(user_agent, "Edge") || + ice_strcasestr(user_agent, "Opera") || + ice_strcasestr(user_agent, "Internet Explorer") || + ice_strcasestr(user_agent, "MSIE")) { + return 1; + } + + return 0; +} + +/* Send HTML formatted sourcetable for browsers */ +static void send_html_sourcetable(connection_t *con, FILE *ifp) { + char szBuffer[2000], c[2]; + int nBytes = 1, nBufferBytes = 0; + char *time; + + time = get_log_time(); + + /* Send HTTP header for HTML */ + sock_write_line (con->sock, "HTTP/1.0 200 OK"); + sock_write_line (con->sock, "Server: NTRIP NtripCaster %s/%s", info.version, info.ntrip_version); + sock_write_line (con->sock, "Content-Type: text/html"); + sock_write_line (con->sock, "Connection: close\r\n"); + + /* Send HTML header */ + sock_write_line (con->sock, ""); + sock_write_line (con->sock, ""); + sock_write_line (con->sock, ""); + sock_write_line (con->sock, "NTRIP Caster - Source Table"); + sock_write_line (con->sock, ""); + sock_write_line (con->sock, ""); + sock_write_line (con->sock, ""); + + /* Server information */ + sock_write_line (con->sock, "
"); + sock_write_line (con->sock, "

NTRIP Caster Source Table

"); + if (info.server_name) + sock_write_line (con->sock, "

Server: %s

", info.server_name); + sock_write_line (con->sock, "

Port: %d

", info.port[0]); + sock_write_line (con->sock, "

Version: %s/%s

", info.version, info.ntrip_version); + sock_write_line (con->sock, "

Time: %s

", time); + sock_write_line (con->sock, "
"); + + if (ifp) { + /* First, display any non-STR/CAS/NET lines */ + sock_write_line (con->sock, "
"); + sock_write_line (con->sock, "

General Information

"); + while (nBytes > 0) { + nBytes = fread(c,sizeof(char),sizeof(char),ifp); + while (((unsigned int)c[0] != 10) && (nBytes > 0)) { + szBuffer[nBufferBytes] = c[0]; + nBufferBytes++; + nBytes = fread(c,sizeof(char),sizeof(char),ifp); + } + szBuffer[nBufferBytes] = '\0'; + + if (nBufferBytes > 0) { + /* Display non-STR/CAS/NET lines as preformatted text */ + if (strncmp(szBuffer, "STR", 3) != 0 && strncmp(szBuffer, "CAS", 3) != 0 && strncmp(szBuffer, "NET", 3) != 0) { + sock_write_line (con->sock, "
%s
", szBuffer); + } + } + nBufferBytes = 0; + } + sock_write_line (con->sock, "
"); + + /* CAS Table */ + rewind(ifp); + nBytes = 1; + nBufferBytes = 0; + + sock_write_line (con->sock, "

Casters (CAS)

"); + sock_write_line (con->sock, ""); + sock_write_line (con->sock, ""); + sock_write_line (con->sock, ""); + sock_write_line (con->sock, ""); + sock_write_line (con->sock, ""); + sock_write_line (con->sock, ""); + sock_write_line (con->sock, ""); + sock_write_line (con->sock, ""); + sock_write_line (con->sock, ""); + + while (nBytes > 0) { + nBytes = fread(c,sizeof(char),sizeof(char),ifp); + while (((unsigned int)c[0] != 10) && (nBytes > 0)) { + szBuffer[nBufferBytes] = c[0]; + nBufferBytes++; + nBytes = fread(c,sizeof(char),sizeof(char),ifp); + } + szBuffer[nBufferBytes] = '\0'; + + if (nBufferBytes > 0 && strncmp(szBuffer, "CAS", 3) == 0) { + char *tokens[30]; + int token_count = 0; + char *token; + char line_copy[2000]; + int i; + + strncpy(line_copy, szBuffer, sizeof(line_copy) - 1); + line_copy[sizeof(line_copy) - 1] = '\0'; + + token = strtok(line_copy, ";"); + while (token && token_count < 30) { + tokens[token_count++] = token; + token = strtok(NULL, ";"); + } + + sock_write_line (con->sock, ""); + + /* Display CAS fields (11 defined fields) */ + for (i = 0; i < 11; i++) { + if (i < token_count && tokens[i] && strlen(tokens[i]) > 0) { + sock_write_line (con->sock, "", tokens[i]); + } else { + sock_write_line (con->sock, ""); + } + } + + /* Display misc data (everything after field 11) */ + sock_write_line (con->sock, ""); + + sock_write_line (con->sock, ""); + } + nBufferBytes = 0; + } + + sock_write_line (con->sock, ""); + sock_write_line (con->sock, "
TypeHostPortIdentifierOperatorNMEACountryLatitudeLongitudeFallback HostFallback PortMisc
%s-"); + if (token_count > 11) { + for (i = 11; i < token_count; i++) { + if (tokens[i] && strlen(tokens[i]) > 0) { + sock_write_line (con->sock, "%s", tokens[i]); + if (i < token_count - 1) sock_write_line (con->sock, "; "); + } + } + } else { + sock_write_line (con->sock, "-"); + } + sock_write_line (con->sock, "
"); + + /* NET Table */ + rewind(ifp); + nBytes = 1; + nBufferBytes = 0; + + sock_write_line (con->sock, "

Networks (NET)

"); + sock_write_line (con->sock, ""); + sock_write_line (con->sock, ""); + sock_write_line (con->sock, ""); + sock_write_line (con->sock, ""); + sock_write_line (con->sock, ""); + sock_write_line (con->sock, ""); + sock_write_line (con->sock, ""); + sock_write_line (con->sock, ""); + + while (nBytes > 0) { + nBytes = fread(c,sizeof(char),sizeof(char),ifp); + while (((unsigned int)c[0] != 10) && (nBytes > 0)) { + szBuffer[nBufferBytes] = c[0]; + nBufferBytes++; + nBytes = fread(c,sizeof(char),sizeof(char),ifp); + } + szBuffer[nBufferBytes] = '\0'; + + if (nBufferBytes > 0 && strncmp(szBuffer, "NET", 3) == 0) { + char *tokens[30]; + int token_count = 0; + char *token; + char line_copy[2000]; + int i; + + strncpy(line_copy, szBuffer, sizeof(line_copy) - 1); + line_copy[sizeof(line_copy) - 1] = '\0'; + + token = strtok(line_copy, ";"); + while (token && token_count < 30) { + tokens[token_count++] = token; + token = strtok(NULL, ";"); + } + + sock_write_line (con->sock, ""); + + /* Display NET fields (8 defined fields) */ + for (i = 0; i < 8; i++) { + if (i < token_count && tokens[i] && strlen(tokens[i]) > 0) { + sock_write_line (con->sock, "", tokens[i]); + } else { + sock_write_line (con->sock, ""); + } + } + + /* Display misc data (everything after field 8) */ + sock_write_line (con->sock, ""); + + sock_write_line (con->sock, ""); + } + nBufferBytes = 0; + } + + sock_write_line (con->sock, ""); + sock_write_line (con->sock, "
TypeIdentifierOperatorAuthenticationFeeWeb NetWeb StrWeb RegMisc
%s-"); + if (token_count > 8) { + for (i = 8; i < token_count; i++) { + if (tokens[i] && strlen(tokens[i]) > 0) { + sock_write_line (con->sock, "%s", tokens[i]); + if (i < token_count - 1) sock_write_line (con->sock, "; "); + } + } + } else { + sock_write_line (con->sock, "-"); + } + sock_write_line (con->sock, "
"); + + /* STR Table */ + rewind(ifp); + nBytes = 1; + nBufferBytes = 0; + + sock_write_line (con->sock, "

Data Streams (STR)

"); + sock_write_line (con->sock, ""); + sock_write_line (con->sock, ""); + sock_write_line (con->sock, ""); + sock_write_line (con->sock, ""); + sock_write_line (con->sock, ""); + sock_write_line (con->sock, ""); + sock_write_line (con->sock, ""); + sock_write_line (con->sock, ""); + sock_write_line (con->sock, ""); + sock_write_line (con->sock, ""); + + while (nBytes > 0) { + nBytes = fread(c,sizeof(char),sizeof(char),ifp); + while (((unsigned int)c[0] != 10) && (nBytes > 0)) { + szBuffer[nBufferBytes] = c[0]; + nBufferBytes++; + nBytes = fread(c,sizeof(char),sizeof(char),ifp); + } + szBuffer[nBufferBytes] = '\0'; + + if (nBufferBytes > 0 && strncmp(szBuffer, "STR", 3) == 0) { + char *tokens[30]; + int token_count = 0; + char *token; + char line_copy[2000]; + int i; + + strncpy(line_copy, szBuffer, sizeof(line_copy) - 1); + line_copy[sizeof(line_copy) - 1] = '\0'; + + token = strtok(line_copy, ";"); + while (token && token_count < 30) { + tokens[token_count++] = token; + token = strtok(NULL, ";"); + } + + sock_write_line (con->sock, ""); + + /* Display STR fields (18 defined fields) */ + for (i = 0; i < 18; i++) { + if (i < token_count && tokens[i] && strlen(tokens[i]) > 0) { + sock_write_line (con->sock, "", tokens[i]); + } else { + sock_write_line (con->sock, ""); + } + } + + /* Display misc data (everything after field 18) */ + sock_write_line (con->sock, ""); + + sock_write_line (con->sock, ""); + } + nBufferBytes = 0; + } + + sock_write_line (con->sock, ""); + sock_write_line (con->sock, "
TypeMountpointIdentifierFormatFormat DetailsCarrierNav SystemNetworkCountryLatitudeLongitudeNMEASolutionGeneratorCompr EncrypAuthenticationFeeBitrateMisc
%s-"); + if (token_count > 18) { + for (i = 18; i < token_count; i++) { + if (tokens[i] && strlen(tokens[i]) > 0) { + sock_write_line (con->sock, "%s", tokens[i]); + if (i < token_count - 1) sock_write_line (con->sock, "; "); + } + } + } else { + sock_write_line (con->sock, "-"); + } + sock_write_line (con->sock, "
"); + } else { + sock_write_line (con->sock, "

No sourcetable available

"); + } + + /* Add informational note at the bottom */ + sock_write_line (con->sock, "
"); + sock_write_line (con->sock, "

Note: This source table has been returned as an HTML page because you requested it using a web browser rather than an NTRIP client. NTRIP clients would receive this data in plain text format.

"); + sock_write_line (con->sock, "
"); + + sock_write_line (con->sock, ""); + sock_write_line (con->sock, ""); + + free (time); +} + void send_sourcetable (connection_t *con) { @@ -372,20 +693,58 @@ send_sourcetable (connection_t *con) { nBufferBytes = 0, fsize = 0; char *time; + const char *user_agent; time = get_log_time(); + user_agent = get_user_agent(con); + + xa_debug(2, "DEBUG: send_sourcetable() User-Agent: [%s]", user_agent ? user_agent : "(null)"); + ifp = fopen("../conf/sourcetable.dat","r"); + + /* Check if this is a browser request */ + if (is_browser(user_agent)) { + xa_debug(2, "DEBUG: Browser detected, sending HTML sourcetable"); + send_html_sourcetable(con, ifp); + if (ifp) fclose(ifp); + free (time); + return; + } + + /* Original NTRIP client handling */ + xa_debug(2, "DEBUG: NTRIP client detected, sending plain text sourcetable"); + sock_write_line (con->sock, "SOURCETABLE 200 OK"); sock_write_line (con->sock, "Server: NTRIP NtripCaster %s/%s", info.version, info.ntrip_version); // sock_write_line (con->sock, "Date: %s %s", time, info.timezone); - ifp = fopen("../conf/sourcetable.dat","r"); if (ifp != NULL) { - fseek(ifp, 0, SEEK_END); - fsize = (int)ftell(ifp); + int filtered_size = 0; + char temp_buffer[2000]; + int temp_nBytes = 1, temp_nBufferBytes = 0; + + /* First pass: calculate filtered content size */ + while (temp_nBytes > 0) { + temp_nBytes = fread(c,sizeof(char),sizeof(char),ifp); + while (((unsigned int)c[0] != 10) && (temp_nBytes > 0)) { + temp_buffer[temp_nBufferBytes] = c[0]; + temp_nBufferBytes++; + temp_nBytes = fread(c,sizeof(char),sizeof(char),ifp); + } + temp_buffer[temp_nBufferBytes] = '\0'; + /* Only count lines that start with "STR" */ + if (temp_nBufferBytes > 0 && strncmp(temp_buffer, "STR", 3) == 0) { + filtered_size += temp_nBufferBytes + 2; /* +2 for \r\n */ + } + temp_nBufferBytes = 0; + } + filtered_size += 13; /* for "ENDSOURCETABLE" + \r\n */ + + /* Reset file pointer for second pass */ rewind(ifp); - + nBytes = 1; + sock_write_line (con->sock, "Content-Type: text/plain"); - sock_write_line (con->sock, "Content-Length: %d\r\n", fsize); + sock_write_line (con->sock, "Content-Length: %d\r\n", filtered_size); while (nBytes > 0) { nBytes = fread(c,sizeof(char),sizeof(char),ifp); @@ -395,7 +754,10 @@ send_sourcetable (connection_t *con) { nBytes = fread(c,sizeof(char),sizeof(char),ifp); } szBuffer[nBufferBytes] = '\0'; - if (nBufferBytes > 0) sock_write_line (con->sock, szBuffer); + /* Only send lines that start with "STR" */ + if (nBufferBytes > 0 && strncmp(szBuffer, "STR", 3) == 0) { + sock_write_line (con->sock, szBuffer); + } nBufferBytes = 0; } diff --git a/ntripcaster/src/ntrip_string.c b/ntripcaster/src/ntrip_string.c index 663f7d1..7b8134c 100644 --- a/ntripcaster/src/ntrip_string.c +++ b/ntripcaster/src/ntrip_string.c @@ -464,6 +464,38 @@ ice_strcasecmp (const char *s1, const char *s2) #endif } +const char * +ice_strcasestr (const char *haystack, const char *needle) +{ + const char *h, *n; + + if (!haystack || !needle) + { + xa_debug (1, "ERROR: ice_strcasestr() called with NULL pointers"); + return NULL; + } + + if (*needle == '\0') + return haystack; + + for (; *haystack; haystack++) + { + h = haystack; + n = needle; + + while (*h && *n && (tolower((unsigned char)*h) == tolower((unsigned char)*n))) + { + h++; + n++; + } + + if (*n == '\0') + return haystack; + } + + return NULL; +} + const char * nullcheck_string (const char *string) { diff --git a/ntripcaster/src/ntrip_string.h b/ntripcaster/src/ntrip_string.h index ce556d1..6c5b8f0 100644 --- a/ntripcaster/src/ntrip_string.h +++ b/ntripcaster/src/ntrip_string.h @@ -53,6 +53,7 @@ char *nice_time(unsigned long int seconds, char *buf); int ice_strcasecmp (const char *s1, const char *s2); int ice_strncmp (const char *s1, const char *s2, size_t n); int ice_strcmp (const char *s1, const char *s2); +const char *ice_strcasestr (const char *haystack, const char *needle); size_t ice_strlen (const char *string); char *nice_time_minutes (unsigned long int minutes, char *buf); const char *nullcheck_string (const char *string); From 616809e19182d13b503c8711ceb84bee0fef7702 Mon Sep 17 00:00:00 2001 From: igricrbx Date: Sun, 1 Jun 2025 04:48:08 +0200 Subject: [PATCH 4/4] Remove rebuild_docker.sh from repository --- rebuild_docker.sh | 17 ----------------- 1 file changed, 17 deletions(-) delete mode 100755 rebuild_docker.sh diff --git a/rebuild_docker.sh b/rebuild_docker.sh deleted file mode 100755 index 7f7e34b..0000000 --- a/rebuild_docker.sh +++ /dev/null @@ -1,17 +0,0 @@ -#!/bin/bash -set -xe - -# Require script to be run as sudo -if [[ $EUID -ne 0 ]]; then - echo "This script must be run as root (sudo)" >&2 - exit 1 -fi - -# Check if container exists -if docker ps -a --format '{{.Names}}' | grep -q '^ntripcaster$'; then - docker stop ntripcaster - docker rm ntripcaster -fi - -docker build -t ntripcaster . -docker run -d --name ntripcaster -p 2101:2101 ntripcaster \ No newline at end of file