(tomcat) branch main updated: Improve security of Unix domain sockets

[email protected]
Newsgroups gmane.comp.jakarta.tomcat.devel
Message-ID <178645385360.2073346.12714365782688671034@gitbox3-he-fi.apache.org>
This is an automated email from the ASF dual-hosted git repository.

markt-asf pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/tomcat.git


The following commit(s) were added to refs/heads/main by this push:
     new f3ca8d4020 Improve security of Unix domain sockets
f3ca8d4020 is described below

commit f3ca8d4020909c5b46557cc364745e232f3c895e
Author: Mark Thomas <[email protected]>
AuthorDate: Wed Jul 8 11:09:03 2026 +0100

    Improve security of Unix domain sockets
---
 .../apache/tomcat/util/net/LocalStrings.properties |   9 +-
 .../tomcat/util/net/LocalStrings_fr.properties     |   4 +-
 .../tomcat/util/net/LocalStrings_ja.properties     |   4 +-
 .../tomcat/util/net/LocalStrings_ko.properties     |   4 +-
 .../tomcat/util/net/LocalStrings_zh_CN.properties  |   4 +-
 java/org/apache/tomcat/util/net/NioEndpoint.java   | 177 ++++++++++++++++++++-
 webapps/docs/changelog.xml                         |   7 +
 webapps/docs/config/http.xml                       |  60 +++++--
 8 files changed, 238 insertions(+), 31 deletions(-)

diff --git a/java/org/apache/tomcat/util/net/LocalStrings.properties b/java/org/apache/tomcat/util/net/LocalStrings.properties
index f433c46a81..d00ffae618 100644
--- a/java/org/apache/tomcat/util/net/LocalStrings.properties
+++ b/java/org/apache/tomcat/util/net/LocalStrings.properties
@@ -85,14 +85,19 @@ endpoint.nio.keyProcessingError=Error processing selection key
 endpoint.nio.latchMustBeZero=Latch must be at count zero or null
 endpoint.nio.nullLatch=Latch cannot be null
 endpoint.nio.nullSocketChannel=Invalid null socket channel while processing poller event
-endpoint.nio.perms.readFail=Failed to set read permissions for Unix domain socket [{0}]
-endpoint.nio.perms.writeFail=Failed to set write permissions for Unix domain socket [{0}]
+endpoint.nio.perms.readFail=Failed to set read permissions for all users on [{0}]
+endpoint.nio.perms.writeFail=Failed to set write permissions for all users on [{0}]
 endpoint.nio.registerFail=Failed to register socket with selector from poller
 endpoint.nio.selectorCloseFail=Failed to close selector when closing the poller
 endpoint.nio.selectorLoopError=Error in selector loop
 endpoint.nio.stopLatchAwaitFail=The pollers did not stop within the expected time
 endpoint.nio.stopLatchAwaitInterrupted=This thread was interrupted while waiting for the pollers to stop
 endpoint.nio.timeoutCme=Exception during processing of timeouts. The code has been checked repeatedly and no concurrent modification has been found. If you are able to repeat this error please open a Tomcat bug and provide the steps to reproduce.
+endpoint.nio.uds.noOwner=The ownership information for the directory where the Unix Domain Socket will be created [{0}] cannot be verified because it is not available
+endpoint.nio.uds.notPosix=The Unix Domain Socket [{0}] is not on a POSIX file system. It may not be possible to configure permissions as requested. The Unix Domain Socket will be created with default permissions.
+endpoint.nio.uds.parentLaxPermissions=The directory where the Unix Domain Socket will be created [{0}] has additional permissions [{1}] compared to the expected permission set.
+endpoint.nio.uds.parentNotPosix=The directory where the Unix Domain Socket will be created [{0}] is not on a POSIX file system. It may not be possible to configure permissions as requested. The directory will be created with default permissions.
+endpoint.nio.uds.parentOwner=The directory where the Unix Domain Socket will be created [{0}] has owner [{1}] but is expected to be either the expected owner [{2}] or the current user [{3}]
 endpoint.noSslHostConfig=No SSLHostConfig element was found with the hostName [{0}] to match the defaultSSLHostConfigName for the connector [{1}]
 endpoint.noSslHostName=No host name was provided for the SSL host configuration
 endpoint.poll.error=Unexpected poller error
