/* * server.c * Server handling. * * Copyright (c) 2022 * Tom MTT. * * 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 * . */ #include "server.h" #ifndef COSMOPOLITAN #include /* for inet_pton */ #include /* for errno, EAGAIN, EFBIG, ENOENT */ #include /* for htons, sockaddr_in, sockaddr_in6, IPPROTO_IPV6 */ #include /* for NULL */ #include /* for free, malloc, realloc */ #include /* for strcmp, strlen */ #include /* for bzero */ #include /* for syslog, LOG_WARNING */ #include /* for setsockopt, bind, socket, SOL_SOCKET, AF_INET */ #include /* for timeval */ #include /* 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); }