summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'src/irc/network.c')
-rw-r--r--src/irc/network.c568
1 files changed, 568 insertions, 0 deletions
diff --git a/src/irc/network.c b/src/irc/network.c
new file mode 100644
index 0000000..edafd4f
--- /dev/null
+++ b/src/irc/network.c
@@ -0,0 +1,568 @@
+#include <stdio.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+
+/* "POSIX.1 does not require the inclusion of <sys/types.h>" */
+/* - man page for setsockopt */
+/* #include <sys/types.h> */
+#include <sys/socket.h>
+#include <sys/time.h>
+
+#include "error.h"
+
+#include "network.h"
+
+static int re_create_socket (struct ZoO_network net [const restrict static 1])
+{
+ struct timeval timeout;
+ const int old_errno = errno;
+
+ errno = 0;
+ timeout.tv_sec = ZoO_NETWORK_TIMEOUT;
+ timeout.tv_usec = 0;
+
+ if (net->connection != -1)
+ {
+ close(net->connection);
+ }
+
+ net->connection =
+ socket
+ (
+ net->addrinfo->ai_family,
+ net->addrinfo->ai_socktype,
+ net->addrinfo->ai_protocol
+ );
+
+ if (net->connection == -1)
+ {
+ ZoO_ERROR("Could not create socket: %s.", strerror(errno));
+
+ goto RETURN_FAILED;
+ }
+
+ if
+ (
+ (
+ setsockopt
+ (
+ net->connection,
+ SOL_SOCKET,
+ SO_RCVTIMEO,
+ (const void *) &timeout,
+ (socklen_t) sizeof(struct timeval)
+ ) < 0
+ )
+ ||
+ (
+ setsockopt
+ (
+ net->connection,
+ SOL_SOCKET,
+ SO_SNDTIMEO,
+ (const void *) &timeout,
+ (socklen_t) sizeof(struct timeval)
+ ) < 0
+ )
+ )
+ {
+ ZoO_ERROR("Could not set timeout on network socket: %s", strerror(errno));
+
+ goto RETURN_FAILED;
+ }
+
+ ZoO_S_DEBUG(ZoO_DEBUG_NETWORK, "(Re)connecting to network...");
+
+ if
+ (
+ connect
+ (
+ net->connection,
+ net->addrinfo->ai_addr,
+ net->addrinfo->ai_addrlen
+ ) != 0
+ )
+ {
+ ZoO_ERROR("Could not establish connection: %s", strerror(errno));
+
+ goto RETURN_FAILED;
+ }
+
+ errno = old_errno;
+
+ return 0;
+
+RETURN_FAILED:
+ errno = old_errno;
+
+ return -1;
+}
+
+static int reconnect (struct ZoO_network net [const restrict static 1])
+{
+ const int old_errno = errno;
+
+ memset(net->in, 0, (sizeof(ZoO_char) * 513));
+ memset(net->out, 0, (sizeof(ZoO_char) * 513));
+ memset(net->buffer, 0, (sizeof(ZoO_char) * 513));
+
+ net->buffer_index = 0;
+ net->buffer_remaining = 0;
+
+ if (re_create_socket(net) < 0)
+ {
+ return -1;
+ }
+
+ snprintf(net->out, 512, "USER %s 8 * :%s\r\n", net->user, net->name);
+
+ if (write(net->connection, net->out, strlen(net->out)) < 1)
+ {
+ goto RETURN_WRITE_FAILED;
+ }
+
+ snprintf(net->out, 512, "NICK %s\r\n", net->nick);
+
+ if (write(net->connection, net->out, strlen(net->out)) < 1)
+ {
+ goto RETURN_WRITE_FAILED;
+ }
+
+ net->buffer_remaining = 0;
+ net->buffer_index = 0;
+
+ ZoO_S_DEBUG(ZoO_DEBUG_NETWORK, "(Re)connected.");
+
+ errno = old_errno;
+
+ return 0;
+
+RETURN_WRITE_FAILED:
+ ZoO_ERROR
+ (
+ "Unable to write to the network: %s",
+ strerror(errno)
+ );
+
+ errno = old_errno;
+
+ return -1;
+}
+
+int ZoO_network_connect
+(
+ struct ZoO_network net [const static 1],
+ const char host [const restrict static 1],
+ const char port [const restrict static 1],
+ const char channel [const restrict static 1],
+ const char user [const restrict static 1],
+ const char name [const restrict static 1],
+ const char nick [const restrict static 1]
+)
+{
+ int error;
+ struct addrinfo hints;
+ const int old_errno = errno;
+
+ net->connection = -1;
+ net->channel = channel;
+ net->user = user;
+ net->name = name;
+ net->nick = nick;
+ net->buffer_index = 0;
+ net->buffer_remaining = 0;
+
+ memset(&hints, 0, sizeof(struct addrinfo));
+ memset(net->in, 0, (sizeof(ZoO_char) * 513));
+ memset(net->out, 0, (sizeof(ZoO_char) * 513));
+ memset(net->buffer, 0, (sizeof(ZoO_char) * 513));
+
+ hints.ai_family = AF_INET;
+ hints.ai_socktype = SOCK_STREAM;
+
+ errno = 0;
+
+ error = getaddrinfo(host, port, &hints, &(net->addrinfo));
+
+ if (error != 0)
+ {
+ if (error == EAI_SYSTEM)
+ {
+ ZoO_ERROR
+ (
+ "Could not retrieve server information: %s.",
+ strerror(errno)
+ );
+ }
+ else
+ {
+ ZoO_FATAL
+ (
+ "Could not retrieve server information: %s.",
+ gai_strerror(error)
+ );
+ }
+
+ errno = old_errno;
+
+ return -1;
+ }
+
+ errno = old_errno;
+
+ reconnect(net);
+
+ return 0;
+}
+
+static void buffer_msg
+(
+ struct ZoO_network net [const static 1]
+)
+{
+ ssize_t in_count, i;
+
+ if (net->buffer_remaining > 0)
+ {
+ in_count = net->buffer_remaining;
+ net->buffer_remaining = 0;
+
+ goto PARSE_READ;
+ }
+
+READ_MORE:
+ in_count = read(net->connection, net->buffer, 512);
+
+ if (in_count <= 0)
+ {
+ ZoO_ERROR("Could not read from network: %s", strerror(errno));
+
+ while (reconnect(net) < 0)
+ {
+ ZoO_S_DEBUG
+ (
+ ZoO_DEBUG_NETWORK,
+ "Attempting new connection in 5s."
+ );
+ sleep(5);
+ }
+
+ goto READ_MORE;
+ }
+
+PARSE_READ:
+ for (i = 0; i < in_count; ++i)
+ {
+ net->in[net->buffer_index] = net->buffer[i];
+
+ if
+ (
+ (net->buffer_index > 0)
+ && (net->in[net->buffer_index - 1] == '\r')
+ && (net->in[net->buffer_index] == '\n')
+ )
+ {
+ net->buffer_remaining = (in_count - (i + 1));
+ net->in_length = (net->buffer_index - 1);
+ net->buffer_index = 0;
+
+ if (net->buffer_remaining > 0)
+ {
+ memmove
+ (
+ (void *) net->buffer,
+ (const void *) (net->buffer + (i + 1)),
+ net->buffer_remaining
+ );
+ }
+
+ return;
+ }
+
+ net->buffer_index += 1;
+
+ if (net->buffer_index > 512)
+ {
+ ZoO_S_WARNING("Incoming message is too long. Discarded.");
+
+ net->buffer_index = 0;
+ net->buffer_remaining = 0;
+
+ break;
+ }
+ }
+
+ goto READ_MORE;
+}
+
+void handle_ping (struct ZoO_network net [const restrict static 1])
+{
+ const int old_errno = errno;
+
+ #if ZoO_RANDOMLY_IGNORE_PING == 1
+ if ((rand() % 10) < 3)
+ {
+ ZoO_S_DEBUG(ZoO_DEBUG_NETWORK, "Ping request ignored.");
+
+ return;
+ }
+
+ #endif
+
+ #if ZoO_DEBUG_NETWORK_PING == 1
+ net->in[net->in_length] = '\0';
+
+ ZoO_DEBUG(ZoO_DEBUG_NETWORK, "[NET->in] %s", net->in);
+
+ net->in[net->in_length] = '\r';
+ #endif
+
+ net->in[1] = 'O';
+
+ errno = 0;
+
+ if (write(net->connection, net->in, (net->in_length + 2)) < 1)
+ {
+ ZoO_ERROR("Could not reply to PING request: %s", strerror(errno));
+
+ errno = old_errno;
+
+ while (reconnect(net) < 0)
+ {
+ ZoO_S_DEBUG
+ (
+ ZoO_DEBUG_NETWORK,
+ "Attempting new connection in 5s."
+ );
+ sleep(5);
+ }
+
+ return;
+ }
+
+ errno = old_errno;
+
+#if ZoO_DEBUG_NETWORK_PING == 1
+ net->in[net->in_length] = '\0';
+
+ ZoO_DEBUG(ZoO_DEBUG_NETWORK, "[NET->out] %s", net->in);
+#endif
+
+}
+
+int ZoO_network_receive
+(
+ struct ZoO_network net [const restrict static 1],
+ size_t msg_offset [const restrict static 1],
+ size_t msg_size [const restrict static 1],
+ enum ZoO_msg_type type [const restrict static 1]
+)
+{
+ const int old_errno = errno;
+ ssize_t cmd, i;
+
+READ_NEW_MSG:
+ buffer_msg(net);
+
+ net->in[net->in_length + 2] = '\0';
+
+ /* XXX: doesn't that prevent net [restrict]? */
+ if (ZoO_IS_PREFIX("PING", net->in))
+ {
+
+ handle_ping(net);
+
+ goto READ_NEW_MSG;
+ }
+
+ if (net->in_length == 0)
+ {
+ goto READ_NEW_MSG;
+ }
+
+ net->in[net->in_length] = '\0';
+
+ ZoO_DEBUG(ZoO_DEBUG_NETWORK, "[NET->in] %s", net->in);
+
+ if (net->in[0] == ':')
+ {
+ cmd = 0;
+
+ for (i = 1; i < 512; i++)
+ {
+ if (net->in[i] == ' ')
+ {
+ cmd = (i + 1);
+
+ break;
+ }
+ }
+
+ if (ZoO_IS_PREFIX("001", (net->in + cmd)))
+ {
+ snprintf
+ (
+ net->out,
+ 512,
+ "JOIN :%s\r\n",
+ net->channel
+ );
+
+ errno = 0;
+
+ if (write(net->connection, net->out, strlen(net->out)) < 1)
+ {
+ ZoO_ERROR
+ (
+ "Could not send JOIN request: %s",
+ strerror(errno)
+ );
+
+ errno = old_errno;
+
+ if (reconnect(net) < 0)
+ {
+ return -1;
+ }
+ }
+
+ errno = old_errno;
+
+ ZoO_DEBUG(ZoO_DEBUG_NETWORK, "[NET->out] %s", net->out);
+
+ goto READ_NEW_MSG;
+ }
+
+ if (ZoO_IS_PREFIX("JOIN", (net->in + cmd)))
+ {
+ for (i = 1; (i < 512) && (net->in[i] != '!'); ++i)
+ {
+ }
+
+ if ((i == 512) || (i == 1))
+ {
+ ZoO_ERROR("Could not find JOIN username: %s", net->in);
+
+ goto READ_NEW_MSG;
+ }
+
+ *msg_offset = 1;
+ *msg_size = (i - 1);
+ net->in[i] = '\0';
+
+ *type = ZoO_JOIN;
+
+ return 0;
+ }
+
+ if (ZoO_IS_PREFIX("PRIVMSG", (net->in + cmd)))
+ {
+
+ for (; i < 512; i++)
+ {
+ if (net->in[i] == ':')
+ {
+ cmd = (i + 1);
+
+ break;
+ }
+ }
+
+ *msg_offset = cmd;
+ *msg_size = (net->in_length - cmd);
+
+ /*net->in[*msg_size - 1] = '\0'; */
+
+ *type = ZoO_PRIVMSG;
+
+ return 0;
+ }
+ }
+
+ if (ZoO_IS_PREFIX("ERROR", (net->in + cmd)))
+ {
+ while (reconnect(net) < 0)
+ {
+ ZoO_S_DEBUG
+ (
+ ZoO_DEBUG_NETWORK,
+ "Attempting new connection in 5s."
+ );
+ sleep(5);
+ }
+ }
+
+ goto READ_NEW_MSG;
+}
+
+int ZoO_network_send (struct ZoO_network net [const restrict static 1])
+{
+ int const old_errno = errno;
+
+ if (ZoO_IS_PREFIX("\001action", net->out))
+ {
+
+ net->out[1] = 'A';
+ net->out[2] = 'C';
+ net->out[3] = 'T';
+ net->out[4] = 'I';
+ net->out[5] = 'O';
+ net->out[6] = 'N';
+
+ snprintf
+ (
+ net->in,
+ 512,
+ "PRIVMSG %s :%s\001\r\n",
+ net->channel,
+ net->out
+ );
+ }
+ else
+ {
+ snprintf
+ (
+ net->in,
+ 512,
+ "PRIVMSG %s :%s\r\n",
+ net->channel,
+ net->out
+ );
+ }
+
+ errno = 0;
+
+ if (write(net->connection, net->in, strlen(net->in)) < 1)
+ {
+ ZoO_ERROR
+ (
+ "Could not send PRIVMSG: %s.",
+ strerror(errno)
+ );
+
+ errno = old_errno;
+
+ if (reconnect(net) < 0)
+ {
+ return -2;
+ }
+ else
+ {
+ return -1;
+ }
+ }
+
+ errno = old_errno;
+
+ ZoO_DEBUG(ZoO_DEBUG_NETWORK, "[NET->out] %s", net->in);
+
+ return 0;
+}
+
+void ZoO_network_disconnect (struct ZoO_network net [const restrict static 1])
+{
+ freeaddrinfo(net->addrinfo);
+ close(net->connection);
+}
+