diff options
Diffstat (limited to 'pa3_proxy/proxy.c')
-rw-r--r-- | pa3_proxy/proxy.c | 362 |
1 files changed, 362 insertions, 0 deletions
diff --git a/pa3_proxy/proxy.c b/pa3_proxy/proxy.c new file mode 100644 index 0000000..a7f2885 --- /dev/null +++ b/pa3_proxy/proxy.c @@ -0,0 +1,362 @@ +// 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); + } +} |