/* TLS redirection daemon v1.1 by Pegasus Epsilon <pegasus@pimpninjas.org>
* (C) 2019 Distribute Unmodified - http://pegasus.pimpninjas.org/license
*
* CFLAGS=-ansi -pedantic -std=c99 -Wall -Werror -Werror=format=0
*
* Changelog:
* + Fixed a null pointer deref when sent blank line first
*
*/
#define _POSIX_C_SOURCE 200809L /* fdopen(3), SA_RESTART */
#include <netinet/in.h> /* htonl(3), htons(3), INADDR_ANY, struct sockaddr_in
* includes sys/types.h - socket(2), bind(2), listen(2), accept(2)
* includes sys/socket.h - socket(2), bind(2), listen(2), accept(2)
*/
#include <sys/wait.h> /* waitpid(2) */
#include <signal.h> /* struct sigaction, sigaction(2), sigemptyset(3),
* SIGCHLD, SA_RESTART
*/
#include <unistd.h> /* fork(2) */
#include <stdbool.h> /* true */
#include <stdlib.h> /* exit(3), malloc(3), free(3) */
#include <errno.h> /* errno */
#include <stdio.h> /* perror(3), FILE, fdopen(3), fgets(3), fprintf(3),
* fclose(3), printf(3), sscanf(3), __fpurge(3)
*/
#include <stdio_ext.h> /* __fpurge(3) */
#include <string.h> /* strncmp(3), strchr(3) */
#define HOST "pimpninjas.org"
#define PORT 80
#define BACKLOG 128
#define BUFSZ 4096
__attribute__ ((noreturn))
void die (char *err) {
perror(err);
exit(errno);
}
void reap (int sig) { while (0 < waitpid(-1, 0, WNOHANG)); }
char *chop (char *str) {
char *idx;
idx = strchr(str, '\r');
if (idx) *idx = 0;
idx = strchr(str, '\n');
if (idx) *idx = 0;
return idx;
}
__attribute__ ((noreturn))
void send_response (FILE *req, char *proto, char *hostname, char *filepath) {
// purge the socket
// this function is absurdly undocumented, jesus christ...
// Windows: fflush(req);
// BSD: fpurge(req);
__fpurge(req);
// proto = HTTP/1.1 if not specified, emit that immediately
proto = proto && *proto ? proto : (char *)"HTTP/1.1";
fprintf(req, "%s ", proto);
// then append the actual request
if ((
*(uint64_t *)proto == *(uint64_t *)"HTTP/1.1" &&
(!hostname || !*hostname)
) || !filepath || !*filepath || '/' != *filepath)
fprintf(req, "400 Bad Request\r\nConnection: close\r\n\r\n");
else {
/* default hostname if none specified with HTTP/1.0 (allowed) */
if (!hostname || !*hostname) hostname = HOST;
printf("Redirecting request for %s%s\n", hostname, filepath);
fprintf(
req,
"302 TLS required\r\n"
"Location: https://%s%s\r\n"
"Connection: close\r\n\r\n",
hostname, filepath
);
}
fclose(req);
if ((char *)"HTTP/1.1" != proto) free(proto);
if (filepath) free(filepath);
if ((char *)HOST != hostname) free(hostname);
exit(0);
}
__attribute__ ((noreturn))
void handle_connection (FILE *req) {
char *buffer = malloc(BUFSZ);
char *filepath = 0, *hostname = 0, *proto = 0;
while (NULL != fgets(buffer, BUFSZ - 1, req)) {
chop(buffer);
if (!*buffer) {
free(buffer);
send_response(req, proto, hostname, filepath);
}
char *spc = strrchr(buffer, ' ');
if (spc && 0 == strncmp(spc, " HTTP/1.", 8)) {
// handle initial request (HTTP/1.x)
sscanf(buffer, "%*s %ms %ms", &filepath, &proto);
} else if (!filepath || !*filepath) {
free(buffer);
send_response(req, proto, hostname, filepath);
} else sscanf(buffer, "Host: %ms", &hostname);
}
/* socket closed unexpectedly, or something has gone wrong.
* clean up what we know we can, and die.
*/
free(buffer);
fclose(req);
exit(0);
}
int main (void) {
int sock;
struct sockaddr_in sin;
socklen_t sin_sz = sizeof(sin);
/* reap our unruly bastard offspring */
struct sigaction sa;
sa.sa_handler = &reap;
sigemptyset(&sa.sa_mask);
sa.sa_flags = SA_RESTART;
if (-1 == sigaction(SIGCHLD, &sa, 0))
die("Failed to set up child process reaping");
if (true == (sock = socket(PF_INET, SOCK_STREAM, 0)))
die("Failed to create socket");
sin.sin_family = AF_INET;
sin.sin_addr.s_addr = htonl(INADDR_ANY);
sin.sin_port = htons(PORT);
if (0 != bind(sock, (struct sockaddr *)&sin, sin_sz))
die("Failed to bind created socket to port");
if (0 != listen(sock, BACKLOG))
die("Failed to listen on bound socket");
for (;;) {
FILE *req = fdopen(accept(sock, (struct sockaddr *)&sin, &sin_sz), "r+");
if (!fork()) handle_connection(req);
else fclose(req);
}
return 0;
}