diff --git a/java/org/apache/tomcat/util/net/LocalStrings_fr.properties b/java/org/apache/tomcat/util/net/LocalStrings_fr.properties
index 3dcec9773a..f75955180b 100644
--- a/java/org/apache/tomcat/util/net/LocalStrings_fr.properties
+++ b/java/org/apache/tomcat/util/net/LocalStrings_fr.properties
@@ -88,8 +88,8 @@ endpoint.nio.keyProcessingError=Erreur lors du traitement de la clé de sélecti
 endpoint.nio.latchMustBeZero=Le compte du latch doit être à zéro ou null
 endpoint.nio.nullLatch=Le latch ne peut être null
 endpoint.nio.nullSocketChannel=Le canal du socket est invalide car null lors du traitement de l'évênement du poller
-endpoint.nio.perms.readFail=Echec d''ajout des permissions en lecture pour le socket de domaine Unix [{0}]
-endpoint.nio.perms.writeFail=Echec d''ajout des permissions en écriture pour le socket de domaine Unix [{0}]
+endpoint.nio.perms.readFail=Échec de l''ajout des droits de lecture pour tous les utilisateurs sur [{0}]
+endpoint.nio.perms.writeFail=Échec de l''ajout des droits d''écriture pour tous les utilisateurs sur [{0}]
 endpoint.nio.registerFail=Echec d'enregistrement du socket avec le sélecteur du poller
 endpoint.nio.selectorCloseFail=Impossible de fermer le sélecteur lors de la fermeture du poller
 endpoint.nio.selectorLoopError=Erreur dans la boucle du sélecteur
diff --git a/java/org/apache/tomcat/util/net/LocalStrings_ja.properties b/java/org/apache/tomcat/util/net/LocalStrings_ja.properties
index 8d98111a9c..1479bc87ea 100644
--- a/java/org/apache/tomcat/util/net/LocalStrings_ja.properties
+++ b/java/org/apache/tomcat/util/net/LocalStrings_ja.properties
@@ -88,8 +88,8 @@ endpoint.nio.keyProcessingError=選択キー処理中のエラー
 endpoint.nio.latchMustBeZero=Latchのカウントは 0 か null でなければなりません。
 endpoint.nio.nullLatch=Latch に null は指定できません。
 endpoint.nio.nullSocketChannel=pollerイベントの処理中の無効なnullソケットチャネル
-endpoint.nio.perms.readFail=Unixドメインソケット [{0}] の読み取り権限の設定に失敗しました
-endpoint.nio.perms.writeFail=Unixドメインソケット [{0}] の書き込み権限の設定に失敗しました
+endpoint.nio.perms.readFail=[{0}] に対する全ユーザーの読み取り権限の設定に失敗しました。
+endpoint.nio.perms.writeFail=[{0}] に対する全ユーザーの書き込み権限の設定に失敗しました。
 endpoint.nio.registerFail=Pollerからソケットのセレクタに登録できませんでした。
 endpoint.nio.selectorCloseFail=Pollerを閉じるときにセレクターを閉じることができませんでした。
 endpoint.nio.selectorLoopError=セレクタの処理ループ中のエラー
diff --git a/java/org/apache/tomcat/util/net/LocalStrings_ko.properties b/java/org/apache/tomcat/util/net/LocalStrings_ko.properties
index b76790a130..3b1ec7017e 100644
--- a/java/org/apache/tomcat/util/net/LocalStrings_ko.properties
+++ b/java/org/apache/tomcat/util/net/LocalStrings_ko.properties
@@ -84,8 +84,8 @@ endpoint.nio.keyProcessingError=Selection 키를 처리 중 오류 발생
 endpoint.nio.latchMustBeZero=Latch는 반드시 널이거나 count가 0이어야 합니다.
 endpoint.nio.nullLatch=Latch가 널이어서는 안됩니다.
 endpoint.nio.nullSocketChannel=PollerEvent를 처리하는 중 유효하지 않은 널 소켓 채널이 발견되었습니다.
