/* Copyright 2016 Alex 'AdUser' Z (ad_user@runbox.com) * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. */ #include "common.h" #include "buf.h" #include "log.h" #include "commands.h" #include "csocket.h" #include #include #include #include typedef struct f2b_conn_t { f2b_buf_t recv; f2b_buf_t send; int sock; } f2b_conn_t; struct f2b_csock_t { f2b_conn_t *clients[MAXCONNS]; const char *path; int sock; }; /* helpers */ static inline int max(int a, int b) { return a > b ? a : b; } /* connection-related functions */ f2b_conn_t * f2b_conn_create(size_t rbuf, size_t wbuf) { f2b_conn_t *conn = NULL; if ((conn = calloc(1, sizeof(f2b_conn_t))) != NULL) { if (f2b_buf_alloc(&conn->recv, rbuf)) { if (f2b_buf_alloc(&conn->send, wbuf)) { return conn; } f2b_buf_free(&conn->recv); } free(conn); } return NULL; } void f2b_conn_destroy(f2b_conn_t *conn) { f2b_buf_free(&conn->recv); f2b_buf_free(&conn->send); free(conn); } int f2b_conn_process(f2b_conn_t *conn, bool in, void (*cb)(const f2b_cmd_t *cmd, f2b_buf_t *res)) { f2b_cmd_t *cmd = NULL; int retval; /* handle incoming data */ if (in) { f2b_log_msg(log_debug, "some incoming data on socket %d", conn->sock); char tmp[RBUF_SIZE] = ""; char *line = NULL; ssize_t read = 0; read = recv(conn->sock, tmp, RBUF_SIZE, MSG_DONTWAIT); if (read == 0) { f2b_log_msg(log_debug, "received connection close on socket %d", conn->sock); return -1; } if (read < 0) { f2b_log_msg(log_error, "received error on sock %d: %s", conn->sock, strerror(errno)); return -1; } if (read > 0) { tmp[read] = '\0'; f2b_buf_append(&conn->recv, tmp, read); f2b_log_msg(log_debug, "received %zd bytes from socket %d", read, conn->sock); /* TODO: properly handle empty lines */ while (conn->recv.data[0] == '\n') { f2b_buf_splice(&conn->recv, 1); break; } /* extract message(s) */ while ((line = f2b_buf_extract(&conn->recv, "\n")) != NULL) { f2b_log_msg(log_debug, "extracted line: %s", line); if ((cmd = f2b_cmd_create(line)) != NULL) { cb(cmd, &conn->send); /* handle command */ f2b_cmd_destroy(cmd); } else { f2b_buf_append(&conn->send, "can't parse input\n", 0); } free(line); } if (conn->recv.used >= conn->recv.size) { f2b_log_msg(log_error, "drop connection on socket %d, recv buffer overflow", conn->sock); return -1; } } } /* handle outgoing data */ if (conn->send.used > 0) { f2b_log_msg(log_debug, "sending %zu bytes to socket %d", conn->send.used, conn->sock); retval = send(conn->sock, conn->send.data, conn->send.used, MSG_DONTWAIT); if (retval > 0) { f2b_buf_splice(&conn->send, retval); f2b_log_msg(log_debug, "sent %d bytes to socket %d (%zu remains)", retval, conn->sock, conn->send.used); } else if (retval < 0 && errno != EAGAIN) { f2b_log_msg(log_error, "can't send() to socket %d: %s", conn->sock, strerror(errno)); return -1; /* remote side closed connection */ } } return 0; } /* control socket-related functions */ f2b_csock_t * f2b_csocket_create(const char *path) { f2b_csock_t *csock; struct sockaddr_un addr; int sock = -1; assert(path != NULL); if ((sock = socket(AF_UNIX, SOCK_STREAM | SOCK_NONBLOCK, 0)) < 0) { f2b_log_msg(log_error, "can't create control socket: %s", strerror(errno)); return NULL; } memset(&addr, 0x0, sizeof(addr)); addr.sun_family = AF_UNIX; strlcpy(addr.sun_path, path, sizeof(addr.sun_path) - 1); unlink(path); if (bind(sock, (struct sockaddr *) &addr, sizeof(struct sockaddr_un)) != 0) { f2b_log_msg(log_error, "bind() on socket failed: %s", strerror(errno)); return NULL; } if (listen(sock, 5) < 0) { f2b_log_msg(log_error, "listen() on socket failed: %s", strerror(errno)); return NULL; } if ((csock = calloc(1, sizeof(f2b_csock_t))) == NULL) { f2b_log_msg(log_error, "can't allocate memory for csocket struct"); shutdown(sock, SHUT_RDWR); unlink(path); return NULL; } csock->sock = sock; csock->path = path; return csock; } void f2b_csocket_destroy(f2b_csock_t *csock) { f2b_conn_t *conn = NULL; assert(csock != NULL); if (csock->sock >= 0) shutdown(csock->sock, SHUT_RDWR); if (csock->path != NULL) unlink(csock->path); for (int i = 0; i < MAXCONNS; i++) { if ((conn = csock->clients[i]) == NULL) continue; f2b_conn_destroy(conn); csock->clients[i] = NULL; } free(csock); return; } void f2b_csocket_poll(f2b_csock_t *csock, void (*cb)(const f2b_cmd_t *cmd, f2b_buf_t *res)) { struct timeval tv = { .tv_sec = 0, .tv_usec = 0 }; fd_set rfds, wfds; f2b_conn_t *conn = NULL; int retval, nfds; assert(csock != NULL); assert(cb != NULL); /* loop / init */ FD_ZERO(&rfds); FD_ZERO(&wfds); FD_SET(csock->sock, &rfds); /* watch for new connections */ /* watch for new data on established connections */ nfds = csock->sock; for (int cnum = 0; cnum < MAXCONNS; cnum++) { if ((conn = csock->clients[cnum]) == NULL) continue; FD_SET(conn->sock, &rfds); if (conn->send.used) FD_SET(conn->sock, &wfds); nfds = max(csock->sock, conn->sock); } /* check for new data */ retval = select(nfds + 1, &rfds, &wfds, NULL, &tv); if (retval < 0) { if (errno == EINTR) { /* interrupted by signal */ } else if (errno == EAGAIN) { /* no data */ } else { f2b_log_msg(log_error, "select() error: %s", strerror(errno)); } return; } if (retval == 0) return; /* no new data */ /* new connection on listening socket? */ if (FD_ISSET(csock->sock, &rfds)) { /* find free connection slot */ int cnum = 0; for (int cnum = 0; cnum < MAXCONNS; cnum++) { if (csock->clients[cnum] == NULL) break; } int sock = -1; /* accept() new connection */ if ((sock = accept(csock->sock, NULL, NULL)) < 0) { f2b_log_msg(log_error, "can't accept() new connection: %s", strerror(errno)); } else if (cnum < MAXCONNS) { if ((conn = f2b_conn_create(RBUF_SIZE, WBUF_SIZE)) != NULL) { f2b_log_msg(log_debug, "new connection accept()ed, socket %d", sock); conn->sock = sock; csock->clients[cnum] = conn; } else { f2b_log_msg(log_error, "can't create new connection"); } } else { f2b_log_msg(log_error, "max number of clients reached, drop connection on socket %d", sock); shutdown(sock, SHUT_RDWR); } } for (int cnum = 0; cnum < MAXCONNS; cnum++) { if ((conn = csock->clients[cnum]) == NULL) continue; retval = f2b_conn_process(conn, FD_ISSET(conn->sock, &rfds), cb); if (retval < 0) { shutdown(conn->sock, SHUT_RDWR); f2b_conn_destroy(conn); csock->clients[cnum] = NULL; } } /* foreach connection(s) */ return; }