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

Reply via email to