commit 3d07f7beafa29d11037eb429d4320434c7124a82 Author: Lorenzo Torres Date: Wed Apr 12 01:24:28 2023 +0200 first commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f6d090c --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +*.core +*.o +posta +config.h diff --git a/COPYING b/COPYING new file mode 100644 index 0000000..7fd0b51 --- /dev/null +++ b/COPYING @@ -0,0 +1,29 @@ +BSD 3-Clause License + +Copyright (c) 2023, 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: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* 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. + +* Neither the name of the copyright holder 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 THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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 THE COPYRIGHT HOLDER OR CONTRIBUTORS 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..b15dcc1 --- /dev/null +++ b/Makefile @@ -0,0 +1,51 @@ +# posta - terminal based email client +# See LICENSE file for copyright and license details. + +include config.mk + +SRC = posta.c utils.c imap.c +OBJ = ${SRC:.c=.o} + +all: options posta + +options: + @echo posta 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 $@ + +posta: ${OBJ} + ${CC} -o $@ ${OBJ} ${LDFLAGS} + +clean: + rm -f posta ${OBJ} posta-${VERSION}.tar.gz + +dist: clean + mkdir -p posta-${VERSION} + cp -R LICENSE Makefile README config.def.h config.mk\ + posta.1 util.h ${SRC} posta.png transient.c posta-${VERSION} + tar -cf posta-${VERSION}.tar posta-${VERSION} + gzip posta-${VERSION}.tar + rm -rf posta-${VERSION} + +install: all + mkdir -p ${DESTDIR}${PREFIX}/bin + cp -f posta ${DESTDIR}${PREFIX}/bin + chmod 755 ${DESTDIR}${PREFIX}/bin/posta + mkdir -p ${DESTDIR}${MANPREFIX}/man1 + sed "s/VERSION/${VERSION}/g" < posta.1 > ${DESTDIR}${MANPREFIX}/man1/posta.1 + chmod 644 ${DESTDIR}${MANPREFIX}/man1/posta.1 + +uninstall: + rm -f ${DESTDIR}${PREFIX}/bin/posta\ + ${DESTDIR}${MANPREFIX}/man1/posta.1 + +.PHONY: all options clean dist install uninstall diff --git a/config.def.h b/config.def.h new file mode 100644 index 0000000..801e4cb --- /dev/null +++ b/config.def.h @@ -0,0 +1,5 @@ +#ifndef CONFIG_H +#define CONFIG_H + + +#endif diff --git a/config.mk b/config.mk new file mode 100644 index 0000000..641d265 --- /dev/null +++ b/config.mk @@ -0,0 +1,28 @@ +# posta version +VERSION = 1.0 + +# Customize below to fit your system + +# paths +PREFIX = /usr +MANPREFIX = ${PREFIX}/share/man + +# OpenBSD (uncomment) +MANPREFIX = ${PREFIX}/man + +# includes and libs +INCS = +LIBS = -lssl -lcrypto + +# flags +CPPFLAGS = -g -D_DEFAULT_SOURCE -D_BSD_SOURCE -D_XOPEN_SOURCE=700L -DVERSION=\"${VERSION}\" ${XINERAMAFLAGS} +#CFLAGS = -g -std=c99 -pedantic -Wall -O0 ${INCS} ${CPPFLAGS} +CFLAGS = -g -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..212fa68 --- /dev/null +++ b/imap.c @@ -0,0 +1,218 @@ +#include "imap.h" +#include "utils.h" +#include +#include +#include +#include +#include +#include +#include +#include + +imap_t *imap_connect(const char *addr, const char *port) +{ + int sock; + char *res; + FILE *sockfile; + imap_t *state; + struct addrinfo* address = NULL; + + getaddrinfo(addr, port, 0, &address); + struct sockaddr_in *p = (struct sockaddr_in *)address->ai_addr; + + if ((sock = create_connection(p)) == -1) + return NULL; + + if ((sockfile = fdopen(sock, "r")) == NULL) + return NULL; + + state = (imap_t *) malloc(sizeof(imap_t)); + state->sock = sock; + state->sockfile = sockfile; + + if (strcmp(port, "993") == 0) { + state->tls = 1; + imap_starttls(state); + } else { + state->tls = 0; + } + + res = imap_response(state); + + if (res[2] != 'O' || res[3] != 'K') + return NULL; + + free(res); + + return state; +} + +void imap_close(imap_t *state) +{ + close_connection(state->sock); + fclose(state->sockfile); + + free(state); +} + +char *imap_response(imap_t *state) +{ + size_t len; + char *res; + char c; + int i; + + len = 15; + res = (char *) calloc(len, sizeof(char)); + + i = 0; + + if (!state->tls) { + c = fgetc(state->sockfile); + } else { + SSL_read(state->ssl, &c, 1); + } + + while (c != '\n') { + res[i] = c; + i++; + + if (i > len) { + len = i+5; + res = (char *) reallocarray(res, len, sizeof(char)); + } + if (!state->tls) { + c = fgetc(state->sockfile); + } else { + SSL_read(state->ssl, &c, 1); + } + } + + if (i < len) + res = (char *) reallocarray(res, i+1, sizeof(char)); + + res[i] = '\0'; + + return res; +} + +int imap_send(imap_t *state, char *cmd) +{ + if (!state->tls) { + return send(state->sock, (const void *)cmd, strlen(cmd), 0); + } else { + return SSL_write(state->ssl, (const void *)cmd, strlen(cmd)) > 0; + } +} + +int imap_capability(imap_t *state) +{ + state->capabilities = 0; + char *cmd, *res, *token; + + cmd = "A0 CAPABILITY\n"; + imap_send(state, cmd); + res = imap_response(state); + token = get_token(res); + + if (!imap_match_token(token, "*")) + return -1; + + token = get_token(NULL); + + if (!imap_match_token(token, TOK_CAPABILITY)) + return -1; + + while ((token = get_token(NULL)) != NULL) { + if (imap_match_token(token, TOK_IMAP4)) + state->capabilities |= IMAP4; + else if (imap_match_token(token, TOK_STARTTLS)) + state->capabilities |= STARTTLS; + } + + free(res); + res = imap_response(state); + free(res); + + return 0; +} + +int imap_match_token(char *t1, const char *t2) +{ + return strcmp(t1, t2) == 0; +} + +int imap_starttls(imap_t* state) +{ + if (!state->tls) { + char *cmd, *res, *token; + + cmd = "A1 STARTTLS\n"; + imap_send(state, cmd); + res = imap_response(state); + token = get_token(res); + + if (!imap_match_token(token, "A1")) + return 1; + + token = get_token(NULL); + + if (!imap_match_token(token, "OK")) + return 2; + + } + + state->ssl_ctx = SSL_CTX_new(TLSv1_2_client_method()); + state->ssl = SSL_new(state->ssl_ctx); + SSL_set_fd(state->ssl, state->sock); + if (SSL_connect(state->ssl) == 0) { + return -1; + } + + state->tls = 1; + + return 0; +} + +int imap_login(imap_t* state, char *username, char *password) +{ + char *cmd, *res, *token; + + size_t datalen = strlen(username) + strlen(password) + 12; + cmd = calloc(datalen, sizeof(char)); + snprintf(cmd, datalen, "A2 LOGIN %s %s\n", username, password); + imap_send(state, cmd); + res = imap_response(state); + token = get_token(res); + + if (!imap_match_token(token, "*")) + return -1; + + token = get_token(NULL); + + if (!imap_match_token(token, TOK_CAPABILITY)) + return -1; + + while ((token = get_token(NULL)) != NULL) { + if (imap_match_token(token, TOK_IMAP4)) + state->capabilities |= IMAP4; + else if (imap_match_token(token, TOK_STARTTLS)) + state->capabilities |= STARTTLS; + } + + free(res); + + + res = imap_response(state); + token = get_token(res); + + if (!imap_match_token(token, "A2")) + return 1; + + token = get_token(NULL); + + if (!imap_match_token(token, TOK_OK)) + return 1; + + return 0; +} diff --git a/imap.h b/imap.h new file mode 100644 index 0000000..23bd205 --- /dev/null +++ b/imap.h @@ -0,0 +1,51 @@ +#ifndef IMAP_H +#define IMAP_H + +#include +#include + +#define TOK_OK "OK" +#define TOK_BAD "BAD" +#define TOK_CAPABILITY "CAPABILITY" +#define TOK_IMAP4 "IMAP4rev1" +#define TOK_STARTTLS "STARTTLS" + +enum imap_caps { + IMAP4 = 1 << 0, + STARTTLS = 1 << 1, +}; + +/* IMAP connection */ +typedef struct { + int sock; + int capabilities; + int tls; + FILE *sockfile; + SSL *ssl; + SSL_CTX *ssl_ctx; +} imap_t; + +/* Connect to an IMAP server */ +imap_t *imap_connect(const char *addr, const char *port); + +/* Close the connection to the IMAP server, automatically free used memory */ +void imap_close(imap_t *state); + +/* Get the server response as a string */ +char *imap_response(imap_t *state); + +int imap_send(imap_t *state, char *cmd); + +/* Query for server capabilities */ +int imap_capability(imap_t *state); + +/* Check for token matching */ +int imap_match_token(char *t1, const char *t2); + +/* Init TLS connection */ +int imap_starttls(imap_t* state); + +/* Login to the IMAP server */ +int imap_login(imap_t* state, char *username, char *password); + +#endif diff --git a/posta.c b/posta.c new file mode 100644 index 0000000..7c11361 --- /dev/null +++ b/posta.c @@ -0,0 +1,39 @@ +#include +#include +#include +#include "utils.h" +#include "imap.h" + +int main(void) +{ + SSL_library_init(); + + imap_t *state = imap_connect("sagittarius-a.org", "993"); + + /* + if (imap_starttls(state) == 0) { + printf("TLS connection enstablished!\n"); + } else { + printf("Can't enstablish TLS connection\n"); + exit(-1); + }*/ + + if (imap_capability(state) != 0) { + printf("Can't get server capabilities!\n"); + exit(-1); + } + + if (state->capabilities & IMAP4) { + printf("IMAP4rev1 found\n"); + } + + if (imap_login(state, "", "") != 0) { + printf("Login failed!\n"); + } else { + printf("Logged successfully!\n"); + } + + imap_close(state); + + return 0; +} diff --git a/utils.c b/utils.c new file mode 100644 index 0000000..2a300cb --- /dev/null +++ b/utils.c @@ -0,0 +1,35 @@ +#include "utils.h" +#include +#include +#include +#include +#include +#include +#include + +int create_connection(struct sockaddr_in *endpoint) +{ + int fd, status; + + if ((fd = socket(AF_INET, SOCK_STREAM, 0)) < 0) { + return -1; + } + + if ((status = connect(fd, (struct sockaddr*)endpoint, sizeof(*endpoint))) < 0) { + printf("Failed to connect!\n"); + return -1; + } + + return fd; +} + +void close_connection(int socket) +{ + close(socket); +} + +char *get_token(char *s) +{ + const char sep[4] = " "; + return strtok(s, sep); +} diff --git a/utils.h b/utils.h new file mode 100644 index 0000000..24d7091 --- /dev/null +++ b/utils.h @@ -0,0 +1,13 @@ +#ifndef UTILS_H +#define UTILS_H + +#include +#include +#include + +int create_connection(struct sockaddr_in *addr); +void close_connection(int socket); + +char *get_token(char *s); + +#endif