commit bc96196ffd4195895ab7118eb7073de8b8bf2103 Author: Lorenzo Torres Date: Mon Aug 19 22:49:04 2024 +0200 feat: first commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8ba9850 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +config.h +**/*.swp +**/*~ +**/*.o +**/*.core +sis diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..e42dfa0 --- /dev/null +++ b/LICENSE @@ -0,0 +1,24 @@ +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 BY ''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. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..5935761 --- /dev/null +++ b/Makefile @@ -0,0 +1,52 @@ +# sis - simple imap server +# See LICENSE file for copyright and license details. + +include config.mk + +SRC = sis.c imap.c +HDR = config.def.h +OBJ = ${SRC:.c=.o} + +all: options sis + +options: + @echo sis build options: + @echo "CFLAGS = ${CFLAGS}" + @echo "LDFLAGS = ${LDFLAGS}" + @echo "CC = ${CC}" + +.c.o: + ${CC} -c ${CFLAGS} $< + +${OBJ}: config.h config.mk + +config.h: + cp config.def.h $@ + +sis: ${OBJ} + ${CC} -o $@ ${OBJ} ${LDFLAGS} + +clean: + rm -f sis ${OBJ} sis-${VERSION}.tar.gz + +dist: clean + mkdir -p sis-${VERSION} + cp -R LICENSE Makefile README config.mk\ + sis.1 ${HDR} ${SRC} sis-${VERSION} + tar -cf sis-${VERSION}.tar sis-${VERSION} + gzip sis-${VERSION}.tar + rm -rf sis-${VERSION} + +install: all + mkdir -p ${DESTDIR}${PREFIX}/bin + cp -f sis ${DESTDIR}${PREFIX}/bin + chmod 755 ${DESTDIR}${PREFIX}/bin/sis + mkdir -p ${DESTDIR}${MANPREFIX}/man1 + sed "s/VERSION/${VERSION}/g" < sis.1 > ${DESTDIR}${MANPREFIX}/man1/sis.1 + chmod 644 ${DESTDIR}${MANPREFIX}/man1/sis.1 + +uninstall: + rm -f ${DESTDIR}${PREFIX}/bin/dwm\ + ${DESTDIR}${MANPREFIX}/man1/dwm.1 + +.PHONY: all options clean dist install uninstall diff --git a/README b/README new file mode 100644 index 0000000..17ccbb6 --- /dev/null +++ b/README @@ -0,0 +1,33 @@ +sis - simple imap server +============================ +sis is an IMAP server, following the unix philosophy, +trying to be as small as possible while providing +a reliable service. + + +Requirements +------------ +In order to build sis you need... a computer + + +Installation +------------ +Edit config.mk to match your local setup (sis is installed into +the /usr/local namespace by default). + +Afterwards enter the following command to build and install sis (if +necessary as root): + + make clean install + + +Running sis +----------- +By default, sis runs in daemon mode, if you want to avoid detaching use the -d option + sis -d + + +Configuration +------------- +The configuration of sis is done by creating a custom config.h +and (re)compiling the source code. diff --git a/config.def.h b/config.def.h new file mode 100644 index 0000000..66660c7 --- /dev/null +++ b/config.def.h @@ -0,0 +1,22 @@ +/* See LICENSE file for copyright and license details. */ + +/* network */ +#define IMAP_PORT 143 +#define IMAPS_PORT 993 +/*- + * Maximum number of connected clients, + * NOTE: each one of these is a currently + * connected client! Unless your server + * has to manage a lot of accounts you + * should be good with the default value. + */ +#define MAX_CLIENTS 30 +/*- + * Maximum size for the command buffer. + * IMAP sends plain text commands, so + * the default value can receive up to + * 8000 characters long commands. + * It should be enough but feel free to + * modify this. + */ +#define CMD_MAX_SIZE 8000 diff --git a/config.mk b/config.mk new file mode 100644 index 0000000..24ca323 --- /dev/null +++ b/config.mk @@ -0,0 +1,27 @@ +# sis version +VERSION = 0.1 + +# Customize below to fit your system + +# paths +PREFIX = /usr +MANPREFIX = ${PREFIX}/share/man + +# OpenBSD (uncomment) +#MANPREFIX = ${PREFIX}/man + +# includes and libs +INCS = -I. +LIBS = +# flags +CPPFLAGS = -DVERSION=\"${VERSION}\" +CFLAGS = -g -std=c99 -pedantic -Wall -O0 ${INCS} ${CPPFLAGS} +#CFLAGS = -std=c99 -pedantic -Wall -Wno-deprecated-declarations -Os ${INCS} ${CPPFLAGS} +LDFLAGS = ${LIBS} + +# Solaris +#CFLAGS = -fast ${INCS} -DVERSION=\"${VERSION}\" +#LDFLAGS = ${LIBS} + +# compiler and linker +CC = cc diff --git a/imap.c b/imap.c new file mode 100644 index 0000000..464f7fa --- /dev/null +++ b/imap.c @@ -0,0 +1,239 @@ +/*- + * 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 +#include +#include +#include +#include +#include +#include +#include +#include + +uint8_t imap_init(uint8_t daemon, imap_t *instance) +{ + imap_t imap; + + /* Create a new socket using IPv4 protocol */ + if ((imap.socket = socket(PF_INET, SOCK_STREAM, 0)) < 0) { + perror("socker"); + return 1; + } + + bzero(&imap.addr, sizeof(struct sockaddr_in)); + + imap.addr.sin_family = AF_INET; + /* From config.h */ + imap.addr.sin_port = htons(IMAP_PORT); + + if (INADDR_ANY) { + imap.addr.sin_addr.s_addr = htonl(INADDR_ANY); + } + + /* Bind the socket to the specified address */ + if ((bind(imap.socket, (struct sockaddr *)&imap.addr, sizeof(imap.addr)) < 0)) { + perror("bind"); + return 2; + } + + /* If daemon mode is activated, detach */ + if (daemon) { + switch (fork()) { + case -1: + perror("fork"); + return 3; + break; + default: + close(imap.socket); + free(instance); + exit(0); + break; + case 0: + break; + } + } + + memcpy(instance, &imap, sizeof(imap)); + + return 0; +} + +int imap_get_max_fd(client_list *list, int master) +{ + int max_fd = 0; + client_list *node = list; + + while (node != NULL) { + if (max_fd < node->socket) { + max_fd = node->socket; + } + + node = node->next; + } + + return max_fd > master ? max_fd : master; +} + +client_list *imap_add_client(client_list *list, int sock) +{ + client_list *node = (client_list *) malloc(sizeof(client_list)); + node->socket = sock; + node->next = list; + node->prev = NULL; + if (list != NULL) { + list->prev = node; + } + + return node; +} + +client_list *imap_remove_client(client_list *list, client_list *node) +{ + if (node->next != NULL) { + node->next->prev = node->prev; + } + + if (node->prev != NULL) { + node->prev->next = node->next; + } + + close(node->socket); + + if (node == list) { + return NULL; + } + + return list; +} + +client_list *imap_remove_sock(client_list *list, int sock) +{ + client_list *node = list; + + while (node != NULL && node->socket != sock) { + node = node->next; + } + + if (node != NULL) { + imap_remove_client(list, node); + } + + return node; +} + +void imap_start(imap_t *instance) +{ + int activity, max_fd, connection; + size_t bytes_read; + char buf[CMD_MAX_SIZE]; + /* List of all the file descriptors (sockets) being used. */ + fd_set fds; + instance->clients = NULL; + client_list *node = instance->clients; + client_list *tmp = instance->clients; + + listen(instance->socket, BACKLOG); + syslog(LOG_INFO, "Listening on %d.", IMAP_PORT); + + for (;;) { + FD_ZERO(&fds); + FD_SET(instance->socket, &fds); + node = instance->clients; + + while (node != NULL) { + FD_SET(node->socket, &fds); + node = node->next; + } + + max_fd = imap_get_max_fd(instance->clients, instance->socket); + activity = select(max_fd + 1, &fds, NULL, NULL, NULL); + + if ((activity < 0) && (errno!=EINTR)) { + perror("select"); + } + + /* New connection. */ + if (FD_ISSET(instance->socket, &fds)) { + if ((connection = accept(instance->socket, NULL, NULL)) < 0) { + perror("accept"); + syslog(LOG_ERR, "Connection failed."); + imap_close(instance); + exit(EXIT_FAILURE); + } + + instance->clients = imap_add_client(instance->clients, connection); + syslog(LOG_INFO, "Connection enstablished."); + FD_SET(connection, &fds); + } + + node = instance->clients; + tmp = node; + while (node != NULL) { + connection = node->socket; + tmp = node->next; + + if (FD_ISSET(connection, &fds)) { + /* Error occured. */ + if ((bytes_read = read(connection, buf, CMD_MAX_SIZE)) < 0) { + perror("recv"); + instance->clients = imap_remove_client(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); + free(node); + FD_CLR(connection, &fds); + syslog(LOG_INFO, "Connection closed."); + } else { + buf[bytes_read] = '\0'; + } + } + + node = tmp; + } + } +} + +void imap_close(imap_t *instance) +{ + client_list *node = instance->clients; + client_list *tmp = node; + while (node != NULL) { + close(node->socket); + tmp = node->next; + free(node); + node = tmp; + } + + close(instance->socket); + free(instance); +} diff --git a/imap.h b/imap.h new file mode 100644 index 0000000..eeba416 --- /dev/null +++ b/imap.h @@ -0,0 +1,64 @@ +/*- + * 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 IMAP_H +#define IMAP_H + +#include +#include +#include + +#define BACKLOG 4 + +typedef struct _client_list { + int32_t socket; + struct _client_list *next; + struct _client_list *prev; +} client_list; + + +typedef struct imap { + int32_t socket; + client_list *clients; + struct sockaddr_in addr; +} imap_t; + +/* Create a new imap_t instance and initialize the server. */ +uint8_t imap_init(uint8_t daemon, imap_t *instance); +/* Start the IMAP server. */ +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); +/* 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); +/* Get the higher file descriptor in the client list */ +int imap_get_max_fd(client_list *list, int master); + +#endif /* ifndef IMAP_H */ diff --git a/sis.c b/sis.c new file mode 100644 index 0000000..7c4ee49 --- /dev/null +++ b/sis.c @@ -0,0 +1,68 @@ +/*- + * 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 +#include +#include +#include + +static imap_t *instance; + +void int_handler(int sig) +{ + if (instance != NULL) { + imap_close(instance); + } +} + +int main(void) +{ + signal(SIGINT, int_handler); + + openlog("sis", LOG_PID, LOG_MAIL); + syslog(LOG_INFO, "Starting sis %s.", VERSION); + + int status = 0; + instance = (imap_t *) malloc(sizeof(imap_t)); + + if ((status = imap_init(0, instance)) != 0) { + perror("imap_init"); + goto ret; + } + + imap_start(instance); + +ret: + if (instance != NULL) { + imap_close(instance); + } + + closelog(); + return status; +}