From 92bed5d520f0b8be44cf275152d95dccb88d1c75 Mon Sep 17 00:00:00 2001 From: Olaf Rempel Date: Sat, 7 Jul 2007 17:24:03 +0200 Subject: [PATCH] new irc impl --- Makefile | 4 +- event.c | 302 +++++++++++++++++++++++++++++++++++++++++++++++++++ event.h | 42 +++++++ irclogbot.c | 239 ++-------------------------------------- ircsession.c | 121 +++++++++++++++++++++ ircsession.h | 34 ++++++ linebuffer.c | 135 +++++++++++++++++++++++ linebuffer.h | 30 +++++ sockaddr.c | 99 +++++++++++++++++ sockaddr.h | 14 +++ tcpsocket.c | 117 ++++++++++++++++++++ tcpsocket.h | 12 ++ 12 files changed, 919 insertions(+), 230 deletions(-) create mode 100644 event.c create mode 100644 event.h create mode 100644 ircsession.c create mode 100644 ircsession.h create mode 100644 linebuffer.c create mode 100644 linebuffer.h create mode 100644 sockaddr.c create mode 100644 sockaddr.h create mode 100644 tcpsocket.c create mode 100644 tcpsocket.h diff --git a/Makefile b/Makefile index 0e6ea45..27f1b6f 100644 --- a/Makefile +++ b/Makefile @@ -1,11 +1,11 @@ CFLAGS := -O2 -pipe -Wall -OBJS := configfile.o logging.o +OBJS := configfile.o event.o ircsession.o linebuffer.o logging.o sockaddr.o tcpsocket.o all: irclogbot irclogbot: $(OBJS) irclogbot.o - $(CC) $(CFLAGS) -o $@ $^ libircclient/src/libircclient.a + $(CC) $(CFLAGS) -o $@ $^ %.o: %.c $(CC) $(CFLAGS) -c $< -o $@ diff --git a/event.c b/event.c new file mode 100644 index 0000000..fd0934f --- /dev/null +++ b/event.c @@ -0,0 +1,302 @@ +/*************************************************************************** + * Copyright (C) 10/2006 by Olaf Rempel * + * razzor@kopf-tisch.de * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program 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 this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ +#include +#include +#include + +#include +#include + +#include "list.h" +#include "logging.h" + +#include "event.h" + +static LIST_HEAD(event_fd_list); +static LIST_HEAD(event_timeout_list); + +struct event_fd { + struct list_head list; + unsigned int flags; + int fd; + int (*read_cb)(int fd, void *privdata); + int (*write_cb)(int fd, void *privdata); + void *read_priv; + void *write_priv; +}; + +struct event_timeout { + struct list_head list; + unsigned int flags; + struct timeval intervall; + struct timeval nextrun; + int (*callback)(void *privdata); + void *privdata; +}; + +struct event_fd * event_add_fd( + struct event_fd *entry, + int fd, + int type, + int (*callback)(int fd, void *privdata), + void *privdata) +{ + /* check valid filediskriptor */ + if (fd < 0 || fd > FD_SETSIZE) { + log_print(LOG_ERROR, "event_add_fd(): invalid fd"); + return NULL; + } + + /* check valid type (read/write) */ + if (!(type & FD_TYPES)) { + log_print(LOG_ERROR, "event_add_fd(): invalid type"); + return NULL; + } + + /* create new entry */ + if (entry == NULL) { + entry = malloc(sizeof(struct event_fd)); + if (entry == NULL) { + log_print(LOG_ERROR, "event_add_fd(): out of memory"); + return NULL; + } + + memset(entry, 0, sizeof(struct event_fd)); + entry->flags |= EVENT_NEW; + entry->fd = fd; + + /* put it on the list */ + list_add_tail(&entry->list, &event_fd_list); + } + + if (type & FD_READ) { + entry->flags = (callback != NULL) ? (entry->flags | FD_READ | EVENT_NEW) : (entry->flags & ~FD_READ); + entry->read_cb = callback; + entry->read_priv = privdata; + + } else if (type & FD_WRITE) { + entry->flags = (callback != NULL) ? (entry->flags | FD_WRITE | EVENT_NEW) : (entry->flags & ~FD_WRITE); + entry->write_cb = callback; + entry->write_priv = privdata; + } + + return entry; +} + +int event_get_fd(struct event_fd *entry) +{ + return (entry != NULL) ? entry->fd: -1; +} + +void event_remove_fd(struct event_fd *entry) +{ + /* mark the event as deleted -> remove in select() loop */ + entry->flags |= EVENT_DELETE; +} + +static void add_timeval(struct timeval *ret, struct timeval *a, struct timeval *b) +{ + ret->tv_usec = a->tv_usec + b->tv_usec; + ret->tv_sec = a->tv_sec + b->tv_sec; + + if (ret->tv_usec >= 1000000) { + ret->tv_usec -= 1000000; + ret->tv_sec++; + } +} + +static void sub_timeval(struct timeval *ret, struct timeval *a, struct timeval *b) +{ + ret->tv_usec = a->tv_usec - b->tv_usec; + ret->tv_sec = a->tv_sec - b->tv_sec; + + if (ret->tv_usec < 0) { + ret->tv_usec += 1000000; + ret->tv_sec--; + } +} + +static int cmp_timeval(struct timeval *a, struct timeval *b) +{ + if (a->tv_sec > b->tv_sec) + return -1; + + if (a->tv_sec < b->tv_sec) + return 1; + + if (a->tv_usec > b->tv_usec) + return -1; + + if (a->tv_usec < b->tv_usec) + return 1; + + return 0; +} + +static void schedule_nextrun(struct event_timeout *entry, struct timeval *now) +{ + add_timeval(&entry->nextrun, now, &entry->intervall); + + struct event_timeout *search; + list_for_each_entry(search, &event_timeout_list, list) { + if (search->nextrun.tv_sec > entry->nextrun.tv_sec) { + list_add_tail(&entry->list, &search->list); + return; + + } else if (search->nextrun.tv_sec == entry->nextrun.tv_sec && + search->nextrun.tv_usec > entry->nextrun.tv_usec) { + list_add_tail(&entry->list, &search->list); + return; + } + } + list_add_tail(&entry->list, &event_timeout_list); +} + +struct event_timeout * event_add_timeout( + struct timeval *timeout, + int (*callback)(void *privdata), + void *privdata) +{ + struct event_timeout *entry; + entry = malloc(sizeof(struct event_timeout)); + if (entry == NULL) { + log_print(LOG_ERROR, "event_add_timeout(): out of memory"); + return NULL; + } + + entry->flags = 0; + memcpy(&entry->intervall, timeout, sizeof(entry->intervall)); + entry->callback = callback; + entry->privdata = privdata; + + struct timeval now; + gettimeofday(&now, NULL); + + schedule_nextrun(entry, &now); + return entry; +} + +void event_remove_timeout(struct event_timeout *entry) +{ + /* mark the event as deleted -> remove in select() loop */ + entry->flags |= EVENT_DELETE; +} + +int event_loop(void) +{ + fd_set *fdsets = malloc(sizeof(fd_set) * 2); + if (fdsets == NULL) { + log_print(LOG_ERROR, "event_loop(): out of memory"); + return -1; + } + + while (1) { + struct timeval timeout, *timeout_p = NULL; + if (!list_empty(&event_timeout_list)) { + struct timeval now; + gettimeofday(&now, NULL); + + struct event_timeout *entry, *tmp; + list_for_each_entry_safe(entry, tmp, &event_timeout_list, list) { + if (entry->flags & EVENT_DELETE) { + list_del(&entry->list); + free(entry); + continue; + } + + /* timeout not elapsed, exit search (since list is sorted) */ + if (cmp_timeval(&entry->nextrun, &now) == -1) + break; + + /* remove event from list */ + list_del(&entry->list); + + /* execute callback, when callback returns 0 -> schedule event again */ + if (entry->callback(entry->privdata)) { + free(entry); + + } else { + schedule_nextrun(entry, &now); + } + } + + if (!list_empty(&event_timeout_list)) { + entry = list_entry(event_timeout_list.next, typeof(*entry), list); + + /* calc select() timeout */ + sub_timeval(&timeout, &entry->nextrun, &now); + timeout_p = &timeout; + } + } + + fd_set *readfds = NULL, *writefds = NULL; + struct event_fd *entry, *tmp; + + list_for_each_entry_safe(entry, tmp, &event_fd_list, list) { + entry->flags &= ~EVENT_NEW; + + if (entry->flags & EVENT_DELETE) { + list_del(&entry->list); + free(entry); + continue; + + } + + if (entry->flags & FD_READ) { + if (readfds == NULL) { + readfds = &fdsets[0]; + FD_ZERO(readfds); + } + FD_SET(entry->fd, readfds); + + } + + if (entry->flags & FD_WRITE) { + if (writefds == NULL) { + writefds = &fdsets[1]; + FD_ZERO(writefds); + } + FD_SET(entry->fd, writefds); + } + } + + int i = select(FD_SETSIZE, readfds, writefds, NULL, timeout_p); + if (i <= 0) { + /* On error, -1 is returned, and errno is set + * appropriately; the sets and timeout become + * undefined, so do not rely on their contents + * after an error. + */ + continue; + + } else { + list_for_each_entry(entry, &event_fd_list, list) { + if (((entry->flags & (FD_READ | EVENT_NEW)) == FD_READ) && FD_ISSET(entry->fd, readfds)) + if (entry->read_cb(entry->fd, entry->read_priv) != 0) + entry->flags |= EVENT_DELETE; + + if (((entry->flags & (FD_WRITE | EVENT_NEW)) == FD_WRITE) && FD_ISSET(entry->fd, writefds)) + if (entry->write_cb(entry->fd, entry->write_priv) != 0) + entry->flags |= EVENT_DELETE; + } + } + } + free(fdsets); +} diff --git a/event.h b/event.h new file mode 100644 index 0000000..05ae63f --- /dev/null +++ b/event.h @@ -0,0 +1,42 @@ +#ifndef _EVENT_H_ +#define _EVENT_H_ + +#include + +#define EVENT_NEW 0x1000 +#define EVENT_DELETE 0x2000 + +#define FD_READ 0x0001 +#define FD_WRITE 0x0002 +#define FD_TYPES (FD_READ | FD_WRITE) + +#define event_add_readfd(entry, fd, callback, privdata) \ + event_add_fd(entry, fd, FD_READ, callback, privdata) + +#define event_add_writefd(entry, fd, callback, privdata) \ + event_add_fd(entry, fd, FD_WRITE, callback, privdata) + +/* inner details are not visible to external users (TODO: size unknown) */ +struct event_fd; +struct event_timeout; + +struct event_fd * event_add_fd( + struct event_fd *entry, + int fd, + int type, + int (*callback)(int fd, void *privdata), + void *privdata); + +int event_get_fd(struct event_fd *entry); +void event_remove_fd(struct event_fd *entry); + +struct event_timeout * event_add_timeout( + struct timeval *timeout, + int (*callback)(void *privdata), + void *privdata); + +void event_remove_timeout(struct event_timeout *entry); + +int event_loop(void); + +#endif /* _EVENT_H_ */ diff --git a/irclogbot.c b/irclogbot.c index 37147ce..447b500 100644 --- a/irclogbot.c +++ b/irclogbot.c @@ -1,244 +1,27 @@ #include #include #include -#include -#include - -#include -#include -#include -#include -#include -#include - -#include "libircclient/include/libircclient.h" #include "configfile.h" +#include "event.h" +#include "ircsession.h" #include "list.h" #include "logging.h" - -#define DEFAULT_CONFIG "irclogbot.conf" -#define DEFAULT_LOGFILE "irclogbot.log" - -static struct option opts[] = { - {"config", 1, 0, 'c'}, - {"debug", 0, 0, 'd'}, - {"help", 0, 0, 'h'}, - {0, 0, 0, 0} -}; - -struct irc_client { - struct list_head list; - - irc_session_t *session; - const char *server; - const char *nickname; - const char *channel; - const char *channelkey; - - struct in_addr addr; -}; - -static LIST_HEAD(client_list); -static irc_callbacks_t irc_cbs; - -static int add_client(const char *parameter, void *privdata) -{ - struct irc_client *entry = malloc(sizeof(struct irc_client)); - if (entry == NULL) { - log_print(LOG_ERROR, "add_client(): out of memory"); - return -1; - } - - if (inet_pton(AF_INET, parameter, &entry->addr) <= 0) { - log_print(LOG_ERROR, "add_client(): inet_pton()"); - free(entry); - return -1; - } - - const char *defserver = config_get_string("global", "server", NULL); - entry->server = config_get_string(parameter, "server", defserver); - if (entry->server == NULL) { - log_print(LOG_ERROR, "add_client(): client %s: no server given", parameter); - free(entry); - return -1; - } - - const char *defchannel = config_get_string("global", "channel", NULL); - entry->channel = config_get_string(parameter, "channel", defchannel); - if (entry->channel == NULL) { - log_print(LOG_ERROR, "add_client(): client %s: no channel given", parameter); - free(entry); - return -1; - } - - const char *defchannelkey = config_get_string("global", "channelkey", NULL); - entry->channelkey = config_get_string(parameter, "channelkey", defchannelkey); - - entry->nickname = config_get_string(parameter, "nickname", NULL); - if (entry->nickname == NULL) { - log_print(LOG_ERROR, "add_client(): client %s: no nickname given", parameter); - free(entry); - return -1; - } - - entry->session = irc_create_session(&irc_cbs); - if (entry->session == NULL) { - log_print(LOG_ERROR, "add_client(): irc_create_session()"); - free(entry); - return -1; - } - - irc_set_ctx(entry->session, entry); - irc_option_set(entry->session, LIBIRC_OPTION_STRIPNICKS); - - int ret = irc_connect(entry->session, entry->server, 6667, NULL, entry->nickname, NULL, NULL); - if (ret != 0) { - log_print(LOG_ERROR, "add_client(): irc_connect(): %s", irc_strerror(irc_errno(entry->session))); - irc_destroy_session(entry->session); - free(entry); - return -1; - } - - /* HACK: irc_connect() sets errno */ - errno = 0; - - log_print(LOG_DEBUG, "added client %s (server: %s chan: %s nickname: %s)", - parameter, entry->server, entry->channel, entry->nickname); - - list_add(&entry->list, &client_list); - return 0; -} - -static void event_connect(irc_session_t *session, const char *event, const char *origin, const char **params, unsigned int count) -{ - struct irc_client *entry = (struct irc_client *) irc_get_ctx(session); - irc_cmd_join(session, entry->channel, entry->channelkey); -} - -static void event_join(irc_session_t *session, const char *event, const char *origin, const char **params, unsigned int count) -{ - struct irc_client *entry = (struct irc_client *) irc_get_ctx(session); - irc_cmd_user_mode(session, "+i"); - - char buf[32] = { "forwarding from " }; - inet_ntop(AF_INET, &entry->addr, buf +16, 16); - - irc_cmd_msg(session, params[0], buf); -} - -static void event_numeric(irc_session_t *session, unsigned int event, const char *origin, const char **params, unsigned int count) -{ - if (event < 400) - return; - - log_print(LOG_INFO, "EVENT %d: %s: %s %s %s %s", - event, (origin ? origin : "unknown"), params[0], - count > 1 ? params[1] : "", - count > 2 ? params[2] : "", - count > 3 ? params[3] : ""); -} +#include "sockaddr.h" int main(int argc, char *argv[]) { - char *config = DEFAULT_CONFIG; - int code, arg = 0, debug = 0; + struct irc_session *session = irc_create_session(); - do { - code = getopt_long(argc, argv, "c:dh", opts, &arg); + parse_sockaddr("83.140.172.211:6667", &session->srv_addr); + session->server_pass = NULL; + session->nickname = "logtest_"; +// session->username = "logtest_"; +// session->realname = "logtest_"; - switch (code) { - case 'c': /* config */ - config = optarg; - break; + irc_connect(session); - case 'd': /* debug */ - debug = 1; - break; + event_loop(); - case 'h': /* help */ - printf("Usage: ctstat [options]\n" - "Options: \n" - " --config -c configfile use this configfile\n" - " --debug -d do not fork and log to stderr\n" - " --help -h this help\n" - "\n"); - exit(0); - break; - - case '?': /* error */ - exit(-1); - break; - - default: /* unknown / all options parsed */ - break; - } - } while (code != -1); - - /* parse config file */ - if (config_parse(config)) - exit(1); - - int sock = socket(PF_INET, SOCK_DGRAM, 0); - if (sock < 0) { - log_print(LOG_ERROR, "socket()"); - return -1; - } - - struct sockaddr_in addr = { - .sin_family = AF_INET, - .sin_port = htons(514), - .sin_addr.s_addr = INADDR_ANY, - }; - - if (bind(sock, (struct sockaddr *)&addr, sizeof(addr)) < 0) { - log_print(LOG_ERROR, "bind()"); - return -1; - } - - irc_cbs.event_connect = event_connect; - irc_cbs.event_join = event_join; - irc_cbs.event_numeric = event_numeric; - - int cnt = config_get_strings("global", "client", add_client, NULL); - if (cnt == 0) { - log_print(LOG_ERROR, "no clients defined"); - close(sock); - exit(1); - } - - char buf[1500]; - while (1) { - fd_set rd_fds, wr_fds; - FD_ZERO(&rd_fds); - FD_ZERO(&wr_fds); - - FD_SET(sock, &rd_fds); - - int maxfd = sock; - struct irc_client *entry; - list_for_each_entry(entry, &client_list, list) { - int ret = irc_add_select_descriptors(entry->session, &rd_fds, &wr_fds, &maxfd); - if (ret != 0) - log_print(LOG_WARN, "irc_add_select_descriptors(): %s", irc_strerror(irc_errno(entry->session))); - } - - select(maxfd +1, &rd_fds, &wr_fds, NULL, NULL); - - int len = 0; - if (FD_ISSET(sock, &rd_fds)) { - unsigned int i = sizeof(addr); - len = recvfrom(sock, buf, sizeof(buf), 0, (struct sockaddr *)&addr, &i); - } - - list_for_each_entry(entry, &client_list, list) { - if (len > 0 && (entry->addr.s_addr == addr.sin_addr.s_addr)) - irc_cmd_msg(entry->session, entry->channel, buf); - - int ret = irc_process_select_descriptors(entry->session, &rd_fds, &wr_fds); - if (ret != 0) - log_print(LOG_WARN, "irc_process_select_descriptors(): %s", irc_strerror(irc_errno(entry->session))); - } - } return 0; } diff --git a/ircsession.c b/ircsession.c new file mode 100644 index 0000000..ccb6334 --- /dev/null +++ b/ircsession.c @@ -0,0 +1,121 @@ +#include +#include +#include +#include + +#include "event.h" +#include "ircsession.h" +#include "linebuffer.h" +#include "logging.h" +#include "sockaddr.h" +#include "tcpsocket.h" + +struct irc_session * irc_create_session(void) +{ + struct irc_session *session = malloc(sizeof(struct irc_session)); + if (session == NULL) + return NULL; + + session->state = IRC_NONE; + session->inbuf = create_linebuffer(4096); + session->outbuf = create_linebuffer(4096); + + return session; +} + +void irc_destroy_session(struct irc_session *session) +{ + linebuffer_free(session->outbuf); + linebuffer_free(session->inbuf); + free(session); +} + +static int irc_write_cb(int fd, void *privdata) +{ + struct irc_session *session = (struct irc_session *)privdata; + linebuffer_writefd(session->outbuf, fd); + + /* remove write fd again */ + event_add_writefd(session->handler, 0, NULL, NULL); + return 0; +} + +int irc_send(struct irc_session *session, const char *fmt, ...) +{ + va_list az; + va_start(az, fmt); + linebuffer_vprintf(session->outbuf, fmt, az); + va_end (az); + + linebuffer_put(session->outbuf, "\r\n", 2); + + /* schedule a write */ + event_add_writefd(session->handler, 0, irc_write_cb, session); + return 0; +} + +static int irc_read_cb(int fd, void *privdata) +{ + struct irc_session *session = (struct irc_session *)privdata; + + int len = linebuffer_readfd(session->inbuf, fd); + if (len <= 0) { + session->state = IRC_DISCONNECTED; + log_print(LOG_DEBUG, "irc_read_cb(): disconnected from %s", get_sockaddr_buf(&session->srv_addr)); + return -1; + } + + char *line; + while ((line = linebuffer_getline(session->inbuf, NULL)) != NULL) { + log_print(LOG_DEBUG, "irc_read_cb(): from %s: %s", get_sockaddr_buf(&session->srv_addr), line); + + if (strncmp(line, "PING", 4) == 0) + irc_send(session, "PONG %s", line +6); + + linebuffer_freeline(session->inbuf); + } + + return 0; +} + +static int irc_connect_cb(int fd, void *privdata) +{ + struct irc_session *session = (struct irc_session *)privdata; + + if (tcp_connect_error(fd)) { + session->state = IRC_CONNECTION_FAILED; + log_print(LOG_DEBUG, "irc_connect_cb(): failed to connect to %s", get_sockaddr_buf(&session->srv_addr)); + return -1; + } + + session->state = IRC_CONNECTED; + event_add_readfd(session->handler, 0, irc_read_cb, session); + event_add_writefd(session->handler, 0, NULL, NULL); + + log_print(LOG_DEBUG, "irc_connect_cb(): connected to %s", get_sockaddr_buf(&session->srv_addr)); + + if (session->server_pass != NULL) + irc_send(session, "PASS %s", session->server_pass); + + irc_send(session, "NICK %s", session->nickname); + irc_send(session, "USER %s unknown unknown :%s", + session->username ? session->username : "nobody", + session->realname ? session->realname : "noname"); + + return 0; +} + +int irc_connect(struct irc_session *session) +{ + /* TODO: check state before connecting */ + + session->sock = tcp_connect_nonblock(&session->srv_addr); + if (session->sock < 0) + return -1; + + session->state = IRC_CONNECTING; + session->handler = event_add_writefd(NULL, session->sock, irc_connect_cb, session); + + log_print(LOG_DEBUG, "irc_connect(): connecting to %s", get_sockaddr_buf(&session->srv_addr)); + return 0; +} diff --git a/ircsession.h b/ircsession.h new file mode 100644 index 0000000..e7e80c7 --- /dev/null +++ b/ircsession.h @@ -0,0 +1,34 @@ +#ifndef _IRCSESSION_H_ +#define _IRCSESSION_H_ + +#include + +struct irc_session { + struct sockaddr_in srv_addr; + struct event_fd *handler; + int sock; + + int state; + struct linebuffer *inbuf; + struct linebuffer *outbuf; + + char *server_pass; + char *nickname; + char *username; + char *realname; +}; + +enum { + IRC_NONE = 0, + IRC_CONNECTING, + IRC_CONNECTION_FAILED, + IRC_CONNECTED, + IRC_DISCONNECTED, +}; + +struct irc_session * irc_create_session(void); +void irc_destroy_session(struct irc_session *session); + +int irc_connect(struct irc_session *session); + +#endif /* _IRCSESSION_H_ */ diff --git a/linebuffer.c b/linebuffer.c new file mode 100644 index 0000000..c8f7d56 --- /dev/null +++ b/linebuffer.c @@ -0,0 +1,135 @@ +#include +#include +#include +#include +#include + +#include "linebuffer.h" + +#define MIN(a,b) (((a) < (b)) ? (a) : (b)) + +struct linebuffer * create_linebuffer(int size) +{ + struct linebuffer *buf = malloc(sizeof(struct linebuffer)); + if (buf == NULL) + return NULL; + + buf->size = size; + buf->data = malloc(buf->size); + if (buf->data == NULL) { + free(buf); + return NULL; + } + + buf->pos = 0; + return buf; +} + +void linebuffer_free(struct linebuffer *buf) +{ + free(buf->data); + free(buf); +} + +int linebuffer_clear(struct linebuffer *buf) +{ + int oldpos = buf->pos; + buf->pos = 0; + return oldpos; +} + +int linebuffer_put(struct linebuffer *buf, const char *src, unsigned int size) +{ + int len = MIN(buf->size - buf->pos, size); + memcpy(buf->data + buf->pos, src, len); + buf->pos += len; + return len; +} + +int linebuffer_readfd(struct linebuffer *buf, int fd) +{ + int len = read(fd, buf->data + buf->pos, buf->size - buf->pos); + if (len <= 0) + return -1; + + buf->pos += len; + return len; +} + +int linebuffer_parsefd(struct linebuffer *buf, int fd) +{ + char tmp[32]; + int len = read(fd, tmp, sizeof(tmp)); + if (len <= 0) + return -1; + + int i; + for (i = 0; i < len; i++) { + /* "understand" backspace */ + if (tmp[i] == 0x08 && buf->pos > 0) { + buf->pos--; + + /* copy */ + } else if (tmp[i] >= ' ' || tmp[i] == '\n') { + *(buf->data + buf->pos++) = tmp[i]; + } + + if (buf->pos > buf->size) + return -1; + } + return 0; +} + +int linebuffer_writefd(struct linebuffer *buf, int fd) +{ + int len = write(fd, buf->data, buf->pos); + if (len <= 0) + return -1; + + /* TODO: now assuming everything is written */ + buf->pos = 0; + return len; +} + +int linebuffer_vprintf(struct linebuffer *buf, const char *fmt, va_list az) +{ + int len = vsnprintf(buf->data + buf->pos, buf->size - buf->pos, fmt, az); + if (len < 0 || len >= (buf->size - buf->pos)) + return -1; + + buf->pos += len; + return len; +} + +int linebuffer_printf(struct linebuffer *buf, const char *fmt, ...) +{ + va_list az; + + va_start(az, fmt); + int ret = linebuffer_vprintf(buf, fmt, az); + va_end(az); + + return ret; +} + +char * linebuffer_getline(struct linebuffer *buf, int *len) +{ + buf->newline = memchr(buf->data, '\n', buf->pos); + if (buf->newline == NULL) + return NULL; + + *(buf->newline) = '\0'; + + if (len != NULL) + *len = buf->newline - buf->data; + + return buf->data; +} + +int linebuffer_freeline(struct linebuffer *buf) +{ + buf->pos -= (buf->newline - buf->data) +1; + memmove(buf->data, buf->newline +1, buf->pos); + buf->newline = NULL; + return buf->pos; +} diff --git a/linebuffer.h b/linebuffer.h new file mode 100644 index 0000000..022db21 --- /dev/null +++ b/linebuffer.h @@ -0,0 +1,30 @@ +#ifndef _LINEBUFFER_H_ +#define _LINEBUFFER_H_ + +#include + +struct linebuffer { + unsigned int size; + unsigned int pos; + + char *newline; + char *data; +}; + +struct linebuffer * create_linebuffer(int size); +void linebuffer_free(struct linebuffer *buf); + +int linebuffer_clear(struct linebuffer *buf); + +int linebuffer_readfd(struct linebuffer *buf, int fd); +int linebuffer_parsefd(struct linebuffer *buf, int fd); +int linebuffer_writefd(struct linebuffer *buf, int fd); + +int linebuffer_put(struct linebuffer *buf, const char *src, unsigned int size); +int linebuffer_vprintf(struct linebuffer *buf, const char *fmt, va_list ap); +int linebuffer_printf(struct linebuffer *buf, const char *fmt, ...); + +char * linebuffer_getline(struct linebuffer *buf, int *len); +int linebuffer_freeline(struct linebuffer *buf); + +#endif /* _LINEBUFFER_H_ */ diff --git a/sockaddr.c b/sockaddr.c new file mode 100644 index 0000000..d2ed9d1 --- /dev/null +++ b/sockaddr.c @@ -0,0 +1,99 @@ +#include +#include +#include + +#include +#include +#include +#include + +int parse_sockaddr(const char *addr, struct sockaddr_in *sa) +{ + char *buf = strdup(addr); + if (buf == NULL) + return -1; + + char *tmp; + char *ipstr = strtok_r(buf, ":", &tmp); + if (ipstr == NULL) { + free(buf); + return -2; + } + + sa->sin_family = AF_INET; + if (inet_pton(AF_INET, ipstr, &sa->sin_addr) <= 0) { + free(buf); + return -3; + } + + char *portstr = strtok_r(NULL, " \r\n", &tmp); + if (portstr == NULL) { + free(buf); + return -4; + } + + int port = atoi(portstr); + if (port < 0 || port > 65535) { + free(buf); + return -5; + } + + sa->sin_port = htons(port); + free(buf); + return 0; +} + +int parse_subnet(const char *addr, struct in_addr *net, struct in_addr *mask) +{ + char *buf = strdup(addr); + if (buf == NULL) + return -1; + + char *tmp; + char *netstr = strtok_r(buf, "/", &tmp); + if (netstr == NULL) { + free(buf); + return -2; + } + + if (inet_pton(AF_INET, netstr, net) <= 0) { + free(buf); + return -3; + } + + char *maskstr = strtok_r(NULL, " \r\n", &tmp); + if (maskstr == NULL) { + mask->s_addr = ~0; + + } else if (inet_pton(AF_INET, maskstr, mask) <= 0) { + int maskbits = atoi(maskstr); + if (maskbits < 0 || maskbits > 32) { + free(buf); + return -4; + } + + mask->s_addr = htonl(~0 << (32 - maskbits)); + } + + free(buf); + return 0; +} + +int get_sockaddr(char *buf, int size, struct sockaddr_in *addr) +{ + return snprintf(buf, size, "%s:%d", inet_ntoa(addr->sin_addr), ntohs(addr->sin_port)); +} + +char * get_sockaddr_buf(struct sockaddr_in *addr) +{ + static char ret[24]; + get_sockaddr(ret, sizeof(ret), addr); + return ret; +} + +int same_sockaddr(struct sockaddr_in *a, struct sockaddr_in *b) +{ + return !((a->sin_family ^ b->sin_family) | + (a->sin_addr.s_addr ^ b->sin_addr.s_addr) | + (a->sin_port ^ b->sin_port)); +} diff --git a/sockaddr.h b/sockaddr.h new file mode 100644 index 0000000..6d1d936 --- /dev/null +++ b/sockaddr.h @@ -0,0 +1,14 @@ +#ifndef _SOCKADDR_H_ +#define _SOCKADDR_H_ + +#include + +int parse_sockaddr(const char *addr, struct sockaddr_in *sa); +int parse_subnet(const char *addr, struct in_addr *net, struct in_addr *mask); + +int get_sockaddr(char *buf, int size, struct sockaddr_in *addr); +char * get_sockaddr_buf(struct sockaddr_in *addr); + +int same_sockaddr(struct sockaddr_in *a, struct sockaddr_in *b); + +#endif /* _SOCKADDR_H_ */ diff --git a/tcpsocket.c b/tcpsocket.c new file mode 100644 index 0000000..b5a33e9 --- /dev/null +++ b/tcpsocket.c @@ -0,0 +1,117 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#include "logging.h" +#include "sockaddr.h" + +int tcp_listen(struct sockaddr_in *sa) +{ + int sock = socket(AF_INET, SOCK_STREAM, 0); + if (sock < 0 ) { + log_print(LOG_ERROR, "tcp_listen_socket(): socket()"); + return -1; + } + + int i = 1; + if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &i, sizeof(i))) { + log_print(LOG_ERROR, "tcp_listen_socket(): setsockopt(SO_REUSEADDR)"); + close(sock); + return -1; + } + + if (bind(sock, (struct sockaddr *)sa, sizeof(*sa))) { + log_print(LOG_ERROR, "tcp_listen_socket(): bind(%s)", get_sockaddr_buf(sa)); + close(sock); + return -1; + } + + if (listen(sock, 8)) { + log_print(LOG_ERROR, "tcp_listen_socket(): listen()"); + close(sock); + return -1; + } + return sock; +} + +int tcp_connect(struct sockaddr_in *sa) +{ + int sock = socket(AF_INET, SOCK_STREAM, 0); + if (sock < 0 ) { + log_print(LOG_ERROR, "tcp_connect_socket(): socket()"); + return -1; + } + + int ret = connect(sock, (struct sockaddr *)sa, sizeof(*sa)); + if (ret != 0) { + log_print(LOG_ERROR, "tcp_connect(): connect(%s)", get_sockaddr_buf(sa)); + close(sock); + return -1; + } + + return sock; +} + +int tcp_connect_nonblock(struct sockaddr_in *sa) +{ + int sock = socket(AF_INET, SOCK_STREAM, 0); + if (sock < 0 ) { + log_print(LOG_ERROR, "tcp_connect_nonblock(): socket()"); + return -1; + } + + int flags = fcntl(sock, F_GETFL, 0); + if (flags < 0) { + log_print(LOG_ERROR, "tcp_connect_nonblock(): fcntl(F_GETFL)"); + close(sock); + return -1; + } + + /* non-blocking connect() */ + if (fcntl(sock, F_SETFL, flags | O_NONBLOCK)) { + log_print(LOG_ERROR, "tcp_connect_nonblock(): fcntl(F_SETFL)"); + close(sock); + return -1; + } + + int ret = connect(sock, (struct sockaddr *)sa, sizeof(*sa)); + if (ret && errno != EINPROGRESS) { + log_print(LOG_ERROR, "tcp_connect_nonblock(): connect(%s)", get_sockaddr_buf(sa)); + close(sock); + return -1; + } + + /* reset EINPROGRESS */ + errno = 0; + + /* all further actions are blocking */ + if (fcntl(sock, F_SETFL, flags)) { + log_print(LOG_ERROR, "tcp_connect_nonblock(): fcntl(F_SETFL)"); + close(sock); + return -1; + } + + return sock; +} + +int tcp_connect_error(int fd) +{ + int err; + unsigned int err_size = sizeof(err); + if (getsockopt(fd, SOL_SOCKET, SO_ERROR, &err, &err_size)) { + log_print(LOG_ERROR, "tcp_connect_error(): getsockopt(SO_ERROR)"); + return -1; + } + + if (err) { + errno = err; + return -1; + } + + return 0; +} diff --git a/tcpsocket.h b/tcpsocket.h new file mode 100644 index 0000000..606dbab --- /dev/null +++ b/tcpsocket.h @@ -0,0 +1,12 @@ +#ifndef _TCPSOCKET_H_ +#define _TCPSOCKET_H_ + +#include + +int tcp_listen(struct sockaddr_in *sa); +int tcp_connect(struct sockaddr_in *sa); + +int tcp_connect_nonblock(struct sockaddr_in *sa); +int tcp_connect_error(int fd); + +#endif // _TCPSOCKET_H_