Author: Pekka Riikonen <priikone@poseidon.pspt.fi>
- Copyright (C) 1997 - 2000 Pekka Riikonen
+ Copyright (C) 1997 - 2001 Pekka Riikonen
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
void silc_server_free(SilcServer server)
{
if (server) {
+#ifdef SILC_SIM
SilcSimContext *sim;
+#endif
if (server->local_list)
silc_free(server->local_list);
return FALSE;
}
+/* Fork server to background and set gid+uid to non-root.
+ Silcd will not run as root, so trying to set either user or group to
+ root will cause silcd to exit. */
+
+void silc_server_daemonise(SilcServer server)
+{
+ /* Are we executing silcd as root or a regular user? */
+ if (geteuid()==0) {
+
+ struct passwd *pw;
+ struct group *gr;
+ char *user, *group;
+
+ if (!server->config->identity || !server->config->identity->user ||
+ !server->config->identity->group) {
+ fprintf(stderr, "Error:"
+ "\tSILC server must not be run as root. For the security of your\n"
+ "\tsystem it is strongly suggested that you run SILC under dedicated\n"
+ "\tuser account. Modify the [Identity] configuration section to run\n"
+ "\tthe server as non-root user.\n");
+ exit(1);
+ }
+
+ /* Get the values given for user and group in configuration file */
+ user=server->config->identity->user;
+ group=server->config->identity->group;
+
+ /* Check whether the user/group information is text */
+ if (atoi(user)!=0 || atoi(group)!=0) {
+ SILC_LOG_DEBUG(("Invalid user and/or group information"));
+ SILC_LOG_DEBUG(("User and/or group given as number"));
+ fprintf(stderr, "Invalid user and/or group information\n");
+ fprintf(stderr, "Please assign them as names, not numbers\n");
+ exit(1);
+ }
+
+ /* Catch the nasty incident of string "0" returning 0 from atoi */
+ if (strcmp("0", user)==0 || strcmp("0", group)==0) {
+ SILC_LOG_DEBUG(("User and/or group configured to 0. Unacceptable"));
+ fprintf(stderr, "User and/or group configured to 0. Exiting\n");
+ exit(1);
+ }
+
+ pw=getpwnam(user);
+ gr=getgrnam(group);
+
+ if (!pw) {
+ fprintf(stderr, "No such user %s found\n", user);
+ exit(1);
+ }
+
+ if (!gr) {
+ fprintf(stderr, "No such group %s found\n", group);
+ exit(1);
+ }
+
+ /* Check whether user and/or group is set to root. If yes, exit
+ immediately. Otherwise, setgid and setuid server to user.group */
+ if (gr->gr_gid==0 || pw->pw_uid==0) {
+ fprintf(stderr, "Error:"
+ "\tSILC server must not be run as root. For the security of your\n"
+ "\tsystem it is strongly suggested that you run SILC under dedicated\n"
+ "\tuser account. Modify the [Identity] configuration section to run\n"
+ "\tthe server as non-root user.\n");
+ exit(1);
+ } else {
+ /* Fork server to background, making it a daemon */
+ if (fork()) {
+ SILC_LOG_DEBUG(("Server started as root. Dropping privileges."));
+ SILC_LOG_DEBUG(("Forking SILC server to background"));
+ exit(0);
+ }
+ setsid();
+
+ SILC_LOG_DEBUG(("Changing to group %s", group));
+ if(setgid(gr->gr_gid)==0) {
+ SILC_LOG_DEBUG(("Setgid to %s", group));
+ } else {
+ SILC_LOG_DEBUG(("Setgid to %s failed", group));
+ fprintf(stderr, "Tried to setgid %s but no such group. Exiting\n",
+ group);
+ exit(1);
+ }
+ SILC_LOG_DEBUG(("Changing to user nobody"));
+ if(setuid(pw->pw_uid)==0) {
+ SILC_LOG_DEBUG(("Setuid to %s", user));
+ } else {
+ SILC_LOG_DEBUG(("Setuid to %s failed", user));
+ fprintf(stderr, "Tried to setuid %s but no such user. Exiting\n",
+ user);
+ exit(1);
+ }
+ }
+ } else {
+ /* Fork server to background, making it a daemon */
+ if (fork()) {
+ SILC_LOG_DEBUG(("Server started as user"));
+ SILC_LOG_DEBUG(("Forking SILC server to background"));
+ exit(0);
+ }
+ setsid();
+ }
+}
+
/* Stops the SILC server. This function is used to shutdown the server.
This is usually called after the scheduler has returned. After stopping
the server one should call silc_server_free. */
protocol. */
silc_socket_alloc(sock, SILC_SOCKET_TYPE_UNKNOWN, NULL, &newsocket);
server->sockets[sock] = newsocket;
- newsocket->hostname = sconn->remote_host;
+ newsocket->hostname = strdup(sconn->remote_host);
+ newsocket->ip = strdup(sconn->remote_host);
newsocket->port = sconn->remote_port;
sconn->sock = newsocket;
SilcSocketConnection sock = ctx->sock;
SilcServerEntry id_entry;
SilcBuffer packet;
+ SilcServerHBContext hb_context;
unsigned char *id_string;
SILC_LOG_DEBUG(("Start"));
server->router = id_entry;
server->router->data.registered = TRUE;
+ /* Perform keepalive. The `hb_context' will be freed automatically
+ when finally calling the silc_socket_free function. XXX hardcoded
+ timeout!! */
+ hb_context = silc_calloc(1, sizeof(*hb_context));
+ hb_context->server = server;
+ silc_socket_set_heartbeat(sock, 600, hb_context,
+ silc_server_perform_heartbeat,
+ server->timeout_queue);
+
out:
/* Free the temporary connection data context */
if (sconn)
SILC_LOG_DEBUG(("Accepting new connection"));
+ server->stat.conn_attempts++;
+
sock = silc_net_accept_connection(server->sock);
if (sock < 0) {
SILC_LOG_ERROR(("Could not accept new connection: %s", strerror(errno)));
+ server->stat.conn_failures++;
return;
}
/*silc_server_send_notify("Server is full, trying to redirect..."); */
} else {
SILC_LOG_ERROR(("Refusing connection, server is full"));
+ server->stat.conn_failures++;
}
return;
}
if ((server->params->require_reverse_mapping && !newsocket->hostname) ||
!newsocket->ip) {
SILC_LOG_ERROR(("IP/DNS lookup failed"));
+ server->stat.conn_failures++;
return;
}
if (!newsocket->hostname)
protocol but will not start it yet. The connector will be the
initiator of the protocol thus we will wait for initiation from
there before we start the protocol. */
+ server->stat.auth_attempts++;
silc_protocol_alloc(SILC_PROTOCOL_SERVER_KEY_EXCHANGE,
&newsocket->protocol, proto_ctx,
silc_server_accept_new_connection_second);
sock->protocol = NULL;
silc_server_disconnect_remote(server, sock, "Server closed connection: "
"Key exchange failed");
+ server->stat.auth_failures++;
return;
}
(SilcServerConnAuthInternalContext *)protocol->context;
SilcServer server = (SilcServer)ctx->server;
SilcSocketConnection sock = ctx->sock;
+ SilcServerHBContext hb_context;
void *id_entry = NULL;
SILC_LOG_DEBUG(("Start"));
sock->protocol = NULL;
silc_server_disconnect_remote(server, sock, "Server closed connection: "
"Authentication failed");
+ server->stat.auth_failures++;
return;
}
break;
}
+ /* Statistics */
+ server->stat.my_clients++;
+ server->stat.clients++;
+ if (server->server_type == SILC_ROUTER)
+ server->stat.cell_clients++;
+
id_entry = (void *)client;
break;
}
break;
}
+ /* Statistics */
+ if (sock->type == SILC_SOCKET_TYPE_SERVER)
+ server->stat.my_servers++;
+ else
+ server->stat.my_routers++;
+ server->stat.servers++;
+
id_entry = (void *)new_server;
/* There is connection to other server now, if it is router then
/* Connection has been fully established now. Everything is ok. */
SILC_LOG_DEBUG(("New connection authenticated"));
+ /* Perform keepalive. The `hb_context' will be freed automatically
+ when finally calling the silc_socket_free function. XXX hardcoded
+ timeout!! */
+ hb_context = silc_calloc(1, sizeof(*hb_context));
+ hb_context->server = server;
+ silc_socket_set_heartbeat(sock, 600, hb_context,
+ silc_server_perform_heartbeat,
+ server->timeout_queue);
+
silc_protocol_free(protocol);
if (ctx->packet)
silc_packet_context_free(ctx->packet);
SilcHmac hmac = NULL;
int ret;
+ if (!sock)
+ return;
+
SILC_LOG_DEBUG(("Processing packet"));
/* Packet sending */
if (type == SILC_TASK_WRITE) {
+ server->stat.packets_sent++;
+
if (sock->outbuf->data - sock->outbuf->head)
silc_buffer_push(sock->outbuf, sock->outbuf->data - sock->outbuf->head);
it later. */
if (ret == -2)
return;
+
+ if (ret == -1)
+ return;
/* The packet has been sent and now it is time to set the connection
back to only for input. When there is again some outgoing data
SILC_LOG_DEBUG(("Premature EOF from connection %d", sock->sock));
+ /* If the closed connection was our primary router connection the
+ start re-connecting phase. */
+ if (!server->standalone && server->server_type == SILC_SERVER &&
+ sock == server->router->connection)
+ silc_task_register(server->timeout_queue, 0,
+ silc_server_connect_to_router,
+ context, 0, 500000,
+ SILC_TASK_TIMEOUT,
+ SILC_TASK_PRI_NORMAL);
+
if (sock->user_data)
silc_server_free_sock_user_data(server, sock);
silc_server_close_connection(server, sock);
return;
}
+ server->stat.packets_received++;
+
/* Get keys and stuff from ID entry */
idata = (SilcIDListData)sock->user_data;
if (idata) {
if (server->server_type == SILC_ROUTER) {
/* Route the packet if it is not destined to us. Other ID types but
server are handled separately after processing them. */
- if (packet->dst_id_type == SILC_ID_SERVER &&
- !(packet->flags & SILC_PACKET_FLAG_FORWARDED) &&
+ if (packet->dst_id_type == SILC_ID_SERVER &&
sock->type != SILC_SOCKET_TYPE_CLIENT &&
SILC_ID_SERVER_COMPARE(packet->dst_id, server->id_string)) {
/* Route the packet to fastest route for the destination ID */
- void *id = silc_id_str2id(packet->dst_id, packet->dst_id_type);
+ void *id = silc_id_str2id(packet->dst_id, packet->dst_id_len,
+ packet->dst_id_type);
+ if (!id)
+ goto out;
silc_server_packet_route(server,
silc_server_route_get(server, id,
packet->dst_id_type),
/*
* Received command reply packet. Received command reply to command. It
* may be reply to command sent by us or reply to command sent by client
- * that we've forwarded.
+ * that we've routed further.
*/
SILC_LOG_DEBUG(("Command Reply packet"));
silc_server_command_reply(server, sock, packet);
proto_ctx->packet = silc_packet_context_dup(packet);
proto_ctx->dest_id_type = packet->src_id_type;
- proto_ctx->dest_id = silc_id_str2id(packet->src_id, packet->src_id_type);
+ proto_ctx->dest_id = silc_id_str2id(packet->src_id, packet->src_id_len,
+ packet->src_id_type);
+ if (!proto_ctx->dest_id)
+ break;
/* Let the protocol handle the packet */
sock->protocol->execute(server->timeout_queue, 0,
proto_ctx->packet = silc_packet_context_dup(packet);
proto_ctx->dest_id_type = packet->src_id_type;
- proto_ctx->dest_id = silc_id_str2id(packet->src_id, packet->src_id_type);
+ proto_ctx->dest_id = silc_id_str2id(packet->src_id, packet->src_id_len,
+ packet->src_id_type);
+ if (!proto_ctx->dest_id)
+ break;
/* Let the protocol handle the packet */
sock->protocol->execute(server->timeout_queue, 0,
silc_server_remove_channel_user(server, sock, packet);
break;
+ case SILC_PACKET_SET_MODE:
+ /*
+ * Received packet to set the mode of channel or client's channel mode.
+ */
+ SILC_LOG_DEBUG(("Set Mode packet"));
+ silc_server_set_mode(server, sock, packet);
+ break;
+
+ case SILC_PACKET_HEARTBEAT:
+ /*
+ * Received heartbeat.
+ */
+ SILC_LOG_DEBUG(("Heartbeat packet"));
+ break;
+
default:
SILC_LOG_ERROR(("Incorrect packet type %d, packet dropped", type));
break;
void silc_server_close_connection(SilcServer server,
SilcSocketConnection sock)
{
-
SILC_LOG_DEBUG(("Closing connection %d", sock->sock));
/* We won't listen for this connection anymore */
va_list ap;
unsigned char buf[4096];
+ if (!sock)
+ return;
+
memset(buf, 0, sizeof(buf));
va_start(ap, fmt);
vsprintf(buf, fmt, ap);
/* XXX must take some info to history before freeing */
/* Send REMOVE_ID packet to routers. */
- silc_server_send_remove_id(server, server->router->connection,
- server->server_type == SILC_SERVER ?
- FALSE : TRUE, user_data->id,
- SILC_ID_CLIENT_LEN, SILC_ID_CLIENT);
+ if (!server->standalone && server->router)
+ silc_server_send_remove_id(server, server->router->connection,
+ server->server_type == SILC_SERVER ?
+ FALSE : TRUE, user_data->id,
+ SILC_ID_CLIENT_LEN, SILC_ID_CLIENT);
/* Free the client entry and everything in it */
silc_idlist_del_data(user_data);
silc_idlist_del_client(server->local_list, user_data);
+ server->stat.my_clients--;
+ server->stat.clients--;
+ if (server->server_type == SILC_ROUTER)
+ server->stat.cell_clients--;
break;
}
case SILC_SOCKET_TYPE_SERVER:
SilcServerEntry user_data = (SilcServerEntry)sock->user_data;
/* Send REMOVE_ID packet to routers. */
- silc_server_send_remove_id(server, server->router->connection,
- server->server_type == SILC_SERVER ?
- FALSE : TRUE, user_data->id,
- SILC_ID_SERVER_LEN, SILC_ID_SERVER);
+ if (!server->standalone && server->router)
+ silc_server_send_remove_id(server, server->router->connection,
+ server->server_type == SILC_SERVER ?
+ FALSE : TRUE, user_data->id,
+ SILC_ID_CLIENT_LEN, SILC_ID_CLIENT);
+
+ /* Then also free all client entries that this server owns as
+ they will become invalid now as well. */
+ silc_server_remove_clients_by_server(server, user_data);
+
+ /* If this was our primary router connection then we're lost to
+ the outside world. */
+ if (server->server_type == SILC_SERVER && server->router == user_data) {
+ server->id_entry->router = NULL;
+ server->router = NULL;
+ server->standalone = TRUE;
+ }
+
+ /* Free the server entry */
+ silc_idlist_del_data(user_data);
+ silc_idlist_del_server(server->local_list, user_data);
+ server->stat.my_servers--;
+ server->stat.servers--;
+ if (server->server_type == SILC_ROUTER)
+ server->stat.cell_servers--;
break;
}
- break;
default:
{
SilcUnknownEntry user_data = (SilcUnknownEntry)sock->user_data;
sock->user_data = NULL;
}
+/* This function is used to remove all client entries by the server `entry'.
+ This is called when the connection is lost to the server. In this case
+ we must invalidate all the client entries owned by the server `entry'. */
+
+int silc_server_remove_clients_by_server(SilcServer server,
+ SilcServerEntry entry)
+{
+ SilcIDCacheList list = NULL;
+ SilcIDCacheEntry id_cache = NULL;
+ SilcClientEntry client = NULL;
+
+ if (silc_idcache_find_by_id(server->local_list->clients,
+ SILC_ID_CACHE_ANY, SILC_ID_CLIENT, &list)) {
+
+ if (silc_idcache_list_first(list, &id_cache)) {
+ while (id_cache) {
+ client = (SilcClientEntry)id_cache->context;
+
+ if (client->router != entry) {
+ if (!silc_idcache_list_next(list, &id_cache))
+ break;
+ else
+ continue;
+ }
+
+ /* Remove the client entry */
+ silc_server_remove_from_channels(server, NULL, client);
+ silc_idlist_del_client(server->local_list, client);
+
+ if (!silc_idcache_list_next(list, &id_cache))
+ break;
+ }
+ }
+ silc_idcache_list_free(list);
+ }
+
+ if (silc_idcache_find_by_id(server->global_list->clients,
+ SILC_ID_CACHE_ANY, SILC_ID_CLIENT, &list)) {
+
+ if (silc_idcache_list_first(list, &id_cache)) {
+ while (id_cache) {
+ client = (SilcClientEntry)id_cache->context;
+
+ if (client->router != entry) {
+ if (!silc_idcache_list_next(list, &id_cache))
+ break;
+ else
+ continue;
+ }
+
+ /* Remove the client entry */
+ silc_server_remove_from_channels(server, NULL, client);
+ silc_idlist_del_client(server->global_list, client);
+
+ if (!silc_idcache_list_next(list, &id_cache))
+ break;
+ }
+ }
+ silc_idcache_list_free(list);
+ }
+
+ return TRUE;
+}
+
/* Checks whether given channel has global users. If it does this returns
TRUE and FALSE if there is only locally connected clients on the channel. */
{
SilcChannelEntry channel;
SilcChannelClientEntry chl;
- SilcBuffer chidp, clidp;
+ SilcBuffer clidp;
SILC_LOG_DEBUG(("Start"));
silc_list_start(client->channels);
while ((chl = silc_list_get(client->channels)) != SILC_LIST_END) {
channel = chl->channel;
- chidp = silc_id_payload_encode(channel->id, SILC_ID_CHANNEL);
- /* Remove from list */
+ /* Remove channel from client's channel list */
silc_list_del(client->channels, chl);
- /* If this client is last one on the channel the channel
- is removed all together. */
- if (silc_list_count(channel->user_list) < 2) {
+ /* Remove channel if there is no users anymore */
+ if (server->server_type == SILC_ROUTER &&
+ silc_list_count(channel->user_list) < 2) {
+ if (!silc_idlist_del_channel(server->local_list, channel))
+ silc_idlist_del_channel(server->global_list, channel);
+ server->stat.my_channels--;
+ continue;
+ }
+
+ /* Remove client from channel's client list */
+ silc_list_del(channel->user_list, chl);
+ silc_free(chl);
+ server->stat.my_chanclients--;
- /* However, if the channel has marked global users then the
- channel is not created locally, and this does not remove the
- channel globally from SILC network, in this case we will
- notify that this client has left the channel. */
+ /* If there is not at least one local user on the channel then we don't
+ need the channel entry anymore, we can remove it safely. */
+ if (server->server_type == SILC_SERVER &&
+ !silc_server_channel_has_local(channel)) {
+ /* Notify about leaving client if this channel has global users. */
if (channel->global_users)
- silc_server_send_notify_to_channel(server, channel, FALSE,
+ silc_server_send_notify_to_channel(server, NULL, channel, FALSE,
SILC_NOTIFY_TYPE_SIGNOFF, 1,
clidp->data, clidp->len);
-
- silc_idlist_del_channel(server->local_list, channel);
+
+ if (!silc_idlist_del_channel(server->local_list, channel))
+ silc_idlist_del_channel(server->global_list, channel);
+ server->stat.my_channels--;
continue;
}
- /* Remove from list */
- silc_list_del(channel->user_list, chl);
- silc_free(chl);
-
/* Send notify to channel about client leaving SILC and thus
the entire channel. */
- silc_server_send_notify_to_channel(server, channel, FALSE,
+ silc_server_send_notify_to_channel(server, NULL, channel, FALSE,
SILC_NOTIFY_TYPE_SIGNOFF, 1,
clidp->data, clidp->len);
- silc_buffer_free(chidp);
}
silc_buffer_free(clidp);
ch = chl->channel;
- /* Remove from list */
+ /* Remove channel from client's channel list */
silc_list_del(client->channels, chl);
- /* If this client is last one on the channel the channel
- is removed all together. */
- if (silc_list_count(channel->user_list) < 2) {
- /* Notify about leaving client if this channel has global users. */
- if (notify && channel->global_users)
- silc_server_send_notify_to_channel(server, channel, FALSE,
- SILC_NOTIFY_TYPE_LEAVE, 1,
- clidp->data, clidp->len);
-
- silc_idlist_del_channel(server->local_list, channel);
+ /* Remove channel if there is no users anymore */
+ if (server->server_type == SILC_ROUTER &&
+ silc_list_count(channel->user_list) < 2) {
+ if (!silc_idlist_del_channel(server->local_list, channel))
+ silc_idlist_del_channel(server->global_list, channel);
silc_buffer_free(clidp);
+ server->stat.my_channels--;
return FALSE;
}
- /* Remove from list */
+ /* Remove client from channel's client list */
silc_list_del(channel->user_list, chl);
silc_free(chl);
+ server->stat.my_chanclients--;
/* If there is no global users on the channel anymore mark the channel
as local channel. */
!silc_server_channel_has_global(channel))
channel->global_users = FALSE;
- /* If tehre is not at least one local user on the channel then we don't
+ /* If there is not at least one local user on the channel then we don't
need the channel entry anymore, we can remove it safely. */
if (server->server_type == SILC_SERVER &&
!silc_server_channel_has_local(channel)) {
- silc_idlist_del_channel(server->local_list, channel);
+ /* Notify about leaving client if this channel has global users. */
+ if (notify && channel->global_users)
+ silc_server_send_notify_to_channel(server, NULL, channel, FALSE,
+ SILC_NOTIFY_TYPE_LEAVE, 1,
+ clidp->data, clidp->len);
+
+ if (!silc_idlist_del_channel(server->local_list, channel))
+ silc_idlist_del_channel(server->global_list, channel);
silc_buffer_free(clidp);
+ server->stat.my_channels--;
return FALSE;
}
/* Send notify to channel about client leaving the channel */
if (notify)
- silc_server_send_notify_to_channel(server, channel, FALSE,
+ silc_server_send_notify_to_channel(server, NULL, channel, FALSE,
SILC_NOTIFY_TYPE_LEAVE, 1,
clidp->data, clidp->len);
break;
SILC_TASK_CALLBACK(silc_server_timeout_remote)
{
- SilcServerConnection sconn = (SilcServerConnection)context;
- SilcSocketConnection sock = sconn->server->sockets[fd];
+ SilcServer server = (SilcServer)context;
+ SilcSocketConnection sock = server->sockets[fd];
+
+ if (!sock)
+ return;
- silc_server_disconnect_remote(sconn->server, sock,
+ if (sock->user_data)
+ silc_server_free_sock_user_data(server, sock);
+
+ silc_server_disconnect_remote(server, sock,
"Server closed connection: "
"Connection timeout");
}
channel_name, entry->id, SILC_ID_CHANNEL_LEN);
}
+ server->stat.my_channels++;
+
return entry;
}
/* Get channel ID */
tmp = silc_channel_key_get_id(payload, &tmp_len);
- id = silc_id_str2id(tmp, SILC_ID_CHANNEL);
+ id = silc_id_str2id(tmp, tmp_len, SILC_ID_CHANNEL);
if (!id) {
channel = NULL;
goto out;
channel = silc_idlist_find_channel_by_id(server->local_list, id, NULL);
if (!channel) {
- SILC_LOG_ERROR(("Received key for non-existent channel"));
- goto out;
+ channel = silc_idlist_find_channel_by_id(server->global_list, id, NULL);
+ if (!channel) {
+ SILC_LOG_ERROR(("Received key for non-existent channel"));
+ goto out;
+ }
}
}
return channel;
}
+
+/* Heartbeat callback. This function is set as argument for the
+ silc_socket_set_heartbeat function. The library will call this function
+ at the set time interval. */
+
+void silc_server_perform_heartbeat(SilcSocketConnection sock,
+ void *hb_context)
+{
+ SilcServerHBContext hb = (SilcServerHBContext)hb_context;
+
+ SILC_LOG_DEBUG(("Sending heartbeat to %s (%s)", sock->hostname,
+ sock->ip));
+
+ /* Send the heartbeat */
+ silc_server_send_heartbeat(hb->server, sock);
+}