proxy

a reverse proxy written in C for forwarding network requests


proxy / proxy.c

proxy.c (9.2K) raw | download
1#define _POSIX_C_SOURCE 200809L
2#include <sys/socket.h>
3#include <sys/wait.h>
4#include <netinet/in.h>
5#include <netdb.h>
6#include <signal.h>
7#include <unistd.h>
8#include <errno.h>
9#include <stdio.h>
10#include <stdlib.h>
11#include <string.h>
12#include <strings.h>
13
14#define MAX_HEADERS 16384
15
16static int
17write_all(int fd, const char *buf, size_t len)
18{
19 while (len > 0) {
20 ssize_t n = write(fd, buf, len);
21 if (n < 0 && errno == EINTR) continue;
22 if (n <= 0) return -1;
23 buf += n;
24 len -= (size_t)n;
25 }
26 return 0;
27}
28
29static void
30send_error(int fd, int status)
31{
32 const char *reason = status == 400 ? "Bad Request" :
33 status == 405 ? "Method Not Allowed" : "Bad Gateway";
34 char response[256];
35 int n = snprintf(response, sizeof(response),
36 "HTTP/1.0 %d %s\r\nContent-Length: 0\r\nConnection: close\r\n%s\r\n",
37 status, reason, status == 405 ? "Allow: GET, HEAD\r\n" : "");
38 if (n > 0 && (size_t)n < sizeof(response))
39 (void)write_all(fd, response, (size_t)n);
40}
41
42static int
43valid_token(const char *text)
44{
45 const char *allowed = "!#$%&'*+-.^_`|~0123456789"
46 "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
47 return *text != '\0' && strspn(text, allowed) == strlen(text);
48}
49
50/* Rebuild bounded headers, replacing connection handling and rejecting bodies. */
51static int
52prepare_request(char *request, char *end, char *forward, size_t *size)
53{
54 char *line = strstr(request, "\r\n");
55 char *method = request, *target, *version, *p;
56 int host = 0, n;
57 if (line == NULL) return 400;
58 *line = '\0';
59 line += 2;
60 target = strchr(method, ' ');
61 if (target == NULL) return 400;
62 *target++ = '\0';
63 version = strchr(target, ' ');
64 if (version == NULL) return 400;
65 *version++ = '\0';
66 if (!valid_token(method) || *target != '/' ||
67 (strcmp(version, "HTTP/1.0") != 0 && strcmp(version, "HTTP/1.1") != 0))
68 return 400;
69 for (p = target; *p; ++p)
70 if ((unsigned char)*p <= 32 || (unsigned char)*p == 127 || *p == '#')
71 return 400;
72 if (strcmp(method, "GET") != 0 && strcmp(method, "HEAD") != 0)
73 return 405;
74 n = snprintf(forward, MAX_HEADERS + 64, "%s %s HTTP/1.0\r\n", method, target);
75 if (n < 0 || n >= MAX_HEADERS + 64) return 400;
76 *size = (size_t)n;
77 while (line < end) {
78 char *next = strstr(line, "\r\n");
79 char *colon, *value, *last;
80 char saved;
81 size_t len;
82 int drop = 0;
83 if (next == NULL || next > end) return 400;
84 len = (size_t)(next - line);
85 *next = '\0';
86 colon = strchr(line, ':');
87 if (colon == NULL) return 400;
88 *colon = '\0';
89 if (!valid_token(line)) return 400;
90 value = colon + 1;
91 for (p = value; *p; ++p)
92 if (((unsigned char)*p < 32 && *p != '\t') || (unsigned char)*p == 127)
93 return 400;
94 while (*value == ' ' || *value == '\t') ++value;
95 last = next;
96 while (last > value && (last[-1] == ' ' || last[-1] == '\t')) --last;
97 saved = *last;
98 *last = '\0';
99 if (strcasecmp(line, "Host") == 0) {
100 if (!*value || ++host > 1) return 400;
101 } else if (strcasecmp(line, "Content-Length") == 0) {
102 if (!*value || strspn(value, "0") != strlen(value)) return 400;
103 drop = 1;
104 } else if (strcasecmp(line, "Transfer-Encoding") == 0 ||
105 strcasecmp(line, "Expect") == 0 || strcasecmp(line, "Upgrade") == 0) {
106 return 400;
107 } else if (strcasecmp(line, "Connection") == 0) {
108 /* Other Connection tokens name headers we do not support forwarding. */
109 if (strcasecmp(value, "close") != 0 && strcasecmp(value, "keep-alive") != 0)
110 return 400;
111 drop = 1;
112 } else if (strcasecmp(line, "Proxy-Connection") == 0 ||
113 strcasecmp(line, "Keep-Alive") == 0 ||
114 strcasecmp(line, "TE") == 0 || strcasecmp(line, "Trailer") == 0) {
115 drop = 1;
116 }
117 if (!drop) {
118 *colon = ':';
119 *last = saved;
120 if (*size + len + 2 > MAX_HEADERS + 40) return 400;
121 memcpy(forward + *size, line, len);
122 *size += len;
123 memcpy(forward + *size, "\r\n", 2);
124 *size += 2;
125 }
126 line = next + 2;
127 }
128 if (strcmp(version, "HTTP/1.1") == 0 && !host) return 400;
129 memcpy(forward + *size, "Connection: close\r\n\r\n", 21);
130 *size += 21;
131 return 0;
132}
133
134static int
135connect_upstream(const char *host, const char *port)
136{
137 struct addrinfo hints = {0}, *addresses, *addr;
138 int fd = -1, result, saved = ECONNREFUSED;
139 hints.ai_family = AF_INET;
140 hints.ai_socktype = SOCK_STREAM;
141 hints.ai_flags = AI_NUMERICSERV;
142 result = getaddrinfo(host, port, &hints, &addresses);
143 if (result != 0) {
144 fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(result));
145 return -1;
146 }
147 for (addr = addresses; addr != NULL; addr = addr->ai_next) {
148 do {
149 fd = socket(addr->ai_family, addr->ai_socktype, addr->ai_protocol);
150 if (fd < 0) { saved = errno; break; }
151 if (connect(fd, addr->ai_addr, addr->ai_addrlen) == 0) break;
152 saved = errno;
153 close(fd);
154 fd = -1;
155 } while (saved == EINTR);
156 if (fd >= 0) break;
157 }
158 freeaddrinfo(addresses);
159 if (fd < 0) { errno = saved; perror("connect"); }
160 return fd;
161}
162
163static void
164handle_client(int client, const char *host, const char *port)
165{
166 char request[MAX_HEADERS + 1], forward[MAX_HEADERS + 64], buf[16384];
167 char *end = NULL;
168 size_t used = 0, size;
169 int upstream, status, replied = 0;
170 while (used < MAX_HEADERS) {
171 ssize_t n = recv(client, request + used, MAX_HEADERS - used, 0);
172 if (n < 0 && errno == EINTR) continue;
173 if (n <= 0) {
174 if (n < 0) perror("recv client");
175 else if (used) send_error(client, 400);
176 return;
177 }
178 if (memchr(request + used, '\0', (size_t)n) != NULL) {
179 send_error(client, 400);
180 return;
181 }
182 used += (size_t)n;
183 request[used] = '\0';
184 end = strstr(request, "\r\n\r\n");
185 if (end != NULL) break;
186 }
187 if (end == NULL) { send_error(client, 400); return; }
188 status = prepare_request(request, end, forward, &size);
189 if (status) { send_error(client, status); return; }
190 upstream = connect_upstream(host, port);
191 if (upstream < 0) { send_error(client, 502); return; }
192 if (write_all(upstream, forward, size) < 0) {
193 perror("write upstream");
194 send_error(client, 502);
195 close(upstream);
196 return;
197 }
198 for (;;) {
199 ssize_t n = recv(upstream, buf, sizeof(buf), 0);
200 if (n < 0 && errno == EINTR) continue;
201 if (n <= 0) {
202 if (n < 0) perror("recv upstream");
203 if (!replied) send_error(client, 502);
204 break;
205 }
206 if (write_all(client, buf, (size_t)n) < 0) break;
207 replied = 1;
208 }
209 close(upstream);
210}
211
212static void
213reap_children(int sig)
214{
215 int saved = errno;
216 pid_t pid;
217 (void)sig;
218 do {
219 pid = waitpid(-1, NULL, WNOHANG);
220 } while (pid > 0 || (pid < 0 && errno == EINTR));
221 errno = saved;
222}
223
224int
225main(int argc, char **argv)
226{
227 long ports[2];
228 int listener, one = 1, i;
229 struct sockaddr_in address = {0};
230 struct sigaction action = {0};
231 if (argc != 4) {
232 fprintf(stderr, "usage: %s listen-port upstream-host upstream-port\n", argv[0]);
233 return 1;
234 }
235 for (i = 0; i < 2; ++i) {
236 char *end;
237 const char *text = argv[i == 0 ? 1 : 3];
238 errno = 0;
239 ports[i] = strtol(text, &end, 10);
240 if (errno || !*text || *end || ports[i] < 1 || ports[i] > 65535) {
241 fprintf(stderr, "invalid port: %s\n", text);
242 return 1;
243 }
244 }
245 sigemptyset(&action.sa_mask);
246 action.sa_handler = SIG_IGN;
247 if (sigaction(SIGPIPE, &action, NULL) < 0) { perror("sigaction"); return 1; }
248 action.sa_handler = reap_children;
249 action.sa_flags = SA_RESTART | SA_NOCLDSTOP;
250 if (sigaction(SIGCHLD, &action, NULL) < 0) { perror("sigaction"); return 1; }
251 listener = socket(AF_INET, SOCK_STREAM, 0);
252 if (listener < 0) { perror("socket"); return 1; }
253 address.sin_family = AF_INET;
254 address.sin_addr.s_addr = htonl(INADDR_ANY);
255 address.sin_port = htons((unsigned short)ports[0]);
256 if (setsockopt(listener, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)) < 0 ||
257 bind(listener, (struct sockaddr *)&address, sizeof(address)) < 0 ||
258 listen(listener, 32) < 0) {
259 perror("proxy");
260 close(listener);
261 return 1;
262 }
263 printf("listening on port %ld\n", ports[0]);
264 fflush(stdout);
265 for (;;) {
266 int client = accept(listener, NULL, NULL);
267 pid_t pid;
268 if (client < 0) {
269 if (errno == EINTR || errno == ECONNABORTED) continue;
270 perror("accept");
271 break;
272 }
273 pid = fork();
274 if (pid == 0) {
275 close(listener);
276 handle_client(client, argv[2], argv[3]);
277 close(client);
278 _exit(0);
279 }
280 if (pid < 0) perror("fork");
281 close(client);
282 }
283 close(listener);
284 return 1;
285}