From 275c99f9960314860f13b59f718af2f506196970 Mon Sep 17 00:00:00 2001
From: pepeto <pepeto69@gmail.com>
Date: Sat, 16 Feb 2013 18:09:38 +0100
Subject: [PATCH] Make initial network protocol compatible with old versions.

---
 common/connection.c        |    1 +
 common/connection.h        |    6 ++++
 common/generate_packets.py |   12 +++----
 common/packets.c           |   85 ++++++++++++++++++++++++++++++++------------
 common/packets.def         |   15 ++++++--
 common/packets.h           |   46 ++++++++++++------------
 doc/HACKING                |   10 +++++-
 server/connecthand.c       |   14 ++++++++
 server/sernet.c            |   13 +++++--
 9 files changed, 145 insertions(+), 57 deletions(-)

diff --git a/common/connection.c b/common/connection.c
index ff671b6..295542b 100644
--- a/common/connection.c
+++ b/common/connection.c
@@ -584,6 +584,7 @@ void connection_common_init(struct connection *pconn)
 {
   pconn->established = FALSE;
   pconn->used = TRUE;
+  packet_header_init(&pconn->packet_header);
   pconn->closing_reason = NULL;
   pconn->last_write = NULL;
   pconn->buffer = new_socket_packet_buffer();
diff --git a/common/connection.h b/common/connection.h
index 7109403..b5ea5b7 100644
--- a/common/connection.h
+++ b/common/connection.h
@@ -115,6 +115,11 @@ struct socket_packet_buffer {
   unsigned char *data;
 };
 
+struct packet_header {
+  unsigned int length : 4;      /* Actually 'enum data_type' */
+  unsigned int type : 4;        /* Actually 'enum data_type' */
+};
+
 #define SPECVEC_TAG byte
 #define SPECVEC_TYPE unsigned char
 #include "specvec.h"
@@ -128,6 +133,7 @@ struct connection {
   int sock;
   bool used;
   bool established;		/* have negotiated initial packets */
+  struct packet_header packet_header;
   char *closing_reason;
 
   /* connection is "observer", not controller; may be observing
diff --git a/common/generate_packets.py b/common/generate_packets.py
index bb737a5..f2d8a71 100755
--- a/common/generate_packets.py
+++ b/common/generate_packets.py
@@ -653,7 +653,7 @@ class Variant:
             self.extra_send_args2=self.extra_send_args2+', force_to_send'
             self.extra_send_args3=self.extra_send_args3+', bool force_to_send'
 
-        self.receive_prototype='static struct %(packet_name)s *receive_%(name)s(struct connection *pc, enum packet_type type)'%self.__dict__
+        self.receive_prototype='static struct %(packet_name)s *receive_%(name)s(struct connection *pc)'%self.__dict__
         self.send_prototype='static int send_%(name)s(struct connection *pc%(extra_send_args)s)'%self.__dict__
 
     # See Field.get_dict
@@ -763,7 +763,7 @@ static char *stats_%(name)s_names[] = {%(names)s};
         temp='''%(send_prototype)s
 {
 <real_packet1><delta_header>  SEND_PACKET_START(%(type)s);
-<log><report><pre1><body><pre2>  <post>SEND_PACKET_END;
+<log><report><pre1><body><pre2><post>  SEND_PACKET_END(%(type)s);
 }
 
 '''
@@ -919,7 +919,7 @@ static char *stats_%(name)s_names[] = {%(names)s};
         if self.delta:
             delta_header='''  %(name)s_fields fields;
   struct %(packet_name)s *old;
-  struct genhash **hash = pc->phs.received + type;
+  struct genhash **hash = pc->phs.received + %(type)s;
 '''
             delta_body1="\n  DIO_BV_GET(&din, fields);\n"
             body1=""
@@ -1138,7 +1138,7 @@ class Packet:
             self.extra_send_args2=self.extra_send_args2+', force_to_send'
             self.extra_send_args3=self.extra_send_args3+', bool force_to_send'
 
-        self.receive_prototype='struct %(name)s *receive_%(name)s(struct connection *pc, enum packet_type type)'%self.__dict__
+        self.receive_prototype='struct %(name)s *receive_%(name)s(struct connection *pc)'%self.__dict__
         self.send_prototype='int send_%(name)s(struct connection *pc%(extra_send_args)s)'%self.__dict__
         if self.want_lsend:
             self.lsend_prototype='void lsend_%(name)s(struct conn_list *dest%(extra_send_args)s)'%self.__dict__
@@ -1273,7 +1273,7 @@ class Packet:
             no=v.no
             result=result+'''
   case %(no)s:
-    return receive_%(name2)s(pc, type);'''%self.get_dict(vars())
+    return receive_%(name2)s(pc);'''%self.get_dict(vars())
         result=result+'''
   default:
     log_debug("Unknown %(type)s variant for connection %%s", conn_description(pc));
@@ -1434,7 +1434,7 @@ def get_get_packet_helper(packets):
 '''
     body=""
     for p in packets:
-        body=body+"  case %(type)s:\n    return receive_%(name)s(pc, type);\n\n"%p.__dict__
+        body=body+"  case %(type)s:\n    return receive_%(name)s(pc);\n\n"%p.__dict__
     extro='''  default:
     log_packet("unknown packet type %d received from %s",
                type, conn_description(pc));
diff --git a/common/packets.c b/common/packets.c
index 309713f..19ba9f0 100644
--- a/common/packets.c
+++ b/common/packets.c
@@ -172,11 +172,12 @@ bool conn_compression_thaw(struct connection *pconn)
 /**************************************************************************
   It returns the request id of the outgoing packet (or 0 if is_server()).
 **************************************************************************/
-int send_packet_data(struct connection *pc, unsigned char *data, int len)
+int send_packet_data(struct connection *pc, unsigned char *data, int len,
+                     enum packet_type packet_type)
 {
   /* default for the server */
   int result = 0;
-  int packet_type = ntohs((data[3] << 8) + data[2]);
+
 
   log_packet("sending packet type=%s(%d) len=%d to %s",
              packet_name(packet_type), packet_type, len,
@@ -329,12 +330,11 @@ void *get_packet_from_connection(struct connection *pc,
     enum packet_type type;
     int itype;
   } utype;
-  int typeb1, typeb2;
   struct data_in din;
 #ifdef USE_COMPRESSION
   bool compressed_packet = FALSE;
+  int header_size = 0;
 #endif
-  int header_size = 4;
   void *data;
 
   if (!pc->used) {
@@ -347,7 +347,7 @@ void *get_packet_from_connection(struct connection *pc,
   }
 
   dio_input_init(&din, pc->buffer->data, pc->buffer->ndata);
-  dio_get_uint16(&din, &len_read);
+  dio_get_type(&din, pc->packet_header.length, &len_read);
 
   /* The non-compressed case */
   whole_packet_len = len_read;
@@ -377,6 +377,7 @@ void *get_packet_from_connection(struct connection *pc,
     return NULL;		/* not all data has been read */
   }
 
+#ifdef USE_COMPRESSION
   if (whole_packet_len < header_size) {
     log_verbose("The packet size is reported to be less than header alone. "
                 "The connection will be closed now.");
@@ -385,7 +386,6 @@ void *get_packet_from_connection(struct connection *pc,
     return NULL;
   }
 
-#ifdef USE_COMPRESSION
   if (compressed_packet) {
     uLong compressed_size = whole_packet_len - header_size;
     /* 
@@ -444,27 +444,17 @@ void *get_packet_from_connection(struct connection *pc,
 
   /*
    * At this point the packet is a plain uncompressed one. These have
-   * to have to be at least 4 bytes in size.
+   * to have to be at least the header bytes in size.
    */
-  if (whole_packet_len < 2+2) {
+  if (whole_packet_len < (data_type_size(pc->packet_header.length)
+                          + data_type_size(pc->packet_header.type))) {
     log_verbose("The packet stream is corrupt. The connection "
                 "will be closed now.");
     connection_close(pc, _("decoding error"));
     return NULL;
   }
 
-  /* Instead of one dio_get_uint16() we do twice dio_get_uint8().
-   * Older (<= 2.4) versions had 8bit type field, and we detect
-   * here if this is initial PACKET_SERVER_JOIN_REQ from such a client. */
-  dio_get_uint8(&din, &typeb1);
-
-  if (typeb1 == PACKET_SERVER_JOIN_REQ) {
-    utype.itype = typeb1;
-  } else {
-    dio_get_uint8(&din, &typeb2);
-    utype.itype = ntohs((typeb2 << 8) + typeb1);
-  }
-
+  dio_get_type(&din, pc->packet_header.type, &utype.itype);
   utype.type = utype.itype;
 
   log_packet("got packet type=(%s)%d len=%d from %s",
@@ -546,6 +536,57 @@ void remove_packet_from_buffer(struct socket_packet_buffer *buffer)
             len, buffer->ndata);
 }
 
+/****************************************************************************
+  Set the packet header field lengths used for the login protocol,
+  before the capability of the connection could be checked.
+
+  NB: These values cannot be changed for backward compatibility reasons.
+****************************************************************************/
+inline void packet_header_init(struct packet_header *packet_header)
+{
+  packet_header->length = DIOT_UINT16;
+  packet_header->type = DIOT_UINT8;
+}
+
+/****************************************************************************
+  Set the packet header field lengths used after the login protocol,
+  after the capability of the connection could be checked.
+****************************************************************************/
+static inline void packet_header_set(struct packet_header *packet_header)
+{
+  /* Ensure we have values initialized in packet_header_init(). */
+  fc_assert(packet_header->length == DIOT_UINT16);
+  fc_assert(packet_header->type == DIOT_UINT8);
+
+  packet_header->length = DIOT_UINT16;
+  packet_header->type = DIOT_UINT16;
+}
+
+/****************************************************************************
+  Modify if needed the packet header field lengths.
+****************************************************************************/
+void post_send_packet_server_join_reply(struct connection *pconn,
+                                        const struct packet_server_join_reply
+                                        *packet)
+{
+  if (packet->you_can_join) {
+    packet_header_set(&pconn->packet_header);
+  }
+}
+
+/****************************************************************************
+  Modify if needed the packet header field lengths.
+****************************************************************************/
+void post_receive_packet_server_join_reply(struct connection *pconn,
+                                           const struct
+                                           packet_server_join_reply *packet)
+{
+  if (packet->you_can_join) {
+    packet_header_set(&pconn->packet_header);
+  }
+}
+
+
 /**************************************************************************
   Sanity check packet
 **************************************************************************/
@@ -557,8 +598,8 @@ bool packet_check(struct data_in *din, struct connection *pc)
     int type, len;
 
     dio_input_rewind(din);
-    dio_get_uint16(din, &len);
-    dio_get_uint16(din, &type);
+    dio_get_type(din, pc->packet_header.length, &len);
+    dio_get_type(din, pc->packet_header.type, &type);
 
     log_packet("received long packet (type %d, len %d, rem %lu) from %s",
                type,
diff --git a/common/packets.def b/common/packets.def
index 4b2353a..7ec0e08 100644
--- a/common/packets.def
+++ b/common/packets.def
@@ -62,8 +62,8 @@ Syntax:
     PACKET_SERVER_JOIN_REPLY are excluded here. These packets should
     never change their number. The packet number can be freely chosen
     as long as it is below 65536 and unique. For backward compatibility  
-    reasons using values from range 1024-1279 ((4x256)-(5*256-1))        
-    should be avoided. 
+    reasons, packets used for the initial protocol (notably before
+    checking the capabilities) must be in range 0-255.
 
    Packet flags:
    -------------
@@ -306,10 +306,12 @@ grouped together. There are the following groups:
 /************** General packets **********************/
 
 # For compatibility with older versions, this number cannot be changed.
+# Used in initial protocol.
 PACKET_PROCESSING_STARTED = 0; sc
 end
 
 # For compatibility with older versions, this number cannot be changed.
+# Used in initial protocol.
 PACKET_PROCESSING_FINISHED = 1; sc
 end
 
@@ -318,6 +320,7 @@ end
 # This packet is the first real (freeciv specific) packet send by the
 # client. The player hasn't been accepted yet.
 # For compatibility with older versions, this number cannot be changed.
+# Used in initial protocol.
 PACKET_SERVER_JOIN_REQ = 4; cs, dsend, no-delta, no-handle
   STRING username[MAX_LEN_NAME];
   STRING capability[MAX_LEN_CAPSTR];
@@ -327,7 +330,8 @@ end
 
 # ... and the server replies.
 # For compatibility with older versions, this number cannot be changed.
-PACKET_SERVER_JOIN_REPLY = 5; sc, no-delta
+# Used in initial protocol.
+PACKET_SERVER_JOIN_REPLY = 5; sc, no-delta, post-send, post-recv
   BOOL you_can_join;
   STRING message[MAX_LEN_MSG];
   STRING capability[MAX_LEN_CAPSTR];
@@ -336,11 +340,13 @@ PACKET_SERVER_JOIN_REPLY = 5; sc, no-delta
   CONNECTION conn_id;
 end
 
+# Used in initial protocol.
 PACKET_AUTHENTICATION_REQ = 6; sc, handle-per-conn, dsend
   AUTH_TYPE type;
   STRING message[MAX_LEN_MSG]; /* explain to the client if there's a problem */
 end
 
+# Used in initial protocol.
 PACKET_AUTHENTICATION_REPLY = 7; cs, no-handle
   STRING password[MAX_LEN_PASSWORD];
 end
@@ -1078,10 +1084,13 @@ PACKET_CONN_PING_INFO = 116; sc, lsend
 end
 
 # For compatibility with older versions, this number cannot be changed.
+# Freeciv servers version < 2.5.0 still can send this packet in
+# initial protocol.
 PACKET_CONN_PING = 88; sc
 end
 
 # For compatibility with older versions, this number cannot be changed.
+# Can be used in initial protocol, if the client received a PACKET_CONN_PING.
 PACKET_CONN_PONG = 89; cs, handle-per-conn
 end
 
diff --git a/common/packets.h b/common/packets.h
index bd312dd..68f6b26 100644
--- a/common/packets.h
+++ b/common/packets.h
@@ -92,51 +92,50 @@ void generic_handle_player_attribute_chunk(struct player *pplayer,
 const char *packet_name(enum packet_type type);
 bool packet_has_game_info_flag(enum packet_type type);
 
+inline void packet_header_init(struct packet_header *packet_header);
+void post_send_packet_server_join_reply(struct connection *pconn,
+                                        const struct packet_server_join_reply
+                                        *packet);
+void post_receive_packet_server_join_reply(struct connection *pconn,
+                                           const struct
+                                           packet_server_join_reply *packet);
+
 void pre_send_packet_player_attribute_chunk(struct connection *pc,
 					    struct packet_player_attribute_chunk
 					    *packet);
 
-#ifdef DEBUG
-#define PACKET_TYPE_SANITY(_type_) \
-  if (((_type_ & 0xff00) >> 8) == PACKET_SERVER_JOIN_REQ) { \
-    log_error("Packet type %s (%d) has upper byte matching old PACKET_SERVER_JOIN_REQ.", \
-              packet_name(_type_), _type_); \
-  }
-#else  /* DEBUG */
-#define PACKET_TYPE_SANITY(_type_)
-#endif /* DEBUG */
-
-#define SEND_PACKET_START(type) \
+#define SEND_PACKET_START(packet_type) \
   unsigned char buffer[MAX_LEN_PACKET]; \
   struct data_out dout; \
   \
   dio_output_init(&dout, buffer, sizeof(buffer)); \
-  dio_put_uint16(&dout, 0); \
-  dio_put_uint16(&dout, type); \
-  PACKET_TYPE_SANITY(type)
+  dio_put_type(&dout, pc->packet_header.length, 0); \
+  dio_put_type(&dout, pc->packet_header.type, packet_type);
 
-#define SEND_PACKET_END \
+#define SEND_PACKET_END(packet_type) \
   { \
     size_t size = dio_output_used(&dout); \
     \
     dio_output_rewind(&dout); \
-    dio_put_uint16(&dout, size); \
+    dio_put_type(&dout, pc->packet_header.length, size); \
     fc_assert(!dout.too_short); \
-    return send_packet_data(pc, buffer, size); \
+    return send_packet_data(pc, buffer, size, packet_type); \
   }
 
-#define RECEIVE_PACKET_START(type, result) \
+#define RECEIVE_PACKET_START(packet_type, result) \
   struct data_in din; \
-  struct type packet_buf, *result = &packet_buf; \
+  struct packet_type packet_buf, *result = &packet_buf; \
   \
-  dio_input_init(&din, pc->buffer->data, 2); \
+  dio_input_init(&din, pc->buffer->data, \
+                 data_type_size(pc->packet_header.length)); \
   { \
     int size; \
   \
-    dio_get_uint16(&din, &size); \
+    dio_get_type(&din, pc->packet_header.length, &size); \
     dio_input_init(&din, pc->buffer->data, MIN(size, pc->buffer->ndata)); \
   } \
-  dio_input_skip(&din, 4);
+  dio_input_skip(&din, (data_type_size(pc->packet_header.length) \
+                        + data_type_size(pc->packet_header.type)));
 
 #define RECEIVE_PACKET_END(result) \
   if (!packet_check(&din, pc)) { \
@@ -151,7 +150,8 @@ void pre_send_packet_player_attribute_chunk(struct connection *pc,
   log_packet("Error on field '" #field "'" __VA_ARGS__); \
   return NULL
 
-int send_packet_data(struct connection *pc, unsigned char *data, int len);
+int send_packet_data(struct connection *pc, unsigned char *data, int len,
+                     enum packet_type packet_type);
 bool packet_check(struct data_in *din, struct connection *pc);
 
 /* Utilities to exchange strings and string vectors. */
diff --git a/doc/HACKING b/doc/HACKING
index 42ed426..97d2d3a 100644
--- a/doc/HACKING
+++ b/doc/HACKING
@@ -175,11 +175,19 @@ independent functions such as dio_put_uint32() and de-serialized with
 functions like dio_get_uint32().
 
 A packet is constituted by header followed by the serialized structure
-data. The header contains the following fields:
+data. The header contains the following fields (the sizes are defined in
+common/packets.c:packet_header_set()):
 
 uint16	:	length		(the length of the entire packet)
 uint16	:	type		(e.g. PACKET_TILE_INFO)
 
+For backward compatibility reasons, packets used for the initial protocol
+(notably before checking the capabilities) have different header fields
+sizes (defined in common/packets.c:packet_header_init()):
+
+uint16	:	length		(the length of the entire packet)
+uint8	:	type		(e.g. PACKET_TILE_INFO)
+
 To demonstrate the route for a packet through the system, here's how
 a unit disband is performed:
 
diff --git a/server/connecthand.c b/server/connecthand.c
index 91516aa..8cf20f4 100644
--- a/server/connecthand.c
+++ b/server/connecthand.c
@@ -329,6 +329,12 @@ bool handle_login_request(struct connection *pconn,
   char msg[MAX_LEN_MSG];
   int kick_time_remaining;
 
+  if (pconn->established || pconn->server.status != AS_NOT_ESTABLISHED) {
+    /* We read the PACKET_SERVER_JOIN_REQ twice from this connection,
+     * this is probably not a Freeciv client. */
+    return FALSE;
+  }
+
   log_normal(_("Connection request from %s from %s"),
              req->username, pconn->addr);
 
@@ -404,6 +410,14 @@ bool handle_login_request(struct connection *pconn,
     }
   } conn_list_iterate_end;
 
+  /* Remove the ping timeout given in sernet.c:server_make_connection(). */
+  fc_assert_msg(1 == timer_list_size(pconn->server.ping_timers),
+                "Ping timer list size %d, should be 1. Have we sent "
+                "a ping to unestablished connection %s?",
+                timer_list_size(pconn->server.ping_timers),
+                conn_description(pconn));
+  timer_list_pop_front(pconn->server.ping_timers);
+
   if (game.server.connectmsg[0] != '\0') {
     log_debug("Sending connectmsg: %s", game.server.connectmsg);
     dsend_packet_connect_msg(pconn, game.server.connectmsg);
diff --git a/server/sernet.c b/server/sernet.c
index 81ba517..aa8a29c 100644
--- a/server/sernet.c
+++ b/server/sernet.c
@@ -590,7 +590,10 @@ enum server_events server_sniff_all_input(void)
                         conn_description(pconn));
             connection_close_server(pconn, _("ping timeout"));
           }
-        } else {
+        } else if (pconn->established) {
+          /* We don't send ping to connection not established, because
+           * we wouldn't be able to handle asynchronous ping/pong with
+           * different packet header size. */
           connection_ping(pconn);
         }
       } conn_list_iterate_end;
@@ -984,6 +987,7 @@ static int server_accept_connection(int sockfd)
 ********************************************************************/
 int server_make_connection(int new_sock, const char *client_addr, const char *client_ip)
 {
+  struct timer *timer;
   int i;
 
   fc_nonblock(new_sock);
@@ -1020,7 +1024,12 @@ int server_make_connection(int new_sock, const char *client_addr, const char *cl
 
       log_verbose("connection (%s) from %s (%s)", 
                   pconn->username, pconn->addr, pconn->server.ipaddr);
-      connection_ping(pconn);
+      /* Give a ping timeout to send the PACKET_SERVER_JOIN_REQ, or close
+       * the mute connection. This timer will be canceled into
+       * connecthand.c:handle_login_request(). */
+      timer = timer_new(TIMER_USER, TIMER_ACTIVE);
+      timer_start(timer);
+      timer_list_append(pconn->server.ping_timers, timer);
       return 0;
     }
   }
-- 
1.7.9.5

