diff options
author | grothoff <grothoff@140774ce-b5e7-0310-ab8b-a85725594a96> | 2011-01-17 08:30:26 +0000 |
---|---|---|
committer | grothoff <grothoff@140774ce-b5e7-0310-ab8b-a85725594a96> | 2011-01-17 08:30:26 +0000 |
commit | 6c4478899f721f4ac33b82ff2e8447c58384d93d (patch) | |
tree | 56cbcd777842a610ab2b00683409d3194adab844 /src/chat | |
parent | 1e78749996f6410e8dcc15aee4d849f8ca3f09ec (diff) |
Mantis #1644, unmodified, patch by vminko
git-svn-id: https://gnunet.org/svn/gnunet@14187 140774ce-b5e7-0310-ab8b-a85725594a96
Diffstat (limited to 'src/chat')
-rw-r--r-- | src/chat/Makefile.am | 39 | ||||
-rw-r--r-- | src/chat/chat.c | 789 | ||||
-rw-r--r-- | src/chat/chat.h | 458 | ||||
-rw-r--r-- | src/chat/gnunet-chat.c | 674 | ||||
-rw-r--r-- | src/chat/gnunet-service-chat.c | 1565 |
5 files changed, 3525 insertions, 0 deletions
diff --git a/src/chat/Makefile.am b/src/chat/Makefile.am new file mode 100644 index 0000000000..ad099e6b1d --- /dev/null +++ b/src/chat/Makefile.am @@ -0,0 +1,39 @@ +INCLUDES = -I$(top_srcdir)/src/include + +if MINGW + WINFLAGS = -Wl,--no-undefined -Wl,--export-all-symbols +endif + +if USE_COVERAGE + AM_CFLAGS = -fprofile-arcs -ftest-coverage +endif + +lib_LTLIBRARIES = libgnunetchat.la + +libgnunetchat_la_SOURCES = \ + chat.c chat.h + +libgnunetchat_la_LIBADD = \ + $(top_builddir)/src/util/libgnunetutil.la + +libgnunetchat_la_LDFLAGS = \ + $(GN_LIB_LDFLAGS) $(WINFLAGS) \ + -version-info 0:0:0 + +bin_PROGRAMS = \ + gnunet-service-chat \ + gnunet-chat + +gnunet_service_chat_SOURCES = \ + gnunet-service-chat.c +gnunet_service_chat_LDADD = \ + $(top_builddir)/src/core/libgnunetcore.la \ + $(top_builddir)/src/util/libgnunetutil.la \ + $(GN_LIBINTL) + +gnunet_chat_SOURCES = \ + gnunet-chat.c +gnunet_chat_LDADD = \ + $(top_builddir)/src/chat/libgnunetchat.la \ + $(top_builddir)/src/util/libgnunetutil.la \ + $(GN_LIBINTL) diff --git a/src/chat/chat.c b/src/chat/chat.c new file mode 100644 index 0000000000..14480c3e21 --- /dev/null +++ b/src/chat/chat.c @@ -0,0 +1,789 @@ +/* + This file is part of GNUnet. + (C) 2008, 2011 Christian Grothoff (and other contributing authors) + + GNUnet is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published + by the Free Software Foundation; either version 3, or (at your + option) any later version. + + GNUnet is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with GNUnet; see the file COPYING. If not, write to the + Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. +*/ + +/** + * @file chat/chat_api.c + * @brief convenience API for sending and receiving chat messages + * @author Christian Grothoff + * @author Nathan Evans + * @author Vitaly Minko + */ + +#include "platform.h" +#include "gnunet_constants.h" +#include "gnunet_protocols.h" +#include "gnunet_signatures.h" +#include "chat.h" + +#define DEBUG_CHAT GNUNET_YES +#define NICK_IDENTITY_PREFIX ".chat_identity_" + + +/** + * Handle for a (joined) chat room. + */ +struct GNUNET_CHAT_Room +{ + struct GNUNET_CLIENT_Connection *client; + + const struct GNUNET_CONFIGURATION_Handle *cfg; + + struct GNUNET_CONTAINER_MetaData *member_info; + + char *room_name; + + struct GNUNET_CRYPTO_RsaPrivateKey *my_private_key; + + struct MemberList *members; + + GNUNET_CHAT_MessageCallback message_callback; + + void *message_callback_cls; + + GNUNET_CHAT_MemberListCallback member_list_callback; + + void *member_list_callback_cls; + + GNUNET_CHAT_MessageConfirmation confirmation_callback; + + void *confirmation_cls; + + uint32_t sequence_number; + + uint32_t msg_options; + +}; + +/** + * Linked list of members in the chat room. + */ +struct MemberList +{ + struct MemberList *next; + + /** + * Description of the member. + */ + struct GNUNET_CONTAINER_MetaData *meta; + + /** + * Member ID (pseudonym). + */ + GNUNET_HashCode id; + +}; + +/** + * Context for transmitting a send-message request. + */ +struct GNUNET_CHAT_SendMessageContext +{ + /** + * Handle for the chat room. + */ + struct GNUNET_CHAT_Room *chat_room; + + /** + * Message that we're sending. + */ + char *message; + + /** + * Options for the message. + */ + enum GNUNET_CHAT_MsgOptions options; + + /** + * Receiver of the message. NULL to send to everyone in the room. + */ + const struct GNUNET_CRYPTO_RsaPublicKeyBinaryEncoded *receiver; + + /** + * Sequence id of the message. + */ + uint32_t sequence_number; + +}; + +/** + * Context for transmitting a confirmation receipt. + */ +struct GNUNET_CHAT_SendReceiptContext +{ + /** + * Handle for the chat room. + */ + struct GNUNET_CHAT_Room *chat_room; + + /** + * The original message that we're going to acknowledge. + */ + struct ReceiveNotificationMessage *received_msg; + +}; + +/** + * Ask client to send a join request. + */ +static int +GNUNET_CHAT_rejoin_room (struct GNUNET_CHAT_Room *chat_room); + + +/** + * Transmit a confirmation receipt to the chat service. + * + * @param cls closure, pointer to the 'struct GNUNET_CHAT_SendReceiptContext' + * @param size number of bytes available in buf + * @param buf where the callee should write the message + * @return number of bytes written to buf + */ +static size_t +transmit_acknowledge_request (void *cls, + size_t size, + void *buf) +{ + struct GNUNET_CHAT_SendReceiptContext *src = cls; + struct ConfirmationReceiptMessage *receipt; + struct GNUNET_CRYPTO_RsaPublicKeyBinaryEncoded pub_key; + uint16_t msg_len; + size_t msg_size; + + if (NULL == buf) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + _("Could not transmit confirmation receipt\n")); + return 0; + } +#if DEBUG_CHAT + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Transmitting confirmation receipt to the service\n"); +#endif + msg_size = sizeof (struct ConfirmationReceiptMessage); + GNUNET_assert (size >= msg_size); + receipt = buf; + receipt->header.size = htons (msg_size); + receipt->header.type = + htons (GNUNET_MESSAGE_TYPE_CHAT_CONFIRMATION_RECEIPT); + receipt->sequence_number = src->received_msg->sequence_number; + receipt->reserved2 = 0; + receipt->timestamp = GNUNET_TIME_absolute_hton (GNUNET_TIME_absolute_get ()); + GNUNET_CRYPTO_rsa_key_get_public (src->chat_room->my_private_key, &pub_key); + GNUNET_CRYPTO_hash (&pub_key, + sizeof (struct GNUNET_CRYPTO_RsaPublicKeyBinaryEncoded), + &receipt->target); + receipt->author = src->received_msg->sender; + receipt->purpose.purpose = + htonl (GNUNET_SIGNATURE_PURPOSE_CHAT_RECEIPT); + receipt->purpose.size = + htonl (msg_size - + sizeof (struct GNUNET_MessageHeader) - + sizeof (uint32_t) - + sizeof (struct GNUNET_CRYPTO_RsaSignature)); + msg_len = ntohs (src->received_msg->header.size) - + sizeof (struct ReceiveNotificationMessage); + GNUNET_CRYPTO_hash (&src->received_msg[1], msg_len, &receipt->content); + GNUNET_assert (GNUNET_OK == + GNUNET_CRYPTO_rsa_sign (src->chat_room->my_private_key, + &receipt->purpose, + &receipt->signature)); + GNUNET_free (src->received_msg); + GNUNET_free (src); + return msg_size; +} + + +/** + * Handles messages received from the service. Calls the proper client + * callback. + */ +static void +process_result (struct GNUNET_CHAT_Room *room, + const struct GNUNET_MessageHeader *reply) +{ + struct LeaveNotificationMessage *leave_msg; + struct JoinNotificationMessage *join_msg; + struct ReceiveNotificationMessage *received_msg; + struct ConfirmationReceiptMessage *receipt; + GNUNET_HashCode id; + struct GNUNET_CONTAINER_MetaData *meta; + struct GNUNET_CHAT_SendReceiptContext *src; + struct MemberList *pos; + struct MemberList *prev; + struct GNUNET_CRYPTO_AesSessionKey key; + char decrypted_msg[MAX_MESSAGE_LENGTH]; + uint16_t size; + uint16_t meta_len; + uint16_t msg_len; + char *message_content; + + size = ntohs (reply->size); + switch (ntohs (reply->type)) + { + case GNUNET_MESSAGE_TYPE_CHAT_JOIN_NOTIFICATION: +#if DEBUG_CHAT + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Got a join notification\n"); +#endif + if (size < sizeof (struct JoinNotificationMessage)) + { + GNUNET_break (0); + return; + } + join_msg = (struct JoinNotificationMessage *) reply; + meta_len = size - sizeof (struct JoinNotificationMessage); + meta = + GNUNET_CONTAINER_meta_data_deserialize ((const char *) &join_msg[1], + meta_len); + if (NULL == meta) + { + GNUNET_break (0); + return; + } + pos = GNUNET_malloc (sizeof (struct MemberList)); + pos->meta = meta; + GNUNET_CRYPTO_hash (&join_msg->public_key, + sizeof (struct GNUNET_CRYPTO_RsaPublicKeyBinaryEncoded), + &pos->id); + GNUNET_PSEUDONYM_add (room->cfg, &pos->id, meta); + room->member_list_callback (room->member_list_callback_cls, + meta, &join_msg->public_key, + ntohl (join_msg->msg_options)); + pos->next = room->members; + room->members = pos; + break; + case GNUNET_MESSAGE_TYPE_CHAT_LEAVE_NOTIFICATION: +#if DEBUG_CHAT + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Got a leave notification\n"); +#endif + if (size < sizeof (struct LeaveNotificationMessage)) + { + GNUNET_break (0); + return; + } + leave_msg = (struct LeaveNotificationMessage *) reply; + room->member_list_callback (room->member_list_callback_cls, + NULL, &leave_msg->user, + GNUNET_CHAT_MSG_OPTION_NONE); + GNUNET_CRYPTO_hash (&leave_msg->user, + sizeof (struct GNUNET_CRYPTO_RsaPublicKeyBinaryEncoded), + &id); + prev = NULL; + pos = room->members; + while ((NULL != pos) && + (0 != memcmp (&pos->id, &id, sizeof (GNUNET_HashCode)))) + { + prev = pos; + pos = pos->next; + } + GNUNET_assert (NULL != pos); + if (NULL == prev) + room->members = pos->next; + else + prev->next = pos->next; + GNUNET_CONTAINER_meta_data_destroy (pos->meta); + GNUNET_free (pos); + break; + case GNUNET_MESSAGE_TYPE_CHAT_MESSAGE_NOTIFICATION: +#if DEBUG_CHAT + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Got a message notification\n"); +#endif + if (size <= sizeof (struct ReceiveNotificationMessage)) + { + GNUNET_break (0); + return; + } + received_msg = (struct ReceiveNotificationMessage *) reply; + if (0 != + (ntohl (received_msg->msg_options) & GNUNET_CHAT_MSG_ACKNOWLEDGED)) + { + src = GNUNET_malloc (sizeof (struct GNUNET_CHAT_SendReceiptContext)); + src->chat_room = room; + src->received_msg = GNUNET_memdup (received_msg, size); + GNUNET_CLIENT_notify_transmit_ready (room->client, + sizeof (struct ConfirmationReceiptMessage), + GNUNET_CONSTANTS_SERVICE_TIMEOUT, + GNUNET_YES, + &transmit_acknowledge_request, + src); + } + msg_len = size - sizeof (struct ReceiveNotificationMessage); + if (0 != + (ntohl (received_msg->msg_options) & GNUNET_CHAT_MSG_PRIVATE)) + { + if (-1 == GNUNET_CRYPTO_rsa_decrypt (room->my_private_key, + &received_msg->encrypted_key, + &key, + sizeof (struct GNUNET_CRYPTO_AesSessionKey))) + { + GNUNET_break (0); + return; + } + msg_len = GNUNET_CRYPTO_aes_decrypt (&received_msg[1], + msg_len, + &key, + (const struct GNUNET_CRYPTO_AesInitializationVector *) INITVALUE, + decrypted_msg); + message_content = decrypted_msg; + } + else + { + message_content = GNUNET_malloc (msg_len + 1); + memcpy (message_content, &received_msg[1], msg_len); + } + message_content[msg_len] = '\0'; + pos = room->members; + while ((NULL != pos) && + (0 != memcmp (&pos->id, + &received_msg->sender, + sizeof (GNUNET_HashCode)))) + pos = pos->next; + GNUNET_assert (NULL != pos); + room->message_callback (room->message_callback_cls, + room, + &received_msg->sender, + pos->meta, + message_content, + ntohl (received_msg->msg_options)); + if (message_content != decrypted_msg) + GNUNET_free (message_content); + break; + case GNUNET_MESSAGE_TYPE_CHAT_CONFIRMATION_NOTIFICATION: +#if DEBUG_CHAT + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Got a confirmation receipt\n"); +#endif + if (size < sizeof (struct ConfirmationReceiptMessage)) + { + GNUNET_break (0); + return; + } + receipt = (struct ConfirmationReceiptMessage *) reply; + if (NULL != room->confirmation_callback) + room->confirmation_callback (room->confirmation_cls, + room, + ntohl (receipt->sequence_number), + GNUNET_TIME_absolute_ntoh (receipt->timestamp), + &receipt->target, + &receipt->content, + &receipt->signature); + break; + default: + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + _("Unknown message type: '%u'\n"), ntohs (reply->type)); + GNUNET_break_op (0); + break; + } +} + + +/** + * Listen for incoming messages on this chat room. Also, support servers going + * away/coming back (i.e. rejoin chat room to keep server state up to date). + * + * @param cls closure, pointer to the 'struct GNUNET_CHAT_Room' + * @param msg message received, NULL on timeout or fatal error + */ +static void +receive_results (void *cls, + const struct GNUNET_MessageHeader *msg) +{ + struct GNUNET_CHAT_Room *chat_room = cls; + +#if DEBUG_CHAT + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Got a message from the service\n"); +#endif + if (0 != (GNUNET_SCHEDULER_REASON_SHUTDOWN & GNUNET_SCHEDULER_get_reason ())) + return; + if (NULL == msg) + { + GNUNET_break (0); + GNUNET_CHAT_rejoin_room (chat_room); + return; + } + process_result (chat_room, msg); + if (NULL == chat_room->client) + return; /* fatal error */ + /* continue receiving */ + GNUNET_CLIENT_receive (chat_room->client, + &receive_results, + chat_room, + GNUNET_TIME_UNIT_FOREVER_REL); +} + + +/** + * Read existing private key from file or create a new one if it does not exist + * yet. + * Returns the private key on success, NULL on error. + */ +static struct GNUNET_CRYPTO_RsaPrivateKey * +GNUNET_CHAT_initPrivateKey (const struct GNUNET_CONFIGURATION_Handle *cfg, + const char *nick_name) +{ + char *home; + char *keyfile; + struct GNUNET_CRYPTO_RsaPrivateKey *privKey; + +#if DEBUG_CHAT + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Initializing private key\n"); +#endif + if (GNUNET_OK != + GNUNET_CONFIGURATION_get_value_filename (cfg, + "chat", + "HOME", + &home)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + _("Configuration option `%s' in section `%s' missing\n"), + "HOME", + "chat"); + return NULL; + } + GNUNET_DISK_directory_create (home); + if (GNUNET_OK != GNUNET_DISK_directory_test (home)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + _("Failed to access chat home directory `%s'\n"), + home); + GNUNET_free (home); + return NULL; + } + /* read or create private key */ + keyfile = + GNUNET_malloc (strlen (home) + strlen (NICK_IDENTITY_PREFIX) + + strlen (nick_name) + 2); + strcpy (keyfile, home); + GNUNET_free (home); + if (keyfile[strlen (keyfile) - 1] != DIR_SEPARATOR) + strcat (keyfile, DIR_SEPARATOR_STR); + strcat (keyfile, NICK_IDENTITY_PREFIX); + strcat (keyfile, nick_name); + privKey = GNUNET_CRYPTO_rsa_key_create_from_file (keyfile); + if (NULL == privKey) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + _("Failed to create/open key in file `%s'\n"), + keyfile); + } + GNUNET_free (keyfile); + return privKey; +} + + +/** + * Transmit a join request to the chat service. + * + * @param cls closure, pointer to the 'struct GNUNET_CHAT_Room' + * @param size number of bytes available in buf + * @param buf where the callee should write the message + * @return number of bytes written to buf + */ +static size_t +transmit_join_request (void *cls, + size_t size, + void *buf) +{ + struct GNUNET_CHAT_Room *chat_room = cls; + struct JoinRequestMessage *join_msg; + char *room; + char *meta; + size_t room_len; + ssize_t meta_len; + size_t size_of_join; + + if (NULL == buf) + { +#if DEBUG_CHAT + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Could not transmit join request\n"); +#endif + return 0; + } +#if DEBUG_CHAT + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Transmitting join request to the service\n"); +#endif + room_len = strlen (chat_room->room_name); + meta_len = GNUNET_CONTAINER_meta_data_get_serialized_size (chat_room->member_info); + size_of_join = sizeof (struct JoinRequestMessage) + meta_len + room_len; + GNUNET_assert (size >= size_of_join); + join_msg = buf; + join_msg->header.size = htons (size); + join_msg->header.type = htons (GNUNET_MESSAGE_TYPE_CHAT_JOIN_REQUEST); + join_msg->msg_options = htonl (chat_room->msg_options); + join_msg->room_name_len = htons (room_len); + join_msg->reserved = htons (0); + GNUNET_CRYPTO_rsa_key_get_public (chat_room->my_private_key, &join_msg->public_key); + room = (char *) &join_msg[1]; + memcpy (room, chat_room->room_name, room_len); + meta = &room[room_len]; + if (GNUNET_SYSERR == + GNUNET_CONTAINER_meta_data_serialize (chat_room->member_info, + &meta, + meta_len, + GNUNET_CONTAINER_META_DATA_SERIALIZE_FULL)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + _("Could not serialize metadata\n")); + return 0; + } + return size_of_join; +} + + +/** + * Ask to send a join request. + */ +static int +GNUNET_CHAT_rejoin_room (struct GNUNET_CHAT_Room *chat_room) +{ + size_t size_of_join; + + size_of_join = sizeof (struct JoinRequestMessage) + + GNUNET_CONTAINER_meta_data_get_serialized_size (chat_room->member_info) + + strlen (chat_room->room_name); + if (NULL == + GNUNET_CLIENT_notify_transmit_ready (chat_room->client, + size_of_join, + GNUNET_CONSTANTS_SERVICE_TIMEOUT, + GNUNET_YES, + &transmit_join_request, + chat_room)) + return GNUNET_SYSERR; + return GNUNET_OK; +} + + +/** + * Leave a chat room. + */ +void +GNUNET_CHAT_leave_room (struct GNUNET_CHAT_Room *chat_room) +{ + struct MemberList *pos; + +#if DEBUG_CHAT + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Leaving the room '%s'\n", chat_room->room_name); +#endif + GNUNET_CLIENT_disconnect (chat_room->client, GNUNET_NO); + GNUNET_free (chat_room->room_name); + GNUNET_CONTAINER_meta_data_destroy (chat_room->member_info); + GNUNET_CRYPTO_rsa_key_free (chat_room->my_private_key); + while (NULL != chat_room->members) + { + pos = chat_room->members; + chat_room->members = pos->next; + GNUNET_CONTAINER_meta_data_destroy (pos->meta); + GNUNET_free (pos); + } + GNUNET_free (chat_room); +} + + +/** + * Join a chat room. + * + * @param cfg configuration + * @param nick_name nickname of the user joining (used to + * determine which public key to use); + * the nickname should probably also + * be used in the member_info (as "EXTRACTOR_TITLE") + * @param member_info information about the joining member + * @param room_name name of the room + * @param msg_options message options of the joining user + * @param messageCallback which function to call if a message has + * been received? + * @param message_cls argument to callback + * @param memberCallback which function to call for join/leave notifications + * @param member_cls argument to callback + * @param confirmationCallback which function to call for confirmations (maybe NULL) + * @param confirmation_cls argument to callback + * @param me member ID (pseudonym) + * @return NULL on error + */ +struct GNUNET_CHAT_Room * +GNUNET_CHAT_join_room (const struct GNUNET_CONFIGURATION_Handle *cfg, + const char *nick_name, + struct GNUNET_CONTAINER_MetaData *member_info, + const char *room_name, + enum GNUNET_CHAT_MsgOptions msg_options, + GNUNET_CHAT_MessageCallback messageCallback, + void *message_cls, + GNUNET_CHAT_MemberListCallback memberCallback, + void *member_cls, + GNUNET_CHAT_MessageConfirmation confirmationCallback, + void *confirmation_cls, + GNUNET_HashCode *me) +{ + struct GNUNET_CHAT_Room *chat_room; + struct GNUNET_CRYPTO_RsaPrivateKey *priv_key; + struct GNUNET_CRYPTO_RsaPublicKeyBinaryEncoded pub_key; + struct GNUNET_CLIENT_Connection *client; + +#if DEBUG_CHAT + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Joining the room '%s'\n", room_name); +#endif + priv_key = GNUNET_CHAT_initPrivateKey (cfg, nick_name); + if (NULL == priv_key) + return NULL; + GNUNET_CRYPTO_rsa_key_get_public (priv_key, &pub_key); + GNUNET_CRYPTO_hash (&pub_key, + sizeof (struct GNUNET_CRYPTO_RsaPublicKeyBinaryEncoded), + me); + GNUNET_PSEUDONYM_add (cfg, me, member_info); + client = GNUNET_CLIENT_connect ("chat", cfg); + if (NULL == client) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + _("Failed to connect to the chat service\n")); + return NULL; + } + chat_room = GNUNET_malloc (sizeof (struct GNUNET_CHAT_Room)); + chat_room->msg_options = msg_options; + chat_room->room_name = GNUNET_strdup (room_name); + chat_room->member_info = GNUNET_CONTAINER_meta_data_duplicate (member_info); + chat_room->my_private_key = priv_key; + chat_room->message_callback = messageCallback; + chat_room->message_callback_cls = message_cls; + chat_room->member_list_callback = memberCallback; + chat_room->member_list_callback_cls = member_cls; + chat_room->confirmation_callback = confirmationCallback; + chat_room->confirmation_cls = confirmation_cls; + chat_room->cfg = cfg; + chat_room->client = client; + chat_room->members = NULL; + GNUNET_CLIENT_receive (client, + &receive_results, + chat_room, + GNUNET_TIME_UNIT_FOREVER_REL); + if (GNUNET_SYSERR == GNUNET_CHAT_rejoin_room (chat_room)) + { + GNUNET_CHAT_leave_room (chat_room); + return NULL; + } + return chat_room; +} + + +/** + * Transmit a send-message request to the chat service. + * + * @param cls closure, pointer to the 'struct GNUNET_CHAT_SendMessageContext' + * @param size number of bytes available in buf + * @param buf where the callee should write the message + * @return number of bytes written to buf + */ +static size_t +transmit_send_request (void *cls, + size_t size, + void *buf) +{ + struct GNUNET_CHAT_SendMessageContext *smc = cls; + struct TransmitRequestMessage *msg_to_send; + size_t msg_size; + + if (NULL == buf) + { +#if DEBUG_CHAT + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Could not transmit a chat message\n"); +#endif + return 0; + } +#if DEBUG_CHAT + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Transmitting a chat message to the service\n"); +#endif + msg_size = strlen (smc->message) + sizeof (struct TransmitRequestMessage); + GNUNET_assert (size >= msg_size); + msg_to_send = buf; + msg_to_send->header.size = htons (msg_size); + msg_to_send->header.type = htons (GNUNET_MESSAGE_TYPE_CHAT_TRANSMIT_REQUEST); + msg_to_send->msg_options = htonl (smc->options); + msg_to_send->sequence_number = htonl (smc->sequence_number); + msg_to_send->reserved = htonl (0); + if (NULL == smc->receiver) + memset (&msg_to_send->target, 0, sizeof (GNUNET_HashCode)); + else + GNUNET_CRYPTO_hash (smc->receiver, + sizeof (struct GNUNET_CRYPTO_RsaPublicKeyBinaryEncoded), + &msg_to_send->target); + memcpy (&msg_to_send[1], smc->message, strlen (smc->message)); + /** + * Client don't encode private messages since public keys of other members are + * stored on the service side. + */ + if (smc->options & GNUNET_CHAT_MSG_AUTHENTICATED) + { + msg_to_send->purpose.purpose = + htonl (GNUNET_SIGNATURE_PURPOSE_CHAT_MESSAGE); + msg_to_send->purpose.size = + htonl (msg_size - + sizeof (struct GNUNET_MessageHeader) - + sizeof (struct GNUNET_CRYPTO_RsaSignature)); + GNUNET_assert (GNUNET_OK == + GNUNET_CRYPTO_rsa_sign (smc->chat_room->my_private_key, + &msg_to_send->purpose, + &msg_to_send->signature)); + } + GNUNET_free (smc->message); + GNUNET_free (smc); + return msg_size; +} + + +/** + * Send a message. + * + * @param room handle for the chat room + * @param message message to be sent + * @param options options for the message + * @param receiver use NULL to send to everyone in the room + * @param sequence_number where to write the sequence id of the message + */ +void +GNUNET_CHAT_send_message (struct GNUNET_CHAT_Room *room, + const char *message, + enum GNUNET_CHAT_MsgOptions options, + const struct GNUNET_CRYPTO_RsaPublicKeyBinaryEncoded *receiver, + uint32_t *sequence_number) +{ + size_t msg_size; + struct GNUNET_CHAT_SendMessageContext *smc; + +#if DEBUG_CHAT + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Sending a message\n"); +#endif + *sequence_number = ++room->sequence_number; + smc = GNUNET_malloc (sizeof (struct GNUNET_CHAT_SendMessageContext)); + smc->chat_room = room; + smc->message = GNUNET_strdup (message); + smc->options = options; + smc->receiver = receiver; + smc->sequence_number = *sequence_number; + msg_size = strlen (message) + sizeof (struct TransmitRequestMessage); + GNUNET_CLIENT_notify_transmit_ready (room->client, + msg_size, + GNUNET_CONSTANTS_SERVICE_TIMEOUT, + GNUNET_YES, + &transmit_send_request, + smc); +} + +/* end of chat.c */ diff --git a/src/chat/chat.h b/src/chat/chat.h new file mode 100644 index 0000000000..a9cf83a8c5 --- /dev/null +++ b/src/chat/chat.h @@ -0,0 +1,458 @@ +/* + This file is part of GNUnet + (C) 2008, 2011 Christian Grothoff (and other contributing authors) + + GNUnet is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published + by the Free Software Foundation; either version 2, or (at your + option) any later version. + + GNUnet is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with GNUnet; see the file COPYING. If not, write to the + Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. +*/ + +/** + * @file chat/chat.h + * @brief support for chat + * @author Christian Grothoff + * @author Nathan Evans + * @author Vitaly Minko + */ + +#ifndef CHAT_H +#define CHAT_H + +#include "gnunet_chat_service.h" + +/** + * Constant IV since we generate a new session key per each message. + */ +#define INITVALUE "InitializationVectorValue" + + +/** + * Client-service messages + */ + +/** + * Notification sent by service to client indicating that we've received a chat + * message. After this struct, the remaining bytes are the actual text message. + * If the mesasge is private, then the text is encrypted, otherwise it's + * plaintext. + */ +struct ReceiveNotificationMessage +{ + /** + * Message type will be GNUNET_MESSAGE_TYPE_CHAT_MESSAGE_NOTIFICATION + */ + struct GNUNET_MessageHeader header; + + /** + * Message options, see GNUNET_CHAT_MsgOptions. + */ + uint32_t msg_options GNUNET_PACKED; + + /** + * Sequence number of the message (unique per sender). + */ + uint32_t sequence_number GNUNET_PACKED; + + /** + * For alignment (should be zero). + */ + uint32_t reserved GNUNET_PACKED; + + /** + * Hash of the public key of the pseudonym of the sender of the message. + * TBD: Should be all zeros for anonymous. + */ + GNUNET_HashCode sender; + + /** + * The encrypted session key. + */ + struct GNUNET_CRYPTO_RsaEncryptedData encrypted_key; + +}; + + +/** + * Request sent by client to transmit a chat message to another room members. + * After this struct, the remaining bytes are the actual message in plaintext. + * Private messages are encrypted on the service side. + */ +struct TransmitRequestMessage +{ + /** + * Message type will be GNUNET_MESSAGE_TYPE_CHAT_TRANSMIT_REQUEST + */ + struct GNUNET_MessageHeader header; + + /** + * For alignment (should be zero). + */ + uint32_t reserved GNUNET_PACKED; + + /** + * Signature confirming receipt. Signature covers everything from header + * through content. + */ + struct GNUNET_CRYPTO_RsaSignature signature; + + /** + * What is being signed and why? + */ + struct GNUNET_CRYPTO_RsaSignaturePurpose purpose; + + /** + * Desired message options, see GNUNET_CHAT_MsgOptions. + */ + uint32_t msg_options GNUNET_PACKED; + + /** + * Sequence number of the message (unique per sender). + */ + uint32_t sequence_number GNUNET_PACKED; + + /** + * Who should receive this message? Set to all zeros for "everyone". + */ + GNUNET_HashCode target; + +}; + + +/** + * Receipt sent from a message receiver to the service to confirm delivery of + * a chat message. + */ +struct ConfirmationReceiptMessage +{ + /** + * Message type will be GNUNET_MESSAGE_TYPE_CHAT_CONFIRMATION_RECEIPT + */ + struct GNUNET_MessageHeader header; + + /** + * For alignment (should be zero). + */ + uint32_t reserved GNUNET_PACKED; + + /** + * Signature confirming receipt. Signature covers everything from header + * through content. + */ + struct GNUNET_CRYPTO_RsaSignature signature; + + /** + * What is being signed and why? + */ + struct GNUNET_CRYPTO_RsaSignaturePurpose purpose; + + /** + * Sequence number of the original message. + */ + uint32_t sequence_number GNUNET_PACKED; + + /** + * For alignment (should be zero). + */ + uint32_t reserved2 GNUNET_PACKED; + + /** + * Time of receipt. + */ + struct GNUNET_TIME_AbsoluteNBO timestamp; + + /** + * Who is confirming the receipt? + */ + GNUNET_HashCode target; + + /** + * Who is the author of the chat message? + */ + GNUNET_HashCode author; + + /** + * Hash of the (possibly encrypted) content. + */ + GNUNET_HashCode content; + +}; + + +/** + * Message send from client to daemon to join a chat room. + * This struct is followed by the room name and then + * the serialized ECRS meta data describing the new member. + */ +struct JoinRequestMessage +{ + /** + * Message type will be GNUNET_MESSAGE_TYPE_CHAT_JOIN_REQUEST + */ + struct GNUNET_MessageHeader header; + + /** + * Options. Set all options that this client is willing to receive. + * For example, if the client does not want to receive anonymous or + * OTR messages but is willing to generate acknowledgements and + * receive private messages, this should be set to + * GNUNET_CHAT_MSG_PRIVATE | GNUNET_CHAT_MSG_ACKNOWLEDGED. + */ + uint32_t msg_options GNUNET_PACKED; + + /** + * Length of the room name. + */ + uint16_t room_name_len GNUNET_PACKED; + + /** + * For alignment (should be zero). + */ + uint16_t reserved GNUNET_PACKED; + uint32_t reserved2 GNUNET_PACKED; + + /** + * Public key of the joining member. + */ + struct GNUNET_CRYPTO_RsaPublicKeyBinaryEncoded public_key; + +}; + + +/** + * Message send by server to client to indicate joining of another room member. + * This struct is followed by the serialized ECRS MetaData describing the new + * member. + */ +struct JoinNotificationMessage +{ + /** + * Message type will be GNUNET_MESSAGE_TYPE_CHAT_JOIN_NOTIFICATION + */ + struct GNUNET_MessageHeader header; + + /** + * Options. Set to all options that the new user is willing to + * process. For example, if the client does not want to receive + * anonymous or OTR messages but is willing to generate + * acknowledgements and receive private messages, this should be set + * to GNUNET_CHAT_MSG_PRIVATE | GNUNET_CHAT_MSG_ACKNOWLEDGED. + */ + uint32_t msg_options GNUNET_PACKED; + + /** + * Public key of the new user. + */ + struct GNUNET_CRYPTO_RsaPublicKeyBinaryEncoded public_key; + +}; + + +/** + * Message send by server to client to indicate leaving of another room member. + */ +struct LeaveNotificationMessage +{ + /** + * Message type will be GNUNET_MESSAGE_TYPE_CHAT_LEAVE_NOTIFICATION + */ + struct GNUNET_MessageHeader header; + + /** + * Reserved (for alignment). + */ + uint32_t reserved GNUNET_PACKED; + + /** + * Who is leaving? + */ + struct GNUNET_CRYPTO_RsaPublicKeyBinaryEncoded user; + +}; + + +/** + * Peer-to-peer messages + */ + +/** + * Message send by one peer to another to indicate joining of another room + * member. This struct is followed by the room name and then the serialized + * ECRS MetaData describing the new member. + */ +struct P2PJoinNotificationMessage +{ + /** + * Message type will be GNUNET_MESSAGE_TYPE_CHAT_P2P_JOIN_NOTIFICATION + */ + struct GNUNET_MessageHeader header; + + /** + * Options. Set all options that this client is willing to receive. + * For example, if the client does not want to receive anonymous or + * OTR messages but is willing to generate acknowledgements and + * receive private messages, this should be set to + * GNUNET_CHAT_MSG_PRIVATE | GNUNET_CHAT_MSG_ACKNOWLEDGED. + */ + uint32_t msg_options GNUNET_PACKED; + + /** + * Length of the room name. + */ + uint16_t room_name_len GNUNET_PACKED; + + /** + * Reserved (should be zero). + */ + uint16_t reserved GNUNET_PACKED; + uint32_t reserved2 GNUNET_PACKED; + + /** + * Public key of the joining member. + */ + struct GNUNET_CRYPTO_RsaPublicKeyBinaryEncoded public_key; + +}; + + +/** + * Message send by one peer to another to indicate leaving of another room + * member. + */ +struct P2PLeaveNotificationMessage +{ + /** + * Message type will be GNUNET_MESSAGE_TYPE_CHAT_P2P_LEAVE_NOTIFICATION + */ + struct GNUNET_MessageHeader header; + + /** + * Reserved (for alignment). + */ + uint32_t reserved GNUNET_PACKED; + + /** + * Who is leaving? + */ + struct GNUNET_CRYPTO_RsaPublicKeyBinaryEncoded user; + +}; + + +/** + * Message send by one peer to another to indicate receiving of a chat message. + * After this struct, the remaining bytes are the actual text message. If the + * mesasge is private, then the text is encrypted, otherwise it's plaintext. + */ +struct P2PReceiveNotificationMessage +{ + /** + * Message type will be GNUNET_MESSAGE_TYPE_CHAT_P2P_MESSAGE_NOTIFICATION + */ + struct GNUNET_MessageHeader header; + + /** + * Message options, see GNUNET_CHAT_MsgOptions. + */ + uint32_t msg_options GNUNET_PACKED; + + /** + * Sequence number of the message (unique per sender). + */ + uint32_t sequence_number GNUNET_PACKED; + + /** + * Reserved (for alignment). + */ + uint32_t reserved GNUNET_PACKED; + + /** + * Hash of the public key of the pseudonym of the sender of the message + * TBD: Should be all zeros for anonymous. + */ + GNUNET_HashCode sender; + + /** + * Who should receive this message? Set to all zeros for "everyone". + */ + GNUNET_HashCode target; + + /** + * The encrypted session key. + */ + struct GNUNET_CRYPTO_RsaEncryptedData encrypted_key; + +}; + + +/** + * Receipt sent from one peer to another to confirm delivery of a chat message. + */ +struct P2PConfirmationReceiptMessage +{ + /** + * Message type will be GNUNET_MESSAGE_TYPE_CHAT_P2P_CONFIRMATION_RECEIPT + */ + struct GNUNET_MessageHeader header; + + /** + * For alignment (should be zero). + */ + uint32_t reserved GNUNET_PACKED; + + /** + * Signature confirming receipt. Signature covers everything from header + * through content. + */ + struct GNUNET_CRYPTO_RsaSignature signature; + + /** + * What is being signed and why? + */ + struct GNUNET_CRYPTO_RsaSignaturePurpose purpose; + + /** + * Sequence number of the original message. + */ + uint32_t msg_sequence_number GNUNET_PACKED; + + /** + * Sequence number of the receipt. + */ + uint32_t sequence_number GNUNET_PACKED; + + /** + * Time of receipt. + */ + struct GNUNET_TIME_AbsoluteNBO timestamp; + + /** + * Who is confirming the receipt? + */ + GNUNET_HashCode target; + + /** + * Who is the author of the chat message? + */ + GNUNET_HashCode author; + + /** + * Hash of the (possibly encrypted) content. + */ + GNUNET_HashCode content; + +}; + +#endif + +/* end of chat.h */ diff --git a/src/chat/gnunet-chat.c b/src/chat/gnunet-chat.c new file mode 100644 index 0000000000..b960db0434 --- /dev/null +++ b/src/chat/gnunet-chat.c @@ -0,0 +1,674 @@ +/* + This file is part of GNUnet. + (C) 2007, 2008, 2011 Christian Grothoff (and other contributing authors) + + GNUnet is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published + by the Free Software Foundation; either version 3, or (at your + option) any later version. + + GNUnet is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with GNUnet; see the file COPYING. If not, write to the + Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. +*/ + +/** + * @file chat/gnunet-chat.c + * @brief Minimal chat command line tool + * @author Christian Grothoff + * @author Nathan Evans + * @author Vitaly Minko + */ + +#include "platform.h" +#include "gnunet_getopt_lib.h" +#include "gnunet_program_lib.h" +#include "gnunet_chat_service.h" +#include <fcntl.h> + +static int ret; + +static const struct GNUNET_CONFIGURATION_Handle *cfg; + +static char *nickname; + +static char *room_name; + +static struct GNUNET_CONTAINER_MetaData *meta; + +static struct GNUNET_CHAT_Room *room; + +static GNUNET_SCHEDULER_TaskIdentifier handle_cmd_task = GNUNET_SCHEDULER_NO_TASK; + +struct ChatCommand +{ + const char *command; + int (*Action) (const char *arguments, const void *xtra); + const char *helptext; +}; + +struct UserList +{ + struct UserList *next; + struct GNUNET_CRYPTO_RsaPublicKeyBinaryEncoded pkey; + int ignored; +}; + +static struct UserList *users; + +static void +free_user_list () +{ + struct UserList *next; + while (NULL != users) + { + next = users->next; + GNUNET_free (users); + users = next; + } +} + +static int do_help (const char *args, const void *xtra); + + +/** + * Callback used for notification about incoming messages. + * + * @param cls closure, NULL + * @param room in which room was the message received? + * @param sender what is the ID of the sender? (maybe NULL) + * @param member_info information about the joining member + * @param message the message text + * @param options options for the message + * @return GNUNET_OK to accept the message now, GNUNET_NO to + * accept (but user is away), GNUNET_SYSERR to signal denied delivery + */ +static int +receive_cb (void *cls, + struct GNUNET_CHAT_Room *room, + const GNUNET_HashCode *sender, + const struct GNUNET_CONTAINER_MetaData *meta, + const char *message, + enum GNUNET_CHAT_MsgOptions options) +{ + char *nick; + const char *fmt; + + if (NULL != sender) + nick = GNUNET_PSEUDONYM_id_to_name (cfg, sender); + else + nick = GNUNET_strdup (_("anonymous")); + fmt = NULL; + switch (options) + { + case GNUNET_CHAT_MSG_OPTION_NONE: + case GNUNET_CHAT_MSG_ANONYMOUS: + fmt = _("`%s' said: %s\n"); + break; + case GNUNET_CHAT_MSG_PRIVATE: + fmt = _("`%s' said to you: %s\n"); + break; + case GNUNET_CHAT_MSG_PRIVATE | GNUNET_CHAT_MSG_ANONYMOUS: + fmt = _("`%s' said to you: %s\n"); + break; + case GNUNET_CHAT_MSG_AUTHENTICATED: + fmt = _("`%s' said for sure: %s\n"); + break; + case GNUNET_CHAT_MSG_PRIVATE | GNUNET_CHAT_MSG_AUTHENTICATED: + fmt = _("`%s' said to you for sure: %s\n"); + break; + case GNUNET_CHAT_MSG_ACKNOWLEDGED: + fmt = _("`%s' was confirmed that you received: %s\n"); + break; + case GNUNET_CHAT_MSG_PRIVATE | GNUNET_CHAT_MSG_ACKNOWLEDGED: + fmt = _("`%s' was confirmed that you and only you received: %s\n"); + break; + case GNUNET_CHAT_MSG_AUTHENTICATED | GNUNET_CHAT_MSG_ACKNOWLEDGED: + fmt = _("`%s' was confirmed that you received from him or her: %s\n"); + break; + case GNUNET_CHAT_MSG_AUTHENTICATED | GNUNET_CHAT_MSG_PRIVATE | GNUNET_CHAT_MSG_ACKNOWLEDGED: + fmt = + _ + ("`%s' was confirmed that you and only you received from him or her: %s\n"); + break; + case GNUNET_CHAT_MSG_OFF_THE_RECORD: + fmt = _("`%s' said off the record: %s\n"); + break; + default: + fmt = _("<%s> said using an unknown message type: %s\n"); + break; + } + fprintf (stdout, fmt, nick, message); + GNUNET_free (nick); + return GNUNET_OK; +} + + +/** + * Callback used for message delivery confirmations. + * + * @param cls closure, NULL + * @param room in which room was the message received? + * @param orig_seq_number sequence number of the original message + * @param timestamp when was the message received? + * @param receiver who is confirming the receipt? + * @param msg_hash hash of the original message + * @param receipt signature confirming delivery + * @return GNUNET_OK to continue, GNUNET_SYSERR to refuse processing further + * confirmations from anyone for this message + */ +static int +confirmation_cb (void *cls, + struct GNUNET_CHAT_Room *room, + uint32_t orig_seq_number, + struct GNUNET_TIME_Absolute timestamp, + const GNUNET_HashCode *receiver, + const GNUNET_HashCode *msg_hash, + const struct GNUNET_CRYPTO_RsaSignature *receipt) +{ + char *nick; + + nick = GNUNET_PSEUDONYM_id_to_name (cfg, receiver); + fprintf (stdout, _("'%s' acknowledged message #%d\n"), nick, orig_seq_number); + return GNUNET_OK; +} + + +/** + * Callback used for notification that another room member has joined or left. + * + * @param member_info will be non-null if the member is joining, NULL if he is + * leaving + * @param member_id hash of public key of the user (for unique identification) + * @param options what types of messages is this member willing to receive? + * @return GNUNET_OK + */ +static int +member_list_cb (void *cls, + const struct GNUNET_CONTAINER_MetaData *member_info, + const struct GNUNET_CRYPTO_RsaPublicKeyBinaryEncoded *member_id, + enum GNUNET_CHAT_MsgOptions options) +{ + char *nick; + GNUNET_HashCode id; + struct UserList *pos; + struct UserList *prev; + + GNUNET_CRYPTO_hash (member_id, + sizeof (struct GNUNET_CRYPTO_RsaPublicKeyBinaryEncoded), + &id); + nick = GNUNET_PSEUDONYM_id_to_name (cfg, &id); + fprintf (stdout, member_info != NULL + ? _("`%s' entered the room\n") : _("`%s' left the room\n"), nick); + GNUNET_free (nick); + if (NULL != member_info) + { + /* user joining */ + pos = GNUNET_malloc (sizeof (struct UserList)); + pos->next = users; + pos->pkey = *member_id; + pos->ignored = GNUNET_NO; + users = pos; + } + else + { + /* user leaving */ + prev = NULL; + pos = users; + while ((NULL != pos) && + (0 != memcmp (&pos->pkey, + member_id, + sizeof (struct GNUNET_CRYPTO_RsaPublicKeyBinaryEncoded)))) + { + prev = pos; + pos = pos->next; + } + if (NULL == pos) + { + GNUNET_break (0); + } + else + { + if (NULL == prev) + users = pos->next; + else + prev->next = pos->next; + GNUNET_free (pos); + } + } + return GNUNET_OK; +} + + +static int +do_transmit (const char *msg, const void *xtra) +{ + uint32_t seq; + GNUNET_CHAT_send_message (room, + msg, + GNUNET_CHAT_MSG_OPTION_NONE, + NULL, &seq); + return GNUNET_OK; +} + + +static int +do_join (const char *arg, const void *xtra) +{ + char *my_name; + GNUNET_HashCode me; + + if (arg[0] == '#') + arg++; /* ignore first hash */ + GNUNET_CHAT_leave_room (room); + free_user_list (); + GNUNET_free (room_name); + room_name = GNUNET_strdup (arg); + room = GNUNET_CHAT_join_room (cfg, + nickname, + meta, + room_name, + -1, + &receive_cb, NULL, + &member_list_cb, NULL, + &confirmation_cb, NULL, &me); + if (NULL == room) + { + fprintf (stdout, _("Could not change username\n")); + return GNUNET_SYSERR; + } + my_name = GNUNET_PSEUDONYM_id_to_name (cfg, &me); + fprintf (stdout, _("Joined room `%s' as user `%s'\n"), room_name, my_name); + GNUNET_free (my_name); + return GNUNET_OK; +} + + +static int +do_nick (const char *msg, const void *xtra) +{ + char *my_name; + GNUNET_HashCode me; + + GNUNET_CHAT_leave_room (room); + free_user_list (); + GNUNET_free (nickname); + GNUNET_CONTAINER_meta_data_destroy (meta); + nickname = GNUNET_strdup (msg); + meta = GNUNET_CONTAINER_meta_data_create (); + GNUNET_CONTAINER_meta_data_insert (meta, + "<gnunet>", + EXTRACTOR_METATYPE_TITLE, + EXTRACTOR_METAFORMAT_UTF8, + "text/plain", + nickname, + strlen(nickname)+1); + room = GNUNET_CHAT_join_room (cfg, + nickname, + meta, + room_name, + -1, + &receive_cb, NULL, + &member_list_cb, NULL, + &confirmation_cb, NULL, &me); + if (NULL == room) + { + fprintf (stdout, _("Could not change username\n")); + return GNUNET_SYSERR; + } + my_name = GNUNET_PSEUDONYM_id_to_name (cfg, &me); + fprintf (stdout, _("Changed username to `%s'\n"), my_name); + GNUNET_free (my_name); + return GNUNET_OK; +} + + +static int +do_names (const char *msg, const void *xtra) +{ + char *name; + struct UserList *pos; + GNUNET_HashCode pid; + + fprintf (stdout, _("Users in room `%s': "), room_name); + pos = users; + while (NULL != pos) + { + GNUNET_CRYPTO_hash (&pos->pkey, + sizeof (struct GNUNET_CRYPTO_RsaPublicKeyBinaryEncoded), + &pid); + name = GNUNET_PSEUDONYM_id_to_name (cfg, &pid); + fprintf (stdout, "`%s' ", name); + GNUNET_free (name); + pos = pos->next; + } + fprintf (stdout, "\n"); + return GNUNET_OK; +} + + +static int +do_pm (const char *msg, const void *xtra) +{ + char *user; + GNUNET_HashCode uid; + GNUNET_HashCode pid; + uint32_t seq; + struct UserList *pos; + + if (NULL == strstr (msg, " ")) + { + fprintf (stderr, _("Syntax: /msg USERNAME MESSAGE")); + return GNUNET_OK; + } + user = GNUNET_strdup (msg); + strstr (user, " ")[0] = '\0'; + msg += strlen (user) + 1; + if (GNUNET_OK != GNUNET_PSEUDONYM_name_to_id (cfg, user, &uid)) + { + fprintf (stderr, _("Unknown user `%s'\n"), user); + GNUNET_free (user); + return GNUNET_OK; + } + pos = users; + while (NULL != pos) + { + GNUNET_CRYPTO_hash (&pos->pkey, + sizeof (struct GNUNET_CRYPTO_RsaPublicKeyBinaryEncoded), + &pid); + if (0 == memcmp (&pid, &uid, sizeof (GNUNET_HashCode))) + break; + pos = pos->next; + } + if (NULL == pos) + { + fprintf (stderr, _("User `%s' is currently not in the room!\n"), user); + GNUNET_free (user); + return GNUNET_OK; + } + GNUNET_CHAT_send_message (room, + msg, + GNUNET_CHAT_MSG_PRIVATE, + &pos->pkey, + &seq); + GNUNET_free (user); + return GNUNET_OK; +} + + +static int +do_transmit_sig (const char *msg, const void *xtra) +{ + uint32_t seq; + GNUNET_CHAT_send_message (room, + msg, + GNUNET_CHAT_MSG_AUTHENTICATED, + NULL, &seq); + return GNUNET_OK; +} + + +static int +do_transmit_ack (const char *msg, const void *xtra) +{ + uint32_t seq; + GNUNET_CHAT_send_message (room, + msg, + GNUNET_CHAT_MSG_ACKNOWLEDGED, + NULL, &seq); + return GNUNET_OK; +} + + +static int +do_quit (const char *args, const void *xtra) +{ + return GNUNET_SYSERR; +} + + +static int +do_unknown (const char *msg, const void *xtra) +{ + fprintf (stderr, _("Unknown command `%s'\n"), msg); + return GNUNET_OK; +} + + +/** + * List of supported IRC commands. The order matters! + */ +static struct ChatCommand commands[] = { + {"/join ", &do_join, + gettext_noop + ("Use `/join #roomname' to join a chat room. Joining a room will cause you" + " to leave the current room")}, + {"/nick ", &do_nick, + gettext_noop + ("Use `/nick nickname' to change your nickname. This will cause you to" + " leave the current room and immediately rejoin it with the new name.")}, + {"/msg ", &do_pm, + gettext_noop + ("Use `/msg nickname message' to send a private message to the specified" + " user")}, + {"/notice ", &do_pm, + gettext_noop ("The `/notice' command is an alias for `/msg'")}, + {"/query ", &do_pm, + gettext_noop ("The `/query' command is an alias for `/msg'")}, + {"/sig ", &do_transmit_sig, + gettext_noop ("Use `/sig message' to send a signed public message")}, + {"/ack ", &do_transmit_ack, + gettext_noop + ("Use `/ack message' to require signed acknowledgment of the message")}, + {"/quit", &do_quit, + gettext_noop ("Use `/quit' to terminate gnunet-chat")}, + {"/leave", &do_quit, + gettext_noop ("The `/leave' command is an alias for `/quit'")}, + {"/names", &do_names, + gettext_noop + ("Use `/names' to list all of the current members in the chat room")}, + {"/help", &do_help, + gettext_noop ("Use `/help command' to get help for a specific command")}, + /* Add standard commands: + /whois (print metadata), + /ignore (set flag, check on receive!) */ + /* Add special commands (currently supported): + + anonymous msgs + + authenticated msgs + */ + /* the following three commands must be last! */ + {"/", &do_unknown, NULL}, + {"", &do_transmit, NULL}, + {NULL, NULL, NULL}, +}; + + +static int +do_help (const char *args, const void *xtra) +{ + int i; + i = 0; + while ((NULL != args) && + (0 != strlen (args)) && (commands[i].Action != &do_help)) + { + if (0 == + strncasecmp (&args[1], &commands[i].command[1], strlen (args) - 1)) + { + fprintf (stdout, "%s\n", gettext (commands[i].helptext)); + return GNUNET_OK; + } + i++; + } + i = 0; + fprintf (stdout, "Available commands:"); + while (commands[i].Action != &do_help) + { + fprintf (stdout, " %s", gettext (commands[i].command)); + i++; + } + fprintf (stdout, "\n"); + fprintf (stdout, "%s\n", gettext (commands[i].helptext)); + return GNUNET_OK; +} + + +static void +do_stop_task (void *cls, + const struct GNUNET_SCHEDULER_TaskContext *tc) +{ + GNUNET_CHAT_leave_room (room); + if (handle_cmd_task != GNUNET_SCHEDULER_NO_TASK) + { + GNUNET_SCHEDULER_cancel (handle_cmd_task); + handle_cmd_task = GNUNET_SCHEDULER_NO_TASK; + } + free_user_list (); + GNUNET_CONTAINER_meta_data_destroy (meta); + GNUNET_free (room_name); + GNUNET_free (nickname); +} + + +void +handle_command (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc) +{ + char message[MAX_MESSAGE_LENGTH + 1]; + int i; + + /* read message from command line and handle it */ + memset (message, 0, MAX_MESSAGE_LENGTH + 1); + if (NULL == fgets (message, MAX_MESSAGE_LENGTH, stdin)) + goto next; + if (strlen (message) == 0) + goto next; + if (message[strlen (message) - 1] == '\n') + message[strlen (message) - 1] = '\0'; + if (strlen (message) == 0) + goto next; + i = 0; + while ((NULL != commands[i].command) && + (0 != strncasecmp (commands[i].command, + message, strlen (commands[i].command)))) + i++; + if (GNUNET_OK != + commands[i].Action (&message[strlen (commands[i].command)], NULL)) + goto out; + +next: + handle_cmd_task = + GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MILLISECONDS, + 100), + &handle_command, + NULL); + return; + +out: + handle_cmd_task = GNUNET_SCHEDULER_NO_TASK; + GNUNET_SCHEDULER_shutdown (); +} + + +/** + * Main function that will be run by the scheduler. + * + * @param cls closure, NULL + * @param args remaining command-line arguments + * @param cfgfile name of the configuration file used (for saving, can be NULL!) + * @param cfg configuration + */ +static void +run (void *cls, + char *const *args, + const char *cfgfile, + const struct GNUNET_CONFIGURATION_Handle *c) +{ + GNUNET_HashCode me; + char *my_name; + + cfg = c; + /* check arguments */ + if (NULL == nickname) + { + fprintf (stderr, _("You must specify a nickname\n")); + ret = -1; + return; + } + if (NULL == room_name) + room_name = GNUNET_strdup ("gnunet"); + meta = GNUNET_CONTAINER_meta_data_create (); + GNUNET_CONTAINER_meta_data_insert (meta, + "<gnunet>", + EXTRACTOR_METATYPE_TITLE, + EXTRACTOR_METAFORMAT_UTF8, + "text/plain", + nickname, + strlen(nickname)+1); + room = GNUNET_CHAT_join_room (cfg, + nickname, + meta, + room_name, + -1, + &receive_cb, NULL, + &member_list_cb, NULL, + &confirmation_cb, NULL, &me); + if (NULL == room) + { + fprintf (stderr, _("Failed to join room `%s'\n"), room_name); + GNUNET_free (room_name); + GNUNET_free (nickname); + GNUNET_CONTAINER_meta_data_destroy (meta); + ret = -1; + return; + } + my_name = GNUNET_PSEUDONYM_id_to_name (cfg, &me); + fprintf (stdout, _("Joined room `%s' as user `%s'\n"), room_name, my_name); + GNUNET_free (my_name); + handle_cmd_task = + GNUNET_SCHEDULER_add_with_priority (GNUNET_SCHEDULER_PRIORITY_UI, + &handle_command, + NULL); + GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_UNIT_FOREVER_REL, + &do_stop_task, + NULL); +} + + +/** + * The main function to chat via GNUnet. + * + * @param argc number of arguments from the command line + * @param argv command line arguments + * @return 0 ok, 1 on error + */ +int +main (int argc, char *const *argv) +{ + int flags; + static const struct GNUNET_GETOPT_CommandLineOption options[] = { + {'n', "nick", "NAME", + gettext_noop ("set the nickname to use (required)"), + 1, &GNUNET_GETOPT_set_string, &nickname}, + {'r', "room", "NAME", + gettext_noop ("set the chat room to join"), + 1, &GNUNET_GETOPT_set_string, &room_name}, + GNUNET_GETOPT_OPTION_END + }; + + flags = fcntl (0, F_GETFL, 0); + flags |= O_NONBLOCK; + fcntl (0, F_SETFL, flags); + return (GNUNET_OK == + GNUNET_PROGRAM_run (argc, + argv, + "gnunet-chat", + gettext_noop ("Join a chat on GNUnet."), + options, &run, NULL)) ? ret : 1; +} + +/* end of gnunet-chat.c */ diff --git a/src/chat/gnunet-service-chat.c b/src/chat/gnunet-service-chat.c new file mode 100644 index 0000000000..4cf8695957 --- /dev/null +++ b/src/chat/gnunet-service-chat.c @@ -0,0 +1,1565 @@ +/* + This file is part of GNUnet. + (C) 2009, 2011 Christian Grothoff (and other contributing authors) + + GNUnet is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published + by the Free Software Foundation; either version 3, or (at your + option) any later version. + + GNUnet is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with GNUnet; see the file COPYING. If not, write to the + Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. +*/ + +/** + * @file chat/gnunet-service-caht.c + * @brief program that tracks template + * @author Christian Grothoff + * @author Vitaly Minko + */ + +#include "platform.h" +#include "gnunet_core_service.h" +#include "gnunet_crypto_lib.h" +#include "gnunet_protocols.h" +#include "gnunet_service_lib.h" +#include "gnunet_signatures.h" +#include "chat.h" + +#define DEBUG_CHAT_SERVICE GNUNET_YES +#define MAX_TRANSMIT_DELAY GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS, 60) +#define QUEUE_SIZE 16 + + +/** + * Linked list of our current clients. + */ +struct ChatClient +{ + struct ChatClient *next; + + /** + * Handle for a chat client (NULL for external clients). + */ + struct GNUNET_SERVER_Client *client; + + /** + * Public key of the client. + */ + struct GNUNET_CRYPTO_RsaPublicKeyBinaryEncoded public_key; + + /** + * Name of the room which the client is in. + */ + char *room; + + /** + * Serialized metadata of the client. + */ + char *member_info; + + /** + * Hash of the public key (for convenience). + */ + GNUNET_HashCode id; + + /** + * Options which the client is willing to receive. + */ + uint32_t msg_options; + + /** + * Length of serialized metadata in member_info. + */ + uint16_t meta_len; + + /** + * Sequence number of the last message sent by the client. + */ + uint32_t msg_sequence_number; + + /** + * Sequence number of the last receipt sent by the client. + * Used to discard already processed receipts. + */ + uint32_t rcpt_sequence_number; + +}; + + +/** + * Handle to the core service (NULL until we've connected to it). + */ +static struct GNUNET_CORE_Handle *core; + +/** + * Our configuration. + */ +static const struct GNUNET_CONFIGURATION_Handle *cfg; + +/** + * The identity of this host. + */ +static const struct GNUNET_PeerIdentity *me; + +/** + * Head of the list of current clients. + */ +static struct ChatClient *client_list_head = NULL; + +/** + * Notification context containing all connected clients. + */ +struct GNUNET_SERVER_NotificationContext *nc = NULL; + + +/** + * Transmit a message notification to the peer. + * + * @param cls closure, pointer to the 'struct P2PReceiveNotificationMessage' + * @param size number of bytes available in buf + * @param buf where the callee should write the message + * @return number of bytes written to buf + */ +static size_t +transmit_message_notification_to_peer (void *cls, + size_t size, + void *buf) +{ + struct P2PReceiveNotificationMessage *my_msg = cls; + struct P2PReceiveNotificationMessage *m = buf; + size_t msg_size; + +#if DEBUG_CHAT_SERVICE + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Transmitting P2P message notification\n"); +#endif + msg_size = ntohs (my_msg->header.size); + GNUNET_assert (size >= msg_size); + GNUNET_assert (NULL != buf); + memcpy (m, my_msg, msg_size); + GNUNET_free (my_msg); + return msg_size; +} + + +/** + * Ask to send a message notification to the peer. + */ +static void +send_message_noficiation (void *cls, + const struct GNUNET_PeerIdentity *peer, + const struct GNUNET_TRANSPORT_ATS_Information *atsi) +{ + struct P2PReceiveNotificationMessage *msg = cls; + struct P2PReceiveNotificationMessage *my_msg; + struct GNUNET_CORE_TransmitHandle *th; + + if (NULL == peer) + GNUNET_free (msg); + else + { +#if DEBUG_CHAT_SERVICE + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Sending message notification to `%s'\n", GNUNET_i2s (peer)); +#endif + my_msg = GNUNET_memdup (msg, ntohs (msg->header.size)); + th = GNUNET_CORE_notify_transmit_ready (core, + 1, + MAX_TRANSMIT_DELAY, + peer, + ntohs (msg->header.size), + &transmit_message_notification_to_peer, + my_msg); + GNUNET_assert (NULL != th); + } +} + + +/** + * A client sent a chat message. Encrypt the message text if the message is + * private. Send the message to local room members and to all connected peers. + * + * @param cls closure, NULL + * @param client identification of the client + * @param message the actual message + */ +static void +handle_transmit_request (void *cls, + struct GNUNET_SERVER_Client *client, + const struct GNUNET_MessageHeader *message) +{ + static GNUNET_HashCode all_zeros; + const struct TransmitRequestMessage *trmsg; + struct ReceiveNotificationMessage *rnmsg; + struct P2PReceiveNotificationMessage *p2p_rnmsg; + struct ChatClient *pos; + struct ChatClient *target; + struct GNUNET_CRYPTO_AesSessionKey key; + char encrypted_msg[MAX_MESSAGE_LENGTH]; + const char *room; + int msg_len; + int priv_msg; + + GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Client sent a chat message\n"); + if (ntohs (message->size) <= sizeof (struct TransmitRequestMessage)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Malformed message: wrong size\n"); + GNUNET_break (0); + GNUNET_SERVER_receive_done (client, GNUNET_SYSERR); + return; + } + trmsg = (const struct TransmitRequestMessage *) message; + msg_len = ntohs (trmsg->header.size) - sizeof (struct TransmitRequestMessage); + priv_msg = (ntohl (trmsg->msg_options) & GNUNET_CHAT_MSG_PRIVATE) != 0; + if (priv_msg) + { +#if DEBUG_CHAT_SERVICE + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Encrypting the message text\n"); +#endif + GNUNET_CRYPTO_aes_create_session_key (&key); + msg_len = GNUNET_CRYPTO_aes_encrypt (&trmsg[1], + msg_len, + &key, + (const struct GNUNET_CRYPTO_AesInitializationVector *) INITVALUE, + encrypted_msg); + if (-1 == msg_len) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Could not encrypt the message text\n"); + GNUNET_break (0); + GNUNET_SERVER_receive_done (client, GNUNET_SYSERR); + return; + } + } + rnmsg = GNUNET_malloc (sizeof (struct ReceiveNotificationMessage) + msg_len); + rnmsg->header.size = htons (sizeof (struct ReceiveNotificationMessage) + + msg_len); + rnmsg->header.type = htons (GNUNET_MESSAGE_TYPE_CHAT_MESSAGE_NOTIFICATION); + rnmsg->msg_options = trmsg->msg_options; + rnmsg->sequence_number = trmsg->sequence_number; + pos = client_list_head; + while ((NULL != pos) && (pos->client != client)) + pos = pos->next; + if (NULL == pos) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "The client is not a member of a chat room. Client has to " + "join a chat room first\n"); + GNUNET_break (0); + GNUNET_SERVER_receive_done (client, GNUNET_SYSERR); + GNUNET_free (rnmsg); + return; + } + room = pos->room; + pos->msg_sequence_number = ntohl (trmsg->sequence_number); + if (0 == (ntohl (trmsg->msg_options) & GNUNET_CHAT_MSG_ANONYMOUS)) + rnmsg->sender = pos->id; + else + memset (&rnmsg->sender, 0, sizeof (GNUNET_HashCode)); + if (priv_msg) + { +#if DEBUG_CHAT_SERVICE + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Encrypting the session key using the public key of '%s'\n", + GNUNET_h2s (&trmsg->target)); +#endif + if (0 == memcmp (&all_zeros, &trmsg->target, sizeof (GNUNET_HashCode))) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Malformed message: private, but no target\n"); + GNUNET_break (0); + GNUNET_SERVER_receive_done (client, GNUNET_SYSERR); + GNUNET_free (rnmsg); + return; + } + memcpy (&rnmsg[1], encrypted_msg, msg_len); + target = client_list_head; + while ((NULL != target) && + (0 != memcmp (&target->id, + &trmsg->target, + sizeof (GNUNET_HashCode)))) + target = target->next; + if (NULL == target) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unknown target of the private message\n"); + GNUNET_break (0); + GNUNET_SERVER_receive_done (client, GNUNET_SYSERR); + GNUNET_free (rnmsg); + return; + } + if (GNUNET_SYSERR == GNUNET_CRYPTO_rsa_encrypt (&key, + sizeof (struct GNUNET_CRYPTO_AesSessionKey), + &target->public_key, + &rnmsg->encrypted_key)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Could not encrypt the session key\n"); + GNUNET_break (0); + GNUNET_SERVER_receive_done (client, GNUNET_SYSERR); + GNUNET_free (rnmsg); + return; + } + } + else + { + memcpy (&rnmsg[1], &trmsg[1], msg_len); + } + pos = client_list_head; +#if DEBUG_CHAT_SERVICE + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Sending message to local room members\n"); +#endif + while (NULL != pos) + { + if ((0 == strcmp (room, pos->room)) && + (NULL != pos->client) && + (pos->client != client)) + { + if (((!priv_msg) || + (0 == memcmp (&trmsg->target, + &pos->id, + sizeof (GNUNET_HashCode)))) && + (0 == (ntohl (trmsg->msg_options) & (~pos->msg_options)))) + { + GNUNET_SERVER_notification_context_unicast (nc, + pos->client, + &rnmsg->header, + GNUNET_NO); + } + } + pos = pos->next; + } +#if DEBUG_CHAT_SERVICE + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Broadcasting message to neighbour peers\n"); +#endif + p2p_rnmsg = GNUNET_malloc (sizeof (struct P2PReceiveNotificationMessage) + + msg_len); + p2p_rnmsg->header.size = htons (sizeof (struct P2PReceiveNotificationMessage) + + msg_len); + p2p_rnmsg->header.type = htons (GNUNET_MESSAGE_TYPE_CHAT_P2P_MESSAGE_NOTIFICATION); + p2p_rnmsg->msg_options = trmsg->msg_options; + p2p_rnmsg->sequence_number = trmsg->sequence_number; + memcpy (&p2p_rnmsg->sender, &rnmsg->sender, sizeof (GNUNET_HashCode)); + p2p_rnmsg->target = trmsg->target; + if (priv_msg) + { + memcpy (&p2p_rnmsg[1], encrypted_msg, msg_len); + memcpy (&p2p_rnmsg->encrypted_key, + &rnmsg->encrypted_key, + sizeof (struct GNUNET_CRYPTO_RsaEncryptedData)); + } + else + { + memcpy (&p2p_rnmsg[1], &trmsg[1], msg_len); + } + GNUNET_CORE_iterate_peers (cfg, + &send_message_noficiation, + p2p_rnmsg); + GNUNET_SERVER_receive_done (client, GNUNET_OK); + GNUNET_free (rnmsg); +} + + +/** + * Transmit a join notification to the peer. + * + * @param cls closure, pointer to the 'struct ChatClient' + * @param size number of bytes available in buf + * @param buf where the callee should write the message + * @return number of bytes written to buf + */ +static size_t +transmit_join_notification_to_peer (void *cls, + size_t size, + void *buf) +{ + struct ChatClient *entry = cls; + struct P2PJoinNotificationMessage *m = buf; + size_t room_len; + size_t meta_len; + size_t msg_size; + char *roomptr; + +#if DEBUG_CHAT_SERVICE + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Transmitting P2P join notification\n"); +#endif + room_len = strlen (entry->room); + meta_len = entry->meta_len; + msg_size = sizeof (struct P2PJoinNotificationMessage) + meta_len + room_len; + GNUNET_assert (size >= msg_size); + GNUNET_assert (NULL != buf); + m = buf; + m->header.type = htons (GNUNET_MESSAGE_TYPE_CHAT_P2P_JOIN_NOTIFICATION); + m->header.size = htons (msg_size); + m->msg_options = htonl (entry->msg_options); + m->room_name_len = htons (room_len); + m->reserved = htons (0); + m->public_key = entry->public_key; + roomptr = (char *) &m[1]; + memcpy (roomptr, entry->room, room_len); + if (meta_len > 0) + memcpy (&roomptr[room_len], entry->member_info, meta_len); + return msg_size; +} + + +/** + * Ask to send a join notification to the peer. + */ +static void +send_join_noficiation (void *cls, + const struct GNUNET_PeerIdentity *peer, + const struct GNUNET_TRANSPORT_ATS_Information *atsi) +{ + struct ChatClient *entry = cls; + struct GNUNET_CORE_TransmitHandle *th; + size_t msg_size; + + if (NULL != peer) + { +#if DEBUG_CHAT_SERVICE + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Sending join notification to `%s'\n", GNUNET_i2s (peer)); +#endif + msg_size = sizeof (struct P2PJoinNotificationMessage) + + strlen (entry->room) + + entry->meta_len; + th = GNUNET_CORE_notify_transmit_ready (core, + 1, + MAX_TRANSMIT_DELAY, + peer, + msg_size, + &transmit_join_notification_to_peer, + entry); + GNUNET_assert (NULL != th); + } +} + + +/** + * A client asked for entering a chat room. Add the new member to the list of + * clients and notify remaining room members. + * + * @param cls closure, NULL + * @param client identification of the client + * @param message the actual message + */ +static void +handle_join_request (void *cls, + struct GNUNET_SERVER_Client *client, + const struct GNUNET_MessageHeader *message) +{ + const struct JoinRequestMessage *jrmsg; + char *room_name; + const char *roomptr; + uint16_t header_size; + uint16_t meta_len; + uint16_t room_name_len; + struct ChatClient *new_entry; + struct ChatClient *entry; + struct JoinNotificationMessage *jnmsg; + struct JoinNotificationMessage *entry_jnmsg; + + GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Client sent a join request\n"); + if (ntohs (message->size) <= sizeof (struct JoinRequestMessage)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Malformed message: wrong size\n"); + GNUNET_break (0); + GNUNET_SERVER_receive_done (client, GNUNET_SYSERR); + return; + } + jrmsg = (const struct JoinRequestMessage *) message; + header_size = ntohs (jrmsg->header.size); + room_name_len = ntohs (jrmsg->room_name_len); + if (header_size - sizeof (struct JoinRequestMessage) <= + room_name_len) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Malformed message: wrong length of the room name\n"); + GNUNET_break (0); + GNUNET_SERVER_receive_done (client, GNUNET_SYSERR); + return; + } + meta_len = + header_size - sizeof (struct JoinRequestMessage) - room_name_len; + roomptr = (const char *) &jrmsg[1]; + room_name = GNUNET_malloc (room_name_len + 1); + memcpy (room_name, roomptr, room_name_len); + room_name[room_name_len] = '\0'; + new_entry = GNUNET_malloc (sizeof (struct ChatClient)); + memset (new_entry, 0, sizeof (struct ChatClient)); + new_entry->client = client; + new_entry->room = room_name; + new_entry->public_key = jrmsg->public_key; + new_entry->meta_len = meta_len; + if (meta_len > 0) + { + new_entry->member_info = GNUNET_malloc (meta_len); + memcpy (new_entry->member_info, &roomptr[room_name_len], meta_len); + } + else + new_entry->member_info = NULL; + GNUNET_CRYPTO_hash (&new_entry->public_key, + sizeof (struct GNUNET_CRYPTO_RsaPublicKeyBinaryEncoded), + &new_entry->id); + new_entry->msg_options = ntohl (jrmsg->msg_options); + new_entry->next = client_list_head; + client_list_head = new_entry; +#if DEBUG_CHAT_SERVICE + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Synchronizing room members between local clients\n"); +#endif + jnmsg = GNUNET_malloc (sizeof (struct JoinNotificationMessage) + meta_len); + jnmsg->header.type = htons (GNUNET_MESSAGE_TYPE_CHAT_JOIN_NOTIFICATION); + jnmsg->header.size = + htons (sizeof (struct JoinNotificationMessage) + meta_len); + jnmsg->msg_options = jrmsg->msg_options; + jnmsg->public_key = new_entry->public_key; + memcpy (&jnmsg[1], &roomptr[room_name_len], meta_len); + GNUNET_SERVER_notification_context_add (nc, client); + entry = client_list_head; + while (NULL != entry) + { + if (0 == strcmp (room_name, entry->room)) + { + if (NULL != entry->client) + GNUNET_SERVER_notification_context_unicast (nc, + entry->client, + &jnmsg->header, + GNUNET_NO); + if (entry->client != client) + { + entry_jnmsg = + GNUNET_malloc (sizeof (struct JoinNotificationMessage) + + entry->meta_len); + entry_jnmsg->header.type = + htons (GNUNET_MESSAGE_TYPE_CHAT_JOIN_NOTIFICATION); + entry_jnmsg->header.size = + htons (sizeof (struct JoinNotificationMessage) + + entry->meta_len); + entry_jnmsg->msg_options = entry->msg_options; + entry_jnmsg->public_key = entry->public_key; + memcpy (&entry_jnmsg[1], entry->member_info, entry->meta_len); + GNUNET_SERVER_notification_context_unicast (nc, + client, + &entry_jnmsg->header, + GNUNET_NO); + GNUNET_free (entry_jnmsg); + } + } + entry = entry->next; + } +#if DEBUG_CHAT_SERVICE + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Broadcasting join notification to neighbour peers\n"); +#endif + GNUNET_CORE_iterate_peers (cfg, + &send_join_noficiation, + new_entry); + GNUNET_SERVER_receive_done (client, GNUNET_OK); + GNUNET_free (jnmsg); +} + +/** + * Transmit a confirmation receipt to the peer. + * + * @param cls closure, pointer to the 'struct P2PConfirmationReceiptMessage' + * @param size number of bytes available in buf + * @param buf where the callee should write the message + * @return number of bytes written to buf + */ +static size_t +transmit_confirmation_receipt_to_peer (void *cls, + size_t size, + void *buf) +{ + struct P2PConfirmationReceiptMessage *receipt = cls; + size_t msg_size; + +#if DEBUG_CHAT_SERVICE + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Transmitting P2P confirmation receipt to '%s'\n", + GNUNET_h2s (&receipt->target)); +#endif + msg_size = sizeof (struct P2PConfirmationReceiptMessage); + GNUNET_assert (size >= msg_size); + GNUNET_assert (NULL != buf); + memcpy (buf, receipt, msg_size); + GNUNET_free (receipt); + return msg_size; +} + + +/** + * Ask to send a confirmation receipt to the peer. + */ +static void +send_confirmation_receipt (void *cls, + const struct GNUNET_PeerIdentity *peer, + const struct GNUNET_TRANSPORT_ATS_Information *atsi) +{ + struct P2PConfirmationReceiptMessage *receipt = cls; + struct P2PConfirmationReceiptMessage *my_receipt; + struct GNUNET_CORE_TransmitHandle *th; + size_t msg_size; + + if (NULL == peer) + GNUNET_free (receipt); + else + { +#if DEBUG_CHAT_SERVICE + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Sending confirmation receipt to `%s'\n", GNUNET_i2s (peer)); +#endif + msg_size = sizeof (struct P2PConfirmationReceiptMessage); + my_receipt = GNUNET_memdup (receipt, + sizeof (struct P2PConfirmationReceiptMessage)); + th = GNUNET_CORE_notify_transmit_ready (core, + 1, + MAX_TRANSMIT_DELAY, + peer, + msg_size, + &transmit_confirmation_receipt_to_peer, + my_receipt); + GNUNET_assert (NULL != th); + } +} + + +/** + * A client sent a confirmation receipt. Broadcast the receipt to all connected + * peers if the author of the original message is a local client. Otherwise + * check the signature and notify the user if the signature is valid. + * + * @param cls closure, NULL + * @param client identification of the client + * @param message the actual message + */ +static void +handle_acknowledge_request (void *cls, + struct GNUNET_SERVER_Client *client, + const struct GNUNET_MessageHeader *message) +{ + const struct ConfirmationReceiptMessage *receipt; + struct ConfirmationReceiptMessage *crmsg; + struct P2PConfirmationReceiptMessage *p2p_crmsg; + struct ChatClient *target; + struct ChatClient *author; + + GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Client sent a confirmation receipt\n"); + receipt = (const struct ConfirmationReceiptMessage *) message; + author = client_list_head; + while ((NULL != author) && + (0 != memcmp (&receipt->author, + &author->id, + sizeof (GNUNET_HashCode)))) + author = author->next; + if (NULL == author) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unknown author of the original message\n"); + GNUNET_break (0); + GNUNET_SERVER_receive_done (client, GNUNET_SYSERR); + return; + } + target = client_list_head; + while ((NULL != target) && + (0 != memcmp (&receipt->target, + &target->id, + sizeof (GNUNET_HashCode)))) + target = target->next; + if (NULL == target) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unknown target of the confirmation receipt\n"); + GNUNET_break (0); + GNUNET_SERVER_receive_done (client, GNUNET_SYSERR); + return; + } + if (NULL == author->client) + { + target->rcpt_sequence_number++; +#if DEBUG_CHAT_SERVICE + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Broadcasting %s's receipt #%u to neighbour peers\n", + GNUNET_h2s (&target->id), target->rcpt_sequence_number); +#endif + p2p_crmsg = GNUNET_malloc (sizeof (struct P2PConfirmationReceiptMessage)); + p2p_crmsg->header.size = htons (sizeof (struct P2PConfirmationReceiptMessage)); + p2p_crmsg->header.type = htons (GNUNET_MESSAGE_TYPE_CHAT_P2P_CONFIRMATION_RECEIPT); + p2p_crmsg->signature = receipt->signature; + p2p_crmsg->purpose = receipt->purpose; + p2p_crmsg->msg_sequence_number = receipt->sequence_number; + p2p_crmsg->timestamp = receipt->timestamp; + p2p_crmsg->target = receipt->target; + p2p_crmsg->author = receipt->author; + p2p_crmsg->content = receipt->content; + p2p_crmsg->sequence_number = htonl (target->rcpt_sequence_number); + GNUNET_CORE_iterate_peers (cfg, + &send_confirmation_receipt, + p2p_crmsg); + } + else + { +#if DEBUG_CHAT_SERVICE + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Verifying signature of the receipt\n"); +#endif + if (GNUNET_OK != + GNUNET_CRYPTO_rsa_verify (GNUNET_SIGNATURE_PURPOSE_CHAT_RECEIPT, + &receipt->purpose, + &receipt->signature, + &target->public_key)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Invalid signature of the receipt\n"); + GNUNET_break (0); + GNUNET_SERVER_receive_done (client, GNUNET_SYSERR); + return; + } +#if DEBUG_CHAT_SERVICE + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Sending receipt to the client which sent the original message\n"); +#endif + crmsg = GNUNET_memdup (receipt, sizeof (struct ConfirmationReceiptMessage)); + crmsg->header.type = htons (GNUNET_MESSAGE_TYPE_CHAT_CONFIRMATION_NOTIFICATION); + GNUNET_SERVER_notification_context_unicast (nc, + author->client, + &crmsg->header, + GNUNET_NO); + GNUNET_free (crmsg); + } + GNUNET_SERVER_receive_done (client, GNUNET_OK); +} + + +/** + * Transmit a leave notification to the peer. + * + * @param cls closure, pointer to the + * 'struct GNUNET_CRYPTO_RsaPublicKeyBinaryEncoded' + * @param size number of bytes available in buf + * @param buf where the callee should write the message + * @return number of bytes written to buf + */ +static size_t +transmit_leave_notification_to_peer (void *cls, + size_t size, + void *buf) +{ + struct GNUNET_CRYPTO_RsaPublicKeyBinaryEncoded *public_key = cls; + struct P2PLeaveNotificationMessage *m = buf; + size_t msg_size; + +#if DEBUG_CHAT_SERVICE + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Transmitting P2P leave notification\n"); +#endif + msg_size = sizeof (struct P2PLeaveNotificationMessage); + GNUNET_assert (size >= msg_size); + GNUNET_assert (NULL != buf); + m = buf; + m->header.type = htons (GNUNET_MESSAGE_TYPE_CHAT_P2P_LEAVE_NOTIFICATION); + m->header.size = htons (msg_size); + m->reserved = htons (0); + m->user = *public_key; + GNUNET_free (public_key); + return msg_size; +} + + +/** + * Ask to send a leave notification to the peer. + */ +static void +send_leave_noficiation (void *cls, + const struct GNUNET_PeerIdentity *peer, + const struct GNUNET_TRANSPORT_ATS_Information *atsi) +{ + struct ChatClient *entry = cls; + struct GNUNET_CORE_TransmitHandle *th; + struct GNUNET_CRYPTO_RsaPublicKeyBinaryEncoded *public_key; + size_t msg_size; + + if (NULL == peer) + { + GNUNET_free (entry->room); + GNUNET_free_non_null (entry->member_info); + GNUNET_free (entry); + } + else + { +#if DEBUG_CHAT_SERVICE + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Sending leave notification to `%s'\n", GNUNET_i2s (peer)); +#endif + msg_size = sizeof (struct P2PLeaveNotificationMessage); + public_key = GNUNET_memdup (&entry->public_key, + sizeof (struct GNUNET_CRYPTO_RsaPublicKeyBinaryEncoded)); + th = GNUNET_CORE_notify_transmit_ready (core, + 1, + MAX_TRANSMIT_DELAY, + peer, + msg_size, + &transmit_leave_notification_to_peer, + public_key); + GNUNET_assert (NULL != th); + } +} + + +/** + * A client disconnected. Remove all of its data structure entries and notify + * remaining room members. + * + * @param cls closure, NULL + * @param client identification of the client + */ +static void +handle_client_disconnect (void *cls, + struct GNUNET_SERVER_Client *client) +{ + struct ChatClient *entry; + struct ChatClient *pos; + struct ChatClient *prev; + struct LeaveNotificationMessage lnmsg; + + GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Client disconnected\n"); + pos = client_list_head; + prev = NULL; + while ((NULL != pos) && (pos->client != client)) + { + prev = pos; + pos = pos->next; + } + if (NULL == pos) + { +#if DEBUG_CHAT_SERVICE + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "No such client. There is nothing to do\n"); +#endif + return; + } + if (NULL == prev) + client_list_head = pos->next; + else + prev->next = pos->next; + entry = client_list_head; +#if DEBUG_CHAT_SERVICE + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Notifying local room members that the client has disconnected\n"); +#endif + lnmsg.header.size = htons (sizeof (struct LeaveNotificationMessage)); + lnmsg.header.type = htons (GNUNET_MESSAGE_TYPE_CHAT_LEAVE_NOTIFICATION); + lnmsg.reserved = htonl (0); + lnmsg.user = pos->public_key; + while (NULL != entry) + { + if ((0 == strcmp (pos->room, entry->room)) && + (NULL != entry->client)) + { + GNUNET_SERVER_notification_context_unicast (nc, + entry->client, + &lnmsg.header, + GNUNET_NO); + } + entry = entry->next; + } +#if DEBUG_CHAT_SERVICE + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Broadcasting leave notification to neighbour peers\n"); +#endif + GNUNET_CORE_iterate_peers (cfg, + &send_leave_noficiation, + pos); +} + + +/** + * Handle P2P join notification. + * + * @param cls closure, always NULL + * @param other the other peer involved + * @param message the actual message + * @param atsi performance information + * @return GNUNET_OK to keep the connection open, + * GNUNET_SYSERR to close it (signal serious error) + */ +static int +handle_p2p_join_notification (void *cls, + const struct GNUNET_PeerIdentity *other, + const struct GNUNET_MessageHeader *message, + const struct GNUNET_TRANSPORT_ATS_Information *atsi) +{ + const struct P2PJoinNotificationMessage *p2p_jnmsg; + char *room_name; + const char *roomptr; + uint16_t header_size; + uint16_t meta_len; + uint16_t room_name_len; + struct ChatClient *new_entry; + struct ChatClient *entry; + struct JoinNotificationMessage *jnmsg; + GNUNET_HashCode id; + + GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Got P2P join notification\n"); + if (ntohs (message->size) <= sizeof (struct P2PJoinNotificationMessage)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Malformed message: wrong size\n"); + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + p2p_jnmsg = (const struct P2PJoinNotificationMessage *) message; + header_size = ntohs (p2p_jnmsg->header.size); + room_name_len = ntohs (p2p_jnmsg->room_name_len); + if (header_size - sizeof (struct P2PJoinNotificationMessage) <= + room_name_len) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Malformed message: wrong length of the room name\n"); + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + GNUNET_CRYPTO_hash (&p2p_jnmsg->public_key, + sizeof (struct GNUNET_CRYPTO_RsaPublicKeyBinaryEncoded), + &id); + entry = client_list_head; + while (NULL != entry) + { + if (0 == memcmp (&entry->id, &id, sizeof (GNUNET_HashCode))) + { +#if DEBUG_CHAT_SERVICE + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "The client has already joined. There is nothing to do\n"); +#endif + return GNUNET_OK; + } + entry = entry->next; + } + meta_len = + header_size - sizeof (struct P2PJoinNotificationMessage) - room_name_len; + roomptr = (const char *) &p2p_jnmsg[1]; + room_name = GNUNET_malloc (room_name_len + 1); + memcpy (room_name, roomptr, room_name_len); + room_name[room_name_len] = '\0'; + new_entry = GNUNET_malloc (sizeof (struct ChatClient)); + memset (new_entry, 0, sizeof (struct ChatClient)); + new_entry->id = id; + new_entry->client = NULL; + new_entry->room = room_name; + new_entry->public_key = p2p_jnmsg->public_key; + new_entry->meta_len = meta_len; + if (meta_len > 0) + { + new_entry->member_info = GNUNET_malloc (meta_len); + memcpy (new_entry->member_info, &roomptr[room_name_len], meta_len); + } + else + new_entry->member_info = NULL; + new_entry->msg_options = ntohl (p2p_jnmsg->msg_options); + new_entry->next = client_list_head; + client_list_head = new_entry; +#if DEBUG_CHAT_SERVICE + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Notifying local room members that we have a new client\n"); +#endif + jnmsg = GNUNET_malloc (sizeof (struct JoinNotificationMessage) + meta_len); + jnmsg->header.type = htons (GNUNET_MESSAGE_TYPE_CHAT_JOIN_NOTIFICATION); + jnmsg->header.size = + htons (sizeof (struct JoinNotificationMessage) + meta_len); + jnmsg->msg_options = p2p_jnmsg->msg_options; + jnmsg->public_key = new_entry->public_key; + memcpy (&jnmsg[1], &roomptr[room_name_len], meta_len); + entry = client_list_head; + while (NULL != entry) + { + if ((0 == strcmp (room_name, entry->room)) && + (NULL != entry->client)) + { + GNUNET_SERVER_notification_context_unicast (nc, + entry->client, + &jnmsg->header, + GNUNET_NO); + } + entry = entry->next; + } +#if DEBUG_CHAT_SERVICE + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Broadcasting join notification to neighbour peers\n"); +#endif + GNUNET_CORE_iterate_peers (cfg, + &send_join_noficiation, + new_entry); + GNUNET_free (jnmsg); + return GNUNET_OK; +} + + +/** + * Handle P2P leave notification. + * + * @param cls closure, always NULL + * @param other the other peer involved + * @param message the actual message + * @param atsi performance information + * @return GNUNET_OK to keep the connection open, + * GNUNET_SYSERR to close it (signal serious error) + */ +static int +handle_p2p_leave_notification (void *cls, + const struct GNUNET_PeerIdentity *other, + const struct GNUNET_MessageHeader *message, + const struct GNUNET_TRANSPORT_ATS_Information *atsi) +{ + const struct P2PLeaveNotificationMessage *p2p_lnmsg; + GNUNET_HashCode id; + struct ChatClient *pos; + struct ChatClient *prev; + struct ChatClient *entry; + struct LeaveNotificationMessage lnmsg; + + GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Got P2P leave notification\n"); + p2p_lnmsg = (const struct P2PLeaveNotificationMessage *) message; + GNUNET_CRYPTO_hash (&p2p_lnmsg->user, + sizeof (struct GNUNET_CRYPTO_RsaPublicKeyBinaryEncoded), + &id); + pos = client_list_head; + prev = NULL; + while (NULL != pos) + { + if (0 == memcmp (&pos->id, &id, sizeof (GNUNET_HashCode))) + break; + prev = pos; + pos = pos->next; + } + if (NULL == pos) + { +#if DEBUG_CHAT_SERVICE + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "No such client. There is nothing to do\n"); +#endif + return GNUNET_OK; + } + if (NULL == prev) + client_list_head = pos->next; + else + prev->next = pos->next; +#if DEBUG_CHAT_SERVICE + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Notifying local room members that the client has gone away\n"); +#endif + lnmsg.header.size = htons (sizeof (struct LeaveNotificationMessage)); + lnmsg.header.type = htons (GNUNET_MESSAGE_TYPE_CHAT_LEAVE_NOTIFICATION); + lnmsg.reserved = htonl (0); + lnmsg.user = pos->public_key; + entry = client_list_head; + while (NULL != entry) + { + if (0 == strcmp (pos->room, entry->room) && + (NULL != entry->client)) + { + GNUNET_SERVER_notification_context_unicast (nc, + entry->client, + &lnmsg.header, + GNUNET_NO); + } + entry = entry->next; + } +#if DEBUG_CHAT_SERVICE + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Broadcasting leave notification to neighbour peers\n"); +#endif + GNUNET_CORE_iterate_peers (cfg, + &send_leave_noficiation, + pos); + return GNUNET_OK; +} + + +/** + * Handle P2P message notification. + * + * @param cls closure, always NULL + * @param other the other peer involved + * @param message the actual message + * @param atsi performance information + * @return GNUNET_OK to keep the connection open, + * GNUNET_SYSERR to close it (signal serious error) + */ +static int +handle_p2p_message_notification (void *cls, + const struct GNUNET_PeerIdentity *other, + const struct GNUNET_MessageHeader *message, + const struct GNUNET_TRANSPORT_ATS_Information *atsi) +{ + const struct P2PReceiveNotificationMessage *p2p_rnmsg; + struct P2PReceiveNotificationMessage *my_p2p_rnmsg; + struct ReceiveNotificationMessage *rnmsg; + struct ChatClient *sender; + struct ChatClient *pos; + static GNUNET_HashCode all_zeros; + int priv_msg; + uint16_t msg_len; + + GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Got P2P message notification\n"); + if (ntohs (message->size) <= sizeof (struct P2PReceiveNotificationMessage)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Malformed message: wrong size\n"); + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + p2p_rnmsg = (const struct P2PReceiveNotificationMessage *) message; + sender = client_list_head; + while ((NULL != sender) && + (0 != memcmp (&sender->id, + &p2p_rnmsg->sender, + sizeof (GNUNET_HashCode)))) + sender = sender->next; + if (NULL == sender) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unknown source. Rejecting the message\n"); + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + if (sender->msg_sequence_number >= ntohl (p2p_rnmsg->sequence_number)) + { +#if DEBUG_CHAT_SERVICE + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "This message has already been handled." + " Sequence numbers (msg/sender): %u/%u\n", + ntohl (p2p_rnmsg->sequence_number), sender->msg_sequence_number); +#endif + return GNUNET_OK; + } + sender->msg_sequence_number = ntohl (p2p_rnmsg->sequence_number); + msg_len = ntohs (p2p_rnmsg->header.size) - + sizeof (struct P2PReceiveNotificationMessage); +#if DEBUG_CHAT_SERVICE + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Sending message to local room members\n"); +#endif + rnmsg = GNUNET_malloc (sizeof (struct ReceiveNotificationMessage) + msg_len); + rnmsg->header.size = htons (sizeof (struct ReceiveNotificationMessage) + + msg_len); + rnmsg->header.type = htons (GNUNET_MESSAGE_TYPE_CHAT_MESSAGE_NOTIFICATION); + rnmsg->msg_options = p2p_rnmsg->msg_options; + rnmsg->sequence_number = p2p_rnmsg->sequence_number; + priv_msg = (0 != memcmp (&all_zeros, + &p2p_rnmsg->target, sizeof (GNUNET_HashCode))); + if (priv_msg) + memcpy (&rnmsg->encrypted_key, + &p2p_rnmsg->encrypted_key, + sizeof (struct GNUNET_CRYPTO_RsaEncryptedData)); + memcpy (&rnmsg->sender, &p2p_rnmsg->sender, sizeof (GNUNET_HashCode)); + memcpy (&rnmsg[1], &p2p_rnmsg[1], msg_len); + pos = client_list_head; + while (NULL != pos) + { + if ((0 == strcmp (sender->room, pos->room)) && + (NULL != pos->client)) + { + if (((!priv_msg) || + (0 == memcmp (&p2p_rnmsg->target, + &pos->id, + sizeof (GNUNET_HashCode)))) && + (0 == (ntohl (p2p_rnmsg->msg_options) & (~pos->msg_options)))) + { + GNUNET_SERVER_notification_context_unicast (nc, + pos->client, + &rnmsg->header, + GNUNET_NO); + } + } + pos = pos->next; + } +#if DEBUG_CHAT_SERVICE + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Broadcasting message notification to neighbour peers\n"); +#endif + my_p2p_rnmsg = GNUNET_memdup (p2p_rnmsg, ntohs (p2p_rnmsg->header.size)); + GNUNET_CORE_iterate_peers (cfg, + &send_message_noficiation, + my_p2p_rnmsg); + GNUNET_free (rnmsg); + return GNUNET_OK; +} + + +/** + * Handle P2P sync request. + * + * @param cls closure, always NULL + * @param other the other peer involved + * @param message the actual message + * @param atsi performance information + * @return GNUNET_OK to keep the connection open, + * GNUNET_SYSERR to close it (signal serious error) + */ +static int +handle_p2p_sync_request (void *cls, + const struct GNUNET_PeerIdentity *other, + const struct GNUNET_MessageHeader *message, + const struct GNUNET_TRANSPORT_ATS_Information *atsi) +{ + struct ChatClient *entry; + struct GNUNET_CORE_TransmitHandle *th; + size_t msg_size; + + GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Got P2P sync request\n"); +#if DEBUG_CHAT_SERVICE + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Notifying the requester of all known clients\n"); +#endif + entry = client_list_head; + while (NULL != entry) + { + msg_size = sizeof (struct P2PJoinNotificationMessage) + + strlen (entry->room) + + entry->meta_len; + th = GNUNET_CORE_notify_transmit_ready (core, + 1, + MAX_TRANSMIT_DELAY, + other, + msg_size, + &transmit_join_notification_to_peer, + entry); + GNUNET_assert (NULL != th); + entry = entry->next; + } + return GNUNET_OK; +} + + +/** + * Handle P2P confirmation receipt. + * + * @param cls closure, always NULL + * @param other the other peer involved + * @param message the actual message + * @param atsi performance information + * @return GNUNET_OK to keep the connection open, + * GNUNET_SYSERR to close it (signal serious error) + */ +static int +handle_p2p_confirmation_receipt (void *cls, + const struct GNUNET_PeerIdentity *other, + const struct GNUNET_MessageHeader *message, + const struct GNUNET_TRANSPORT_ATS_Information *atsi) +{ + const struct P2PConfirmationReceiptMessage *p2p_crmsg; + struct P2PConfirmationReceiptMessage *my_p2p_crmsg; + struct ConfirmationReceiptMessage *crmsg; + struct ChatClient *target; + struct ChatClient *author; + + GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Got P2P confirmation receipt\n"); + p2p_crmsg = (const struct P2PConfirmationReceiptMessage *) message; + target = client_list_head; + while ((NULL != target) && + (0 != memcmp (&target->id, + &p2p_crmsg->target, + sizeof (GNUNET_HashCode)))) + target = target->next; + if (NULL == target) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unknown source of the receipt. Rejecting the message\n"); + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + if (target->rcpt_sequence_number >= ntohl (p2p_crmsg->sequence_number)) + { +#if DEBUG_CHAT_SERVICE + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "This receipt has already been handled." + " Sequence numbers (msg/sender): %u/%u\n", + ntohl (p2p_crmsg->sequence_number), target->rcpt_sequence_number); +#endif + return GNUNET_OK; + } + target->rcpt_sequence_number = ntohl (p2p_crmsg->sequence_number); + author = client_list_head; + while ((NULL != author) && + (0 != memcmp (&author->id, + &p2p_crmsg->author, + sizeof (GNUNET_HashCode)))) + author = author->next; + if (NULL == author) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unknown addressee. Rejecting the receipt\n"); + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + + if (NULL == author->client) + { +#if DEBUG_CHAT_SERVICE + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "The author of the original message is not a local client." + " Broadcasting receipt to neighbour peers\n"); +#endif + my_p2p_crmsg = GNUNET_memdup (p2p_crmsg, sizeof (struct P2PConfirmationReceiptMessage)); + GNUNET_CORE_iterate_peers (cfg, + &send_confirmation_receipt, + my_p2p_crmsg); + } + else + { +#if DEBUG_CHAT_SERVICE + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "The author of the original message is a local client." + " Verifying signature of the receipt\n"); +#endif + crmsg = GNUNET_malloc (sizeof (struct ConfirmationReceiptMessage)); + crmsg->header.size = htons (sizeof (struct ConfirmationReceiptMessage)); + crmsg->header.type = htons (GNUNET_MESSAGE_TYPE_CHAT_CONFIRMATION_NOTIFICATION); + crmsg->signature = p2p_crmsg->signature; + crmsg->purpose = p2p_crmsg->purpose; + crmsg->sequence_number = p2p_crmsg->msg_sequence_number; + crmsg->reserved2 = 0; + crmsg->timestamp = p2p_crmsg->timestamp; + crmsg->target = p2p_crmsg->target; + crmsg->author = p2p_crmsg->author; + crmsg->content = p2p_crmsg->content; + if (GNUNET_OK != + GNUNET_CRYPTO_rsa_verify (GNUNET_SIGNATURE_PURPOSE_CHAT_RECEIPT, + &crmsg->purpose, + &crmsg->signature, + &target->public_key)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Invalid signature of the receipt\n"); + GNUNET_break_op (0); + return GNUNET_SYSERR; + } +#if DEBUG_CHAT_SERVICE + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "The author of the original message is a local client." + " Sending receipt to the client\n"); +#endif + GNUNET_SERVER_notification_context_unicast (nc, + author->client, + &crmsg->header, + GNUNET_NO); + GNUNET_free (crmsg); + } + return GNUNET_OK; +} + + +/** + * Transmit a sync request to the peer. + * + * @param cls closure, NULL + * @param size number of bytes available in buf + * @param buf where the callee should write the message + * @return number of bytes written to buf + */ +static size_t +transmit_sync_request_to_peer (void *cls, + size_t size, + void *buf) +{ + struct GNUNET_MessageHeader *m = buf; + size_t msg_size; + +#if DEBUG_CHAT_SERVICE + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Transmitting P2P sync request\n"); +#endif + msg_size = sizeof (struct GNUNET_MessageHeader); + GNUNET_assert (size >= msg_size); + GNUNET_assert (NULL != buf); + m = buf; + m->type = htons (GNUNET_MESSAGE_TYPE_CHAT_P2P_SYNC_REQUEST); + m->size = htons (msg_size); + return msg_size; +} + + +/** + * Method called whenever a peer connects. + * + * @param cls closure + * @param peer peer identity this notification is about + * @param atsi performance data + */ +static void +peer_connect_handler (void *cls, + const struct GNUNET_PeerIdentity *peer, + const struct GNUNET_TRANSPORT_ATS_Information *atsi) +{ + struct GNUNET_CORE_TransmitHandle *th; + + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Peer connected: %s\n", GNUNET_i2s (peer)); + if (0 == memcmp (peer, me, sizeof (struct GNUNET_PeerIdentity))) + return; + th = GNUNET_CORE_notify_transmit_ready (core, + 1, + MAX_TRANSMIT_DELAY, + peer, + sizeof (struct GNUNET_MessageHeader), + &transmit_sync_request_to_peer, + NULL); + GNUNET_assert (NULL != th); +} + + +/** + * Method called whenever a peer disconnects. + * + * @param cls closure, not used + * @param peer peer identity this notification is about + */ +static void +peer_disconnect_handler (void *cls, + const struct GNUNET_PeerIdentity *peer) +{ + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Peer disconnected: %s\n", GNUNET_i2s (peer)); +} + + +/** + * Task run during shutdown. + * + * @param cls unused + * @param tc unused + */ +static void +cleanup_task (void *cls, + const struct GNUNET_SCHEDULER_TaskContext *tc) +{ + GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Cleaning up\n"); + if (NULL != core) + { + GNUNET_CORE_disconnect (core); + core = NULL; + } + if (NULL != nc) + { + GNUNET_SERVER_notification_context_destroy (nc); + nc = NULL; + } +} + + +/** + * To be called on core init/fail. + * + * @param cls closure, NULL + * @param server handle to the server for this service + * @param identity the public identity of this peer + * @param publicKey the public key of this peer + */ +static void +core_init (void *cls, + struct GNUNET_CORE_Handle *server, + const struct GNUNET_PeerIdentity *my_identity, + const struct GNUNET_CRYPTO_RsaPublicKeyBinaryEncoded *publicKey) +{ + GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Core initialized\n"); + me = my_identity; +} + + +/** + * Process chat requests. + * + * @param cls closure, NULL + * @param server the initialized server + * @param cfg configuration to use + */ +static void +run (void *cls, + struct GNUNET_SERVER_Handle *server, + const struct GNUNET_CONFIGURATION_Handle *c) +{ + static const struct GNUNET_SERVER_MessageHandler handlers[] = + { + { &handle_join_request, NULL, + GNUNET_MESSAGE_TYPE_CHAT_JOIN_REQUEST, 0 }, + { &handle_transmit_request, NULL, + GNUNET_MESSAGE_TYPE_CHAT_TRANSMIT_REQUEST, 0 }, + { &handle_acknowledge_request, NULL, + GNUNET_MESSAGE_TYPE_CHAT_CONFIRMATION_RECEIPT, + sizeof (struct ConfirmationReceiptMessage) }, + { NULL, NULL, 0, 0 } + }; + static const struct GNUNET_CORE_MessageHandler p2p_handlers[] = + { + { &handle_p2p_join_notification, + GNUNET_MESSAGE_TYPE_CHAT_P2P_JOIN_NOTIFICATION, 0 }, + { &handle_p2p_leave_notification, + GNUNET_MESSAGE_TYPE_CHAT_P2P_LEAVE_NOTIFICATION, + sizeof (struct P2PLeaveNotificationMessage) }, + { &handle_p2p_message_notification, + GNUNET_MESSAGE_TYPE_CHAT_P2P_MESSAGE_NOTIFICATION, 0 }, + { &handle_p2p_sync_request, + GNUNET_MESSAGE_TYPE_CHAT_P2P_SYNC_REQUEST, + sizeof (struct GNUNET_MessageHeader) }, + { &handle_p2p_confirmation_receipt, + GNUNET_MESSAGE_TYPE_CHAT_P2P_CONFIRMATION_RECEIPT, + sizeof (struct P2PConfirmationReceiptMessage) }, + { NULL, 0, 0 } + }; + + GNUNET_log_setup ("gnunet-service-chat", +#if DEBUG_CHAT_SERVICE + "DEBUG", +#else + "WARNING", +#endif + NULL); + cfg = c; + nc = GNUNET_SERVER_notification_context_create (server, 16); + GNUNET_SERVER_add_handlers (server, handlers); + core = GNUNET_CORE_connect (cfg, + QUEUE_SIZE, + NULL, + &core_init, + &peer_connect_handler, + &peer_disconnect_handler, + NULL, + NULL, GNUNET_NO, + NULL, GNUNET_NO, + p2p_handlers); + GNUNET_SERVER_disconnect_notify (server, + &handle_client_disconnect, + NULL); + GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_UNIT_FOREVER_REL, + &cleanup_task, + NULL); +} + + +/** + * The main function for the chat service. + * + * @param argc number of arguments from the command line + * @param argv command line arguments + * @return 0 ok, 1 on error + */ +int +main (int argc, char *const *argv) +{ + return (GNUNET_OK == + GNUNET_SERVICE_run (argc, + argv, + "chat", + GNUNET_SERVICE_OPTION_NONE, + &run, NULL)) ? 0 : 1; +} + +/* end of gnunet-service-chat.c */ |