shithub: choc

Download patch

ref: 78206090ebc2a2cbe150e440f6831cae810aa48b
parent: f1f8ecdeeadab2242c80cfa442c44c9d18a6fd6e
parent: 2855c1081facf4812cc74a48c880bc8832b0ede1
author: Simon Howard <[email protected]>
date: Sat Sep 30 22:19:15 EDT 2017

Merge pull request #943 from fragglet/sdl2-branch

Introduce a network protocol versioning scheme.

This will allow backwards-compatible protocol changes in future,
and also allow forks to remain compatible with Chocolate Doom.

--- a/src/net_client.c
+++ b/src/net_client.c
@@ -426,6 +426,41 @@
     NET_CL_SendTics(starttic, endtic);
 }
 
+// Parse a SYN packet received back from the server indicating a successful
+// connection attempt.
+static void NET_CL_ParseSYN(net_packet_t *packet)
+{
+    net_protocol_t protocol;
+    char *server_version;
+
+    server_version = NET_ReadSafeString(packet);
+    if (server_version == NULL)
+    {
+        return;
+    }
+
+    protocol = NET_ReadProtocol(packet);
+    if (protocol == NET_PROTOCOL_UNKNOWN)
+    {
+        return;
+    }
+
+    // We are now successfully connected.
+    client_connection.state = NET_CONN_STATE_CONNECTED;
+    client_connection.protocol = protocol;
+
+    // Even though we have negotiated a compatible protocol, the game may still
+    // desync. Chocolate Doom's philosophy makes this unlikely, but if we're
+    // playing with a forked version, or even against a different version that
+    // fixes a compatibility issue, we may still have problems.
+    if (strcmp(server_version, PACKAGE_STRING) != 0)
+    {
+        fprintf(stderr, "NET_CL_ParseSYN: This is '%s', but the server is "
+                "'%s'. It is possible that this mismatch may cause the game "
+                "to desync.\n", PACKAGE_STRING, server_version);
+    }
+}
+
 // data received while we are waiting for the game to start
 
 static void NET_CL_ParseWaitingData(net_packet_t *packet)
@@ -795,7 +830,7 @@
 {
     char *msg;
 
-    msg = NET_ReadString(packet);
+    msg = NET_ReadSafeString(packet);
 
     if (msg == NULL)
     {
@@ -802,9 +837,7 @@
         return;
     }
 
-    printf("Message from server: ");
-
-    NET_SafePuts(msg);
+    printf("Message from server:\n%s\n", msg);
 }
 
 // parse a received packet