-endpoint.nio.perms.readFail=Unix 도메인 소켓 [{0}]에 읽기를 허용하는 데에 실패했습니다.
-endpoint.nio.perms.writeFail=Unix 도메인 소켓 [{0}]에 쓰기를 허용하는 데에 실패했습니다.
+endpoint.nio.perms.readFail=[{0}]에 대한 모든 사용자의 읽기 권한을 허용하지 못했습니다.
+endpoint.nio.perms.writeFail=[{0}]에 대한 모든 사용자의 쓰기 권한을 허용하지 못했습니다.
 endpoint.nio.registerFail=Poller로부터의 selector와 함께, 소켓을 등록하지 못했습니다.
 endpoint.nio.selectorCloseFail=Poller를 닫을 때, selector를 닫지 못했습니다.
 endpoint.nio.selectorLoopError=Selector 루프 내에서 오류 발생
diff --git a/java/org/apache/tomcat/util/net/LocalStrings_zh_CN.properties b/java/org/apache/tomcat/util/net/LocalStrings_zh_CN.properties
index 41e0583bab..f452f15cd1 100644
--- a/java/org/apache/tomcat/util/net/LocalStrings_zh_CN.properties
+++ b/java/org/apache/tomcat/util/net/LocalStrings_zh_CN.properties
@@ -84,8 +84,8 @@ endpoint.nio.keyProcessingError=处理选择键时出错
 endpoint.nio.latchMustBeZero=闩锁必须为0或空
 endpoint.nio.nullLatch=闩锁不能为空
 endpoint.nio.nullSocketChannel=处理轮训事件时出现无效的空套接字通道
-endpoint.nio.perms.readFail=设置Unix域套接字[{0}]的读取权限失败
-endpoint.nio.perms.writeFail=设置Unix域套接字[{0}]的写取权限失败
+endpoint.nio.perms.readFail=无法为 [{0}] 上的所有用户设置读取权限
+endpoint.nio.perms.writeFail=无法为 [{0}] 上的所有用户设置写入权限
 endpoint.nio.registerFail=无法用轮询器中的选择器注册套接字。
 endpoint.nio.selectorCloseFail=关闭轮询器时未能关闭选择器
 endpoint.nio.selectorLoopError=选择器循环出错
diff --git a/java/org/apache/tomcat/util/net/NioEndpoint.java b/java/org/apache/tomcat/util/net/NioEndpoint.java
index 34cb4d2a69..21b0cadbbb 100644
--- a/java/org/apache/tomcat/util/net/NioEndpoint.java
+++ b/java/org/apache/tomcat/util/net/NioEndpoint.java
@@ -39,8 +39,10 @@ import java.nio.channels.ServerSocketChannel;
 import java.nio.channels.SocketChannel;
 import java.nio.channels.WritableByteChannel;
 import java.nio.file.Files;
+import java.nio.file.LinkOption;
 import java.nio.file.Path;
 import java.nio.file.Paths;
+import java.nio.file.attribute.BasicFileAttributes;
 import java.nio.file.attribute.FileAttribute;
 import java.nio.file.attribute.PosixFilePermission;
 import java.nio.file.attribute.PosixFilePermissions;
@@ -197,6 +199,93 @@ public class NioEndpoint extends AbstractNetworkChannelEndpoint<NioChannel,Socke
     }
 
 
