TLS1.2 and command execution

This commit is contained in:
Lorenzo Torres 2024-08-20 04:57:58 +02:00
parent bc96196ffd
commit 33bb5b8775
10 changed files with 459 additions and 19 deletions

2
.gitignore vendored
View file

@ -3,4 +3,6 @@ config.h
**/*~ **/*~
**/*.o **/*.o
**/*.core **/*.core
**/*.pem
vgcore*
sis sis

View file

@ -3,8 +3,8 @@
include config.mk include config.mk
SRC = sis.c imap.c SRC = sis.c imap.c utils.c
HDR = config.def.h HDR = config.def.h imap.h utils.h imap.routines
OBJ = ${SRC:.c=.o} OBJ = ${SRC:.c=.o}
all: options sis all: options sis
@ -18,7 +18,7 @@ options:
.c.o: .c.o:
${CC} -c ${CFLAGS} $< ${CC} -c ${CFLAGS} $<
${OBJ}: config.h config.mk ${OBJ}: config.h imap.routines config.mk
config.h: config.h:
cp config.def.h $@ cp config.def.h $@

View file

@ -3,6 +3,7 @@
/* network */ /* network */
#define IMAP_PORT 143 #define IMAP_PORT 143
#define IMAPS_PORT 993 #define IMAPS_PORT 993
#define TLS_ENABLED 1
/*- /*-
* Maximum number of connected clients, * Maximum number of connected clients,
* NOTE: each one of these is a currently * NOTE: each one of these is a currently
@ -20,3 +21,19 @@
* modify this. * modify this.
*/ */
#define CMD_MAX_SIZE 8000 #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
};

View file

@ -12,11 +12,11 @@ MANPREFIX = ${PREFIX}/share/man
# includes and libs # includes and libs
INCS = -I. INCS = -I.
LIBS = LIBS = -lssl -lcrypto
# flags # flags
CPPFLAGS = -DVERSION=\"${VERSION}\" CPPFLAGS = -DVERSION=\"${VERSION}\"
CFLAGS = -g -std=c99 -pedantic -Wall -O0 ${INCS} ${CPPFLAGS} CFLAGS := -std=c99 -pedantic -Wall -O0 -Wno-gnu-label-as-value -Wno-gnu-zero-variadic-macro-arguments ${INCS} ${CPPFLAGS}
#CFLAGS = -std=c99 -pedantic -Wall -Wno-deprecated-declarations -Os ${INCS} ${CPPFLAGS} CFLAGS := ${CFLAGS} -g
LDFLAGS = ${LIBS} LDFLAGS = ${LIBS}
# Solaris # Solaris

239
imap.c
View file

