ecen5273

ECEN5273 Coursework
git clone https://git.pngu.org/ecen5273
Log | Files | Refs | README

proxy.c (12105B)


// HTTP Caching Proxy
// Jack Sangdahl
// 28 October 2024
#include "proxy.h"

static int timeout;

int main(int argc, char **argv) {
  if (argc < 3) {
    fprintf(stderr, "usage: %s <port number> <cache timeout>\n", argv[0]);
    return EXIT_FAILURE;
  }
  signal(SIGINT, handle_sigint);
  int port_num = atoi(argv[1]);
  timeout = atoi(argv[2]);
  struct stat st = {0};
  if (stat(CACHE_DIRECTORY, &st) == -1)
    mkdir(CACHE_DIRECTORY, 0700);
  pthread_t tid;
  pthread_attr_t attr;
  if (pthread_attr_init(&attr)) {
    perror("creating thread attributes failed");
    return EXIT_FAILURE;
  }
  if (pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED)) {
    perror("setting detached state failed");
    return EXIT_FAILURE;
  }
  int sockfd = socket(AF_INET, SOCK_STREAM, 0);
  if (sockfd < 0) {
    perror("socket failed");
    return EXIT_FAILURE;
  }
  printf("socket created\n");
  struct sockaddr_in server_addr, client_addr;
  int server_addr_len = sizeof(server_addr);
  int client_addr_len = sizeof(client_addr);
  server_addr.sin_family = AF_INET;
  server_addr.sin_port = htons(port_num);
  server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
  if (bind(sockfd, (struct sockaddr *)&server_addr, server_addr_len)) {
    perror("binding failed");
    return EXIT_FAILURE;
  }
  printf("socket bound to address\n");
  if (listen(sockfd, SOMAXCONN)) {
    perror("listening failed");
    return EXIT_FAILURE;
  }
  printf("listening for connections...\n");
  while (1) {
    int *new_sockfd = (int *)malloc(sizeof(int));
    *new_sockfd = accept(sockfd, (struct sockaddr *)&client_addr,
                         (socklen_t *)&client_addr_len);
    if (*new_sockfd < 0)
      perror("accepting client connection failed");
    else
      printf("new connection accepted\n");
    if (pthread_create(&tid, &attr, handle_connection, (void *)new_sockfd)) {
      free(new_sockfd);
      perror("creating thread failed");
    }
  }
  return EXIT_SUCCESS;
}

