Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package svxlink for openSUSE:Factory checked in at 2026-10-17 21:24:55 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/svxlink (Old) and /work/SRC/openSUSE:Factory/.svxlink.new.3698736 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "svxlink" Sat Oct 17 21:24:55 2026 rev:8 rq:1385259 version:unknown Changes: -------- --- /work/SRC/openSUSE:Factory/svxlink/svxlink.changes 2026-08-09 21:39:42.695196444 +0200 +++ /work/SRC/openSUSE:Factory/.svxlink.new.3698736/svxlink.changes 2026-10-17 21:24:57.955787773 +0200 @@ -1,0 +2,13 @@ +Fri Oct 16 12:50:54 UTC 2026 - Martin Pluskal <[email protected]> + +- Update to version 26.05.2: security bugfix release with no + assigned CVE IDs (GHSA-m4q5-8r78-5gqg, GHSA-2m63-p9h9-p5rf, + GHSA-6w9x-486p-ggrj) + * SvxLink server 1.10.2: harden ModuleMetarInfo against + malformed METAR crashes and a cloud-token hang, fix + Reflector broadcast iterator invalidation + * EchoLib 1.3.8 (ModuleEchoLink 1.6.3): bound-check NDATA + packets, sanitize SDES callsigns against TCL injection, + fix proxy buffering and APRS UDP overflows + +------------------------------------------------------------------- Old: ---- svxlink-26.05.1.tar.gz New: ---- svxlink-26.05.2.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ svxlink.spec ++++++ --- /var/tmp/diff_new_pack.1sAfCO/_old 2026-10-17 21:24:58.813823567 +0200 +++ /var/tmp/diff_new_pack.1sAfCO/_new 2026-10-17 21:24:58.815823651 +0200 @@ -18,16 +18,16 @@ %define QTEL 1.3.0 # Version for the EchoLib library -%define ECHOLIB 1.3.7 +%define ECHOLIB 1.3.8 # Version for the Async library %define LIBASYNC 1.9.1 # SvxLink versions -%define SVXLINK 1.10.1 +%define SVXLINK 1.10.2 %define REMOTERX 1.6.1 # Sounds version %define SOUNDS 25.05 Name: svxlink -Version: 26.05.1 +Version: 26.05.2 Release: 0 Summary: Multi purpose voice services system for ham radio License: GPL-2.0-only ++++++ svxlink-26.05.1.tar.gz -> svxlink-26.05.2.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/svxlink-26.05.1/src/doc/README-26.05.2.adoc new/svxlink-26.05.2/src/doc/README-26.05.2.adoc --- old/svxlink-26.05.1/src/doc/README-26.05.2.adoc 1970-01-01 01:00:00.000000000 +0100 +++ new/svxlink-26.05.2/src/doc/README-26.05.2.adoc 2026-10-09 19:32:28.000000000 +0200 @@ -0,0 +1,62 @@ += SvxLink release 26.05.2 -- 09 Oct 2026 + +This is a SECURITY bugfix release for the 26.05 release. Several security +related bugs have been fixed. Have a look at the ChangeLogs below for an +overview of all fixes. + +In depth description of each security patch can be found at the +https://github.com/sm0svx/svxlink/security/advisories?state=published[GitHub Security Advisories page]. + +''' + +== Complete ChangeLog for SvxLink Server version 1.10.2 + +* Security: ModuleMetarInfo: malformed METAR report → uncaught + std::out_of_range crash and 100% CPU infinite-loop hang (remote) + (GHSA-m4q5-8r78-5gqg) + Credit: Mark Rose <[email protected]> + +* Security: SvxReflector: Use-after-free / iterator invalidation in + Reflector::broadcastMsg when a client is disconnected due to a write + failure (GHSA-6w9x-486p-ggrj) + Credit: mark-mcu + +* Security: LocationInfo: Stack buffer overflow via unbounded sprintf in + APRS UDP status reporting (GHSA-6w9x-486p-ggrj) + Credit: mark-mcu + +* ModuleMetarInfo: The libcurl handle was not removed from the multi + handle before being cleaned up (GHSA-6w9x-486p-ggrj) + Credit: mark-mcu + +* ModuleEchoLink: The PTY object was not deleted on module cleanup + (GHSA-6w9x-486p-ggrj) + Credit: mark-mcu + + +== Complete ChangeLog for Qtel version 1.3.0 + +No changes in this release. + + +== Complete ChangeLog for EchoLib version 1.3.8 + +* Security: EchoLink Qso: unvalidated non-audio (NDATA) packet length → + out-of-bounds read and std::string invalid-range crash (network) + (GHSA-2m63-p9h9-p5rf) + Credit: Mark Rose <[email protected]> + +* Security: EchoLink: Remote callsign received in SDES packets not + sanitized, which could lead to TCL command injection in SvxLink and + buffer overflows (GHSA-6w9x-486p-ggrj) + Credit: mark-mcu + +* Security: EchoLink proxy: Missing bounds check when buffering + unprocessed TCP data could overflow the receive buffer + (GHSA-6w9x-486p-ggrj) + Credit: mark-mcu + + +== Complete ChangeLog for the Async library version 1.9.1 + +No changes in this release. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/svxlink-26.05.1/src/echolib/ChangeLog new/svxlink-26.05.2/src/echolib/ChangeLog --- old/svxlink-26.05.1/src/echolib/ChangeLog 2026-07-26 13:22:42.000000000 +0200 +++ new/svxlink-26.05.2/src/echolib/ChangeLog 2026-10-09 19:32:28.000000000 +0200 @@ -1,3 +1,23 @@ + 1.3.8 -- 09 Oct 2026 +---------------------- + +* Security: EchoLink Qso: unvalidated non-audio (NDATA) packet length → + out-of-bounds read and std::string invalid-range crash (network) + (GHSA-2m63-p9h9-p5rf) + Credit: Mark Rose <[email protected]> + +* Security: EchoLink: Remote callsign received in SDES packets not + sanitized, which could lead to TCL command injection in SvxLink and + buffer overflows (GHSA-6w9x-486p-ggrj) + Credit: mark-mcu + +* Security: EchoLink proxy: Missing bounds check when buffering + unprocessed TCP data could overflow the receive buffer + (GHSA-6w9x-486p-ggrj) + Credit: mark-mcu + + + 1.3.7 -- 26 Jul 2026 ---------------------- diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/svxlink-26.05.1/src/echolib/EchoLinkDispatcher.cpp new/svxlink-26.05.2/src/echolib/EchoLinkDispatcher.cpp --- old/svxlink-26.05.1/src/echolib/EchoLinkDispatcher.cpp 2026-07-26 13:22:42.000000000 +0200 +++ new/svxlink-26.05.2/src/echolib/EchoLinkDispatcher.cpp 2026-10-09 19:32:28.000000000 +0200 @@ -11,7 +11,7 @@ \verbatim EchoLib - A library for EchoLink communication -Copyright (C) 2003-2014 Tobias Blomberg / SM0SVX +Copyright (C) 2003-2026 Tobias Blomberg / SM0SVX This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -341,6 +341,10 @@ char *strtok_buf_ptr = strtok_buf; char *remote_call = strtok_r(remote_id, " \t\n\r", &strtok_buf_ptr); const char *remote_name = strtok_r(NULL, " \t\n\r", &strtok_buf_ptr); + if (remote_call != 0) + { + sanitizeCallsign(remote_call); + } if ((remote_call != 0) && (remote_call[0] != 0)) { if (remote_name == 0) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/svxlink-26.05.1/src/echolib/EchoLinkProxy.cpp new/svxlink-26.05.2/src/echolib/EchoLinkProxy.cpp --- old/svxlink-26.05.1/src/echolib/EchoLinkProxy.cpp 2026-07-26 13:22:42.000000000 +0200 +++ new/svxlink-26.05.2/src/echolib/EchoLinkProxy.cpp 2026-10-09 19:32:28.000000000 +0200 @@ -543,6 +543,10 @@ memcpy(recv_buf + recv_buf_cnt, buf, len); recv_buf_cnt += len; int processed = tcpDataReceived(recv_buf, recv_buf_cnt); + if (processed < 0) + { + processed = 0; + } if (processed >= recv_buf_cnt) { recv_buf_cnt = 0; @@ -556,8 +560,17 @@ else { int processed = tcpDataReceived(buf, len); + if (processed < 0) + { + processed = 0; + } if (processed < len) { + if (len - processed > recv_buf_size) + { + reset(); + return; + } recv_buf_cnt = len - processed; memcpy(recv_buf, buf + processed, recv_buf_cnt); } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/svxlink-26.05.1/src/echolib/EchoLinkQso.cpp new/svxlink-26.05.2/src/echolib/EchoLinkQso.cpp --- old/svxlink-26.05.1/src/echolib/EchoLinkQso.cpp 2026-07-26 13:22:42.000000000 +0200 +++ new/svxlink-26.05.2/src/echolib/EchoLinkQso.cpp 2026-10-09 19:32:28.000000000 +0200 @@ -9,7 +9,7 @@ \verbatim EchoLib - A library for EchoLink communication -Copyright (C) 2003-2014 Tobias Blomberg / SM0SVX +Copyright (C) 2003-2026 Tobias Blomberg / SM0SVX This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -648,7 +648,12 @@ size_t pos = str.find_first_of(" \t\n\r"); if (pos != string::npos) { - remote_call = str.substr(0, pos); + remote_id[pos] = 0; + sanitizeCallsign(remote_id); + if (remote_id[0] != 0) + { + remote_call = remote_id; + } pos = str.find_first_not_of(" \t\n\r", pos); if (pos != string::npos) { @@ -691,7 +696,12 @@ " since we are disconnected.\n"; return; } - + + if (len <= 0) + { + return; + } + if(buf[0] != 0xc0) /* Not an audio packet */ { handleNonAudioPacket(buf, len); @@ -707,13 +717,20 @@ { //printData(buf, len); - if(memcmp(buf+1, "NDATA", 5) == 0) + if (len < 7) + { + cerr << "Ignoring short non-audio packet received:\n"; + printData(buf, len); + return; + } + + if (memcmp(buf+1, "NDATA", 5) == 0) { if (buf[6] == 0x0d) // Remote station info / conference status { char *info_buf = reinterpret_cast<char *>(buf); char *null = (char *)memchr(info_buf, 0, len); - if (null == 0) + if (null == 0 || null < info_buf + 7) { cerr << "Malformed info packet received:\n"; printData(buf, len); @@ -736,7 +753,7 @@ { char *chat_buf = reinterpret_cast<char *>(buf); char *null = (char *)memchr(buf, 0, len); - if (null == 0) + if (null == 0 || null < chat_buf + 6) { cerr << "Malformed chat packet received:\n"; printData(buf, len); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/svxlink-26.05.1/src/echolib/rtpacket.cpp new/svxlink-26.05.2/src/echolib/rtpacket.cpp --- old/svxlink-26.05.1/src/echolib/rtpacket.cpp 2026-07-26 13:22:42.000000000 +0200 +++ new/svxlink-26.05.2/src/echolib/rtpacket.cpp 2026-10-09 19:32:28.000000000 +0200 @@ -275,3 +275,33 @@ return sawsdes; } + +/* SANITIZECALLSIGN -- Remove characters that are not valid in an + EchoLink callsign and limit the length of it. + + The callsign received in an SDES packet is controlled by the remote + station. It is used by applications in for example TCL event strings + and fixed size buffers so make sure that it only contain characters + that are valid in a callsign (e.g. SM0SVX-L, *ECHOTEST*) and that it + is of a reasonable length. The string is modified in place. */ + +/***************************************************/ + +void sanitizeCallsign(char *call) +{ + const int max_len = 32; + char *dst = call; + const char *src; + + for (src = call; (*src != 0) && (dst - call < max_len); ++src) + { + char ch = *src; + if (((ch >= 'A') && (ch <= 'Z')) || ((ch >= 'a') && (ch <= 'z')) || + ((ch >= '0') && (ch <= '9')) || + (ch == '-') || (ch == '/') || (ch == '*') || (ch == '_')) + { + *dst++ = ch; + } + } + *dst = 0; +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/svxlink-26.05.1/src/echolib/rtpacket.h new/svxlink-26.05.2/src/echolib/rtpacket.h --- old/svxlink-26.05.1/src/echolib/rtpacket.h 2026-07-26 13:22:42.000000000 +0200 +++ new/svxlink-26.05.2/src/echolib/rtpacket.h 2026-10-09 19:32:28.000000000 +0200 @@ -14,5 +14,6 @@ bool parseSDES(char *, unsigned char *, int, unsigned char); bool isRTCPByepacket(unsigned char *, int); bool isRTCPSdespacket(unsigned char *, int); +void sanitizeCallsign(char *); #endif diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/svxlink-26.05.1/src/locationinfo/AprsUdpClient.cpp new/svxlink-26.05.2/src/locationinfo/AprsUdpClient.cpp --- old/svxlink-26.05.1/src/locationinfo/AprsUdpClient.cpp 2026-07-26 13:22:42.000000000 +0200 +++ new/svxlink-26.05.2/src/locationinfo/AprsUdpClient.cpp 2026-10-09 19:32:28.000000000 +0200 @@ -6,7 +6,7 @@ \verbatim SvxLink - A Multi Purpose Voice Services System for Ham Radio Use -Copyright (C) 2003-2025 Tobias Blomberg / SM0SVX +Copyright (C) 2003-2026 Tobias Blomberg / SM0SVX This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -33,6 +33,7 @@ ****************************************************************************/ #include <iostream> +#include <algorithm> #include <cstdio> #include <cmath> #include <cstring> @@ -272,7 +273,9 @@ if (sock.initOk()) { - char sdes_packet[256]; + // Room for the header, two SDES items of max 255 bytes each, the + // end marker and padding + char sdes_packet[1024]; int sdes_len = buildSdesPacket(sdes_packet); //std::cout << "### AprsUdpClient::sendLocationInfo" << std::endl; @@ -302,7 +305,7 @@ #define addText(block, text) \ do { \ - int sl = strlen(text); \ + int sl = std::min(strlen(text), size_t(255)); \ *block++ = sl; \ memcpy(block, text, sl); \ block += sl; \ @@ -336,10 +339,10 @@ sprintf(info, " On @"); break; case 1: - sprintf(info, "=%s ", curr_call.c_str()); + snprintf(info, sizeof(info), "=%s ", curr_call.c_str()); break; default: - sprintf(info, "+%s ", curr_call.c_str()); + snprintf(info, sizeof(info), "+%s ", curr_call.c_str()); break; } break; @@ -371,19 +374,21 @@ ap = p + 8; *ap++ = RTCP_SDES_CNAME; - sprintf(tmp, "%s-%s/%d", loc_cfg.mycall.c_str(), loc_cfg.prefix.c_str(), - getPasswd(loc_cfg.mycall)); + snprintf(tmp, sizeof(tmp), "%s-%s/%d", + loc_cfg.mycall.c_str(), loc_cfg.prefix.c_str(), + getPasswd(loc_cfg.mycall)); addText(ap, tmp); *ap++ = RTCP_SDES_LOC; - sprintf(tmp, ")EL-%.6s!%s0PHG%c%c%c%c/%06d/%03d%6s%02d%02d\r\n", - loc_cfg.mycall.c_str(), pos, - getPowerParam(loc_cfg.power), - getHeightParam(loc_cfg.height), - getGainParam(loc_cfg.gain), - getDirectionParam(loc_cfg.beam_dir), - loc_cfg.frequency, getToneParam(), - info, utc.tm_hour, utc.tm_min); + snprintf(tmp, sizeof(tmp), + ")EL-%.6s!%s0PHG%c%c%c%c/%06d/%03d%6s%02d%02d\r\n", + loc_cfg.mycall.c_str(), pos, + getPowerParam(loc_cfg.power), + getHeightParam(loc_cfg.height), + getGainParam(loc_cfg.gain), + getDirectionParam(loc_cfg.beam_dir), + loc_cfg.frequency, getToneParam(), + info, utc.tm_hour, utc.tm_min); addText(ap, tmp); *ap++ = RTCP_SDES_END; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/svxlink-26.05.1/src/svxlink/ChangeLog new/svxlink-26.05.2/src/svxlink/ChangeLog --- old/svxlink-26.05.1/src/svxlink/ChangeLog 2026-07-26 13:22:42.000000000 +0200 +++ new/svxlink-26.05.2/src/svxlink/ChangeLog 2026-10-09 19:32:28.000000000 +0200 @@ -1,3 +1,30 @@ + 1.10.2 -- 09 Oct 2026 +----------------------- + +* Security: ModuleMetarInfo: malformed METAR report → uncaught + std::out_of_range crash and 100% CPU infinite-loop hang (remote) + (GHSA-m4q5-8r78-5gqg) + Credit: Mark Rose <[email protected]> + +* Security: SvxReflector: Use-after-free / iterator invalidation in + Reflector::broadcastMsg when a client is disconnected due to a write + failure (GHSA-6w9x-486p-ggrj) + Credit: mark-mcu + +* Security: LocationInfo: Stack buffer overflow via unbounded sprintf in + APRS UDP status reporting (GHSA-6w9x-486p-ggrj) + Credit: mark-mcu + +* ModuleMetarInfo: The libcurl handle was not removed from the multi + handle before being cleaned up (GHSA-6w9x-486p-ggrj) + Credit: mark-mcu + +* ModuleEchoLink: The PTY object was not deleted on module cleanup + (GHSA-6w9x-486p-ggrj) + Credit: mark-mcu + + + 1.10.1 -- 26 Jul 2026 ----------------------- diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/svxlink-26.05.1/src/svxlink/modules/echolink/ModuleEchoLink.cpp new/svxlink-26.05.2/src/svxlink/modules/echolink/ModuleEchoLink.cpp --- old/svxlink-26.05.1/src/svxlink/modules/echolink/ModuleEchoLink.cpp 2026-07-26 13:22:42.000000000 +0200 +++ new/svxlink-26.05.2/src/svxlink/modules/echolink/ModuleEchoLink.cpp 2026-10-09 19:32:28.000000000 +0200 @@ -6,7 +6,7 @@ \verbatim A module (plugin) for the multi purpose tranciever frontend system. -Copyright (C) 2004-2014 Tobias Blomberg / SM0SVX +Copyright (C) 2004-2026 Tobias Blomberg / SM0SVX This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -580,6 +580,8 @@ dir = 0; delete proxy; proxy = 0; + delete pty; + pty = 0; delete cbc_timer; cbc_timer = 0; delete dbc_timer; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/svxlink-26.05.1/src/svxlink/modules/metarinfo/ModuleMetarInfo.cpp new/svxlink-26.05.2/src/svxlink/modules/metarinfo/ModuleMetarInfo.cpp --- old/svxlink-26.05.1/src/svxlink/modules/metarinfo/ModuleMetarInfo.cpp 2026-07-26 13:22:42.000000000 +0200 +++ new/svxlink-26.05.2/src/svxlink/modules/metarinfo/ModuleMetarInfo.cpp 2026-10-09 19:32:28.000000000 +0200 @@ -42,6 +42,7 @@ #include <algorithm> #include <queue> #include <regex.h> +#include <stdexcept> /**************************************************************************** @@ -185,8 +186,7 @@ ~Http() { - if (pending_curl) - curl_easy_cleanup(pending_curl); + removePendingCurl(); while (!url_queue.empty()) { curl_easy_cleanup(url_queue.front()); @@ -211,7 +211,7 @@ if (handle_count == 0) { disableAllWatches(); - curl_easy_cleanup(pending_curl); + removePendingCurl(); if (url_queue.empty()) { pending_curl = 0; @@ -236,7 +236,7 @@ if (handle_count == 0) { disableAllWatches(); - curl_easy_cleanup(pending_curl); + removePendingCurl(); if (url_queue.empty()) { pending_curl = 0; @@ -254,6 +254,16 @@ update_timer.reset(); } /* onActivity */ + void removePendingCurl(void) + { + if (pending_curl != 0) + { + curl_multi_remove_handle(multi_handle, pending_curl); + curl_easy_cleanup(pending_curl); + pending_curl = 0; + } + } /* removePendingCurl */ + static size_t callback(char *contents, size_t size, size_t nmemb, void *userp) { @@ -760,7 +770,10 @@ //pos = (cmdit->substr(0,1)).c_str(); //spos= mypad[pos[0]]; string spos = mypad[(*cmdit)[0]]; - icao += spos.substr(cmdit->length(),1); + if (cmdit->length() < spos.length()) + { + icao += spos.substr(cmdit->length(),1); + } } } @@ -926,7 +939,7 @@ cout << "XML-METAR: " << metar << endl; } - if (met_utc.length() == 20 && !isvalidUTC(met_utc)) + if (met_utc.length() != 20 || !isvalidUTC(met_utc)) { stringstream temp; cout << "Metar information outdated" << endl; @@ -960,6 +973,12 @@ return; } + if (values.size() < 2) + { + cout << "ERROR: wrong Metarfile format, too few lines received" << endl; + return; + } + metar = values.back(); // contains the METAR values.pop_back(); std::string metartime = values.back(); // and the time at UTC @@ -1028,367 +1047,396 @@ int ModuleMetarInfo::handleMetar(std::string input) { - std::string current; - std::string tempstr; - std::stringstream temp; - StrList values; - bool is_false = false; - bool endflag = false; - bool nceiling = false; - float temp_view = 0; - int metartoken; - - metartoken = 0; - - // This is a raw MEATAR-report: - // - // FBJW 071300Z 09013KT 9999 FEW030 29/15 Q1023 RMK ... - // - temp << "metar \"" << input << "\""; - say(temp); - - temp << "announce_airport " << icao; - say(temp); - - splitStr(values, input, " "); - StrList::iterator it = values.begin(); - - while (it != values.end() && !endflag) { - - current = *it; - - // == current.tolower() - transform(current.begin(),current.end(),current.begin(), - (int(*)(int))tolower); - - metartoken = checkToken(current); + std::string current; + std::string tempstr; + std::stringstream temp; + StrList values; + bool is_false = false; + bool endflag = false; + bool nceiling = false; + float temp_view = 0; + int metartoken; - switch (metartoken) - { - - case INTOKEN: - break; - - case UTC: - temp << "metreport_time " << current.substr(2,4); - say(temp); - break; + metartoken = 0; - case AUTO: - // temp << " automatic_station "; - break; - - case WIND: - if (isWind(tempstr, current)) - { - temp << "wind " << tempstr; - say(temp); - } - break; + // This is a raw MEATAR-report: + // + // FBJW 071300Z 09013KT 9999 FEW030 29/15 Q1023 RMK ... + // + temp << "metar \"" << input << "\""; + say(temp); - case VALUEVARIES: - isValueVaries(tempstr, current); - if (!nceiling) - temp << "windvaries " << tempstr; - else - { - temp << "ceilingvaries " << tempstr; - nceiling = false; - } - say(temp); - nceiling = false; - break; + temp << "announce_airport " << icao; + say(temp); - case IS1STPARTOFVIEW: - temp_view = atof(current.c_str()); - break; - - case ISPARTOFMILES: - isPartofMiles(tempstr, current); - temp_view += atof(tempstr.c_str()); - temp << "visibility " << temp_view << " unit_miles"; - say(temp); - break; + splitStr(values, input, " "); + StrList::iterator it = values.begin(); - case ISVIEW: - if (isView(tempstr, current)) - { - temp << "visibility " << tempstr; - say(temp); - } - break; + // Advance "it" to the next token, used where a token's value is carried + // in the following token (e.g. PEAKWIND, WINDSHIFT, RMKVISIBILITY). + // If there is no next token, set endflag and leave "it" untouched so + // that the loop's trailing "it++" does not step past values.end(). + auto advanceOrEnd = [&it, &values, &endflag]() -> bool + { + StrList::iterator next = it; + ++next; + if (next == values.end()) + { + endflag = true; + return false; + } + it = next; + return true; + }; - case WORDSEXT: - temp << "say " << current << longmsg; - say(temp); - break; + try + { + while (it != values.end() && !endflag) { - case WORDSNOEXT: - temp << "say " << current; - say(temp); - break; + current = *it; - case CLOUDSVALID: - if (ispObscurance(tempstr, current)) - { - if (!is_false) // only once - { - processEvent("say clouds"); - is_false = true; - } - temp << "clouds " << tempstr; - say(temp); - } - break; + // == current.tolower() + transform(current.begin(),current.end(),current.begin(), + (int(*)(int))tolower); - case VERTICALVIEW: - isVerticalView(tempstr, current); - temp << "ceiling " << tempstr; - say(temp); - break; + metartoken = checkToken(current); - case ACTUALWX: - if (isActualWX(tempstr, current)) - { - temp << "actualWX " << tempstr; - say(temp); - } - break; + switch (metartoken) + { + case INTOKEN: + break; - case RVR: - if (isRVR(tempstr, current)) - { - temp << "rvr " << tempstr; - say(temp); - } - break; - - case TEMPERATURE: - validTemp(tempstr, current); - temp << "temperature " << tempstr; - say(temp); - validDp(tempstr, current); - temp << "dewpoint " << tempstr; - say(temp); - break; + case UTC: + temp << "metreport_time " << current.substr(2,4); + say(temp); + break; - case QNH: - isQnh(tempstr, current); - temp << tempstr; - say(temp); - is_false = false; - break; + case AUTO: + // temp << " automatic_station "; + break; - case RUNWAY: - isRunway(tempstr, current); - temp << "runway " << tempstr; + case WIND: + if (isWind(tempstr, current)) + { + temp << "wind " << tempstr; say(temp); - break; + } + break; - case ALLRWYSTATE: - isRwyState(tempstr, current); - temp << "runwaystate " << tempstr; - say(temp); - break; + case VALUEVARIES: + isValueVaries(tempstr, current); + if (!nceiling) + temp << "windvaries " << tempstr; + else + { + temp << "ceilingvaries " << tempstr; + nceiling = false; + } + say(temp); + nceiling = false; + break; + + case IS1STPARTOFVIEW: + temp_view = atof(current.c_str()); + break; + + case ISPARTOFMILES: + isPartofMiles(tempstr, current); + temp_view += atof(tempstr.c_str()); + temp << "visibility " << temp_view << " unit_miles"; + say(temp); + break; - case TIME: - isTime(tempstr, current); - temp << "time " << tempstr; + case ISVIEW: + if (isView(tempstr, current)) + { + temp << "visibility " << tempstr; say(temp); - break; + } + break; - case FORECAST: - temp << "trend " << current << longmsg; - say(temp); - break; + case WORDSEXT: + temp << "say " << current << longmsg; + say(temp); + break; + + case WORDSNOEXT: + temp << "say " << current; + say(temp); + break; - case RMK: - if (!remarks) - endflag = true; - else + case CLOUDSVALID: + if (ispObscurance(tempstr, current)) + { + if (!is_false) // only once { - temp << "remarks"; - say(temp); + processEvent("say clouds"); + is_false = true; } - break; - - case SLP: - temp << "slp " << getSlp(current); + temp << "clouds " << tempstr; say(temp); - break; + } + break; - case SNOWCLOSED: - processEvent("snowclosed"); - break; + case VERTICALVIEW: + isVerticalView(tempstr, current); + temp << "ceiling " << tempstr; + say(temp); + break; - case PEAKWIND: - it++; - current = *it; - if (getPeakWind(tempstr, current)) - { - temp << "peakwind " << tempstr; - say(temp); - } - break; + case ACTUALWX: + if (isActualWX(tempstr, current)) + { + temp << "actualWX " << tempstr; + say(temp); + } + break; - case NOSPECI: - processEvent("nospeci"); - break; + case RVR: + if (isRVR(tempstr, current)) + { + temp << "rvr " << tempstr; + say(temp); + } + break; - case WINDSHIFT: - it++; - current = *it; - temp << "windshift " << current; - say(temp); - break; + case TEMPERATURE: + validTemp(tempstr, current); + temp << "temperature " << tempstr; + say(temp); + validDp(tempstr, current); + temp << "dewpoint " << tempstr; + say(temp); + break; + + case QNH: + isQnh(tempstr, current); + temp << tempstr; + say(temp); + is_false = false; + break; + + case RUNWAY: + isRunway(tempstr, current); + temp << "runway " << tempstr; + say(temp); + break; + + case ALLRWYSTATE: + isRwyState(tempstr, current); + temp << "runwaystate " << tempstr; + say(temp); + break; + + case TIME: + isTime(tempstr, current); + temp << "time " << tempstr; + say(temp); + break; + + case FORECAST: + temp << "trend " << current << longmsg; + say(temp); + break; + + case RMK: + if (!remarks) + endflag = true; + else + { + temp << "remarks"; + say(temp); + } + break; - case AUTOTYPE: - temp << "say " << current << longmsg; - say(temp); - break; + case SLP: + temp << "slp " << getSlp(current); + say(temp); + break; + + case SNOWCLOSED: + processEvent("snowclosed"); + break; + + case PEAKWIND: + if (!advanceOrEnd()) break; + current = *it; + if (getPeakWind(tempstr, current)) + { + temp << "peakwind " << tempstr; + say(temp); + } + break; - case RMKVISIBILITY: + case NOSPECI: + processEvent("nospeci"); + break; + + case WINDSHIFT: + if (!advanceOrEnd()) break; + current = *it; + temp << "windshift " << current; + say(temp); + break; + + case AUTOTYPE: + temp << "say " << current << longmsg; + say(temp); + break; + + case RMKVISIBILITY: + if (!advanceOrEnd()) break; + current = *it; +/* temp << "rmk_visibility "; + // check if a direction is given? + if (checkDirection(tempstr, current)) + { + temp << "dir_" << tempstr << " "; it++; current = *it; -/* temp << "rmk_visibility "; - // check if a direction is given? - if (checkDirection(tempstr, current)) - { - temp << "dir_" << tempstr << " "; - it++; - current = *it; - } + } - if (getRmkVisibility(tempstr, current)) - { - temp << tempstr; - say(temp); - } + if (getRmkVisibility(tempstr, current)) + { + temp << tempstr; + say(temp); + } */ - break; - - case FROPA: - break; - - case LIGHTNING: - temp << "ltg " << getLightning(current); - say(temp); - break; - - case VIRGA: - break; - - case CEILING: - nceiling = true; - break; - - case DAYTEMPMAX: - temp << "max_daytemp " << getTempTime(current); - say(temp); - break; - - case DAYTEMPMIN: - temp << "min_daytemp " << getTempTime(current); - say(temp); - break; - - case FLIGHTLEVEL: - current.erase(0,2); - temp << "flightlevel " << current; - say(temp); - break; - - case WORDSINRMK: - temp << "say " << current; - say(temp); - break; - - case TEMPOOBSCURATION: - temp << "tempo_obscuration " << current.substr(-4,2) - << " " << current.substr(-2,2); - say(temp); - break; - - case TEMPINRMK: - temp << "rmk_tempdew " << getTempinRmk(current); - say(temp); - break; - - case MINMAXTEMP: - temp << "rmk_minmaxtemp " << getTempinRmk(current); - say(temp); - break; - - case MINTEMP: - temp << "rmk_mintemp " << getTemp(current); - say(temp); - break; + break; - case MAXTEMP: - temp << "rmk_maxtemp " << getTemp(current); - say(temp); - break; - - case PRESSURETENDENCY: - temp << "rmk_pressure " << getPressureinRmk(current); - say(temp); - break; - - case PRECIPITATION1: - temp << "rmk_precipitation 1 " << getPrecipitationinRmk(current); - say(temp); - break; + case FROPA: + break; - case PRECIPITATION6: - temp << "rmk_precipitation 6 " << getPrecipitationinRmk(current); - say(temp); - break; - - case PRECIPITATION24: - temp << "rmk_precipitation 24 " << getPrecipitationinRmk(current); - say(temp); - break; - - case PRECIPINRMK: - cout << "PRECIPINRMK\n"; - temp << "rmk_precip " << getPrecipitation(current); - say(temp); - break; - - case NOSEVEREWX: - temp << "say " << current; - say(temp); - break; - - case CLOUDTYPE: - temp << "cloudtypes" << getCloudType(current); - say(temp); - break; + case LIGHTNING: + temp << "ltg " << getLightning(current); + say(temp); + break; + + case VIRGA: + break; + + case CEILING: + nceiling = true; + break; + + case DAYTEMPMAX: + temp << "max_daytemp " << getTempTime(current); + say(temp); + break; + + case DAYTEMPMIN: + temp << "min_daytemp " << getTempTime(current); + say(temp); + break; + + case FLIGHTLEVEL: + current.erase(0,2); + temp << "flightlevel " << current; + say(temp); + break; + + case WORDSINRMK: + temp << "say " << current; + say(temp); + break; - case QFEINRMK: - temp << "qfe " << current.erase(0,3); - say(temp); - break; - - case MAINTENANCE: - temp << "say maintenance_needed"; + case TEMPOOBSCURATION: + if (current.length() >= 4) + { + temp << "tempo_obscuration " + << current.substr(current.length()-4,2) + << " " << current.substr(current.length()-2,2); say(temp); - break; - - case INVALID: - break; + } + break; - case END: - endflag = true; - break; + case TEMPINRMK: + temp << "rmk_tempdew " << getTempinRmk(current); + say(temp); + break; + + case MINMAXTEMP: + temp << "rmk_minmaxtemp " << getTempinRmk(current); + say(temp); + break; + + case MINTEMP: + temp << "rmk_mintemp " << getTemp(current); + say(temp); + break; + + case MAXTEMP: + temp << "rmk_maxtemp " << getTemp(current); + say(temp); + break; + + case PRESSURETENDENCY: + temp << "rmk_pressure " << getPressureinRmk(current); + say(temp); + break; + + case PRECIPITATION1: + temp << "rmk_precipitation 1 " << getPrecipitationinRmk(current); + say(temp); + break; + + case PRECIPITATION6: + temp << "rmk_precipitation 6 " << getPrecipitationinRmk(current); + say(temp); + break; + + case PRECIPITATION24: + temp << "rmk_precipitation 24 " << getPrecipitationinRmk(current); + say(temp); + break; + + case PRECIPINRMK: + cout << "PRECIPINRMK\n"; + temp << "rmk_precip " << getPrecipitation(current); + say(temp); + break; + + case NOSEVEREWX: + temp << "say " << current; + say(temp); + break; + + case CLOUDTYPE: + temp << "cloudtypes" << getCloudType(current); + say(temp); + break; + + case QFEINRMK: + temp << "qfe " << current.erase(0,3); + say(temp); + break; + + case MAINTENANCE: + temp << "say maintenance_needed"; + say(temp); + break; + + case INVALID: + break; + + case END: + endflag = true; + break; - default: - break; - } - // cout << current << endl; - it++; - } - return 1; + default: + break; + } + //cout << current << endl; + it++; + } + } /* try */ + catch (const std::exception &e) + { + cout << "*** ERROR: Exception while parsing METAR token \"" << current + << "\": " << e.what() << endl; + return 0; + } + return 1; } @@ -1493,6 +1541,7 @@ while (token.length() > 0) { + bool matched = false; for (a=0; a<15; a++) { if (token.find(clouds[a],0) != string::npos) @@ -1501,8 +1550,13 @@ token.erase(0,clouds[a].length()); ss << token.substr(0,1); token.erase(0,1); + matched = true; } } + if (!matched) + { + break; + } } return ss.str(); @@ -1531,6 +1585,8 @@ if (token.length() < 8 || token.length() > 11) return false; splitStr(tlist, token, "/"); + if (tlist.size() < 2) return false; + ss << tlist[0].substr(0,3) << " "; // direction ss << tlist[0].substr(3,2) << " "; // velocity @@ -1621,6 +1677,8 @@ { stringstream ss; + if (token.length() < 6) return ""; + (atoi(token.substr(3,1).c_str()) > 6) ? ss << "9" : ss << "10"; ss << token.substr(3,2) << "." << token.substr(5,1); return ss.str(); @@ -2081,7 +2139,10 @@ std::map <string, string>::iterator tt; tt = shdesig.find(token.substr(0,2)); // fm -> from, tl -> until - ss << tt->second; + if (tt != shdesig.end()) + { + ss << tt->second; + } ss << " " << token.substr(2,4); retval = ss.str(); } /* isTime */ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/svxlink-26.05.1/src/svxlink/reflector/Reflector.cpp new/svxlink-26.05.2/src/svxlink/reflector/Reflector.cpp --- old/svxlink-26.05.1/src/svxlink/reflector/Reflector.cpp 2026-07-26 13:22:42.000000000 +0200 +++ new/svxlink-26.05.2/src/svxlink/reflector/Reflector.cpp 2026-10-09 19:32:28.000000000 +0200 @@ -391,7 +391,12 @@ void Reflector::broadcastMsg(const ReflectorMsg& msg, const ReflectorClient::Filter& filter) { - for (const auto& item : m_client_con_map) + // Iterate over a copy of the client map since a failed write will + // disconnect the client, which will remove it from the map. Client + // objects are not deleted until later so the pointers in the copy + // stay valid. A disconnected client will not be in the CONNECTED state. + auto client_con_map_copy = m_client_con_map; + for (const auto& item : client_con_map_copy) { ReflectorClient *client = item.second; if (filter(client) && diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/svxlink-26.05.1/src/versions new/svxlink-26.05.2/src/versions --- old/svxlink-26.05.1/src/versions 2026-07-26 13:22:42.000000000 +0200 +++ new/svxlink-26.05.2/src/versions 2026-10-09 19:32:28.000000000 +0200 @@ -1,26 +1,26 @@ # Project release version -PROJECT=26.05.1 +PROJECT=26.05.2 # Version for the Qtel application QTEL=1.3.0 # Version for the EchoLib library -LIBECHOLIB=1.3.7 +LIBECHOLIB=1.3.8 # Version for the Async library LIBASYNC=1.9.1 # SvxLink versions -SVXLINK=1.10.1 +SVXLINK=1.10.2 MODULE_HELP=1.0.1 MODULE_PARROT=1.1.2 -MODULE_ECHO_LINK=1.6.2 +MODULE_ECHO_LINK=1.6.3 MODULE_TCL=1.0.2 MODULE_PROPAGATION_MONITOR=1.0.2 MODULE_TCL_VOICE_MAIL=1.0.4 MODULE_SELCALLENC=1.0.1 MODULE_DTMF_REPEATER=1.0.3 -MODULE_METAR_INFO=1.2.4 +MODULE_METAR_INFO=1.2.5 MODULE_FRN=1.1.2 MODULE_TRX=1.0.2 @@ -37,4 +37,4 @@ SVXSERVER=0.0.7 # Version for SvxReflector -SVXREFLECTOR=1.4.1 +SVXREFLECTOR=1.4.2