+    /**
+     * Permissions which will be checked for / set on the parent directory of the Unix Domain Socket path before the
+     * Unix Domain Socket is created.
+     */
+    private String unixDomainSocketParentPermissions = null;
+
+    /**
+     * Sets the permissions that will be checked for / set on the parent directory of the Unix Domain Socket path
+     * before the Unix Domain Socket is created.
+     *
+     * @param unixDomainSocketParentPermissions the permissions string
+     */
+    public void setUnixDomainSocketParentPermissions(String unixDomainSocketParentPermissions) {
+        this.unixDomainSocketParentPermissions = unixDomainSocketParentPermissions;
+    }
+
+    /**
+     * Returns the permissions that will be checked for / set on the parent directory of the Unix Domain Socket path
+     * before the Unix Domain Socket is created.
+     *
+     * @return the permissions string
+     */
+    public String getUnixDomainSocketParentPermissions() {
+        return unixDomainSocketParentPermissions;
+    }
+
+    private Set<PosixFilePermission> getUnixDomainSocketParentPermissionsInternal() {
+        String permissionString = getUnixDomainSocketParentPermissions();
+        boolean derivePermissions = false;
+
+        if (permissionString == null) {
+            permissionString = getUnixDomainSocketPathPermissions();
+            /*
+             * UDS permissions will be file permissions which will be missing the execute bit required at the directory
+             * level and may include write permissions which are not required so set flag to derive a sensible default.
+             */
+            derivePermissions = true;
+        }
+
+        if (permissionString == null || permissionString.isBlank()) {
+            return null;
+        }
+
+        Set<PosixFilePermission> result = PosixFilePermissions.fromString(permissionString);
+        if (derivePermissions) {
+            // Add execute before removing write
+            if (result.contains(PosixFilePermission.OWNER_READ) || result.contains(PosixFilePermission.OWNER_WRITE)) {
+                result.add(PosixFilePermission.OWNER_EXECUTE);
+            }
+            if (result.contains(PosixFilePermission.GROUP_READ) || result.contains(PosixFilePermission.GROUP_WRITE)) {
+                result.add(PosixFilePermission.GROUP_EXECUTE);
+            }
+            if (result.contains(PosixFilePermission.OTHERS_READ) || result.contains(PosixFilePermission.OTHERS_WRITE)) {
+                result.add(PosixFilePermission.OTHERS_EXECUTE);
+            }
+            result.remove(PosixFilePermission.GROUP_WRITE);
+            result.remove(PosixFilePermission.OTHERS_WRITE);
+        }
+
+        return result;
+    }
+
+
+    /**
+     * If the parent directory for the UDS already exists, the name of the expected owner.
+     */
+    private String unixDomainSocketParentOwner = null;
+
+    /**
+     * Returns the expected name of the owner of the parent directory for the UDS if it already exists.
+     *
+     * @return the owner name
+     */
+    public String getUnixDomainSocketParentOwner() {
+        return unixDomainSocketParentOwner;
+    }
+
+    /**
+     * Sets the expected name of the owner of the parent directory for the UDS if it already exists.
+     *
+     * @param unixDomainSocketParentOwner the expected owner name
+     */
+    public void setUnixDomainSocketParentOwner(String unixDomainSocketParentOwner) {
+        this.unixDomainSocketParentOwner = unixDomainSocketParentOwner;
+    }
+
+
     /**
      * Priority of the poller thread.
      */
