Instantly share code, notes, and snippets.
Created
June 16, 2025 15:15
-
Star
0
(0)
You must be signed in to star a gist -
Fork
0
(0)
You must be signed in to fork a gist
-
Save aquantus-ru/17429f741a2fe4c685954beaa90c4ab8 to your computer and use it in GitHub Desktop.
gcc -o http_share http_share.c
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #define _GNU_SOURCE | |
| #include <arpa/inet.h> | |
| #include <errno.h> | |
| #include <fcntl.h> | |
| #include <netinet/in.h> | |
| #include <signal.h> | |
| #include <stdio.h> | |
| #include <stdlib.h> | |
| #include <string.h> | |
| #include <sys/socket.h> | |
| #include <sys/stat.h> | |
| #include <sys/types.h> | |
| #include <sys/wait.h> | |
| #include <unistd.h> | |
| #include <time.h> | |
| #define BUFFER_SIZE 8192 | |
| static volatile int keep_running = 1; | |
| void int_handler(int dummy) { | |
| keep_running = 0; | |
| } | |
| static char guid[33]; // 32 hex + null | |
| // Generate a simple random hex GUID | |
| void generate_guid(char *buf, size_t len) { | |
| const char *hex = "0123456789abcdef"; | |
| for (size_t i = 0; i < len - 1; i++) { | |
| buf[i] = hex[rand() % 16]; | |
| } | |
| buf[len - 1] = '\0'; | |
| } | |
| void log_message(FILE *logf, const char *fmt, ...) { | |
| va_list args; | |
| va_start(args, fmt); | |
| vfprintf(logf, fmt, args); | |
| fflush(logf); | |
| va_end(args); | |
| } | |
| // Get MIME type based on file extension | |
| const char *get_mime_type(const char *path) { | |
| const char *ext = strrchr(path, '.'); | |
| if (!ext) return "application/octet-stream"; | |
| if (strcmp(ext, ".html") == 0) return "text/html"; | |
| if (strcmp(ext, ".css") == 0) return "text/css"; | |
| if (strcmp(ext, ".js") == 0) return "application/javascript"; | |
| if (strcmp(ext, ".png") == 0) return "image/png"; | |
| if (strcmp(ext, ".jpg") == 0 || strcmp(ext, ".jpeg") == 0) return "image/jpeg"; | |
| if (strcmp(ext, ".gif") == 0) return "image/gif"; | |
| if (strcmp(ext, ".mp4") == 0) return "video/mp4"; | |
| return "application/octet-stream"; | |
| } | |
| // Write HTTP 404 response (empty, no message per requirement) | |
| void send_404(int client_sock) { | |
| const char *msg = "HTTP/1.1 404 Not Found\r\nConnection: close\r\n\r\n"; | |
| write(client_sock, msg, strlen(msg)); | |
| } | |
| // Send 200 OK + Content-Type header | |
| void send_response_header(int client_sock, const char *mime, size_t content_length) { | |
| char header[256]; | |
| int len = snprintf(header, sizeof(header), | |
| "HTTP/1.1 200 OK\r\n" | |
| "Content-Type: %s\r\n" | |
| "Content-Length: %zu\r\n" | |
| "Accept-Ranges: bytes\r\n" | |
| "Connection: close\r\n\r\n", | |
| mime, content_length); | |
| write(client_sock, header, len); | |
| } | |
| // Serve the file in chunks | |
| void serve_file(int client_sock, const char *filepath, FILE *logf, const char *client_ip) { | |
| int fd = open(filepath, O_RDONLY); | |
| if (fd == -1) { | |
| send_404(client_sock); | |
| return; | |
| } | |
| struct stat st; | |
| if (fstat(fd, &st) == -1) { | |
| close(fd); | |
| send_404(client_sock); | |
| return; | |
| } | |
| const char *mime = get_mime_type(filepath); | |
| send_response_header(client_sock, mime, st.st_size); | |
| char buffer[BUFFER_SIZE]; | |
| ssize_t r; | |
| while ((r = read(fd, buffer, sizeof(buffer))) > 0) { | |
| if (write(client_sock, buffer, r) != r) break; | |
| } | |
| close(fd); | |
| log_message(logf, "[INFO] GET /shared?file=%s from %s\n", guid, client_ip); | |
| } | |
| // Parse IP from sockaddr for logging | |
| void sockaddr_to_ip(struct sockaddr_in *addr, char *ip_str, size_t size) { | |
| inet_ntop(AF_INET, &(addr->sin_addr), ip_str, size); | |
| } | |
| // Fork & exec ssh tunnel, capture output line with tunnel url, print clean URL | |
| pid_t start_ssh_tunnel(int port, char *tunnel_url, size_t url_size) { | |
| int pipefd[2]; | |
| if (pipe(pipefd) == -1) { | |
| perror("pipe"); | |
| return -1; | |
| } | |
| pid_t pid = fork(); | |
| if (pid < 0) { | |
| perror("fork"); | |
| return -1; | |
| } | |
| if (pid == 0) { | |
| // Child: redirect stdout to pipe write end | |
| close(pipefd[0]); | |
| dup2(pipefd[1], STDOUT_FILENO); | |
| dup2(pipefd[1], STDERR_FILENO); // redirect stderr too to silence unwanted messages | |
| close(pipefd[1]); | |
| // No tty, quiet, strict host key checking off | |
| char port_arg[32]; | |
| snprintf(port_arg, sizeof(port_arg), "%d", port); | |
| execlp("ssh", "ssh", "-T", | |
| "-oLogLevel=ERROR", | |
| "-oStrictHostKeyChecking=no", | |
| "-R", "80:localhost", port_arg, | |
| "serveo.net", NULL); | |
| perror("execlp ssh"); | |
| exit(1); | |
| } | |
| // Parent: close write end, read tunnel url line | |
| close(pipefd[1]); | |
| FILE *fp = fdopen(pipefd[0], "r"); | |
| if (!fp) { | |
| perror("fdopen"); | |
| return -1; | |
| } | |
| // Wait for a line containing serveo.net URL or timeout after ~10 sec | |
| char line[512]; | |
| time_t start = time(NULL); | |
| while (time(NULL) - start < 10) { | |
| if (fgets(line, sizeof(line), fp) == NULL) { | |
| usleep(100000); // wait 0.1s before retry | |
| continue; | |
| } | |
| // Example line contains: | |
| // "Forwarding HTTP traffic from https://8ebdf2fd66251c3d9ef059a37002f2e5.serveo.net" | |
| if (strstr(line, "Forwarding HTTP traffic from ")) { | |
| char *url_start = strstr(line, "https://"); | |
| if (url_start) { | |
| size_t len = strcspn(url_start, "\r\n"); | |
| if (len >= url_size) len = url_size - 1; | |
| strncpy(tunnel_url, url_start, len); | |
| tunnel_url[len] = '\0'; | |
| break; | |
| } | |
| } | |
| } | |
| fclose(fp); | |
| return pid; | |
| } | |
| void usage(const char *progname) { | |
| printf( | |
| "Usage: %s -f <file> [-p <port>] [-l <stdout|file>] [-d] [-t]\n" | |
| " -f <file> File to share (required)\n" | |
| " -p <port> Port to listen on (default 8080)\n" | |
| " -l <logging> Logging: stdout or filename (default stdout)\n" | |
| " -d Run as daemon\n" | |
| " -t Use serveo.net SSH tunnel\n" | |
| " -h Show this help\n", | |
| progname); | |
| } | |
| int main(int argc, char *argv[]) { | |
| int opt; | |
| char *filepath = NULL; | |
| int port = 8080; | |
| FILE *logf = stdout; | |
| char logfile_path[256] = {0}; | |
| int daemon_mode = 0; | |
| int use_tunnel = 0; | |
| pid_t ssh_pid = -1; | |
| char tunnel_url[256] = {0}; | |
| srand(time(NULL)); | |
| while ((opt = getopt(argc, argv, "f:p:l:dth")) != -1) { | |
| switch (opt) { | |
| case 'f': | |
| filepath = optarg; | |
| break; | |
| case 'p': | |
| port = atoi(optarg); | |
| if (port <= 0 || port > 65535) { | |
| fprintf(stderr, "Invalid port number.\n"); | |
| return 1; | |
| } | |
| break; | |
| case 'l': | |
| if (strcmp(optarg, "stdout") == 0) { | |
| logf = stdout; | |
| } else { | |
| strncpy(logfile_path, optarg, sizeof(logfile_path) - 1); | |
| } | |
| break; | |
| case 'd': | |
| daemon_mode = 1; | |
| break; | |
| case 't': | |
| use_tunnel = 1; | |
| break; | |
| case 'h': | |
| default: | |
| usage(argv[0]); | |
| return 0; | |
| } | |
| } | |
| if (!filepath) { | |
| fprintf(stderr, "File to share is required (-f).\n"); | |
| usage(argv[0]); | |
| return 1; | |
| } | |
| // Open logfile if specified | |
| if (logfile_path[0]) { | |
| logf = fopen(logfile_path, "a"); | |
| if (!logf) { | |
| perror("Failed to open log file"); | |
| return 1; | |
| } | |
| } | |
| // Daemonize if requested | |
| if (daemon_mode) { | |
| pid_t pid = fork(); | |
| if (pid < 0) { | |
| perror("fork"); | |
| return 1; | |
| } | |
| if (pid > 0) { | |
| // parent exits | |
| return 0; | |
| } | |
| // child continues | |
| if (setsid() < 0) { | |
| perror("setsid"); | |
| return 1; | |
| } | |
| fclose(stdin); | |
| fclose(stdout); | |
| fclose(stderr); | |
| // Reopen stdout/stderr to logfile or /dev/null | |
| if (logf != stdout) { | |
| dup2(fileno(logf), STDOUT_FILENO); | |
| dup2(fileno(logf), STDERR_FILENO); | |
| } else { | |
| freopen("/dev/null", "w", stdout); | |
| freopen("/dev/null", "w", stderr); | |
| } | |
| } | |
| generate_guid(guid, sizeof(guid)); | |
| // Validate file exists | |
| struct stat st; | |
| if (stat(filepath, &st) != 0 || !S_ISREG(st.st_mode)) { | |
| fprintf(stderr, "File not found or not a regular file: %s\n", filepath); | |
| return 1; | |
| } | |
| int server_sock = socket(AF_INET, SOCK_STREAM, 0); | |
| if (server_sock < 0) { | |
| perror("socket"); | |
| return 1; | |
| } | |
| int optval = 1; | |
| setsockopt(server_sock, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval)); | |
| struct sockaddr_in server_addr = {0}; | |
| server_addr.sin_family = AF_INET; | |
| server_addr.sin_port = htons(port); | |
| server_addr.sin_addr.s_addr = INADDR_ANY; | |
| if (bind(server_sock, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) { | |
| perror("bind"); | |
| return 1; | |
| } | |
| if (listen(server_sock, 10) < 0) { | |
| perror("listen"); | |
| return 1; | |
| } | |
| // Setup signal handler for clean exit | |
| signal(SIGINT, int_handler); | |
| signal(SIGTERM, int_handler); | |
| // Start tunnel if requested | |
| if (use_tunnel) { | |
| log_message(logf, "[INFO] Starting tunnel via serveo.net\n"); | |
| ssh_pid = start_ssh_tunnel(port, tunnel_url, sizeof(tunnel_url)); | |
| if (ssh_pid < 0) { | |
| log_message(logf, "[ERROR] Failed to start SSH tunnel\n"); | |
| if (logf != stdout) fclose(logf); | |
| return 1; | |
| } | |
| if (tunnel_url[0]) { | |
| log_message(logf, "[TUNNEL] Forwarding HTTP traffic from %s\n", tunnel_url); | |
| log_message(logf, "[INFO] Tunnel ready.\n"); | |
| // Print clean URL | |
| printf("Link: %s/shared?file=%s\n", tunnel_url, guid); | |
| fflush(stdout); | |
| } else { | |
| log_message(logf, "[ERROR] Tunnel URL not found.\n"); | |
| } | |
| } else { | |
| // No tunnel: get local IP address (use 127.0.0.1 fallback) | |
| char ipbuf[INET_ADDRSTRLEN] = "127.0.0.1"; | |
| int sock_tmp = socket(AF_INET, SOCK_DGRAM, 0); | |
| if (sock_tmp >= 0) { | |
| struct sockaddr_in serv; | |
| memset(&serv, 0, sizeof(serv)); | |
| serv.sin_family = AF_INET; | |
| serv.sin_addr.s_addr = inet_addr("8.8.8.8"); // Google DNS IP | |
| serv.sin_port = htons(53); | |
| connect(sock_tmp, (struct sockaddr *)&serv, sizeof(serv)); | |
| struct sockaddr_in name; | |
| socklen_t namelen = sizeof(name); | |
| if (getsockname(sock_tmp, (struct sockaddr *)&name, &namelen) == 0) { | |
| inet_ntop(AF_INET, &name.sin_addr, ipbuf, sizeof(ipbuf)); | |
| } | |
| close(sock_tmp); | |
| } | |
| printf("Link: http://%s:%d/shared?file=%s\n", ipbuf, port, guid); | |
| fflush(stdout); | |
| } | |
| log_message(logf, "[INFO] Serving file '%s' on port %d with GUID %s\n", filepath, port, guid); | |
| // Main accept loop | |
| while (keep_running) { | |
| struct sockaddr_in client_addr; | |
| socklen_t addrlen = sizeof(client_addr); | |
| int client_sock = accept(server_sock, (struct sockaddr *)&client_addr, &addrlen); | |
| if (client_sock < 0) { | |
| if (errno == EINTR) continue; | |
| perror("accept"); | |
| break; | |
| } | |
| char client_ip[INET_ADDRSTRLEN]; | |
| sockaddr_to_ip(&client_addr, client_ip, sizeof(client_ip)); | |
| pid_t pid = fork(); | |
| if (pid < 0) { | |
| perror("fork"); | |
| close(client_sock); | |
| continue; | |
| } | |
| if (pid == 0) { | |
| // Child: handle request and exit | |
| close(server_sock); | |
| // Read HTTP request header (only the first line) | |
| char req[BUFFER_SIZE] = {0}; | |
| ssize_t r = read(client_sock, req, sizeof(req) - 1); | |
| if (r <= 0) { | |
| close(client_sock); | |
| exit(0); | |
| } | |
| req[r] = 0; | |
| // Parse GET line | |
| char method[16], uri[1024]; | |
| if (sscanf(req, "%15s %1023s", method, uri) != 2) { | |
| close(client_sock); | |
| exit(0); | |
| } | |
| // Check if URI matches /shared?file=<guid> | |
| if (strcmp(method, "GET") != 0) { | |
| send_404(client_sock); | |
| close(client_sock); | |
| exit(0); | |
| } | |
| // Check prefix "/shared?file=" | |
| const char *prefix = "/shared?file="; | |
| if (strncmp(uri, prefix, strlen(prefix)) != 0) { | |
| // No error, just close connection silently | |
| close(client_sock); | |
| exit(0); | |
| } | |
| const char *requested_guid = uri + strlen(prefix); | |
| if (strcmp(requested_guid, guid) != 0) { | |
| // No error, close silently | |
| close(client_sock); | |
| exit(0); | |
| } | |
| serve_file(client_sock, filepath, logf, client_ip); | |
| close(client_sock); | |
| exit(0); | |
| } | |
| close(client_sock); | |
| // Reap zombies | |
| while (waitpid(-1, NULL, WNOHANG) > 0) | |
| ; | |
| } | |
| // Cleanup on exit | |
| if (ssh_pid > 0) { | |
| kill(ssh_pid, SIGTERM); | |
| waitpid(ssh_pid, NULL, 0); | |
| } | |
| close(server_sock); | |
| if (logf != stdout) fclose(logf); | |
| return 0; | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment