#include "sws.h"
#include <sys/select.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <dirent.h>
#include <errno.h>
#include <netdb.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sysexits.h>
#include <time.h>
#include <unistd.h>
#include "cgi.h"
#include "header.h"
#include "logging.h"
#include "response.h"
#include "structures.h"
#include "utils.h"
#define NO_CONN (-1)
static int
resolve_max_conn()
{
int max_client_conns = sysconf(_SC_OPEN_MAX);
if (max_client_conns == -1)
{
(void)fprintf(stderr,
"%s: warning - sysconf could not resolve process' max "
"number of open files. Defaulting to 1\n",
getprogname());
return 1;
}
/* hard limited as on some OSes, such as OmniOS, ulimit -n returns 65536,
* which is large enough to cause a stack overflow / stack smash here.
* 1024 is the limit on NetBSD and Linux, so it should suffice as a
* reasonable upper bound */
if (max_client_conns > 1024)
{
max_client_conns = 1024;
}
/* defensive, if by some magic open_max is set to be less than 0 and not -1
*/
if (max_client_conns <= 0)
{
max_client_conns = 1;
}
if (max_client_conns > 5)
{
/* If there's enough room for at least one connection, max_conn (the
* server below) should not include stdin (0), stdout (1), stderr (2),
* logfile (3), or the server's socket fd (4), for a total of 5. */
max_client_conns -= 5;
}
/* if that number is lower than 5, various other functions will complain */
return max_client_conns;
}
int
start_server(char* dir_arg, struct flag_control* fc)
{
/* we need to ignore SIGPIPE in the event we send some initial/header data
* to a client and they immediately disconnect before we serve
* them the remainder of the data and such */
ignore_sigpipe();
/* it this fails, sws wil exit */
int max_conn = fc->d ? 1 : resolve_max_conn();
int server_fd;
struct sockaddr_storage host_server_addr;
socklen_t host_addr_size;
int domain_type = AF_INET6;
int ipv6_only = 0;
if (fc->ip_addr != NULL && strchr(fc->ip_addr, ':') == NULL)
{
domain_type = AF_INET; /* Must be IPv4 if no colon was specified */
}
/* Most of the dual stack socket stuff was taken from:
* https://stevens.netmeister.org/631/apue-code/09/
* The rest (including the multiplexed IO) was taken directly from a
* previous CS 392 project
*/
server_fd = socket(domain_type, SOCK_STREAM, 0);
if (server_fd < 0)
{
(void)fprintf(stderr, "%s: socket: %s\n", getprogname(),
strerror(errno));
exit(EXIT_FAILURE);
}
if (domain_type == AF_INET6)
{
if (setsockopt(server_fd, IPPROTO_IPV6, IPV6_V6ONLY, &ipv6_only,
sizeof(ipv6_only)) < 0)
{
(void)fprintf(stderr, "%s: setsockopt: %s\n", getprogname(),
strerror(errno));
exit(EXIT_FAILURE);
}
}
memset(&host_server_addr, 0, sizeof(host_server_addr));
if (domain_type == AF_INET) /* IPv4 */
{
struct sockaddr_in* ipv4sock = (struct sockaddr_in*)&host_server_addr;
ipv4sock->sin_family = AF_INET;
ipv4sock->sin_port = htons(fc->port);
ipv4sock->sin_addr.s_addr =
(fc->ip_addr == NULL) ? INADDR_ANY : inet_addr(fc->ip_addr);
host_addr_size = sizeof(*ipv4sock);
if (inet_pton(AF_INET, fc->ip_addr, &ipv4sock->sin_addr) != 1)
{
(void)fprintf(stderr,
"%s: inet_pton (IPv4): invalid address specified\n",
getprogname());
exit(EXIT_FAILURE);
}
}
else
{
struct sockaddr_in6* ipv6sock = (struct sockaddr_in6*)&host_server_addr;
ipv6sock->sin6_family = AF_INET6;
ipv6sock->sin6_port = htons(fc->port);
if (fc->ip_addr == NULL)
{
ipv6sock->sin6_addr =
in6addr_any; /* all IPv6 + IPv4 (dual stack) */
}
else if (inet_pton(AF_INET6, fc->ip_addr, &ipv6sock->sin6_addr) != 1)
{
(void)fprintf(stderr,
"%s: inet_pton (IPv6): invalid address specified\n",
getprogname());
exit(EXIT_FAILURE);
}
host_addr_size = sizeof(*ipv6sock);
}
if (bind(server_fd, (struct sockaddr*)&host_server_addr, host_addr_size) <
0)
{
(void)fprintf(stderr, "%s: bind: %s\n", getprogname(), strerror(errno));
exit(EXIT_FAILURE);
}
if (listen(server_fd, max_conn) == 0)
{
printf("[Debug] Server listening on %s:%d\n",
(fc->ip_addr == NULL ? "all IP addresses on this machine"
: fc->ip_addr),
fc->port);
}
else
{
(void)fprintf(stderr, "%s: listen: %s\n", getprogname(),
strerror(errno));
exit(EXIT_FAILURE);
}
fd_set myset;
int max_fd = server_fd;
int n_conn = 0;
int cfds[max_conn];
struct sockaddr_storage client_addrs[max_conn];
for (int i = 0; i < max_conn; i++)
{
cfds[i] = NO_CONN;
}
while (1)
{
FD_ZERO(&myset);
FD_SET(server_fd, &myset);
max_fd = server_fd;
for (int i = 0; i < max_conn; i++)
{
if (cfds[i] != NO_CONN)
{
FD_SET(cfds[i], &myset);
if (cfds[i] > max_fd)
{
max_fd = cfds[i];
}
}
}
if (select(max_fd + 1, &myset, NULL, NULL, NULL) < 0)
{
(void)fprintf(stderr, "%s: select: %s\n", getprogname(),
strerror(errno));
break;
}
if (FD_ISSET(server_fd, &myset))
{
struct sockaddr_storage client_addr;
socklen_t client_addr_size = sizeof(client_addr);
int client_fd = accept(server_fd, (struct sockaddr*)&client_addr,
&client_addr_size);
if (client_fd < 0)
{
(void)fprintf(stderr, "%s: accept: %s\n", getprogname(),
strerror(errno));
continue;
}
if (n_conn < max_conn)
{
n_conn++;
(void)printf("[Debug] New connection '%d' accepted\n",
client_fd);
for (int i = 0; i < max_conn; i++)
{
if (cfds[i] == NO_CONN)
{
cfds[i] = client_fd;
client_addrs[i] = client_addr;
break;
}
}
}
else
{
(void)printf(
"[Debug] Max connection limit reached. Refusing new "
"connection.\n");
(void)close(client_fd);
}
}
for (int i = 0; i < max_conn; i++)
{
if (cfds[i] != NO_CONN && FD_ISSET(cfds[i], &myset))
{
char buffer[PATH_MAX];
/* May need to change the read here to be more reliable */
int recvbytes = read(cfds[i], buffer, sizeof(buffer));
if (recvbytes > 0)
{
buffer[recvbytes] = '\0';
/* if debug is not enabled, the daemonization would have
* suppressed stdout */
printf("[Debug] Received Data: %s\n", buffer);
// found in structures.h
REQUEST request = {0};
request.client_fd = cfds[i];
request.response_size =
0; /* unnecessary assignment but for clarity */
CGI cgi = {0};
int response_status = 0;
if (parse_input(&request, buffer) != HTTP_OK)
{
response_status = 400;
send_status_and_headers(&request, response_status);
goto log;
}
try_remove_dots(request.path);
resolve_path(&request, &cgi, fc, dir_arg);
if (request.request_type == HEAD)
{
if (fc->c && starts_with_cgibin(request.path))
{
response_status =
exec_cgi_and_respond(&request, &cgi, true);
}
else
{
response_status =
attempt_serve_main(&request, true);
}
}
else if (request.request_type == GET)
{
if (fc->c && starts_with_cgibin(request.path))
{
response_status =
exec_cgi_and_respond(&request, &cgi, false);
}
else
{
response_status =
attempt_serve_main(&request, false);
}
}
log:
if (fc->l)
{
/* writes IP, Date, "request string" status
* RESPONSE-length, meaning it has to go after
* processing*/
log_client_data(client_addrs[i], fc->log_file, fc->d,
&request);
}
(void)close(cfds[i]);
cfds[i] = NO_CONN;
n_conn--;
(void)printf(
"[Debug] Closed connection '%d': %s %s -> %d\n",
request.client_fd,
request.request_type == HEAD ? "HEAD" : "GET",
request.path, response_status);
}
else
{
n_conn--;
(void)close(cfds[i]);
/* if debug is not enabled, the daemonization would have
* suppressed stdout */
(void)printf("[Debug] Connection '%d' has been broken "
"(remote client lost connection)\n",
cfds[i]);
cfds[i] = NO_CONN;
}
}
}
}
/* Normally unreachable */
(void)close(server_fd);
return EXIT_SUCCESS;
}