Update copyright year.
[umurmur.git] / src / client.c
index e190a0b9f67fa30f94ebed2fc8a6951dd8f9a765..9bca358d2369f0c40312d481c23d8c0675016700 100644 (file)
@@ -1,5 +1,5 @@
-/* Copyright (C) 2009-2011, Martin Johansson <martin@fatbob.nu>
-   Copyright (C) 2005-2011, Thorvald Natvig <thorvald@natvig.com>
+/* Copyright (C) 2009-2013, Martin Johansson <martin@fatbob.nu>
+   Copyright (C) 2005-2013, Thorvald Natvig <thorvald@natvig.com>
 
    All rights reserved.
 
@@ -45,6 +45,7 @@
 #include "channel.h"
 #include "version.h"
 #include "voicetarget.h"
+#include "ban.h"
 
 extern char system_string[], version_string[];
 
@@ -56,6 +57,7 @@ void Client_free(client_t *client);
 declare_list(clients);
 static int clientcount; /* = 0 */
 static int maxBandwidth;
+bool_t bOpus = true;
 
 int iCodecAlpha, iCodecBeta;
 bool_t bPreferAlpha;
@@ -90,9 +92,9 @@ int Client_getfds(struct pollfd *pollfds)
 
 void Client_janitor()
 {
-       struct dlist *itr;
+       struct dlist *itr, *save;
        int bwTop = maxBandwidth + maxBandwidth / 4;
-       list_iterate(itr, &clients) {
+       list_iterate_safe(itr, save, &clients) {
                client_t *c;
                c = list_get_entry(itr, client_t, node);
                Log_debug("Client %s BW available %d", c->username, c->availableBandwidth);
@@ -100,12 +102,13 @@ void Client_janitor()
                if (c->availableBandwidth > bwTop)
                        c->availableBandwidth = bwTop;
                
-               if (Timer_isElapsed(&c->lastActivity, 1000000LL * INACTICITY_TIMEOUT)) {
+               if (Timer_isElapsed(&c->lastActivity, 1000000LL * INACTIVITY_TIMEOUT)) {
                        /* No activity from client - assume it is lost and close. */
                        Log_info_client(c, "Timeout, closing.");
                        Client_free(c);
                }
        }
+       Ban_pruneBanned();
 }
 
 void Client_codec_add(client_t *client, int codec)
@@ -146,21 +149,74 @@ codec_t *Client_codec_iterate(client_t *client, codec_t **codec_itr)
        return cd;
 }
 
-void recheckCodecVersions()
+void Client_token_add(client_t *client, char *token_string)
+{
+       token_t *token;
+
+       if (client->tokencount >= MAX_TOKENS)
+               return;
+       token = malloc(sizeof(token_t));
+       if (token == NULL)
+               Log_fatal("Out of memory");
+       init_list_entry(&token->node);
+       token->token = strdup(token_string);
+       if (token->token == NULL)
+               Log_fatal("Out of memory");
+       list_add_tail(&token->node, &client->tokens);
+       client->tokencount++;
+}
+
+bool_t Client_token_match(client_t *client, char *str)
+{
+       token_t *token;
+       struct dlist *itr;
+       
+       if (list_empty(&client->tokens))
+               return false;
+       list_iterate(itr, &client->tokens) {
+               token = list_get_entry(itr, token_t, node);
+               if (strncasecmp(token->token, str, MAX_TOKENSIZE) == 0)
+                       return true;
+       }
+       return false;
+}
+
+void Client_token_free(client_t *client)
+{
+       struct dlist *itr, *save;
+       token_t *token;
+       
+       list_iterate_safe(itr, save, &client->tokens) {
+               token = list_get_entry(itr, token_t, node);
+               list_del(&token->node);
+               free(token->token);
+               free(token);
+       }
+       client->tokencount = 0;
+}
+
+
+#define OPUS_WARN_USING "<strong>WARNING:</strong> Your client doesn't support the Opus codec the server is using, you won't be able to talk or hear anyone. Please upgrade your Mumble client."
+#define OPUS_WARN_SWITCHING "<strong>WARNING:</strong> Your client doesn't support the Opus codec the server is switching to, you won't be able to talk or hear anyone. Please upgrade your Mumble client."
+void recheckCodecVersions(client_t *connectingClient)
 {
        client_t *client_itr = NULL;
        int max = 0, version, current_version;
+       int users = 0, opus = 0;
        message_t *sendmsg;
        struct dlist codec_list, *itr, *save;
        codec_t *codec_itr, *cd;
        bool_t found;
-       
+       bool_t enableOpus;
+
        init_list_entry(&codec_list);
        
        while (Client_iterate(&client_itr) != NULL) {
                codec_itr = NULL;
+               if (client_itr->codec_count == 0 && !client_itr->bOpus)
+                       continue;
                while (Client_codec_iterate(client_itr, &codec_itr) != NULL) {
-                       found = false;
+                       found = false;                      
                        list_iterate(itr, &codec_list) {
                                cd = list_get_entry(itr, codec_t, node);
                                if (cd->codec == codec_itr->codec) {
@@ -179,8 +235,15 @@ void recheckCodecVersions()
                                list_add_tail(&cd->node, &codec_list);
                        }
                }
+               users++;
+               if (client_itr->bOpus)
+                       opus++;
        }
-       
+       if (users == 0) 
+               return;
+
+       enableOpus = ((opus * 100 / users) >= getIntConf(OPUS_THRESHOLD));
+
        list_iterate(itr, &codec_list) {
                cd = list_get_entry(itr, codec_t, node);
                if (cd->count > max) {
@@ -192,33 +255,49 @@ void recheckCodecVersions()
                list_del(&list_get_entry(itr, codec_t, node)->node);
                free(list_get_entry(itr, codec_t, node));
        }
-       
+
        current_version = bPreferAlpha ? iCodecAlpha : iCodecBeta;
-       if (current_version == version)
+       if (current_version != version) {
+               // If we don't already use the compat bitstream version set
+               // it as alpha and announce it. If another codec now got the
+               // majority set it as the opposite of the currently valid bPreferAlpha
+               // and announce it.
+               if (version == (uint32_t)0x8000000b)
+                       bPreferAlpha = true;
+               else
+                       bPreferAlpha = !bPreferAlpha;
+               
+               if (bPreferAlpha)
+                       iCodecAlpha = version;
+               else
+                       iCodecBeta = version;
+       } else if (bOpus && enableOpus) {
+               if (connectingClient && !connectingClient->bOpus)
+                       Client_textmessage(connectingClient, OPUS_WARN_USING);
                return;
-       // If we don't already use the compat bitstream version set
-       // it as alpha and announce it. If another codec now got the
-       // majority set it as the opposite of the currently valid bPreferAlpha
-       // and announce it.
-       if (version == (uint32_t)0x8000000b)
-               bPreferAlpha = true;
-       else
-               bPreferAlpha = ! bPreferAlpha;
+       }
 
-       if (bPreferAlpha)
-               iCodecAlpha = version;
-       else
-               iCodecBeta = version;
-       
        sendmsg = Msg_create(CodecVersion);
        sendmsg->payload.codecVersion->alpha = iCodecAlpha;
        sendmsg->payload.codecVersion->beta = iCodecBeta;
        sendmsg->payload.codecVersion->prefer_alpha = bPreferAlpha;
+       sendmsg->payload.codecVersion->has_opus = true;
+       sendmsg->payload.codecVersion->opus = enableOpus;
+
        Client_send_message_except(NULL, sendmsg);
        
-       Log_info("CELT codec switch 0x%x 0x%x (prefer 0x%x)", iCodecAlpha, iCodecBeta,
-                        bPreferAlpha ? iCodecAlpha : iCodecBeta);
+       if (enableOpus && !bOpus) {
+               client_itr = NULL;
+               while (Client_iterate(&client_itr) != NULL) {
+                       if ((client_itr->authenticated || client_itr == connectingClient) &&
+                           !client_itr->bOpus) {
+                               Client_textmessage(client_itr, OPUS_WARN_SWITCHING);
+                       }
+               }
+               Log_info("OPUS codec %s", bOpus ? "enabled" : "disabled");      
+       }
        
+       bOpus = enableOpus;
 }
 
 static int findFreeSessionId()
@@ -242,7 +321,11 @@ int Client_add(int fd, struct sockaddr_in *remote)
 {
        client_t *newclient;
        message_t *sendmsg;
-       
+
+       if (Ban_isBannedAddr((in_addr_t *)&remote->sin_addr)) {
+               Log_info("Address %s banned. Disconnecting", inet_ntoa(remote->sin_addr));
+               return -1;
+       }
        newclient = malloc(sizeof(client_t));
        if (newclient == NULL)
                Log_fatal("Out of memory");
@@ -270,6 +353,7 @@ int Client_add(int fd, struct sockaddr_in *remote)
        init_list_entry(&newclient->node);
        init_list_entry(&newclient->voicetargets);
        init_list_entry(&newclient->codecs);
+       init_list_entry(&newclient->tokens);
        
        list_add_tail(&newclient->node, &clients);
        clientcount++;
@@ -290,6 +374,7 @@ void Client_free(client_t *client)
 {
        struct dlist *itr, *save;
        message_t *sendmsg;
+       bool_t authenticatedLeft = client->authenticated;
 
        if (client->authenticated) {
                int leave_id;
@@ -309,6 +394,7 @@ void Client_free(client_t *client)
        }
        Client_codec_free(client);
        Voicetarget_free_all(client);
+       Client_token_free(client);
        
        list_del(&client->node);
        if (client->ssl)
@@ -326,6 +412,9 @@ void Client_free(client_t *client)
        if (client->context)
                free(client->context);
        free(client);
+
+       if (authenticatedLeft)
+               recheckCodecVersions(NULL); /* Can use better codec now? */
 }
 
 void Client_close(client_t *client)
@@ -354,10 +443,10 @@ int Client_read_fd(int fd)
                        break;
                }
        }
-       if (client == NULL)
-               Log_fatal("No client found for fd %d", fd);
-       
-       return Client_read(client);
+       if (client != NULL)
+               return Client_read(client);
+       else
+               return -1;
 }
 
 int Client_read(client_t *client)
@@ -425,14 +514,21 @@ int Client_read(client_t *client)
                                client->readBlockedOnWrite = true;
                                return 0;
                        }