@@ -826,6 +859,10 @@
     {
         switch (packet_type)
         {
+            case NET_PACKET_TYPE_SYN:
+                NET_CL_ParseSYN(packet);
+                break;
+
             case NET_PACKET_TYPE_WAITING_DATA:
                 NET_CL_ParseWaitingData(packet);
                 break;
@@ -921,6 +958,7 @@
     NET_WriteInt16(packet, NET_PACKET_TYPE_SYN);
     NET_WriteInt32(packet, NET_MAGIC_NUMBER);
     NET_WriteString(packet, PACKAGE_STRING);
+    NET_WriteProtocolList(packet);
     NET_WriteConnectData(packet, data);
     NET_WriteString(packet, net_player_name);
     NET_Conn_SendPacket(&client_connection, packet);
@@ -927,8 +965,7 @@
     NET_FreePacket(packet);
 }
 
-// connect to a server
-
+// Connect to a server
 boolean NET_CL_Connect(net_addr_t *addr, net_connect_data_t *data)
 {
     int start_time;
@@ -959,7 +996,7 @@
 
     // Initialize connection
 
-    NET_Conn_InitClient(&client_connection, addr);
+    NET_Conn_InitClient(&client_connection, addr, NET_PROTOCOL_UNKNOWN);
 
     // try to connect
 
--- a/src/net_common.c
+++ b/src/net_common.c
@@ -19,6 +19,7 @@
 
 #include "doomtype.h"
 #include "d_mode.h"
+#include "i_system.h"
 #include "i_timer.h"
 
 #include "net_common.h"
@@ -34,9 +35,19 @@
 
 #define KEEPALIVE_PERIOD 1
 
+// String names for the enum values in net_protocol_t, which are what is
+// sent over the wire. Every enum value must have an entry in this list.
+static struct
+{
+    net_protocol_t protocol;
+    const char *name;
+} protocol_names[] = {
+    {NET_PROTOCOL_CHOCOLATE_DOOM_0, "CHOCOLATE_DOOM_0"},
+};
+
 // reliable packet that is guaranteed to reach its destination
 
-struct net_reliable_packet_s 
+struct net_reliable_packet_s
 {
     net_packet_t *packet;
     int last_send_time;
@@ -44,11 +55,13 @@
     net_reliable_packet_t *next;
 };
 
-static void NET_Conn_Init(net_connection_t *conn, net_addr_t *addr)
+static void NET_Conn_Init(net_connection_t *conn, net_addr_t *addr,
+                          net_protocol_t protocol)
 {
     conn->last_send_time = -1;
     conn->num_retries = 0;
     conn->addr = addr;
+    conn->protocol = protocol;
     conn->reliable_packets = NULL;
     conn->reliable_send_seq = 0;
     conn->reliable_recv_seq = 0;
@@ -56,18 +69,20 @@
 
 // Initialize as a client connection
 
-void NET_Conn_InitClient(net_connection_t *conn, net_addr_t *addr)
+void NET_Conn_InitClient(net_connection_t *conn, net_addr_t *addr,
+                         net_protocol_t protocol)
 {
-    NET_Conn_Init(conn, addr);
+    NET_Conn_Init(conn, addr, protocol);
     conn->state = NET_CONN_STATE_CONNECTING;
 }
 
 // Initialize as a server connection
 
-void NET_Conn_InitServer(net_connection_t *conn, net_addr_t *addr)
+void NET_Conn_InitServer(net_connection_t *conn, net_addr_t *addr,
+                         net_protocol_t protocol)
 {
-    NET_Conn_Init(conn, addr);
-    conn->state = NET_CONN_STATE_WAITING_ACK;
+    NET_Conn_Init(conn, addr, protocol);
+    conn->state = NET_CONN_STATE_CONNECTED;
 }
 
 // Send a packet to a connection
@@ -80,38 +95,6 @@
     NET_SendPacket(conn->addr, packet);
 }
 
-// parse an ACK packet from a client
-
-static void NET_Conn_ParseACK(net_connection_t *conn, net_packet_t *packet)
-{
-    net_packet_t *reply;
-
-    if (conn->state == NET_CONN_STATE_CONNECTING)
-    {
-        // We are a client
-
-        // received a response from the server to our SYN
-
-        conn->state = NET_CONN_STATE_CONNECTED;
-
-        // We must send an ACK reply to the server's ACK
-
-        reply = NET_NewPacket(10);
-        NET_WriteInt16(reply, NET_PACKET_TYPE_ACK);
-        NET_Conn_SendPacket(conn, reply);
-        NET_FreePacket(reply);
-    }
-    
-    if (conn->state == NET_CONN_STATE_WAITING_ACK)
-    {
-        // We are a server
-
-        // Client is connected
-        
-        conn->state = NET_CONN_STATE_CONNECTED;
-    }
-}
-
 static void NET_Conn_ParseDisconnect(net_connection_t *conn, net_packet_t *packet)
 {
     net_packet_t *reply;
@@ -151,7 +134,7 @@
 {
     char *msg;
 
-    msg = NET_ReadString(packet);
+    msg = NET_ReadSafeString(packet);
 
     if (msg == NULL)
     {
@@ -165,8 +148,7 @@
         conn->state = NET_CONN_STATE_DISCONNECTED;
         conn->disconnect_reason = NET_DISCONNECT_REMOTE;
 
-        printf("Rejected by server: ");
-        NET_SafePuts(msg);
+        printf("Rejected by server: %s\n", msg);
     }
 }
 
@@ -282,9 +264,6 @@
     
     switch (*packet_type)
     {
-        case NET_PACKET_TYPE_ACK:
-            NET_Conn_ParseACK(conn, packet);
-            break;
         case NET_PACKET_TYPE_DISCONNECT:
             NET_Conn_ParseDisconnect(conn, packet);
             break;
@@ -370,35 +349,6 @@
             conn->reliable_packets->last_send_time = nowtime;
         }
     }
-    else if (conn->state == NET_CONN_STATE_WAITING_ACK)
-    {
-        if (conn->last_send_time < 0
-         || nowtime - conn->last_send_time > 1000)
-        {
-            // it has been a second since the last ACK was sent, and 
-            // still no reply.
-
-            if (conn->num_retries < MAX_RETRIES)
-            {
-                // send another ACK
-
-                packet = NET_NewPacket(10);
-                NET_WriteInt16(packet, NET_PACKET_TYPE_ACK);
-                NET_Conn_SendPacket(conn, packet);
-                NET_FreePacket(packet);
-                conn->last_send_time = nowtime;
-
-                ++conn->num_retries;
-            }
-            else 
-            {
-                // no more retries allowed.
-
-                conn->state = NET_CONN_STATE_DISCONNECTED;
-                conn->disconnect_reason = NET_DISCONNECT_TIMEOUT;
-            }
-        }
-    }
     else if (conn->state == NET_CONN_STATE_DISCONNECTING)
     {
         // Waiting for a reply to our DISCONNECT request.
@@ -530,5 +480,108 @@
         return false;
 
     return true;
+}
+
+static net_protocol_t ParseProtocolName(const char *name)
+{
+    int i;
+
+    for (i = 0; i < arrlen(protocol_names); ++i)
+    {
+        if (!strcmp(protocol_names[i].name, name))
+        {
+            return protocol_names[i].protocol;
+        }
+    }
+
+    return NET_PROTOCOL_UNKNOWN;
+}
+
+// NET_ReadProtocol reads a single string-format protocol name from the given
+// packet, returning NET_PROTOCOL_UNKNOWN if the string describes an unknown
+// protocol.
+net_protocol_t NET_ReadProtocol(net_packet_t *packet)
+{
+    const char *name;
+
+    name = NET_ReadString(packet);
+    if (name == NULL)
+    {
+        return NET_PROTOCOL_UNKNOWN;
+    }
+
+    return ParseProtocolName(name);
+}
+
+// NET_WriteProtocol writes a single string-format protocol name to a packet.
+void NET_WriteProtocol(net_packet_t *packet, net_protocol_t protocol)
+{
+    int i;
+
+    for (i = 0; i < arrlen(protocol_names); ++i)
+    {
+        if (protocol_names[i].protocol == protocol)
+        {
+            NET_WriteString(packet, protocol_names[i].name);
+            return;
+        }
+    }
+
+    // If you add an entry to the net_protocol_t enum, a corresponding entry
+    // must be added to the protocol_names list.
+    I_Error("NET_WriteProtocol: protocol %d missing from protocol_names "
+            "list; please add it.", protocol);
+}
+
+// NET_ReadProtocolList reads a list of string-format protocol names from
+// the given packet, returning a single protocol number. The protocol that is
+// returned is the last protocol in the list that is a supported protocol. If
+// no recognized protocols are read, NET_PROTOCOL_UNKNOWN is returned.
+net_protocol_t NET_ReadProtocolList(net_packet_t *packet)
+{
+    net_protocol_t result;
+    unsigned int num_protocols;
+    int i;
+
+    if (!NET_ReadInt8(packet, &num_protocols))
+    {
+        return NET_PROTOCOL_UNKNOWN;
+    }
+
+    result = NET_PROTOCOL_UNKNOWN;
+
+    for (i = 0; i < num_protocols; ++i)
+    {
+        net_protocol_t p;
+        const char *name;
+
+        name = NET_ReadString(packet);
+        if (name == NULL)
+        {
+            return NET_PROTOCOL_UNKNOWN;
+        }
+
+        p = ParseProtocolName(name);
+        if (p != NET_PROTOCOL_UNKNOWN)
+        {
+            result = p;
+        }
+    }
+
+    return result;
+}
+
+// NET_WriteProtocolList writes a list of string-format protocol names into
+// the given packet, all the supported protocols in the net_protocol_t enum.
+void NET_WriteProtocolList(net_packet_t *packet)
+{
+    int i;
+
+    NET_WriteInt8(packet, NET_NUM_PROTOCOLS);
+
+    for (i = 0; i < NET_NUM_PROTOCOLS; ++i)
+    {
+        NET_WriteProtocol(packet, i);
+    }
 }
 
--- a/src/net_common.h
+++ b/src/net_common.h
@@ -21,28 +21,18 @@
 #include "net_defs.h"
 #include "net_packet.h"
 
-typedef enum 
+typedef enum
 {
-    // sending syn packets, waiting for an ACK reply 
-    // (client side)
-
+    // Client has sent a SYN, is waiting for a SYN in response.
     NET_CONN_STATE_CONNECTING,
 
-    // received a syn, sent an ack, waiting for an ack reply
-    // (server side)
-
-    NET_CONN_STATE_WAITING_ACK,
-    
-    // successfully connected
-
+    // Successfully connected.
     NET_CONN_STATE_CONNECTED,
 
-    // sent a DISCONNECT packet, waiting for a DISCONNECT_ACK reply
-
+    // Sent a DISCONNECT packet, waiting for a DISCONNECT_ACK reply
     NET_CONN_STATE_DISCONNECTING,
 
-    // client successfully disconnected
-
+    // Client successfully disconnected
     NET_CONN_STATE_DISCONNECTED,
 
     // We are disconnected, but in a sleep state, waiting for several
@@ -50,7 +40,6 @@
     // to arrive, and we need to send another one.  We keep this as
     // a valid connection for a few seconds until we are sure that
     // the other end has successfully disconnected as well.
-
     NET_CONN_STATE_DISCONNECTED_SLEEP,
 
 } net_connstate_t;
@@ -82,6 +71,7 @@
     net_connstate_t state;
     net_disconnect_reason_t disconnect_reason;
     net_addr_t *addr;
+    net_protocol_t protocol;
     int last_send_time;
     int num_retries;
     int keepalive_send_time;
@@ -93,8 +83,10 @@
 
 
 void NET_Conn_SendPacket(net_connection_t *conn, net_packet_t *packet);
-void NET_Conn_InitClient(net_connection_t *conn, net_addr_t *addr);
-void NET_Conn_InitServer(net_connection_t *conn, net_addr_t *addr);
+void NET_Conn_InitClient(net_connection_t *conn, net_addr_t *addr,
+                         net_protocol_t protocol);
+void NET_Conn_InitServer(net_connection_t *conn, net_addr_t *addr,
+                         net_protocol_t protocol);
 boolean NET_Conn_Packet(net_connection_t *conn, net_packet_t *packet,
                         unsigned int *packet_type);
 void NET_Conn_Disconnect(net_connection_t *conn);
@@ -101,10 +93,15 @@
 void NET_Conn_Run(net_connection_t *conn);
 net_packet_t *NET_Conn_NewReliable(net_connection_t *conn, int packet_type);
 
-// Other miscellaneous common functions
+// Protocol list exchange.
+net_protocol_t NET_ReadProtocol(net_packet_t *packet);
+void NET_WriteProtocol(net_packet_t *packet, net_protocol_t protocol);
+net_protocol_t NET_ReadProtocolList(net_packet_t *packet);
+void NET_WriteProtocolList(net_packet_t *packet);
 
+// Other miscellaneous common functions
 unsigned int NET_ExpandTicNum(unsigned int relative, unsigned int b);
-boolean NET_ValidGameSettings(GameMode_t mode, GameMission_t mission, 
+boolean NET_ValidGameSettings(GameMode_t mode, GameMission_t mission,
                               net_gamesettings_t *settings);
 
 #endif /* #ifndef NET_COMMON_H */
--- a/src/net_defs.h
+++ b/src/net_defs.h
@@ -98,20 +98,43 @@
     void *handle;
 };
 
-// magic number sent when connecting to check this is a valid client
+// Magic number sent when connecting to check this is a valid client
+#define NET_MAGIC_NUMBER     1454104972U
 
-#define NET_MAGIC_NUMBER 3436803284U
+// Old magic number used by Chocolate Doom versions before v3.0:
+#define NET_OLD_MAGIC_NUMBER 3436803284U
 
 // header field value indicating that the packet is a reliable packet
 
 #define NET_RELIABLE_PACKET (1 << 15)
 
+// Supported protocols. If you're developing a fork of Chocolate
+// Doom, you can add your own entry to this list while maintaining
+// compatibility with Chocolate Doom servers. Higher-numbered enum values
+// will be preferred when negotiating a protocol for the client and server
+// to use, so the order matters.
+// NOTE: The values in this enum do not have any special value outside of
+// the program they're compiled in. What matters is the string representation.
+typedef enum
+{
+    // Protocol introduced with Chocolate Doom v3.0. Each compatibility-
+    // breaking change to the network protocol will produce a new protocol
+    // number in this enum.
+    NET_PROTOCOL_CHOCOLATE_DOOM_0,
+
+    // Add your own protocol here; be sure to add a name for it to the list
+    // in net_common.c too.
+
+    NET_NUM_PROTOCOLS,
+    NET_PROTOCOL_UNKNOWN,
+} net_protocol_t;
+
 // packet types
 
 typedef enum
 {
     NET_PACKET_TYPE_SYN,
-    NET_PACKET_TYPE_ACK,
+    NET_PACKET_TYPE_ACK, // deprecated
     NET_PACKET_TYPE_REJECTED,
     NET_PACKET_TYPE_KEEPALIVE,
     NET_PACKET_TYPE_WAITING_DATA,
--- a/src/net_packet.c
+++ b/src/net_packet.c
@@ -15,6 +15,7 @@
 //      Network packet manipulation (net_packet_t)
 //
 
+#include <ctype.h>
 #include <string.h>
 #include "m_misc.h"
 #include "net_packet.h"
@@ -202,6 +203,37 @@
     return start;
 }
 
+// Read a string from the packet, but (potentially) modify it to strip
+// out any unprintable characters which could be malicious control codes.
+// Note that this may modify the original packet contents.
+char *NET_ReadSafeString(net_packet_t *packet)
+{
+    char *r, *w, *result;
+
+    result = NET_ReadString(packet);
+    if (result == NULL)
+    {
+        return NULL;
+    }
+
+    // w is always <= r, so we never produce a longer string than the original.
+    w = result;
+    for (r = result; *r != '\0'; ++r)
+    {
+        // TODO: This is a very naive way of producing a safe string; only
+        // ASCII characters are allowed. Probably this should really support
+        // UTF-8 characters as well.
+        if (isprint(*r) || *r == '\n')
+        {
+            *w = *r;
+            ++w;
+        }
+    }
+    *w = '\0';
+
+    return result;
+}
+
 // Dynamically increases the size of a packet
 
 static void NET_IncreasePacket(net_packet_t *packet)
@@ -270,7 +302,7 @@
     packet->len += 4;
 }
 
-void NET_WriteString(net_packet_t *packet, char *string)
+void NET_WriteString(net_packet_t *packet, const char *string)
 {
     byte *p;
     size_t string_size;
--- a/src/net_packet.h
+++ b/src/net_packet.h
@@ -33,12 +33,13 @@
 boolean NET_ReadSInt32(net_packet_t *packet, signed int *data);
 
 char *NET_ReadString(net_packet_t *packet);
+char *NET_ReadSafeString(net_packet_t *packet);
 
 void NET_WriteInt8(net_packet_t *packet, unsigned int i);
 void NET_WriteInt16(net_packet_t *packet, unsigned int i);
 void NET_WriteInt32(net_packet_t *packet, unsigned int i);
 
-void NET_WriteString(net_packet_t *packet, char *string);
+void NET_WriteString(net_packet_t *packet, const char *string);
 
 #endif /* #ifndef NET_PACKET_H */
 
--- a/src/net_query.c
+++ b/src/net_query.c
@@ -738,7 +738,7 @@
         printf("(game running) ");
     }
 
-    NET_SafePuts(data->description);
+    printf("%s\n", data->description);
 }
 
 void NET_LANQuery(void)
