Tom MTT
16f516d6ce
Added support for cosmopolitan libc. Simply do `make COSMO=yes` and you'll get feuille.com, a working αcτµαlly pδrταblε εxεcµταblε pastebin server. + version bump.
251 lines
7.9 KiB
C
251 lines
7.9 KiB
C
/*
|
|
* server.c
|
|
* Server handling.
|
|
*
|
|
* Copyright (c) 2022
|
|
* Tom MTT. <tom@heimdall.pm>
|
|
*
|
|
* This file is licensed under the 3-Clause BSD License.
|
|
* You should have received a copy of the 3-Clause BSD License
|
|
* along with this program. If not, see
|
|
* <https://basedwa.re/tmtt/feuille/src/branch/main/LICENSE>.
|
|
*/
|
|
|
|
#include "server.h"
|
|
|
|
#ifndef COSMOPOLITAN
|
|
#include <arpa/inet.h> /* for inet_pton */
|
|
#include <errno.h> /* for errno, EAGAIN, EFBIG, ENOENT */
|
|
#include <netinet/in.h> /* for htons, sockaddr_in, sockaddr_in6, IPPROTO_IPV6 */
|
|
#include <stdio.h> /* for NULL */
|
|
#include <stdlib.h> /* for free, malloc, realloc */
|
|
#include <string.h> /* for strcmp, strlen */
|
|
#include <strings.h> /* for bzero */
|
|
#include <syslog.h> /* for syslog, LOG_WARNING */
|
|
#include <sys/socket.h> /* for setsockopt, bind, socket, SOL_SOCKET, AF_INET */
|
|
#include <sys/time.h> /* for timeval */
|
|
#include <unistd.h> /* for close */
|
|
#endif
|
|
|
|
#include "feuille.h" /* for Settings, settings */
|
|
#include "util.h" /* for verbose */
|
|
/**
|
|
* Initialize the server socket.
|
|
* -> the actual socket.
|
|
*/
|
|
int initialize_server()
|
|
{
|
|
int server;
|
|
|
|
/* initialize socket IPv4 and IPv6 addresses */
|
|
verbose(3, "initializing address structs...");
|
|
|
|
struct sockaddr_in server_address_v4;
|
|
bzero(&server_address_v4, sizeof(server_address_v4));
|
|
|
|
int ipv6_only = 1;
|
|
struct sockaddr_in6 server_address_v6;
|
|
bzero(&server_address_v6, sizeof(server_address_v6));
|
|
|
|
if (strcmp(settings.address, "*") == 0) {
|
|
settings.address = "::";
|
|
ipv6_only = 0;
|
|
}
|
|
|
|
/* dirty hack to detect and convert IPv4 / IPv6 addresses */
|
|
verbose(3, "detecting address family...");
|
|
|
|
if (inet_pton(AF_INET, settings.address, &server_address_v4.sin_addr) == 1) {
|
|
verbose(3, "IPv4 address detected.");
|
|
|
|
/* set socket family and port */
|
|
server_address_v4.sin_family = AF_INET;
|
|
server_address_v4.sin_port = htons(settings.port);
|
|
|
|
/* create socket */
|
|
verbose(3, "creating server socket...");
|
|
|
|
if ((server = socket(AF_INET, SOCK_STREAM, 0)) < 0)
|
|
return -1;
|
|
|
|
/* set socket options */
|
|
verbose(3, "setting socket options...");
|
|
|
|
/* reuse address when restarting feuille */
|
|
verbose(3, " SO_REUSEADDR...");
|
|
|
|
if (setsockopt(server, SOL_SOCKET, SO_REUSEADDR, &(int){ 1 }, sizeof(int)) < 0)
|
|
return -1;
|
|
|
|
/* bind address and port */
|
|
verbose(3, "binding address on the socket...");
|
|
|
|
if (bind(server, (struct sockaddr *)&server_address_v4, sizeof(server_address_v4)) < 0)
|
|
return -1;
|
|
|
|
} else if (inet_pton(AF_INET6, settings.address, &server_address_v6.sin6_addr) == 1) {
|
|
verbose(3, "IPv6 address detected.");
|
|
|
|
/* set socket family and port */
|
|
server_address_v6.sin6_family = AF_INET6;
|
|
server_address_v6.sin6_port = htons(settings.port);
|
|
|
|
/* create socket */
|
|
verbose(3, "creating server socket...");
|
|
|
|
if ((server = socket(AF_INET6, SOCK_STREAM, 0)) < 0)
|
|
return -1;
|
|
|
|
/* set socket options */
|
|
verbose(3, "setting socket options...");
|
|
|
|
/* reuse address when restarting feuille */
|
|
verbose(3, " SO_REUSEADDR...");
|
|
|
|
if (setsockopt(server, SOL_SOCKET, SO_REUSEADDR, &(int){ 1 }, sizeof(int)) < 0)
|
|
return -1;
|
|
|
|
/* Enable dual-stack mode on supported platforms */
|
|
verbose(3, " IPV6_V6ONLY...");
|
|
|
|
if (setsockopt(server, IPPROTO_IPV6, IPV6_V6ONLY, &ipv6_only, sizeof(ipv6_only)) < 0) {
|
|
syslog(LOG_WARNING, "dual-stack mode is disabled on your platform.");
|
|
syslog(LOG_WARNING, "feuille will only listen on the `::' IPv6 address.");
|
|
puts("");
|
|
}
|
|
|
|
/* bind address and port */
|
|
verbose(3, "binding address on the socket...");
|
|
|
|
if (bind(server, (struct sockaddr *)&server_address_v6, sizeof(server_address_v6)) < 0)
|
|
return -1;
|
|
|
|
} else
|
|
return -1;
|
|
|
|
/* start listening to incoming connections */
|
|
verbose(3, "starting to listen on the socket...");
|
|
if (listen(server, 1) < 0)
|
|
return -1;
|
|
|
|
return server;
|
|
}
|
|
|
|
/**
|
|
* Accept incoming connections.
|
|
* socket: the server socket.
|
|
* -> the socket associated with the connection.
|
|
*/
|
|
int accept_connection(int socket)
|
|
{
|
|
/* only needed for cosmopolitan libc */
|
|
/* we don't do anything with this struct yet */
|
|
struct sockaddr_in address;
|
|
int addrlen = sizeof(address);
|
|
|
|
/* accept the connection */
|
|
/* TODO: maybe retrieve IP address for logging? *maybe* */
|
|
int connection = accept(socket, (struct sockaddr*)&address, (socklen_t*)&addrlen);
|
|
|
|
/* set the timeout for the connection */
|
|
struct timeval timeout = { settings.timeout, 0 };
|
|
|
|
if (setsockopt(connection, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout)) < 0)
|
|
return -1;
|
|
|
|
if (setsockopt(connection, SOL_SOCKET, SO_SNDTIMEO, &timeout, sizeof(timeout)) < 0)
|
|
return -1;
|
|
|
|
return connection;
|
|
}
|
|
|
|
/**
|
|
* Close a connection.
|
|
* connection: the socket associated with the connection.
|
|
*/
|
|
void close_connection(int connection)
|
|
{
|
|
/* prevent reading from / writing to the socket */
|
|
shutdown(connection, SHUT_RDWR);
|
|
|
|
/* close the socket */
|
|
close(connection);
|
|
}
|
|
|
|
/**
|
|
* Read the incoming data from a connection.
|
|
* connection: the socket associated with the connection.
|
|
* -> the string containing all data sent, or NULL if an error occured. Needs to be freed.
|
|
*/
|
|
unsigned long read_paste(int connection, char **output)
|
|
{
|
|
|
|
unsigned long buffer_size = settings.buffer_size;
|
|
unsigned long total_size = 0;
|
|
|
|
/* allocate buffer to store the data */
|
|
char *buffer;
|
|
if ((buffer = malloc((buffer_size + 1) * sizeof(char))) == NULL)
|
|
return 0;
|
|
|
|
/* read all data until EOF is received, or max file size is reached, or the socket timeouts... */
|
|
/* each time, the data is appended to the buffer, once it's been reallocated a larger size */
|
|
errno = 0;
|
|
long size;
|
|
while ((size = recv(connection, buffer + total_size, buffer_size - total_size, 0)) > 0) {
|
|
total_size += size;
|
|
|
|
/* have we reached max file size? */
|
|
if (total_size >= settings.max_size) {
|
|
/* yup, free the buffer and return an error */
|
|
free(buffer);
|
|
errno = EFBIG;
|
|
return 0;
|
|
}
|
|
|
|
/* have we reached the end of the buffer? */
|
|
if (total_size == buffer_size) {
|
|
/* yup, increase the buffer size */
|
|
buffer_size += settings.buffer_size;
|
|
|
|
/* reallocate the buffer with a larger size */
|
|
void *tmp;
|
|
if ((tmp = realloc(buffer, (buffer_size + 1) * sizeof(char))) == NULL) {
|
|
free(buffer);
|
|
return 0;
|
|
}
|
|
|
|
buffer = tmp;
|
|
}
|
|
}
|
|
|
|
/* is the buffer empty? */
|
|
if (total_size == 0) {
|
|
/* yup, free the buffer and return an error */
|
|
if (errno != EAGAIN)
|
|
errno = ENOENT;
|
|
|
|
free(buffer);
|
|
return 0;
|
|
}
|
|
|
|
/* end the buffer with a newline if there's none */
|
|
if (buffer[total_size - 1] != '\n') {
|
|
buffer[total_size] = '\n';
|
|
total_size++; /* add newline to total size */
|
|
}
|
|
|
|
*output = buffer;
|
|
return total_size;
|
|
}
|
|
|
|
/**
|
|
* Send a response to the client.
|
|
* connection: the socket associated with the connection.
|
|
* data: the string to be sent.
|
|
* -> -1 on error, number of bytes sent on success.
|
|
*/
|
|
int send_response(int connection, char *data) {
|
|
return send(connection, data, strlen(data), 0);
|
|
}
|