/* * Copyright (C) 2019 Red Hat, Inc. * SPDX-License-Identifier: LGPL-2.1-or-later */ #include "config.h" #include "httpredirect.h" #include #include #include #include #include #include static char * read_line (FILE *stream, char *buffer, size_t sizeof_buffer, size_t *offset) { if (*offset >= sizeof_buffer) return NULL; char *line = fgets (buffer + *offset, sizeof_buffer - *offset, stream); if (line == NULL) return NULL; /* Make sure we're terminated with \r\n or \n */ size_t line_length = strcspn (line, "\r\n"); const char *ending = line + line_length; if (strcmp (ending, "\r\n") != 0 && strcmp (ending, "\n") != 0) return NULL; /* Discard the ending */ line[line_length++] = '\0'; *offset += line_length; return line; } static bool write_error (FILE *output) { fprintf (output, "HTTP/1.1 400 Client Error\r\n" "Connection: close\r\n" "\r\n" "Incorrect request.\r\n"); return false; } static bool http_redirect (FILE *input, FILE *output) { char buffer[10000]; size_t offset = 0; char *request_line = read_line (input, buffer, sizeof buffer, &offset); if (request_line == NULL) return write_error (output); char *path = strchr (request_line, ' '); if (path == NULL) return write_error (output); path++; char *end_path = strchr (path, ' '); if (end_path == NULL) return write_error (output); *end_path = '\0'; const char *host = NULL; const char *header; do { header = read_line (input, buffer, sizeof buffer, &offset); if (header == NULL) return write_error (output); #define HOST_HEADER "Host:" if (strncmp (header, HOST_HEADER, strlen (HOST_HEADER)) == 0) { if (host != NULL) return write_error (output); host = header + strlen (HOST_HEADER); host += strspn (host, " \t"); } } while (header[0] != '\0'); if (!host) return write_error (output); /* Defense-in-depth: validate that host and path don't contain CR or LF. The current read_line() implementation * already prevents this, but add explicit validation just in case, and reject invalid values. */ if (strchr (host, '\r') || strchr (host, '\n') || strchr (path, '\r') || strchr (path, '\n')) return write_error (output); fprintf (output, "HTTP/1.1 301 Moved Permanently\r\n" "Location: https://%s%s\r\n" "Connection: close\r\n" "\r\n", host, path); return true; } static void * http_redirect_start (void *arg) { FILE *stream = arg; http_redirect (stream, stream); fclose (stream); return NULL; } int http_redirect_connect (void) { int sv[2]; int r = socketpair (AF_UNIX, SOCK_STREAM, 0, sv); if (r != 0) return -1; /* At this point we're going to succeed and return sv[1] to the * caller. We need to make sure that sv[0] gets closed one way or * another: * - if we fail to create the stream, close() * - if we fail to spawn the thread, fclose() * - otherwise, the thread calls fclose() */ FILE *stream = fdopen (sv[0], "r+"); if (stream == NULL) close (sv[0]); else { pthread_attr_t attr; pthread_t thread; pthread_attr_init (&attr); pthread_attr_setdetachstate (&attr, PTHREAD_CREATE_DETACHED); if (pthread_create (&thread, &attr, &http_redirect_start, stream)) fclose (stream); pthread_attr_destroy (&attr); } return sv[1]; } #ifdef HTTP_REDIRECT_STANDALONE int main (void) { return http_redirect (stdin, stdout) ? 0 : 1; } #endif