--- a/src/net_server.c
+++ b/src/net_server.c
@@ -213,7 +213,7 @@
     va_start(args, s);
     M_vsnprintf(buf, sizeof(buf), s, args);
     va_end(args);
-    
+
     for (i=0; i<MAXNETNODES; ++i)
     {
         if (ClientConnected(&clients[i]))
@@ -222,7 +222,7 @@
         }
     }
 
-    NET_SafePuts(buf);
+    printf("%s\n", buf);
 }
 
 
@@ -555,16 +555,14 @@
     NET_FreePacket(packet);
 }
 
-static void NET_SV_InitNewClient(net_client_t *client, 
-                                 net_addr_t *addr,
-                                 char *player_name)
+static void NET_SV_InitNewClient(net_client_t *client, net_addr_t *addr,
+                                 net_protocol_t protocol)
 {
     client->active = true;
     client->connect_time = I_GetTimeMS();
-    NET_Conn_InitServer(&client->connection, addr);
+    NET_Conn_InitServer(&client->connection, addr, protocol);
     client->addr = addr;
     client->last_send_time = -1;
-    client->name = M_StringDuplicate(player_name);
 
     // init the ticcmd send queue
 
@@ -580,99 +578,121 @@
 
 // parse a SYN from a client(initiating a connection)
 
-static void NET_SV_ParseSYN(net_packet_t *packet, 
-                            net_client_t *client,
+static void NET_SV_ParseSYN(net_packet_t *packet, net_client_t *client,
                             net_addr_t *addr)
 {
     unsigned int magic;
     net_connect_data_t data;
+    net_packet_t *reply;
+    net_protocol_t protocol;
     char *player_name;
     char *client_version;
+    int num_players;
     int i;
 
-    // read the magic number
-
+    // Read the magic number and check it is the expected one.
     if (!NET_ReadInt32(packet, &magic))
     {
         return;
     }
 
-    if (magic != NET_MAGIC_NUMBER)
+    switch (magic)
     {
-        // invalid magic number
+        case NET_MAGIC_NUMBER:
+            break;
 
-        return;
+        case NET_OLD_MAGIC_NUMBER:
+            NET_SV_SendReject(addr,
+                "You are using an old client version that is not supported by "
+                "this server. This server is running " PACKAGE_STRING ".");
+            return;
+
+        default:
+            return;
     }
 
-    // Check the client version is the same as the server
-
+    // Read the client version string. We actually now only use this when
+    // sending a reject message, as we only reject if we can't negotiate a
+    // common protocol (below).
     client_version = NET_ReadString(packet);
-
     if (client_version == NULL)
     {
         return;
     }
 
-    if (strcmp(client_version, PACKAGE_STRING) != 0)
+    // Read the client's list of accepted protocols. Net play between forks
+    // of Chocolate Doom is accepted provided that they can negotiate a
+    // common accepted protocol.
+    protocol = NET_ReadProtocolList(packet);
+    if (protocol == NET_PROTOCOL_UNKNOWN)
     {
-        //!
-        // @category net
-        //
-        // When running a netgame server, ignore version mismatches between
-        // the server and the client. Using this option may cause game
-        // desyncs to occur, or differences in protocol may mean the netgame
-        // will simply not function at all.
-        //
+        char reject_msg[256];
 
-        if (M_CheckParm("-ignoreversion") == 0)
-        {
-            NET_SV_SendReject(addr,
-                "Different " PACKAGE_NAME " versions cannot play a net game!\n"
-                "Version mismatch: server version is: " PACKAGE_STRING);
-            return;
-        }
+        M_snprintf(reject_msg, sizeof(reject_msg),
+            "Version mismatch: server version is: " PACKAGE_STRING "; "
+            "client is: %s. No common compatible protocol could be "
+            "negotiated.", client_version);
+        NET_SV_SendReject(addr, reject_msg);
+        return;
     }
 
-    // read the game mode and mission
-
-    if (!NET_ReadConnectData(packet, &data))
+    // Read connect data, and check that the game mode/mission are valid
+    // and the max_players value is in a sensible range.
+    if (!NET_ReadConnectData(packet, &data)
+     || !D_ValidGameMode(data.gamemission, data.gamemode)
+     || data.max_players > NET_MAXPLAYERS)
     {
         return;
     }
 
-    if (!D_ValidGameMode(data.gamemission, data.gamemode))
+    // Read the player's name
+    player_name = NET_ReadString(packet);
+    if (player_name == NULL)
     {
         return;
     }
 
-    // Check max_players value. This must be in a sensible range.
+    // At this point we have received a valid SYN.
 
-    if (data.max_players > NET_MAXPLAYERS)
+    // Not accepting new connections?
+    if (server_state != SERVER_WAITING_LAUNCH)
     {
+        NET_SV_SendReject(addr,
+                          "Server is not currently accepting connections");
         return;
     }
 
-    // read the player's name
+    // Before accepting a new client, check that there is a slot free.
+    NET_SV_AssignPlayers();
+    num_players = NET_SV_NumPlayers();
 
-    player_name = NET_ReadString(packet);
-
-    if (player_name == NULL)
+    if ((!data.drone && num_players >= NET_SV_MaxPlayers())
+     || NET_SV_NumClients() >= MAXNETNODES)
     {
+        NET_SV_SendReject(addr, "Server is full!");
         return;
     }
 
-    // received a valid SYN
+    // TODO: Add server option to allow rejecting clients which set
+    // lowres_turn.  This is potentially desirable as the presence of such
+    // clients affects turning resolution.
 
-    // not accepting new connections?
+    // Adopt the game mode and mission of the first connecting client:
+    if (num_players == 0 && !data.drone)
+    {
+        sv_gamemode = data.gamemode;
+        sv_gamemission = data.gamemission;
+    }
 
-    if (server_state != SERVER_WAITING_LAUNCH)
+    // Check the connecting client is playing the same game as all
+    // the other clients
+    if (data.gamemode != sv_gamemode || data.gamemission != sv_gamemission)
     {
-        NET_SV_SendReject(addr, "Server is not currently accepting connections");
+        NET_SV_SendReject(addr, "You are playing the wrong game!");
         return;
     }
 
-    // allocate a client slot if there isn't one already
-
+    // Allocate a client slot if there isn't one already
     if (client == NULL)
     {
         // find a slot, or return if none found
@@ -702,67 +722,30 @@
         }
     }
 
-    // New client?
-
-    if (!client->active)
+    // Client already connected?
+    if (client->active)
     {
-        int num_players;
+        return;
+    }
 
-        // Before accepting a new client, check that there is a slot
-        // free
+    // Activate, initialize connection
+    NET_SV_InitNewClient(client, addr, protocol);
 
-        NET_SV_AssignPlayers();
-        num_players = NET_SV_NumPlayers();
+    // Save the SHA1 checksums and other details.
+    memcpy(client->wad_sha1sum, data.wad_sha1sum, sizeof(sha1_digest_t));
+    memcpy(client->deh_sha1sum, data.deh_sha1sum, sizeof(sha1_digest_t));
+    client->is_freedoom = data.is_freedoom;
+    client->max_players = data.max_players;
+    client->name = M_StringDuplicate(player_name);
+    client->recording_lowres = data.lowres_turn;
+    client->drone = data.drone;
+    client->player_class = data.player_class;
 
-        if ((!data.drone && num_players >= NET_SV_MaxPlayers())
-         || NET_SV_NumClients() >= MAXNETNODES)
-        {
-            NET_SV_SendReject(addr, "Server is full!");
-            return;
-        }
-
-        // TODO: Add server option to allow rejecting clients which
-        // set lowres_turn.  This is potentially desirable as the 
-        // presence of such clients affects turning resolution.
-
-        // Adopt the game mode and mission of the first connecting client
-
-        if (num_players == 0 && !data.drone)
-        {
-            sv_gamemode = data.gamemode;
-            sv_gamemission = data.gamemission;
-        }
-
-        // Save the SHA1 checksums
-
-        memcpy(client->wad_sha1sum, data.wad_sha1sum, sizeof(sha1_digest_t));
-        memcpy(client->deh_sha1sum, data.deh_sha1sum, sizeof(sha1_digest_t));
-        client->is_freedoom = data.is_freedoom;
-        client->max_players = data.max_players;
-
-        // Check the connecting client is playing the same game as all
-        // the other clients
-
-        if (data.gamemode != sv_gamemode || data.gamemission != sv_gamemission)
-        {
-            NET_SV_SendReject(addr, "You are playing the wrong game!");
-            return;
-        }
-
-        // Activate, initialize connection
-
-        NET_SV_InitNewClient(client, addr, player_name);
-
-        client->recording_lowres = data.lowres_turn;
-        client->drone = data.drone;
-        client->player_class = data.player_class;
-    }
-
-    if (client->connection.state == NET_CONN_STATE_WAITING_ACK)
-    {
-        // force an acknowledgement
-        client->connection.last_send_time = -1;
-    }
+    // Send a reply back to the client, indicating a successful connection
+    // and specifying the protocol that will be used for communications.
+    reply = NET_Conn_NewReliable(&client->connection, NET_PACKET_TYPE_SYN);
+    NET_WriteString(reply, PACKAGE_STRING);
+    NET_WriteProtocol(reply, protocol);
 }
 
 // Parse a launch packet. This is sent by the key player when the "start"