void *handle_connection(void *a) {
  int connection = *(int *)a;
  struct sockaddr_in server_addr;
  server_addr.sin_family = AF_INET;
  server_addr.sin_port = htons(80);
  struct prefetcher_args pf;
  pthread_t prefetch_tid;
  pthread_attr_t prefetch_attr;
  if (pthread_attr_init(&prefetch_attr)) {
    perror("creating prefetcher thread attributes failed");
    pthread_exit(NULL);
  }
  if (pthread_attr_setdetachstate(&prefetch_attr, PTHREAD_CREATE_DETACHED)) {
    perror("setting prefetcher detached state failed");
    pthread_exit(NULL);
  }
  while (1) {
    bool prefetch = false;
    char client_proxy_data[DATA_SIZE];
    char server_proxy_data[DATA_SIZE];
    char proxy_client_data[DATA_SIZE];
    char http_version[10], hostname[100], request_type[10], url[DATA_SIZE],
        md5_checksum[33], cache_filename[50], extracted_ip[20], port_str[10],
        cached_ip[20];
    ssize_t bytes_recv =
        recv(connection, client_proxy_data, sizeof(client_proxy_data), 0);
    if (!bytes_recv) {
      printf("request complete\n");
      break;
    }
    sscanf(client_proxy_data, "%s %s %s", request_type, url, http_version);
    if (!strcmp(request_type, "GET")) {
      if (strstr(url, "https")) {
        strcpy(proxy_client_data, RSP_400);
        send(connection, proxy_client_data, strlen(proxy_client_data),
             MSG_NOSIGNAL);
        send(connection, MSG_400_URL, strlen(MSG_400_URL), MSG_NOSIGNAL);
        continue;
      }
      if (file_cached(url)) {
        printf("valid cache file found\n");
        send_cached_file(url, connection);
      } else {
        calculate_md5sum(url, md5_checksum);
        sprintf(cache_filename, "%s%s", CACHE_DIRECTORY, md5_checksum);
        printf("no file found in cache\n");
        sscanf(url, "%*[^/]%*c%*c%[^/]", hostname);
        printf("hostname: %s\n", hostname);
        if (strchr(hostname, ':')) {
          sscanf(hostname, "%[^:]%*c%[^/]", extracted_ip, port_str);
          if (blacklisted(extracted_ip)) {
            strcpy(proxy_client_data, RSP_403);
            send(connection, proxy_client_data, strlen(proxy_client_data),
                 MSG_NOSIGNAL);
            send(connection, MSG_403, strlen(MSG_403), MSG_NOSIGNAL);
            continue;
          }
          server_addr.sin_addr.s_addr = inet_addr(extracted_ip);
          server_addr.sin_port = htons(atoi(port_str));
        } else {
          if (blacklisted(hostname)) {
            strcpy(proxy_client_data, RSP_403);
            send(connection, proxy_client_data, strlen(proxy_client_data),
                 MSG_NOSIGNAL);
            send(connection, MSG_403, strlen(MSG_403), MSG_NOSIGNAL);
            continue;
          }
          if (hostname_cached(hostname, cached_ip))
            server_addr.sin_addr.s_addr = inet_addr(cached_ip);
          else {
            struct hostent *server = gethostbyname(hostname);
            if (!server) {
              perror("getting server info failed");
              continue;
            }
            memcpy((char *)&server_addr.sin_addr, server->h_addr,
                   server->h_length);
            cache_hostname(hostname, inet_ntoa(server_addr.sin_addr));
          }
        }
        int server_sockfd = socket(AF_INET, SOCK_STREAM, 0);
        if (server_sockfd < 0) {
          perror("server socket failed");
          continue;
        }
        if (connect(server_sockfd, (struct sockaddr *)&server_addr,
                    sizeof(server_addr)) < 0 &&
            errno != EISCONN) {
          perror("connecting to host failed");
          continue;
        }
        send(server_sockfd, client_proxy_data, strlen(client_proxy_data),
             MSG_NOSIGNAL);
        FILE *fp = fopen(cache_filename, "w");
        time_t curr = time(NULL);
        fprintf(fp, "%lu\n", curr);
        while ((bytes_recv = recv(server_sockfd, server_proxy_data,
                                  sizeof(server_proxy_data), 0))) {
          if (strstr(server_proxy_data, "<html"))
            prefetch = true;
          printf("bytes received: %zd\n", bytes_recv);
          fwrite(server_proxy_data, sizeof(char), bytes_recv, fp);
          send(connection, server_proxy_data, bytes_recv, MSG_NOSIGNAL);
          memset(server_proxy_data, 0, sizeof(server_proxy_data));
        }
        long file_size = ftell(fp);
        fclose(fp);
        if (file_size < 20)
          remove(cache_filename);
        if (prefetch) {
          strcpy(pf.filename, cache_filename);
          strcpy(pf.hostname, hostname);
          strcpy(pf.ipaddr, inet_ntoa(server_addr.sin_addr));
          if (pthread_create(&prefetch_tid, &prefetch_attr, prefetcher,
                             (void *)&pf))
            perror("creating prefetcher thread failed");
        }
      }
    } else {
      strcpy(proxy_client_data, RSP_400);
      send(connection, proxy_client_data, strlen(proxy_client_data),
           MSG_NOSIGNAL);
      send(connection, MSG_400_METHOD, strlen(MSG_400_METHOD), MSG_NOSIGNAL);
    }
  }
  close(connection);
  pthread_exit(NULL);
}

void *prefetcher(void *a) {
  struct prefetcher_args pf = *(struct prefetcher_args *)a;
  char html_filename[50];
  char server_hostname[100];
  char server_ipaddr[20];
  strcpy(html_filename, pf.filename);
  strcpy(server_hostname, pf.hostname);
  strcpy(server_ipaddr, pf.ipaddr);
  FILE *html_ptr = fopen(html_filename, "r");
  if (!html_ptr) {
    perror("opening html file failed");
    pthread_exit(NULL);
  }
  struct sockaddr_in server_addr;
  server_addr.sin_family = AF_INET;
  server_addr.sin_port = htons(80);
  server_addr.sin_addr.s_addr = inet_addr(server_ipaddr);
  char *line;
  size_t length;
  while (getline(&line, &length, html_ptr) != -1) {
    char cache_path[50], http_request[DATA_SIZE], recv_data[DATA_SIZE],
        md5_url[33], full_url[207], rel_url[100],
        *href_pos = strstr(line, "href");
    if (href_pos) {
      int server_sockfd = socket(AF_INET, SOCK_STREAM, 0);
      if (server_sockfd < 0) {
        perror("server socket failed");
        pthread_exit(NULL);
      }
      if (connect(server_sockfd, (struct sockaddr *)&server_addr,
                  sizeof(server_addr)) < 0 &&
          errno != EISCONN) {
        perror("connecting to host failed");
        pthread_exit(NULL);
      }
      char *url_start = strstr(line, "http://");
      if (url_start)
        sscanf(url_start, "%[^\"]", full_url);
      else {
        sscanf(href_pos, "%*[^=]%*c%*c%[^\"]", rel_url);
        if (rel_url[0] == '/')
          sprintf(full_url, "http://%s%s", server_hostname, rel_url);
        else
          sprintf(full_url, "http://%s/%s", server_hostname, rel_url);
      }
      sprintf(http_request, "GET %s HTTP/1.0\r\n\r\n", full_url);
      calculate_md5sum(full_url, md5_url);
      sprintf(cache_path, "%s%s", CACHE_DIRECTORY, md5_url);
      send(server_sockfd, http_request, strlen(http_request), MSG_NOSIGNAL);
      printf("cache path: %s\n", cache_path);
      FILE *fp = fopen(cache_path, "w");
      time_t curr = time(NULL);
      fprintf(fp, "%lu\n", curr);
      ssize_t bytes_recv;
      while (
          (bytes_recv = recv(server_sockfd, recv_data, sizeof(recv_data), 0))) {
        fwrite(recv_data, sizeof(char), bytes_recv, fp);
        memset(recv_data, 0, sizeof(recv_data));
      }
      long file_size = ftell(fp);
      fclose(fp);
      if (file_size < 20)
        remove(cache_path);
      printf("prefetched link: %s\n", full_url);
      close(server_sockfd);
    } else
      continue;
  }
  fclose(html_ptr);
  printf("prefetching complete\n");
  pthread_exit(NULL);
}