-                       else if (SSLi_get_error(client->ssl, rc) == SSLI_ERROR_ZERO_RETURN) {
+                       else if (SSLi_get_error(client->ssl, rc) == SSLI_ERROR_ZERO_RETURN || 
+                                SSLi_get_error(client->ssl, rc) == 0) {
                                Log_info_client(client, "Connection closed by peer");
-                               if (!client->shutdown_wait)
-                                       Client_close(client);
+                               Client_close(client);
                        }
                        else {
                                if (SSLi_get_error(client->ssl, rc) == SSLI_ERROR_SYSCALL) {
-                                       Log_info_client(client, "Connection closed by peer");
+                                       if (errno == 0)
+                                               Log_info_client(client, "Connection closed by peer");
+                                       else
+                                               Log_info_client(client,"Error: %s  - Closing connection (code %d)", 
+                                                               strerror(errno));
+                               }
+                               else if (SSLi_get_error(client->ssl, rc) == SSLI_ERROR_CONNRESET) {
+                                       Log_info_client(client, "Connection reset by peer");
                                }
                                else {
                                        Log_info_client(client, "SSL error: %d - Closing connection", SSLi_get_error(client->ssl, rc));
@@ -457,10 +553,10 @@ int Client_write_fd(int fd)
                        break;
                }
        }
-       if (client == NULL)
-               Log_fatal("No client found for fd %d", fd);
-       Client_write(client);
-       return 0;
+       if (client != NULL)
+               return Client_write(client);
+       else
+               return -1;
 }
 
 int Client_write(client_t *client)