--- a/src/net_structrw.c
+++ b/src/net_structrw.c
@@ -17,7 +17,6 @@
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
-#include <ctype.h>
 
 #include "doomtype.h"
 #include "m_misc.h"
@@ -122,7 +121,7 @@
 {
     boolean result;
 
-    query->version = NET_ReadString(packet);
+    query->version = NET_ReadSafeString(packet);
 
     result = query->version != NULL
           && NET_ReadInt8(packet, (unsigned int *) &query->server_state)
@@ -133,7 +132,7 @@
     
     if (result)
     {
-        query->description = NET_ReadString(packet);
+        query->description = NET_ReadSafeString(packet);
 
         return query->description != NULL;
     }   
@@ -551,22 +550,3 @@
     NET_WriteBlob(packet, seed, sizeof(prng_seed_t));
 }
 
-// "Safe" version of puts, for displaying messages received from the
-// network.
-
-void NET_SafePuts(char *s)
-{
-    char *p;
-
-    // Do not do a straight "puts" of the string, as this could be
-    // dangerous (sending control codes to terminals can do all
-    // kinds of things)
-
-    for (p=s; *p; ++p)
-    {
-        if (isprint(*p) || *p == '\n')
-            putchar(*p);
-    }
-
-    putchar('\n');
-}
--- a/src/net_structrw.h
+++ b/src/net_structrw.h
@@ -43,8 +43,6 @@
 void NET_WriteWaitData(net_packet_t *packet, net_waitdata_t *data);
 boolean NET_ReadWaitData(net_packet_t *packet, net_waitdata_t *data);
 
-void NET_SafePuts(char *msg);
-
 boolean NET_ReadPRNGSeed(net_packet_t *packet, prng_seed_t seed);
 void NET_WritePRNGSeed(net_packet_t *packet, prng_seed_t seed);