void calculate_md5sum(char *url, char *md5_checksum) {
  EVP_MD_CTX *ctx = EVP_MD_CTX_new();
  EVP_DigestInit_ex(ctx, EVP_md5(), NULL);
  EVP_DigestUpdate(ctx, url, strlen(url));
  uint md5_digest_len = EVP_MD_size(EVP_md5());
  u_char *md5_digest =
      (u_char *)OPENSSL_malloc(md5_digest_len * sizeof(u_char));
  EVP_DigestFinal_ex(ctx, md5_digest, &md5_digest_len);
  for (int i = 0; i < 16; ++i)
    sprintf(md5_checksum + i * 2, "%02x", (uint)md5_digest[i]);
  EVP_MD_CTX_free(ctx);
  OPENSSL_free(md5_digest);
}

void send_cached_file(char *url, int connection) {
  char md5_checksum[33];
  char filename[50];
  char data_read[DATA_SIZE];
  size_t bytes_read;
  char *line;
  size_t length;
  calculate_md5sum(url, md5_checksum);
  sprintf(filename, "%s%s", CACHE_DIRECTORY, md5_checksum);
  FILE *fp = fopen(filename, "r");
  if (getline(&line, &length, fp) == -1)
    return;
  while ((bytes_read = fread(data_read, sizeof(char), sizeof(data_read), fp))) {
    send(connection, data_read, bytes_read, MSG_NOSIGNAL);
    memset(data_read, 0, sizeof(data_read));
  }
  fclose(fp);
}

bool file_cached(char *url) {
  char md5_checksum[33];
  char filename[50];
  time_t curr = time(NULL), begin, diff;
  char *line;
  size_t length;
  calculate_md5sum(url, md5_checksum);
  sprintf(filename, "%s%s", CACHE_DIRECTORY, md5_checksum);
  FILE *fp = fopen(filename, "r");
  if (!fp)
    return false;
  else {
    getline(&line, &length, fp);
    sscanf(line, "%ld", &begin);
    diff = (curr - begin);
    if (diff > timeout) {
      fclose(fp);
      remove(filename);
      return false;
    }
    fclose(fp);
    return true;
  }
}

bool blacklisted(char *ipaddr) {
  FILE *block_list_ptr = fopen(BLOCKLIST_FILENAME, "a+");
  char *line;
  size_t length;
  while (getline(&line, &length, block_list_ptr) != -1)
    if (strstr(line, ipaddr))
      return true;
  return false;
}

void cache_hostname(char *hostname, char *ipaddr) {
  FILE *fp = fopen(HOST_CACHE_FILENAME, "a+");
  fprintf(fp, "%s,%s\n", hostname, ipaddr);
  fclose(fp);
}

bool hostname_cached(char *hostname, char *ipaddr) {
  strcpy(ipaddr, "none");
  char *line;
  size_t length;
  FILE *fp = fopen(HOST_CACHE_FILENAME, "w+");
  while (getline(&line, &length, fp) != -1)
    if (strstr(line, hostname)) {
      sscanf(line, "%*[^,]%*c%s", ipaddr);
      break;
    }
  fclose(fp);
  return !strcmp(ipaddr, "none") ? false : true;
}

void handle_sigint(int sig) {
  if (sig == SIGINT) {
    printf("\nterminating proxy...\n");
    exit(EXIT_SUCCESS);
  }
}