@@ -487,10 +583,15 @@ int Client_write(client_t *client)
                        return 0;
                }
                else {
-                       if (SSLi_get_error(client->ssl, rc) == SSLI_ERROR_SYSCALL)
-                               Log_warn("Client_write: Error: %s  - Closing connection", strerror(errno));
-                       else
-                               Log_warn("Client_write: SSL error: %d - Closing connection.", SSLi_get_error(client->ssl, rc));
+                       if (SSLi_get_error(client->ssl, rc) == SSLI_ERROR_SYSCALL) {
+                               Log_info_client(client, "Error: %s  - Closing connection", strerror(errno));
+                       }
+                       else if (SSLi_get_error(client->ssl, rc) == SSLI_ERROR_CONNRESET) {
+                               Log_info_client(client, "Connection reset by peer");
+                       }
+                       else {
+                               Log_info_client(client, "SSL error: %d - Closing connection.", SSLi_get_error(client->ssl, rc));
+                       }
                        Client_free(client);
                        return -1;
                }
@@ -562,6 +663,27 @@ client_t *Client_iterate(client_t **client_itr)
        return c;
 }
 
+void Client_textmessage(client_t *client, char *text)
+{
+       char *message;
+       uint32_t *tree_id;
+       message_t *sendmsg = NULL;
+
+       message = malloc(strlen(text) + 1);
+       if (!message)
+               Log_fatal("Out of memory");
+       tree_id = malloc(sizeof(uint32_t));
+       if (!tree_id)
+               Log_fatal("Out of memory");
+       *tree_id = 0;
+       sendmsg = Msg_create(TextMessage);
+       sendmsg->payload.textMessage->message = message;
+       sendmsg->payload.textMessage->n_tree_id = 1;
+       sendmsg->payload.textMessage->tree_id = tree_id;
+       strcpy(message, text);
+       Client_send_message(client, sendmsg);
+}
+
 
 int Client_send_message_except(client_t *client, message_t *msg)
 {
@@ -704,6 +826,9 @@ int Client_read_udp()
        case UDPVoiceSpeex:
        case UDPVoiceCELTAlpha:
        case UDPVoiceCELTBeta:
+               if (bOpus)
+                       break;
+       case UDPVoiceOpus:
                Client_voiceMsg(itr, buffer, len);
                break;
        case UDPPing:
@@ -721,7 +846,7 @@ out:
 
 static inline void Client_send_voice(client_t *src, client_t *dst, uint8_t *data, int len, int poslen)
 {
-       if (IS_AUTH(dst) && dst != src && !dst->deaf) {
+       if (IS_AUTH(dst) && dst != src && !dst->deaf && !dst->self_deaf) {
                if (poslen > 0 && /* Has positional data */
                        src->context != NULL && dst->context != NULL && /* ...both source and destination has context */
                        strcmp(src->context, dst->context) == 0) /* ...and the contexts match */
@@ -739,14 +864,14 @@ int Client_voiceMsg(client_t *client, uint8_t *data, int len)
        pds_t *pds = Pds_create(buffer + 1, UDP_PACKET_SIZE - 1);
        unsigned int type = data[0] & 0xe0;
        unsigned int target = data[0] & 0x1f;
-       unsigned int poslen, counter;
+       unsigned int poslen, counter, size;
        int offset, packetsize;
        voicetarget_t *vt;
        
        channel_t *ch = (channel_t *)client->channel;
        struct dlist *itr;
        
-       if (!client->authenticated || client->mute)
+       if (!client->authenticated || client->mute || client->self_mute)
                goto out;
        
        packetsize = 20 + 8 + 4 + len;
@@ -755,13 +880,19 @@ int Client_voiceMsg(client_t *client, uint8_t *data, int len)
        client->availableBandwidth -= packetsize;
        
        Timer_restart(&client->idleTime);
+       Timer_restart(&client->lastActivity);
        
        counter = Pds_get_numval(pdi); /* step past session id */
-       do {
-               counter = Pds_next8(pdi);
-               offset = Pds_skip(pdi, counter & 0x7f);
-       } while ((counter & 0x80) && offset > 0);
-
+       if ((type >> 5) != UDPVoiceOpus) {
+               do {
+                       counter = Pds_next8(pdi);
+                       offset = Pds_skip(pdi, counter & 0x7f);
+               } while ((counter & 0x80) && offset > 0);
+       } else {
+               size = Pds_get_numval(pdi);
+               Pds_skip(pdi, size & 0x1fff);
+       }
+               
        poslen = pdi->maxsize - pdi->offset; /* For stripping of positional info */
        
        Pds_add_numval(pds, client->sessionId);