From 33bb5b8775be1543533366ed8d04405cefd08e14 Mon Sep 17 00:00:00 2001 From: Lorenzo Torres Date: Tue, 20 Aug 2024 04:57:58 +0200 Subject: [PATCH] TLS1.2 and command execution --- .gitignore | 2 + Makefile | 6 +- config.def.h | 17 ++++ config.mk | 6 +- imap.c | 239 ++++++++++++++++++++++++++++++++++++++++++++++++-- imap.h | 36 +++++++- imap.routines | 77 ++++++++++++++++ sis.c | 1 + utils.c | 59 +++++++++++++ utils.h | 35 ++++++++ 10 files changed, 459 insertions(+), 19 deletions(-) create mode 100644 imap.routines create mode 100644 utils.c create mode 100644 utils.h diff --git a/.gitignore b/.gitignore index 8ba9850..3db0f54 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,6 @@ config.h **/*~ **/*.o **/*.core +**/*.pem +vgcore* sis diff --git a/Makefile b/Makefile index 5935761..0c31815 100644 --- a/Makefile +++ b/Makefile @@ -3,8 +3,8 @@ include config.mk -SRC = sis.c imap.c -HDR = config.def.h +SRC = sis.c imap.c utils.c +HDR = config.def.h imap.h utils.h imap.routines OBJ = ${SRC:.c=.o} all: options sis @@ -18,7 +18,7 @@ options: .c.o: ${CC} -c ${CFLAGS} $< -${OBJ}: config.h config.mk +${OBJ}: config.h imap.routines config.mk config.h: cp config.def.h $@ diff --git a/config.def.h b/config.def.h index 66660c7..e4eb0c8 100644 --- a/config.def.h +++ b/config.def.h @@ -3,6 +3,7 @@ /* network */ #define IMAP_PORT 143 #define IMAPS_PORT 993 +#define TLS_ENABLED 1 /*- * Maximum number of connected clients, * NOTE: each one of these is a currently @@ -20,3 +21,19 @@ * modify this. */ #define CMD_MAX_SIZE 8000 + +static char *imap_capabilities[] = { + "IMAP4rev1", + "STARTTLS", + "AUTH=GSSAPI", + "LOGINDISABLED", + NULL +}; + +static char *imaps_capabilities[] = { + "IMAP4rev1", + "STARTTLS", + "AUTH=GSSAPI", + "AUTH=PLAIN", + NULL +}; diff --git a/config.mk b/config.mk index 24ca323..518989f 100644 --- a/config.mk +++ b/config.mk @@ -12,11 +12,11 @@ MANPREFIX = ${PREFIX}/share/man # includes and libs INCS = -I. -LIBS = +LIBS = -lssl -lcrypto # flags CPPFLAGS = -DVERSION=\"${VERSION}\" -CFLAGS = -g -std=c99 -pedantic -Wall -O0 ${INCS} ${CPPFLAGS} -#CFLAGS = -std=c99 -pedantic -Wall -Wno-deprecated-declarations -Os ${INCS} ${CPPFLAGS} +CFLAGS := -std=c99 -pedantic -Wall -O0 -Wno-gnu-label-as-value -Wno-gnu-zero-variadic-macro-arguments ${INCS} ${CPPFLAGS} +CFLAGS := ${CFLAGS} -g LDFLAGS = ${LIBS} # Solaris diff --git a/imap.c b/imap.c index 464f7fa..512c08e 100644 --- a/imap.c +++ b/imap.c @@ -35,11 +35,36 @@ #include #include #include +#include #include +struct { + char *key; + uint8_t id; +} cmd_map[] = { + /* https://datatracker.ietf.org/doc/html/rfc3501#section-6.1.1 */ + { "capability", 0x00 }, + /* https://datatracker.ietf.org/doc/html/rfc3501#section-6.1.2 */ + { "noop", 0x01 }, + /* https://datatracker.ietf.org/doc/html/rfc3501#section-6.1.3 */ + { "logout", 0x02 }, + /* https://datatracker.ietf.org/doc/html/rfc3501#section-6.2.1 */ + { "starttls", 0x03 }, + /* https://datatracker.ietf.org/doc/html/rfc3501#section-6.2.2 */ + { "authenticate", 0x04 }, + /* https://datatracker.ietf.org/doc/html/rfc3501#section-6.2.3 */ + { "login", 0x05 }, + /* Invalid command */ + { NULL, 0xff } +}; + +#define CMD_MAP_LAST 0x05 + uint8_t imap_init(uint8_t daemon, imap_t *instance) { imap_t imap; + imap.ssl_ctx = NULL; + imap.ssl = 0; /* Create a new socket using IPv4 protocol */ if ((imap.socket = socket(PF_INET, SOCK_STREAM, 0)) < 0) { @@ -49,9 +74,10 @@ uint8_t imap_init(uint8_t daemon, imap_t *instance) bzero(&imap.addr, sizeof(struct sockaddr_in)); + imap.state = IMAP_STATE_NO_AUTH; imap.addr.sin_family = AF_INET; /* From config.h */ - imap.addr.sin_port = htons(IMAP_PORT); + imap.addr.sin_port = htons(TLS_ENABLED ? IMAPS_PORT : IMAP_PORT); if (INADDR_ANY) { imap.addr.sin_addr.s_addr = htonl(INADDR_ANY); @@ -80,6 +106,11 @@ uint8_t imap_init(uint8_t daemon, imap_t *instance) } } + if (TLS_ENABLED) { + imap_create_ssl_ctx(&imap); + imap_starttls(&imap, imap.clients); + } + memcpy(instance, &imap, sizeof(imap)); return 0; @@ -101,9 +132,20 @@ int imap_get_max_fd(client_list *list, int master) return max_fd > master ? max_fd : master; } -client_list *imap_add_client(client_list *list, int sock) +client_list *imap_add_client(imap_t *instance, client_list *list, int sock) { client_list *node = (client_list *) malloc(sizeof(client_list)); + /* Check if TLS is active */ + if (instance->ssl) { + node->ssl = SSL_new(instance->ssl_ctx); + SSL_set_fd(node->ssl, sock); + SSL_set_accept_state(node->ssl); + SSL_do_handshake(node->ssl); + node->fd = SSL_get_fd(node->ssl); + } else { + node->fd = sock; + } + node->socket = sock; node->next = list; node->prev = NULL; @@ -114,7 +156,7 @@ client_list *imap_add_client(client_list *list, int sock) return node; } -client_list *imap_remove_client(client_list *list, client_list *node) +client_list *imap_remove_client(imap_t *instance, client_list *list, client_list *node) { if (node->next != NULL) { node->next->prev = node->prev; @@ -124,6 +166,12 @@ client_list *imap_remove_client(client_list *list, client_list *node) node->prev->next = node->next; } + /* Check if TLS is active */ + if (instance->ssl) { + SSL_shutdown(node->ssl); + SSL_free(node->ssl); + } + close(node->socket); if (node == list) { @@ -133,7 +181,7 @@ client_list *imap_remove_client(client_list *list, client_list *node) return list; } -client_list *imap_remove_sock(client_list *list, int sock) +client_list *imap_remove_sock(imap_t *instance, client_list *list, int sock) { client_list *node = list; @@ -142,12 +190,24 @@ client_list *imap_remove_sock(client_list *list, int sock) } if (node != NULL) { - imap_remove_client(list, node); + imap_remove_client(instance, list, node); } return node; } +void imap_starttls(imap_t *imap, client_list *list) +{ + imap->ssl = 1; + + while (list != NULL) { + list->ssl = SSL_new(imap->ssl_ctx); + list->fd = SSL_get_fd(list->ssl); + SSL_set_fd(list->ssl, list->socket); + list = list->next; + } +} + void imap_start(imap_t *instance) { int activity, max_fd, connection; @@ -188,7 +248,7 @@ void imap_start(imap_t *instance) exit(EXIT_FAILURE); } - instance->clients = imap_add_client(instance->clients, connection); + instance->clients = imap_add_client(instance, instance->clients, connection); syslog(LOG_INFO, "Connection enstablished."); FD_SET(connection, &fds); } @@ -201,20 +261,33 @@ void imap_start(imap_t *instance) if (FD_ISSET(connection, &fds)) { /* Error occured. */ - if ((bytes_read = read(connection, buf, CMD_MAX_SIZE)) < 0) { + if ((bytes_read = imap_read(node, buf, CMD_MAX_SIZE, instance->ssl)) < 0) { perror("recv"); - instance->clients = imap_remove_client(instance->clients, node); + instance->clients = imap_remove_client(instance, instance->clients, node); free(node); FD_CLR(connection, &fds); syslog(LOG_ERR, "Failed to receive data."); /* Somebody disconnected */ } else if (bytes_read == 0) { - instance->clients = imap_remove_client(instance->clients, node); + instance->clients = imap_remove_client(instance, instance->clients, node); free(node); FD_CLR(connection, &fds); syslog(LOG_INFO, "Connection closed."); } else { buf[bytes_read] = '\0'; + imap_cmd cmd = imap_parse_cmd(buf); + uint8_t res = imap_cmd_exec(cmd, node, instance->ssl, instance->state); + if (res == IMAP_LOGOUT) { + instance->clients = imap_remove_client(instance, instance->clients, node); + free(node); + FD_CLR(connection, &fds); + syslog(LOG_INFO, "Client logout."); + } else if (res == IMAP_STARTTLS) { + imap_starttls(instance, instance->clients); + } + if (cmd.params != NULL) { + free(cmd.params); + } } } @@ -235,5 +308,153 @@ void imap_close(imap_t *instance) } close(instance->socket); + SSL_CTX_free(instance->ssl_ctx); free(instance); } + +uint8_t imap_match_cmd(char *cmd, size_t len) +{ + strnlower(cmd, len); + + for (int i=0; cmd_map[i].key != NULL; i++) { + if (strncmp(cmd_map[i].key, cmd, len) == 0) { + return cmd_map[i].id; + } + } + + return 0xff; +} + +imap_cmd imap_parse_cmd(char *s) +{ + imap_cmd cmd; + strstrip(s); + size_t params = 0, id_len = 0, i = 0; + char *cpy; + printf("%s\n", s); + + cmd.params = NULL; + /* Copy the first 4 characters of the command in the tag field */ + memcpy(cmd.tag, s, 4); + s += 5; + + while (*s != '\n' && *s != '\0' && *s != ' ' && *s > 0) { + s++; + id_len++; + } + + if (id_len == 0) { + cmd.id = 0xff; + return cmd; + } + + id_len -= 1; + s -= id_len+1; + cmd.id = imap_match_cmd(s, id_len); + s += id_len+2; + + char *tok; + cpy = (char *) calloc(strlen(s), sizeof(char)); + strcpy(cpy, s); + for (tok = strtok(cpy, " "); tok; tok = strtok(NULL, " ")) { + params++; + } + + free(cpy); + + if (params > 0) { + cmd.params = (char **) calloc(params, sizeof(char **)); + for (tok = strtok(s, " "); tok; tok = strtok(NULL, " ")) { + cmd.params[i] = tok; + i++; + } + cmd.p_count = params; + } + + return cmd; +} + +#include + +uint8_t imap_cmd_exec(imap_cmd cmd, client_list *node, uint8_t ssl, uint8_t state) +{ + if (cmd.id < 0x0 || cmd.id > CMD_MAP_LAST) { + return IMAP_FAIL; + } + void *routines[] = { + &&capability, + &&noop, + &&logout, + &&starttls, + &&auth, + &&login + }; + goto *routines[cmd.id]; + IMAP_ROUTINE(capability) + IMAP_ROUTINE(noop) + IMAP_ROUTINE(logout) + IMAP_ROUTINE(starttls) + IMAP_ROUTINE(auth) + IMAP_ROUTINE(login) + + return IMAP_SUCCESS; +} + +void imap_create_ssl_ctx(imap_t *imap) +{ + const SSL_METHOD *method; + + method = TLS_server_method(); + imap->ssl_ctx = SSL_CTX_new(method); + if (!imap->ssl_ctx) { + perror("Unable to create SSL context"); + ERR_print_errors_fp(stderr); + exit(EXIT_FAILURE); + } + + if (SSL_CTX_use_certificate_file(imap->ssl_ctx, "ca-cert.pem", SSL_FILETYPE_PEM) <= 0) { + ERR_print_errors_fp(stderr); + exit(EXIT_FAILURE); + } + + if (SSL_CTX_use_PrivateKey_file(imap->ssl_ctx, "ca-key.pem", SSL_FILETYPE_PEM) <= 0 ) { + ERR_print_errors_fp(stderr); + exit(EXIT_FAILURE); + } +} + +int imap_read(client_list *node, char *buf, size_t len, uint8_t ssl) +{ + if (ssl) { + return SSL_read(node->ssl, buf, len); + } else { + return read(node->socket, buf, len); + } +} + +void imap_write(client_list *node, uint8_t ssl, char *fmt, ...) +{ + va_list(args); + va_start(args, fmt); + char buf[CMD_MAX_SIZE]; + vsprintf(buf, fmt, args); + + if (!ssl) { + write(node->socket, buf, strlen(buf)); + } else { + SSL_write(node->ssl, buf, strlen(buf)); + } +} + +void imap_flush(client_list *node, uint8_t ssl) +{ + FILE *fd; + + if (!ssl) { + fd = fdopen(node->socket, "w"); + } else { + fd = fdopen(SSL_get_wfd(node->ssl), "w"); + } + + fflush(fd); +} diff --git a/imap.h b/imap.h index eeba416..c973be3 100644 --- a/imap.h +++ b/imap.h @@ -31,11 +31,22 @@ #include #include #include +#include +#include #define BACKLOG 4 +#define IMAP_SUCCESS 0x0 +#define IMAP_FAIL 0x1 +#define IMAP_LOGOUT 0x2 +#define IMAP_STARTTLS 0x3 + +#define IMAP_STATE_NO_AUTH 0x0 +#define IMAP_STATE_AUTH 0x1 +#define IMAP_STATE_SELECTED 0x2 typedef struct _client_list { - int32_t socket; + int32_t socket, fd; + SSL *ssl; struct _client_list *next; struct _client_list *prev; } client_list; @@ -45,8 +56,17 @@ typedef struct imap { int32_t socket; client_list *clients; struct sockaddr_in addr; + uint8_t state, ssl; + SSL_CTX *ssl_ctx; } imap_t; +typedef struct { + char tag[4]; + uint8_t id; + size_t p_count; + char **params; +} imap_cmd; + /* Create a new imap_t instance and initialize the server. */ uint8_t imap_init(uint8_t daemon, imap_t *instance); /* Start the IMAP server. */ @@ -54,11 +74,19 @@ void imap_start(imap_t *instance); /* Close all connections and free the allocated memory. */ void imap_close(imap_t *instance); /* Add client to client list */ -client_list *imap_add_client(client_list *list, int sock); +client_list *imap_add_client(imap_t *instance, client_list *list, int sock); /* Close connection with client */ -client_list *imap_remove_client(client_list *list, client_list *node); -client_list *imap_remove_sock(client_list *list, int sock); +client_list *imap_remove_client(imap_t *instance, client_list *list, client_list *node); +client_list *imap_remove_sock(imap_t *instance, client_list *list, int sock); /* Get the higher file descriptor in the client list */ int imap_get_max_fd(client_list *list, int master); +imap_cmd imap_parse_cmd(char *s); +uint8_t imap_match_cmd(char *cmd, size_t len); +void imap_create_ssl_ctx(imap_t *imap); +void imap_starttls(imap_t *imap, client_list *list); +int imap_read(client_list *node, char *buf, size_t len, uint8_t ssl); +void imap_write(client_list *node, uint8_t ssl, char *fmt, ...); +void imap_flush(client_list *node, uint8_t ssl); +uint8_t imap_cmd_exec(imap_cmd cmd, client_list *node, uint8_t ssl, uint8_t state); #endif /* ifndef IMAP_H */ diff --git a/imap.routines b/imap.routines new file mode 100644 index 0000000..3f90f12 --- /dev/null +++ b/imap.routines @@ -0,0 +1,77 @@ +// vim: set ft=c: + +#define IMAP_ROUTINE(name) \ +name: { \ + return imap_routine_##name(cmd, node, ssl, state); \ +} + +#define IMAP_ROUTINE_END imap_flush(node, ssl); +#define IMAP_ROUTINE_OK(routine) \ + imap_write(node, ssl, "%s OK " #routine " completed\n", cmd.tag); +#define IMAP_ROUTINE_BAD_TAG \ + imap_write(node, ssl, "%s BAD", cmd.tag); +#define IMAP_ROUTINE_BAD \ + imap_write(node, ssl, "* BAD"); +#define IMAP_STRING(fmt, ...) \ + imap_write(node, ssl, fmt, ##__VA_ARGS__); +#define IMAP_NLINE imap_write(node, ssl, "\n"); +#define IMAP_CHECK_STATE(s) \ + if (state != IMAP_STATE_##s) { \ + IMAP_ROUTINE_BAD \ + return IMAP_FAIL; \ + } + +static inline uint8_t imap_routine_capability(imap_cmd cmd, client_list *node, uint8_t ssl, uint8_t state) +{ + char *cap; + IMAP_STRING("* CAPABILITY") + if (!ssl) { + for (int i=0; (cap = imap_capabilities[i]); i++) { + IMAP_STRING(" %s", cap) + } + } else { + for (int i=0; (cap = imaps_capabilities[i]); i++) { + IMAP_STRING(" %s", cap) + } + } + IMAP_NLINE; + IMAP_ROUTINE_OK(CAPABILITY) + IMAP_ROUTINE_END + + return IMAP_SUCCESS; +} + +static inline uint8_t imap_routine_noop(imap_cmd cmd, client_list *node, uint8_t ssl, uint8_t state) +{ + IMAP_ROUTINE_OK(NOOP) + IMAP_ROUTINE_END + + return IMAP_SUCCESS; +} + +static inline uint8_t imap_routine_logout(imap_cmd cmd, client_list *node, uint8_t ssl, uint8_t state) +{ + IMAP_STRING("* BYE IMAP4rev1 Server logging out\n") + IMAP_ROUTINE_OK(LOGOUT) + IMAP_ROUTINE_END + return IMAP_LOGOUT; +} + +static inline uint8_t imap_routine_starttls(imap_cmd cmd, client_list *node, uint8_t ssl, uint8_t state) +{ + IMAP_CHECK_STATE(NO_AUTH) + + IMAP_STRING("%s OK Begin TLS negotiation now\n", cmd.tag) + IMAP_ROUTINE_END + return IMAP_STARTTLS; +} + +static inline uint8_t imap_routine_auth(imap_cmd cmd, client_list *node, uint8_t ssl, uint8_t state) +{ + return IMAP_SUCCESS; +} + +static inline uint8_t imap_routine_login(imap_cmd cmd, client_list *node, uint8_t ssl, uint8_t state) +{ + return IMAP_SUCCESS; +} diff --git a/sis.c b/sis.c index 7c4ee49..08baf9c 100644 --- a/sis.c +++ b/sis.c @@ -44,6 +44,7 @@ void int_handler(int sig) int main(void) { signal(SIGINT, int_handler); + signal(SIGPIPE, SIG_IGN); openlog("sis", LOG_PID, LOG_MAIL); syslog(LOG_INFO, "Starting sis %s.", VERSION); diff --git a/utils.c b/utils.c new file mode 100644 index 0000000..5114cf8 --- /dev/null +++ b/utils.c @@ -0,0 +1,59 @@ +/*- + * Copyright (c) 2024, Lorenzo Torres + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED ''AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include + +void strstrip(char *str) +{ + int i, x; + for (i=x=0; str[i]; ++i) { + if (!isspace(str[i]) || (i > 0 && !isspace(str[i-1]))) { + str[x++] = str[i]; + } + if (str[i] == '\t') { + str[i] = ' '; + } + if (str[i+1] == '\0' && isspace(str[i])) { + str[i] = '\0'; + } + } + str[x] = '\0'; +} + +void strlower(char* str) +{ + strnlower(str, strlen(str)); +} + +void strnlower(char *str, size_t len) +{ + for (int i=0; i < len; i++) { + str[i] = tolower(str[i]); + } +} diff --git a/utils.h b/utils.h new file mode 100644 index 0000000..25578e1 --- /dev/null +++ b/utils.h @@ -0,0 +1,35 @@ +/*- + * Copyright (c) 2024, Lorenzo Torres + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED ''AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef UTILS_H +#define UTILS_H + +void strstrip(char* str); +void strlower(char* str); +void strnlower(char *str, size_t len); + +#endif /* ifndef UTILS_H */