@@ -312,9 +401,79 @@ public class NioEndpoint extends AbstractNetworkChannelEndpoint<NioChannel,Socke
                 throw new IllegalArgumentException(sm.getString("endpoint.init.bind.inherited"));
             }
         } else if (getUnixDomainSocketPath() != null) {
+            /*
+             * If permissions are configured, need to create the parent folder first with the correct permissions to
+             * avoid a TOCTOU issue with the Unix Domain Socket.
+             */
+            Set<PosixFilePermission> parentPosixPermissions = getUnixDomainSocketParentPermissionsInternal();
+            if (parentPosixPermissions != null) {
+                Path udsPath = Paths.get(getUnixDomainSocketPath()).toAbsolutePath();
+                Path parentPath = udsPath.getParent();
+                File parentFile = parentPath.toFile();
+
+                boolean posixSupported = parentPath.getFileSystem().supportedFileAttributeViews().contains("posix");
+
+                if (!Files.exists(parentPath, LinkOption.NOFOLLOW_LINKS) || !Files.readAttributes(
+                        parentPath, BasicFileAttributes.class, LinkOption.NOFOLLOW_LINKS).isDirectory()) {
+                    if (posixSupported) {
+                        /*
+                         * The immediate parent will be configured with the requested permissions. If other parents are
+                         * created, they will be created with the specified permissions masked as per the current
+                         * process's UMASK.
+                         */
+                        Files.createDirectories(parentPath, PosixFilePermissions.asFileAttribute(parentPosixPermissions));
+                        FileAttribute<Set<PosixFilePermission>> attrs = PosixFilePermissions.asFileAttribute(parentPosixPermissions);
+                        Files.setAttribute(parentPath, attrs.name(), attrs.value());
+                    } else {
+                        Files.createDirectories(parentPath);
+                    }
+                }
+
+                // Directory should exist now
+                if (posixSupported) {
+                    // Check permissions are at least as restrictive as those specified.
+                    Set<PosixFilePermission> currentPosixPermissions =
+                            Files.getPosixFilePermissions(parentPath, LinkOption.NOFOLLOW_LINKS);
+                    currentPosixPermissions.removeAll(parentPosixPermissions);
+                    if (!currentPosixPermissions.isEmpty()) {
+                        throw new IllegalStateException(sm.getString("endpoint.nio.uds.parentLaxPermissions",
+                                parentPath, currentPosixPermissions));
+                    }
+                } else {
+                    log.warn(sm.getString("endpoint.nio.uds.parentNotPosix", parentPath));
+                    setPermissionsForNonPosixFile(parentFile, parentPosixPermissions);
+                }
+
+                /*
+                 * Check the owner - should either be the current user (if created above) or the expected owner (if
+                 * pre-created).
+                 */
+                String expectedOwner = getUnixDomainSocketParentOwner();
+                if (expectedOwner == null || !expectedOwner.isEmpty()) {
+                    String owner;
+                    try {
+                        owner = Files.getOwner(parentPath, LinkOption.NOFOLLOW_LINKS).getName();
+                    } catch (Throwable t) {
+                        ExceptionUtils.handleThrowable(t);
+                        throw new IllegalStateException(sm.getString("endpoint.nio.uds.noOwner", parentPath), t);
+                    }
+                    if (!owner.equals(expectedOwner)) {
+                        String currentUser = System.getProperty("user.name");
+                        if (!owner.equals(currentUser)) {
+                            throw new IllegalStateException(sm.getString("endpoint.nio.uds.parentOwner",
+                                    parentPath, owner, expectedOwner, currentUser));
+
+                        }
+                    }
+                }
+            }
+
+            // Create the Unix Domain Socket
             SocketAddress sa = UnixDomainSocketAddress.of(getUnixDomainSocketPath());
             serverSock = ServerSocketChannel.open(StandardProtocolFamily.UNIX);
             serverSock.bind(sa, getAcceptCount());
+
+            // If permissions are configured, set them on the Unix Domain Socket
             if (getUnixDomainSocketPathPermissions() != null) {
                 Path path = Paths.get(getUnixDomainSocketPath());
                 Set<PosixFilePermission> permissions =
@@ -323,13 +482,9 @@ public class NioEndpoint extends AbstractNetworkChannelEndpoint<NioChannel,Socke
                     FileAttribute<Set<PosixFilePermission>> attrs = PosixFilePermissions.asFileAttribute(permissions);
                     Files.setAttribute(path, attrs.name(), attrs.value());
                 } else {
+                    log.warn(sm.getString("endpoint.nio.uds.notPosix", path));
                     File file = path.toFile();
-                    if (permissions.contains(PosixFilePermission.OTHERS_READ) && !file.setReadable(true, false)) {
-                        log.warn(sm.getString("endpoint.nio.perms.readFail", file.getPath()));
-                    }
-                    if (permissions.contains(PosixFilePermission.OTHERS_WRITE) && !file.setWritable(true, false)) {
-                        log.warn(sm.getString("endpoint.nio.perms.writeFail", file.getPath()));
-                    }
+                    setPermissionsForNonPosixFile(file, permissions);
                 }
             }
         } else {
@@ -342,6 +497,16 @@ public class NioEndpoint extends AbstractNetworkChannelEndpoint<NioChannel,Socke
     }
 
 
+    private void setPermissionsForNonPosixFile(File file, Set<PosixFilePermission> permissions) {
+        if (permissions.contains(PosixFilePermission.OTHERS_READ) && !file.setReadable(true, false)) {
+            log.warn(sm.getString("endpoint.nio.perms.readFail", file.getAbsolutePath()));
+        }
+        if (permissions.contains(PosixFilePermission.OTHERS_WRITE) && !file.setWritable(true, false)) {
+            log.warn(sm.getString("endpoint.nio.perms.writeFail", file.getAbsolutePath()));
+        }
+    }
+
+
     /**
      * Start the NIO endpoint, creating acceptor, poller threads.
      */
diff --git a/webapps/docs/changelog.xml b/webapps/docs/changelog.xml
index 17bf053b37..c37ae9230d 100644
--- a/webapps/docs/changelog.xml
+++ b/webapps/docs/changelog.xml
@@ -361,6 +361,13 @@
         of how early in the HEADERS frame processing an error is detected.
         (markt)
       </fix>
