Take stream reference correctly in packet error callback
[silc.git] / apps / silcd / server.c
index 45231d05708ba94b80d3a4cdf0ba614e047d846b..f2153b359b673b6591f7677635c21b7c00b107c7 100644 (file)
@@ -201,6 +201,8 @@ static void silc_server_packet_eos(SilcPacketEngine engine,
 
   if (server->router_conn && server->router_conn->sock == stream &&
       !server->router && server->standalone) {
+    if (idata->sconn && idata->sconn->callback)
+      (*idata->sconn->callback)(server, NULL, idata->sconn->callback_context);
     silc_server_create_connections(server);
     silc_server_free_sock_user_data(server, stream, NULL);
   } else {
@@ -212,6 +214,8 @@ static void silc_server_packet_eos(SilcPacketEngine engine,
         server->backup_closed = TRUE;
     }
 
+    if (idata->sconn && idata->sconn->callback)
+      (*idata->sconn->callback)(server, NULL, idata->sconn->callback_context);
     silc_server_free_sock_user_data(server, stream, NULL);
   }
 
@@ -224,12 +228,17 @@ SILC_TASK_CALLBACK(silc_server_packet_error_timeout)
   SilcPacketStream stream = context;
   SilcIDListData idata = silc_packet_get_context(stream);
 
-  if (!idata)
+  if (!idata || !silc_packet_stream_is_valid(stream)) {
+    silc_packet_stream_unref(stream);
     return;
+  }
 
   if (server->router_conn && server->router_conn->sock == stream &&
       !server->router && server->standalone) {
+    if (idata->sconn && idata->sconn->callback)
+      (*idata->sconn->callback)(server, NULL, idata->sconn->callback_context);
     silc_server_create_connections(server);
+    silc_server_free_sock_user_data(server, stream, NULL);
   } else {
     /* If backup disconnected then mark that resuming will not be allowed */
      if (server->server_type == SILC_ROUTER && !server->backup_router &&
@@ -239,10 +248,15 @@ SILC_TASK_CALLBACK(silc_server_packet_error_timeout)
         server->backup_closed = TRUE;
     }
 
+    if (idata->sconn && idata->sconn->callback)
+      (*idata->sconn->callback)(server, NULL, idata->sconn->callback_context);
     silc_server_free_sock_user_data(server, stream, NULL);
   }
 
   silc_server_close_connection(server, stream);
+
+  /* Release our stream reference */
+  silc_packet_stream_unref(stream);
 }
 
 /* Packet engine callback to indicate error */
@@ -259,7 +273,7 @@ static void silc_server_packet_error(SilcPacketEngine engine,
   const char *ip;
   SilcUInt16 port;
 
-  SILC_LOG_DEBUG(("Packet error, sock %p", stream));
+  SILC_LOG_DEBUG(("Packet error %d, sock %p", error, stream));
 
   if (!idata || !sock)
     return;
@@ -274,9 +288,17 @@ static void silc_server_packet_error(SilcPacketEngine engine,
   if (!silc_packet_stream_is_valid(stream))
     return;
 
+  /* We must take reference of the stream */
+  silc_packet_stream_ref(stream);
+
+  /* In case we get here many times, register only one timeout */
+  silc_schedule_task_del_by_all(server->schedule, 0,
+                               silc_server_packet_error_timeout, stream);
+
+  /* Close connection with random timeout */
   silc_schedule_task_add_timeout(server->schedule,
-                                silc_server_packet_error_timeout,
-                                stream, 0, 0);
+                                silc_server_packet_error_timeout, stream,
+                                silc_rng_get_byte(server->rng) % 10, 0);
 }
 
 /* Packet stream callbacks */
@@ -698,6 +720,7 @@ void silc_server_free(SilcServer server)
   silc_free(server->local_list);
   silc_free(server->global_list);
   silc_free(server->server_name);
+  silc_free(server->id);
   silc_free(server);
 
   silc_hmac_unregister_all();