@ -35,11 +35,36 @@
#include <syslog.h> #include <syslog.h>
#include <errno.h> #include <errno.h>
#include <config.h> #include <config.h>
#include <utils.h>
#include <imap.h> #include <imap.h>
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) uint8_t imap_init(uint8_t daemon, imap_t *instance)
{ {
imap_t imap; imap_t imap;
imap.ssl_ctx = NULL;
imap.ssl = 0;
/* Create a new socket using IPv4 protocol */ /* Create a new socket using IPv4 protocol */
if ((imap.socket = socket(PF_INET, SOCK_STREAM, 0)) < 0) { 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)); bzero(&imap.addr, sizeof(struct sockaddr_in));
imap.state = IMAP_STATE_NO_AUTH;
imap.addr.sin_family = AF_INET; imap.addr.sin_family = AF_INET;
/* From config.h */ /* From config.h */
imap.addr.sin_port = htons(IMAP_PORT); imap.addr.sin_port = htons(TLS_ENABLED ? IMAPS_PORT : IMAP_PORT);
if (INADDR_ANY) { if (INADDR_ANY) {
imap.addr.sin_addr.s_addr = htonl(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)); memcpy(instance, &imap, sizeof(imap));
return 0; return 0;
@ -101,9 +132,20 @@ int imap_get_max_fd(client_list *list, int master)
return max_fd > master ? max_fd : 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)); 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->socket = sock;
node->next = list; node->next = list;
node->prev = NULL; node->prev = NULL;
@ -114,7 +156,7 @@ client_list *imap_add_client(client_list *list, int sock)
return node; 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) { if (node->next != NULL) {
node->next->prev = node->prev; 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; node->prev->next = node->next;
} }
/* Check if TLS is active */
if (instance->ssl) {
SSL_shutdown(node->ssl);
SSL_free(node->ssl);
}
close(node->socket); close(node->socket);
if (node == list) { if (node == list) {
@ -133,7 +181,7 @@ client_list *imap_remove_client(client_list *list, client_list *node)
return list; 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; client_list *node = list;
@ -142,12 +190,24 @@ client_list *imap_remove_sock(client_list *list, int sock)
} }
if (node != NULL) { if (node != NULL) {
imap_remove_client(list, node); imap_remove_client(instance, list, node);
} }
return 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) void imap_start(imap_t *instance)
{ {
int activity, max_fd, connection; int activity, max_fd, connection;
@ -188,7 +248,7 @@ void imap_start(imap_t *instance)
exit(EXIT_FAILURE); 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."); syslog(LOG_INFO, "Connection enstablished.");
FD_SET(connection, &fds); FD_SET(connection, &fds);
} }
@ -201,20 +261,33 @@ void imap_start(imap_t *instance)
if (FD_ISSET(connection, &fds)) { if (FD_ISSET(connection, &fds)) {
/* Error occured. */ /* 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"); perror("recv");
instance->clients = imap_remove_client(instance->clients, node); instance->clients = imap_remove_client(instance, instance->clients, node);
free(node); free(node);
FD_CLR(connection, &fds); FD_CLR(connection, &fds);
syslog(LOG_ERR, "Failed to receive data."); syslog(LOG_ERR, "Failed to receive data.");
/* Somebody disconnected */ /* Somebody disconnected */
} else if (bytes_read == 0) { } else if (bytes_read == 0) {
instance->clients = imap_remove_client(instance->clients, node); instance->clients = imap_remove_client(instance, instance->clients, node);
free(node); free(node);
FD_CLR(connection, &fds); FD_CLR(connection, &fds);
syslog(LOG_INFO, "Connection closed."); syslog(LOG_INFO, "Connection closed.");
} else { } else {
buf[bytes_read] = '\0'; 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); close(instance->socket);
SSL_CTX_free(instance->ssl_ctx);
free(instance); 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 <imap.routines>
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);
}

36
imap.h
View file

@ -31,11 +31,22 @@
#include <stdint.h> #include <stdint.h>
#include <stddef.h> #include <stddef.h>
#include <netinet/in.h> #include <netinet/in.h>
#include <openssl/ssl.h>
#include <openssl/err.h>
#define BACKLOG 4 #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 { typedef struct _client_list {
int32_t socket; int32_t socket, fd;
SSL *ssl;
struct _client_list *next; struct _client_list *next;
struct _client_list *prev; struct _client_list *prev;
} client_list; } client_list;
@ -45,8 +56,17 @@ typedef struct imap {
int32_t socket; int32_t socket;
client_list *clients; client_list *clients;
struct sockaddr_in addr; struct sockaddr_in addr;
uint8_t state, ssl;
SSL_CTX *ssl_ctx;
} imap_t; } 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. */ /* Create a new imap_t instance and initialize the server. */
uint8_t imap_init(uint8_t daemon, imap_t *instance); uint8_t imap_init(uint8_t daemon, imap_t *instance);
/* Start the IMAP server. */ /* Start the IMAP server. */
@ -54,11 +74,19 @@ void imap_start(imap_t *instance);
/* Close all connections and free the allocated memory. */ /* Close all connections and free the allocated memory. */
void imap_close(imap_t *instance); void imap_close(imap_t *instance);
/* Add client to client list */ /* 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 */ /* Close connection with client */
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);
client_list *imap_remove_sock(client_list *list, int sock); client_list *imap_remove_sock(imap_t *instance, client_list *list, int sock);
/* Get the higher file descriptor in the client list */ /* Get the higher file descriptor in the client list */
int imap_get_max_fd(client_list *list, int master); 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 */ #endif /* ifndef IMAP_H */

77
imap.routines Normal file
View file

@ -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;
}

1
sis.c
View file

@ -44,6 +44,7 @@ void int_handler(int sig)
int main(void) int main(void)
{ {
signal(SIGINT, int_handler); signal(SIGINT, int_handler);
signal(SIGPIPE, SIG_IGN);
openlog("sis", LOG_PID, LOG_MAIL); openlog("sis", LOG_PID, LOG_MAIL);
syslog(LOG_INFO, "Starting sis %s.", VERSION); syslog(LOG_INFO, "Starting sis %s.", VERSION);

59
utils.c Normal file
View file

@ -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 <organization> 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 <COPYRIGHT HOLDER> 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 <ctype.h>
#include <string.h>
#include <utils.h>
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]);
}
}

35
utils.h Normal file
View file

@ -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 <organization> 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 <COPYRIGHT HOLDER> 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 */