+      <add>
+        Add new attributes (<code>unixDomainSocketParentPermissions</code> and
+        <code>unixDomainSocketParentOwner</code>) to the NIO connector to
+        provide additional control over the security of Unix Domain Sockets.
+        Additional checks (enabled by default) have also been added for the
+        directory where the Unix Domain Socket will be created.(markt)
+      </add>
     </changelog>
   </subsection>
   <subsection name="Jasper">
diff --git a/webapps/docs/config/http.xml b/webapps/docs/config/http.xml
index 99fe11301d..eeadfd17d0 100644
--- a/webapps/docs/config/http.xml
+++ b/webapps/docs/config/http.xml
@@ -1022,12 +1022,44 @@
 
       <attribute name="unixDomainSocketPathPermissions" required="false">
         <p>Where supported, the posix permissions that will be applied to the
-        to the Unix Domain Socket specified with
-        <code>unixDomainSocketPath</code> above. The
-        permissions are specified as a string of nine characters, in three sets
-        of three: (r)ead, (w)rite and e(x)ecute for owner, group and others
-        respectively. If a permission is not granted, a hyphen is used. If
-        unspecified, the permissions default to <code>rw-rw-rw-</code>.</p>
+        Unix Domain Socket specified with <code>unixDomainSocketPath</code>
+        above.  If the Unix Domain Socket is required to be created securely,
+        these permissions should be selected as to limit access to trusted
+        users.</p>
+        <p>The permissions are specified as a string of nine characters, in
+        three sets of three: (r)ead, (w)rite and e(x)ecute for owner, group and
+        others respectively. If a permission is not granted, a hyphen is
+        used.</p>
+        <p>If unspecified, no permissions are explicitly set meaning that the
+        resulting permissions will be determined by the current umask.</p>
+      </attribute>
+
+      <attribute name="unixDomainSocketParentOwner" required="false">
+        <p>The expected ownership of the directory where the Unix Domain Socket
+        will be created. The directory must be owned either by this user or the
+        current user associated with the Tomcat process.</p>
+        <p>This attribute is ignored if
+        <code>unixDomainSocketParentPermissions</code> are skipped. If not
+        ignored, the ownership checks on the parent directory, can be skipped by
+        setting this attribute to the empty string.</p>
+        <p>If unspecified, the default value is unset.</p>
+      </attribute>
+
+      <attribute name="unixDomainSocketParentPermissions" required="false">
+        <p>Where supported, the posix permissions that will be checked on /
+        applied to the directory where the Unix Domain Socket will be created.
+        If the Unix Domain Socket is required to be created securely, these
+        permissions should be selected as to limit access to trusted users.</p>
+        <p>To skip the permission checks on the parent directory, set this
+        attribute to the empty string.</p>
+        <p>The permissions are specified as a string of nine characters, in
+        three sets of three: (r)ead, (w)rite and e(x)ecute for owner, group and
+        others respectively. If a permission is not granted, a hyphen is used.
+        If unspecified, the value of
+        <code>unixDomainSocketPathPermissions</code> is used with the addition
+        of the appropriate execute permission for every read and/or write
+        permission that is specified and the removal of group and other write
+        permissions if present.</p>
       </attribute>
 
       <attribute name="useInheritedChannel" required="false">
@@ -1134,16 +1166,14 @@
   that support Unix Domain Sockets will bind to the socket at the given path.
   </p>
 
-  <p>For users of Java 16 and higher, support is provided within the NIO
-  connectors.
-  </p>
+  <p>Support is provided within the NIO connector.</p>
 
-  <p>The socket path is created with read and write permissions for all
-  users. To protect this socket, place it in a directory with suitable
-  permissions appropriately configured to restrict access as required.
-  Alternatively, on platforms that support posix permissions, the
-  permissions on the socket can be set directly with the
-  <code>unixDomainSocketPathPermissions</code> option.
+  <p>By default, the socket path is created with the current umask. To protect
+  this socket, place it in a directory with suitable permissions appropriately
+  configured to restrict access as required. Alternatively, on platforms that
+  support posix permissions, the permissions for the containing folder can be
+  set with <code>unixDomainSocketParentPermissions</code> and the permissions
+  for the socket can be set with <code>unixDomainSocketPathPermissions</code>.
   </p>
 
   <p>Tomcat will automatically remove the socket on server shutdown. If the
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.