@@ -1322,7 +1345,8 @@ void silc_server_create_connection(SilcServer server,
   sconn->no_conf = dynamic;
   sconn->server = server;
 
-  SILC_LOG_DEBUG(("Created connection %p", sconn));
+  SILC_LOG_DEBUG(("Created connection %p to %s:%d", sconn,
+                 remote_host, port));
 
   silc_schedule_task_add_timeout(server->schedule, silc_server_connect_router,
                                 sconn, 0, 0);
@@ -1353,7 +1377,15 @@ silc_server_ke_auth_compl(SilcConnAuth connauth, SilcBool success,
 
   if (success == FALSE) {
     /* Authentication failed */
-    /* XXX retry connecting */
+
+    /* Try reconnecting if configuration wants it */
+    if (!sconn->no_reconnect) {
+      silc_schedule_task_add_timeout(server->schedule,
+                                    silc_server_connect_to_router_retry,
+                                    sconn, 1, 0);
+      silc_dlist_del(server->conns, sconn);
+      return;
+    }
 
     if (sconn->callback)
       (*sconn->callback)(server, NULL, sconn->callback_context);
@@ -1448,6 +1480,15 @@ silc_server_ke_auth_compl(SilcConnAuth connauth, SilcBool success,
                                                  SILC_ID_SERVER),
                                      NULL, sconn->sock);
     if (!id_entry) {
+      /* Try reconnecting if configuration wants it */
+      if (!sconn->no_reconnect) {
+        silc_schedule_task_add_timeout(server->schedule,
+                                      silc_server_connect_to_router_retry,
+                                      sconn, 1, 0);
+        silc_dlist_del(server->conns, sconn);
+        return;
+      }
+
       if (sconn->callback)
        (*sconn->callback)(server, NULL, sconn->callback_context);
       silc_server_free_sock_user_data(server, sconn->sock, NULL);
@@ -1462,6 +1503,7 @@ silc_server_ke_auth_compl(SilcConnAuth connauth, SilcBool success,
     idata->status |= (SILC_IDLIST_STATUS_REGISTERED |
                      SILC_IDLIST_STATUS_LOCAL);
     idata->sconn = sconn;
+    idata->sconn->callback = NULL;
 
     /* Statistics */
     server->stat.my_routers++;
@@ -1574,28 +1616,38 @@ static void silc_server_ke_completed(SilcSKE ske, SilcSKEStatus status,
 {
   SilcPacketStream sock = context;
   SilcUnknownEntry entry = silc_packet_get_context(sock);
-  SilcServerConnection sconn = entry->data.sconn;
-  SilcServer server = entry->server;
-  SilcServerConfigRouter *conn = sconn->conn.ref_ptr;
+  SilcServerConnection sconn;
+  SilcServer server;
+  SilcServerConfigRouter *conn;
   SilcAuthMethod auth_meth = SILC_AUTH_NONE;
   void *auth_data = NULL;
   SilcUInt32 auth_data_len = 0;
   SilcConnAuth connauth;
   SilcCipher send_key, receive_key;
   SilcHmac hmac_send, hmac_receive;
-  SilcHash hash;
-
-  SILC_LOG_DEBUG(("Connection %p, SKE completed, entry %p", sconn, entry));
 
+  server = entry->server;
+  sconn = entry->data.sconn;
+  conn = sconn->conn.ref_ptr;
   entry->op = NULL;
 
+  SILC_LOG_DEBUG(("Connection %p, SKE completed, entry %p", sconn, entry));
+
   if (status != SILC_SKE_STATUS_OK) {
     /* SKE failed */
     SILC_LOG_ERROR(("Error (%s) during Key Exchange protocol with %s (%s)",
                    silc_ske_map_status(status), entry->hostname, entry->ip));
-
-    /* XXX retry connecting */
     silc_ske_free(ske);
+
+    /* Try reconnecting if configuration wants it */
+    if (!sconn->no_reconnect) {
+      silc_schedule_task_add_timeout(server->schedule,
+                                    silc_server_connect_to_router_retry,
+                                    sconn, 1, 0);
+      silc_dlist_del(server->conns, sconn);
+      return;
+    }
+
     if (sconn->callback)
       (*sconn->callback)(server, NULL, sconn->callback_context);
     silc_server_free_sock_user_data(server, sconn->sock, NULL);
@@ -1608,12 +1660,19 @@ static void silc_server_ke_completed(SilcSKE ske, SilcSKEStatus status,
 
   /* Set the keys into use.  The data will be encrypted after this. */
   if (!silc_ske_set_keys(ske, keymat, prop, &send_key, &receive_key,
-                        &hmac_send, &hmac_receive, &hash)) {
+                        &hmac_send, &hmac_receive, NULL)) {
+    silc_ske_free(ske);
 
-    /* XXX retry connecting */
+    /* Try reconnecting if configuration wants it */
+    if (!sconn->no_reconnect) {
+      silc_schedule_task_add_timeout(server->schedule,
+                                    silc_server_connect_to_router_retry,
+                                    sconn, 1, 0);
+      silc_dlist_del(server->conns, sconn);
+      return;
+    }
 
     /* Error setting keys */
-    silc_ske_free(ske);
     if (sconn->callback)
       (*sconn->callback)(server, NULL, sconn->callback_context);
     silc_server_free_sock_user_data(server, sconn->sock, NULL);
@@ -1629,10 +1688,18 @@ static void silc_server_ke_completed(SilcSKE ske, SilcSKEStatus status,
   connauth = silc_connauth_alloc(server->schedule, ske,
                                 server->config->conn_auth_timeout);
   if (!connauth) {
-    /* XXX retry connecting */
+    silc_ske_free(ske);
+
+    /* Try reconnecting if configuration wants it */
+    if (!sconn->no_reconnect) {
+      silc_schedule_task_add_timeout(server->schedule,
+                                    silc_server_connect_to_router_retry,
+                                    sconn, 1, 0);
+      silc_dlist_del(server->conns, sconn);
+      return;
+    }
 
     /** Error allocating auth protocol */
-    silc_ske_free(ske);
     if (sconn->callback)
       (*sconn->callback)(server, NULL, sconn->callback_context);
     silc_server_free_sock_user_data(server, sconn->sock, NULL);
@@ -1689,6 +1756,16 @@ void silc_server_start_key_exchange(SilcServerConnection sconn)
   if (!sconn->sock) {
     SILC_LOG_ERROR(("Cannot connect: cannot create packet stream"));
     silc_stream_destroy(sconn->stream);
+
+    /* Try reconnecting if configuration wants it */
+    if (!sconn->no_reconnect) {
+      silc_schedule_task_add_timeout(server->schedule,
+                                    silc_server_connect_to_router_retry,
+                                    sconn, 1, 0);
+      silc_dlist_del(server->conns, sconn);
+      return;
+    }
+
     if (sconn->callback)
       (*sconn->callback)(server, NULL, sconn->callback_context);
     silc_server_connection_free(sconn);
@@ -1699,9 +1776,19 @@ void silc_server_start_key_exchange(SilcServerConnection sconn)
   /* Set source ID to packet stream */
   if (!silc_packet_set_ids(sconn->sock, SILC_ID_SERVER, server->id,
                           0, NULL)) {
+    silc_packet_stream_destroy(sconn->sock);
+
+    /* Try reconnecting if configuration wants it */
+    if (!sconn->no_reconnect) {
+      silc_schedule_task_add_timeout(server->schedule,
+                                    silc_server_connect_to_router_retry,
+                                    sconn, 1, 0);
+      silc_dlist_del(server->conns, sconn);
+      return;
+    }
+
     if (sconn->callback)
       (*sconn->callback)(server, NULL, sconn->callback_context);
-    silc_packet_stream_destroy(sconn->sock);
     silc_server_connection_free(sconn);
     return;
   }
@@ -1710,11 +1797,25 @@ void silc_server_start_key_exchange(SilcServerConnection sconn)
   entry = silc_calloc(1, sizeof(*entry));
   if (!entry) {
     silc_packet_stream_destroy(sconn->sock);
+
+    /* Try reconnecting if configuration wants it */
+    if (!sconn->no_reconnect) {
+      silc_schedule_task_add_timeout(server->schedule,
+                                    silc_server_connect_to_router_retry,
+                                    sconn, 1, 0);
+      silc_dlist_del(server->conns, sconn);
+      return;
+    }
+
+    if (sconn->callback)
+      (*sconn->callback)(server, NULL, sconn->callback_context);
     silc_server_connection_free(sconn);
     return;
   }
   entry->server = server;
   entry->data.sconn = sconn;
+  entry->data.conn_type = SILC_CONN_UNKNOWN;
+  entry->data.status |= SILC_IDLIST_STATUS_LOCAL;
   silc_packet_set_context(sconn->sock, entry);
 
   SILC_LOG_DEBUG(("Created unknown connection %p", entry));
@@ -1732,9 +1833,19 @@ void silc_server_start_key_exchange(SilcServerConnection sconn)
                       server->public_key, server->private_key, sconn);
   if (!ske) {
     silc_free(entry);
+    silc_packet_stream_destroy(sconn->sock);
+
+    /* Try reconnecting if configuration wants it */
+    if (!sconn->no_reconnect) {
+      silc_schedule_task_add_timeout(server->schedule,
+                                    silc_server_connect_to_router_retry,
+                                    sconn, 1, 0);
+      silc_dlist_del(server->conns, sconn);
+      return;
+    }
+
     if (sconn->callback)
       (*sconn->callback)(server, NULL, sconn->callback_context);
-    silc_packet_stream_destroy(sconn->sock);
     silc_server_connection_free(sconn);
     return;
   }
@@ -1777,7 +1888,7 @@ SILC_TASK_CALLBACK(silc_server_connect_to_router_retry)
 
   /* If we've reached max retry count, give up. */
   if ((sconn->retry_count > param->reconnect_count) &&
-      !param->reconnect_keep_trying) {
+      sconn->no_reconnect) {
     SILC_LOG_ERROR(("Could not connect, giving up"));
 
     if (sconn->callback)
@@ -1824,10 +1935,16 @@ static void silc_server_connection_established(SilcNetStatus status,
     SILC_LOG_ERROR(("Could not connect to %s:%d: %s",
                    sconn->remote_host, sconn->remote_port,
                    silc_net_get_error_string(status)));
-
-    if (sconn->callback)
-      (*sconn->callback)(server, NULL, sconn->callback_context);
-    silc_server_connection_free(sconn);
+    if (!sconn->no_reconnect) {
+      silc_schedule_task_add_timeout(sconn->server->schedule,
+                                    silc_server_connect_to_router_retry,
+                                    sconn, 1, 0);
+      silc_dlist_del(server->conns, sconn);
+    } else {
+      if (sconn->callback)
+       (*sconn->callback)(server, NULL, sconn->callback_context);
+      silc_server_connection_free(sconn);
+    }
     break;
 
   default:
@@ -1878,6 +1995,8 @@ SILC_TASK_CALLBACK(silc_server_connect_router)
       SILC_LOG_INFO(("Unconfigured %s connection %s:%d, cannot connect",
                     (sconn->backup ? "backup router" : "router"),
                     sconn->remote_host, sconn->remote_port));
+      if (sconn->callback)
+       (*sconn->callback)(server, NULL, sconn->callback_context);
       silc_server_connection_free(sconn);
       return;
     }
@@ -1894,6 +2013,8 @@ SILC_TASK_CALLBACK(silc_server_connect_router)
   if (!sconn->op) {
     SILC_LOG_ERROR(("Could not connect to router %s:%d",
                    sconn->remote_host, sconn->remote_port));
+    if (sconn->callback)
+      (*sconn->callback)(server, NULL, sconn->callback_context);
     silc_server_connection_free(sconn);
     return;
   }
@@ -1912,6 +2033,7 @@ SILC_TASK_CALLBACK(silc_server_connect_to_router)
   SilcServer server = context;
   SilcServerConnection sconn;
   SilcServerConfigRouter *ptr;
+  SilcServerConfigConnParams *param;
 
   /* Don't connect if we are shutting down. */
   if (server->server_shutdown)
@@ -1987,6 +2109,8 @@ SILC_TASK_CALLBACK(silc_server_connect_to_router)
       }
     }
 
+    param = (ptr->param ? ptr->param : &server->config->param);
+
     /* Allocate connection object for hold connection specific stuff. */
     sconn = silc_calloc(1, sizeof(*sconn));
     if (!sconn)
@@ -1999,6 +2123,7 @@ SILC_TASK_CALLBACK(silc_server_connect_to_router)
       sconn->backup_replace_ip = strdup(ptr->backup_replace_ip);
       sconn->backup_replace_port = ptr->backup_replace_port;
     }
+    sconn->no_reconnect = param->reconnect_keep_trying == FALSE;
 
     SILC_LOG_DEBUG(("Created connection %p", sconn));
 
@@ -2228,6 +2353,8 @@ silc_server_accept_auth_compl(SilcConnAuth connauth, SilcBool success,
       entry->data.conn_type = SILC_CONN_CLIENT;
 
       /* Statistics */
+      SILC_LOG_DEBUG(("stat.clients %d->%d", server->stat.clients,
+                     server->stat.clients + 1));
       server->stat.my_clients++;
       server->stat.clients++;
       server->stat.cell_clients++;
@@ -2499,6 +2626,7 @@ silc_server_accept_auth_compl(SilcConnAuth connauth, SilcBool success,
   sconn->remote_port = port;
   silc_dlist_add(server->conns, sconn);
   idata->sconn = sconn;
+  idata->sconn->callback = NULL;
   idata->last_receive = time(NULL);
 
   /* Add the common data structure to the ID entry. */
@@ -2578,9 +2706,11 @@ silc_server_accept_completed(SilcSKE ske, SilcSKEStatus status,
   idata->rekey = rekey;
   idata->public_key = silc_pkcs_public_key_copy(prop->public_key);
   pk = silc_pkcs_public_key_encode(idata->public_key, &pk_len);
-  silc_hash_make(server->sha1hash, pk, pk_len, idata->fingerprint);
-
-  silc_hash_alloc(silc_hash_get_name(prop->hash), &idata->hash);
+  if (pk) {
+    silc_hash_make(server->sha1hash, pk, pk_len, idata->fingerprint);
+    silc_free(pk);
+  }
+  idata->hash = hash;
 
   SILC_LOG_DEBUG(("Starting connection authentication"));
   server->stat.auth_attempts++;
@@ -2713,6 +2843,7 @@ static void silc_server_accept_new_connection(SilcNetStatus status,
   entry->port = port;
   entry->server = server;
   entry->data.conn_type = SILC_CONN_UNKNOWN;
+  entry->data.status |= SILC_IDLIST_STATUS_LOCAL;
   silc_packet_set_context(packet_stream, entry);
 
   SILC_LOG_DEBUG(("Created unknown connection %p", entry));
@@ -2790,6 +2921,18 @@ static void silc_server_rekey_completion(SilcSKE ske,
                                 sock, idata->sconn->rekey_timeout, 0);
 }
 
+/* Helper to stop future rekeys on a link. */
+void silc_server_stop_rekey(SilcServer server, SilcClientEntry client)
+{
+  if (!client->connection)
+    return;
+
+  SILC_LOG_DEBUG(("Stopping rekey for client %p", client));
+
+  silc_schedule_task_del_by_all(server->schedule, 0, silc_server_do_rekey,
+                               client->connection);
+}
+
 /* Rekey callback.  Start rekey as initiator */
 
 SILC_TASK_CALLBACK(silc_server_do_rekey)
@@ -2802,7 +2945,7 @@ SILC_TASK_CALLBACK(silc_server_do_rekey)
   SILC_LOG_DEBUG(("Perform rekey, sock %p", sock));
 
   /* Do not execute rekey with disabled connections */
-  if (idata->status & SILC_IDLIST_STATUS_DISABLED)
+  if (idata->status & SILC_IDLIST_STATUS_DISABLED || !idata->rekey)
     return;
 
   /* If another protocol is active do not start rekey */
@@ -2866,6 +3009,11 @@ static void silc_server_rekey(SilcServer server, SilcPacketStream sock,
   SilcIDListData idata = silc_packet_get_context(sock);
   SilcSKE ske;
 
+  if (!idata->rekey) {
+    silc_packet_free(packet);
+    return;
+  }
+
   SILC_LOG_DEBUG(("Executing rekey protocol with %s:%d [%s], sock %p",
                  idata->sconn->remote_host, idata->sconn->remote_port,
                  SILC_CONNTYPE_STRING(idata->conn_type), sock));
@@ -2952,7 +3100,8 @@ void silc_server_disconnect_remote(SilcServer server,
   if (!sock)
     return;
 
-  SILC_LOG_DEBUG(("Disconnecting remote host, sock %p", sock));
+  SILC_LOG_DEBUG(("Disconnecting remote host, sock %p, status %d", sock,
+                 status));
 
   va_start(ap, status);
   cp = va_arg(ap, char *);
@@ -3012,7 +3161,13 @@ void silc_server_free_client_data(SilcServer server,
   }
 
   /* Update statistics */
-  server->stat.my_clients--;
+
+  /* Local detached clients aren't counted. */
+  if (!client->local_detached)
+    server->stat.my_clients--;
+  SILC_LOG_DEBUG(("stat.clients %d->%d", server->stat.clients,
+                 server->stat.clients - 1));
+  SILC_VERIFY(server->stat.clients > 0);
   server->stat.clients--;
   if (server->stat.cell_clients)
     server->stat.cell_clients--;
@@ -3073,11 +3228,13 @@ void silc_server_free_sock_user_data(SilcServer server,
     if (idata->sconn && idata->sconn->op) {
       SILC_LOG_DEBUG(("Abort active protocol"));
       silc_async_abort(idata->sconn->op, NULL, NULL);
+      idata->sconn->op = NULL;
     }
     if (idata->conn_type == SILC_CONN_UNKNOWN &&
         ((SilcUnknownEntry)idata)->op) {
       SILC_LOG_DEBUG(("Abort active protocol"));
       silc_async_abort(((SilcUnknownEntry)idata)->op, NULL, NULL);
+      idata->sconn->op = NULL;
     }
   }
 
@@ -3154,7 +3311,7 @@ void silc_server_free_sock_user_data(SilcServer server,
 
            /* We'll need to constantly try to reconnect to the primary
               router so that we'll see when it comes back online. */
-           silc_server_create_connection(server, FALSE, FALSE, ip, port,
+           silc_server_create_connection(server, TRUE, FALSE, ip, port,
                                         silc_server_backup_connected,
                                         NULL);
          }
@@ -4891,7 +5048,7 @@ void silc_server_save_users_on_channel(SilcServer server,
     }
 
     if (!(client->data.status & SILC_IDLIST_STATUS_REGISTERED)) {
-      SILC_LOG_ERROR(("Attempting to add unregistered client to channel ",
+      SILC_LOG_ERROR(("Attempting to add unregistered client to channel "
                      "%s", channel->channel_name));
       continue;
     }