diff --git a/src/csocket.c b/src/csocket.c new file mode 100644 index 0000000..65e1995 --- /dev/null +++ b/src/csocket.c @@ -0,0 +1,241 @@ +/* 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 "cmsg.h" +#include "csocket.h" +#include "log.h" + +int +f2b_csocket_create(const char *path) { + struct sockaddr_un addr; + int csock = -1; + int flags = -1; + + assert(path != NULL); + + if ((csock = socket(AF_UNIX, SOCK_DGRAM, 0)) < 0) { + f2b_log_msg(log_error, "can't create control socket: %s", strerror(errno)); + return -1; + } + + memset(&addr, 0x0, sizeof(addr)); + addr.sun_family = AF_UNIX; + strlcpy(addr.sun_path, path, sizeof(addr.sun_path) - 1); + + if ((flags = fcntl(csock, F_GETFL, 0)) < 0) + return -1; + if (fcntl(csock, F_SETFL, flags | O_NONBLOCK) < 0) { + f2b_log_msg(log_error, "can't set non-blocking mode on socket: %s", strerror(errno)); + return -1; + } + + unlink(path); + if (bind(csock, (struct sockaddr *) &addr, sizeof(struct sockaddr_un)) != 0) { + f2b_log_msg(log_error, "bind() on socket failed: %s", strerror(errno)); + return -1; + } + + return csock; +} + +void +f2b_csocket_destroy(int sock, const char *path) { + assert(path != NULL); + + if (sock >= 0) + close(sock); + unlink(path); + + return; +} + +int +f2b_csocket_connect(const char *spath, const char *cpath) { + struct sockaddr_un caddr, saddr; + int csock = -1; + + assert(spath != NULL); + assert(cpath != NULL); + + memset(&saddr, 0x0, sizeof(caddr)); + memset(&caddr, 0x0, sizeof(saddr)); + + caddr.sun_family = AF_LOCAL; + strlcpy(caddr.sun_path, cpath, sizeof(caddr.sun_path)); + saddr.sun_family = AF_LOCAL; + strlcpy(saddr.sun_path, spath, sizeof(saddr.sun_path)); + + if ((csock = socket(AF_UNIX, SOCK_DGRAM, 0)) < 0) { + f2b_log_msg(log_error, "can't create control socket"); + return -1; + } + + if (bind(csock, (struct sockaddr *) &caddr, sizeof(struct sockaddr_un)) != 0) { + f2b_log_msg(log_error, "bind() to local side of socket failed: %s", strerror(errno)); + return -1; + } + + if (connect(csock, (struct sockaddr *) &saddr, sizeof(struct sockaddr_un)) != 0) { + f2b_log_msg(log_error, "connect() to socket failed: %s", strerror(errno)); + return -1; + } + + return csock; +} + +void +f2b_csocket_disconnect(int sock, const char *cpath) { + unlink(cpath); + if (sock >= 0) + close(sock); + return; +} + +/** + * @brief Recieve and unpack control message + * @param csock Opened socket fd + * @param cmsg Control message pointer + * @param addr Pointer for sender address store + * @param addrlen Size of address storage + * @return >0 on success, 0 on no avalilable messages, <0 on error + */ +int +f2b_csocket_recv(int csock, f2b_cmsg_t *cmsg, struct sockaddr_storage *addr, socklen_t *addrlen) { + struct msghdr msg; + uint16_t size; + int ret; + + assert(csock >= 0); + assert(cmsg != NULL); + + struct iovec iov[] = { + { .iov_len = sizeof(cmsg->magic), .iov_base = &cmsg->magic[0] }, + { .iov_len = sizeof(cmsg->version), .iov_base = &cmsg->version }, + { .iov_len = sizeof(cmsg->type), .iov_base = &cmsg->type }, + { .iov_len = sizeof(cmsg->flags), .iov_base = &cmsg->flags }, + { .iov_len = sizeof(cmsg->size), .iov_base = &size /* need ntohs */ }, + { .iov_len = sizeof(cmsg->pass), .iov_base = &cmsg->pass[0] }, + { .iov_len = sizeof(cmsg->data), .iov_base = &cmsg->data[0] }, + }; + + memset(&msg, 0x0, sizeof(msg)); + msg.msg_name = addr; + msg.msg_namelen = *addrlen; + msg.msg_iov = iov; + msg.msg_iovlen = 7; + + ret = recvmsg(csock, &msg, 0); + if (ret < 0 && errno == EAGAIN) + return 0; /* non-blocking mode & no messages */ + if (ret < 0) { + f2b_log_msg(log_error, "recvmsg(): %s", strerror(errno)); + return ret; + } + if (msg.msg_flags & MSG_TRUNC) { + f2b_log_msg(log_warn, "damaged cmsg on socket: truncated"); + return -1; + } + if (memcmp(cmsg->magic, "F2B", 3) != 0) { + f2b_log_msg(log_warn, "damaged cmsg on socket: no magic"); + return -1; + } + if (cmsg->version != F2B_PROTO_VER) { + f2b_log_msg(log_warn, "damaged cmsg on socket: version mismatch"); + return -1; + } + if (cmsg->type >= CMD_MAX_NUMBER) { + f2b_log_msg(log_warn, "damaged cmsg on socket: unknown command type"); + return -1; + } + cmsg->size = ntohs(size); + if (ret != (cmsg->size + 16)) { + f2b_log_msg(log_warn, "damaged cmsg on socket: expected %u bytes, got %u", cmsg->size + 16, ret); + return -1; + } + *addrlen = msg.msg_namelen; + + return ret; +} + +/** + * @brief Pack and send control message + * @param csock Opened socket fd + * @param cmsg Control message pointer + * @param addr Pointer for destination address store + * @param addrlen Size of address storage + * @return >0 on success + */ +int +f2b_csocket_send(int csock, f2b_cmsg_t *cmsg, struct sockaddr_storage *addr, socklen_t *addrlen) { + struct msghdr msg; + uint16_t size; + int ret; + + assert(csock >= 0); + assert(cmsg != NULL); + + struct iovec iov[] = { + { .iov_len = sizeof(cmsg->magic), .iov_base = &cmsg->magic[0] }, + { .iov_len = sizeof(cmsg->version), .iov_base = &cmsg->version }, + { .iov_len = sizeof(cmsg->type), .iov_base = &cmsg->type }, + { .iov_len = sizeof(cmsg->flags), .iov_base = &cmsg->flags }, + { .iov_len = sizeof(cmsg->size), .iov_base = &size /* need htons */ }, + { .iov_len = sizeof(cmsg->pass), .iov_base = &cmsg->pass[0] }, + { .iov_len = cmsg->size, .iov_base = &cmsg->data[0] }, + }; + size = htons(cmsg->size); + + memset(&msg, 0x0, sizeof(msg)); + msg.msg_name = addr; + msg.msg_namelen = *addrlen; + msg.msg_iov = iov; + msg.msg_iovlen = 7; + + if ((ret = sendmsg(csock, &msg, 0)) <= 0) + f2b_log_msg(log_error, "sendmsg(): %s", strerror(errno)); + + return ret; +} + +/** + * @return -1 on error, 0 on no messages, and > 0 on some messages processed + */ +int +f2b_csocket_poll(int csock, void (*cb)(const f2b_cmsg_t *cmsg, char *res, size_t ressize)) { + char res[DATA_LEN_MAX + 1]; + f2b_cmsg_t cmsg; + struct sockaddr_storage addr; + socklen_t addrlen; + int ret, processed = 0; + + assert(csock >= 0); + assert(cb != NULL); + + while (1) { + memset(&cmsg, 0x0, sizeof(cmsg)); + memset(&addr, 0x0, sizeof(addr)); + addrlen = sizeof(addr); + ret = f2b_csocket_recv(csock, &cmsg, &addr, &addrlen); + if (ret <= 0) + break; /* no messages or error */ + /* TODO: check auth */ + cb(&cmsg, res, sizeof(res)); + if (cmsg.flags & CMSG_FLAG_NEED_REPLY) { + memset(&cmsg, 0x0, sizeof(cmsg)); + strncpy(cmsg.magic, "F2B", sizeof(cmsg.magic)); + strlcpy(cmsg.data, res, sizeof(cmsg.data)); + cmsg.version = F2B_PROTO_VER; + cmsg.type = CMD_RESP; + cmsg.size = strlen(res); + cmsg.data[cmsg.size] = '\0'; + ret = f2b_csocket_send(csock, &cmsg, &addr, &addrlen); + } + processed++; + } + + return processed; +} diff --git a/src/csocket.h b/src/csocket.h new file mode 100644 index 0000000..3ac1178 --- /dev/null +++ b/src/csocket.h @@ -0,0 +1,24 @@ +/* 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. + */ +#ifndef F2B_CSOCKET_H_ +#define F2B_CSOCKET_H_ + +#include +#include +#include + +int f2b_csocket_create (const char *path); +void f2b_csocket_destroy(int csock, const char *path); + +int f2b_csocket_connect(const char *spath, const char *cpath); +void f2b_csocket_disconnect(int sock, const char *cpath); + +int f2b_csocket_poll(int csock, void (*cb)(const f2b_cmsg_t *cmsg, char *res, size_t ressize)); +int f2b_csocket_send(int csock, f2b_cmsg_t *cmsg, struct sockaddr_storage *addr, socklen_t *socklen); +int f2b_csocket_recv(int csock, f2b_cmsg_t *cmsg, struct sockaddr_storage *addr, socklen_t *socklen); + +#endif /* F2B_CSOCKET_H_ */