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_