swps.c
1/* swps - Static Web Page Server
2 * -----------------------------
3 * This programs purpose is to serve a single static file as a web server.
4 * Besides serving a file in the root, swps also handles 301, 403 and 404.
5 *
6 *
7 * MIT License
8 *
9 * © 2019 Chris Noxz <chris@noxz.tech>
10 *
11 * Permission is hereby granted, free of charge, to any person obtaining a
12 * copy of this software and associated documentation files (the "Software"),
13 * to deal in the Software without restriction, including without limitation
14 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
15 * and/or sell copies of the Software, and to permit persons to whom the
16 * Software is furnished to do so, subject to the following conditions:
17 *
18 * The above copyright notice and this permission notice shall be included in
19 * all copies or substantial portions of the Software.
20 *
21 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
22 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
24 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
26 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
27 * SOFTWARE.
28 */
29
30#include <stdarg.h>
31#include <stdio.h>
32#include <stdlib.h>
33#include <unistd.h>
34#include <string.h>
35#include <time.h>
36#include <fcntl.h>
37#include <signal.h>
38#include <sys/socket.h>
39#include <arpa/inet.h>
40
41#define BUFSIZE 8096
42#define DATESIZE 64
43#define SUCCESS 200
44#define REDIRECT 301
45#define FORBIDDEN 403
46#define NOTFOUND 404
47#define BROKEN_PIPE 1001
48
49/* functions */
50static void die(int, const char *, ...);
51static int handle(int, unsigned char *);
52static int respond(int, int, unsigned char *, const char *);
53static void serve(int);
54static void stopserver(int);
55static int toint(const char *);
56
57static int serverfd;
58static const char *server = "swps 0.1";
59static const char *usage ="usage: swps PORT FILE\n"
60 "Serve html FILE as a web page on specified PORT\n\n"
61 "Options\n"
62 " -h display this help and exit\n\n"
63 "Full documentation and source code <https://noxz.tech/software/swps>\n";
64static const char *path = NULL;
65static const char *response_200 =
66 "HTTP/1.1 200 OK\n"
67 "Date: %s\n"
68 "Server: %s\n"
69 "Content-Length: %ld\n"
70 "Content-Type: text/html\n"
71 "Connection: closed\n\n";
72static const char *response_301 =
73 "HTTP/1.1 301 Moved Permanently\n"
74 "Location: /\n";
75static const char *response_403 =
76 "HTTP/1.1 403 Forbidden\n"
77 "Date: %s\n"
78 "Server: %s\n"
79 "Content-Length: 153\n"
80 "Content-Type: text/html\n"
81 "Connection: closed\n\n"
82
83 "<html><head>\n"
84 "<title>403 Forbidden</title>\n"
85 "</head><body>\n"
86 "<h1>Forbidden</h1>\n"
87 "The requested URL, or operation is not allowed on this server.\n"
88 "</body></html>\n";
89static const char *response_404 =
90 "HTTP/1.1 404 Not Found\n"
91 "Date: %s\n"
92 "Server: %s\n"
93 "Content-Length: 140\n"
94 "Content-Type: text/html\n"
95 "Connection: closed\n\n"
96
97 "<html><head>\n"
98 "<title>404 Not Found</title>\n"
99 "</head><body>\n"
100 "<h1>Not Found</h1>\n"
101 "The requested URL / was not found on this server.\n"
102 "</body></html>\n";
103
104int
105toint(const char *s)
106{
107 const char *p = s;
108 int n = -1;
109
110 while (*p && (n >= 0 || (n = 0) == 0)) {
111 if (*p >= 48 && *p <= 57)
112 n = (n * 10) + ((*p) - 48);
113 else
114 return -1;
115 p++;
116 }
117
118 return n;
119}
120
121void
122die(int n, const char *msg, ...)
123{
124 va_list arguments;
125
126 va_start(arguments, msg);
127 if (msg)
128 vfprintf(stderr, msg, arguments);
129 va_end(arguments);
130
131 if (n >= 0)
132 exit(n);
133}
134
135int
136respond(int type, int fd, unsigned char *ip, const char *pt)
137{
138 static char buffer[BUFSIZE + 1];
139 static char date[DATESIZE + 1];
140 time_t t = time(NULL);
141 struct tm *tm = gmtime(&t);
142 int filefd;
143 long l;
144
145 strftime(date, DATESIZE, "%a, %d %b %Y %H:%M:%S GMT", tm);
146
147 switch (type) {
148 case SUCCESS:
149 if ((filefd = open(path, O_RDONLY)) == -1)
150 return respond(NOTFOUND, fd, ip, pt);
151 l = (long)lseek(filefd, (off_t)0, SEEK_END);
152 lseek(filefd, (off_t)0, SEEK_SET);
153 sprintf(buffer, response_200, date, server, l);
154 if (write(fd, buffer, strlen(buffer)) == -1)
155 return BROKEN_PIPE;
156 while ((l = read(filefd, buffer, BUFSIZE)) > 0)
157 write(fd, buffer, l);
158 close(filefd);
159 break;
160 case REDIRECT:
161 sprintf(buffer, response_301, date, server);
162 if (write(fd, buffer, strlen(buffer)) == -1)
163 return BROKEN_PIPE;
164 break;
165 case FORBIDDEN:
166 sprintf(buffer, response_403, date, server);
167 if (write(fd, buffer, strlen(buffer)) == -1)
168 return BROKEN_PIPE;
169 break;
170 case NOTFOUND:
171 sprintf(buffer, response_404, date, server);
172 if (write(fd, buffer, strlen(buffer)) == -1)
173 return BROKEN_PIPE;
174 break;
175 }
176
177 die(-1, "[%s] %d %d.%d.%d.%d %s\n",
178 date, type, ip[0], ip[1], ip[2], ip[3], pt);
179 return type;
180}
181
182int
183handle(int clientfd, unsigned char *ip)
184{
185 static char buffer[BUFSIZE + 1], *pt;
186 long r;
187
188 if ((r = read(clientfd, buffer, BUFSIZE)) <= 0)
189 return respond(FORBIDDEN, clientfd, ip, NULL);
190
191 /* verify operation to be GET */
192 if (strncmp(buffer,"GET ", 4))
193 return respond(FORBIDDEN, clientfd, ip, NULL);
194
195 /* get requested uri */
196 pt = buffer + 4;
197 while (*pt) {
198 if (*pt == ' ') {
199 *pt = 0;
200 break;
201 }
202 pt++;
203 }
204 pt = buffer + 4;
205
206 /* redirect everything but '/' */
207 if (strncmp(pt, "/", strlen(pt)))
208 return respond(REDIRECT, clientfd, ip, pt);
209
210 /* serve the path */
211 return respond(SUCCESS, clientfd, ip, pt);
212}
213
214void
215stopserver(int signum)
216{
217 if (signum == SIGINT || signum == SIGTERM) {
218 close(serverfd);
219 die(0, "\nShutting down...\n");
220 }
221}
222
223void
224serve(int serverfd)
225{
226 static struct sockaddr_in c_addr;
227 unsigned char *ip;
228 unsigned int l;
229 int clientfd;
230
231 /* define the only way out... */
232 if (signal(SIGINT, &stopserver) == SIG_ERR)
233 die(1, "Error: system call 'signal'\n");
234 if (signal(SIGTERM, &stopserver) == SIG_ERR)
235 die(1, "Error: system call 'signal'\n");
236 if (signal(SIGPIPE, SIG_IGN) == SIG_ERR)
237 die(1, "Error: system call 'signal'\n");
238
239 /* ...and server forever */
240 for (;;) {
241 l = sizeof(c_addr);
242 if ((clientfd = accept(serverfd, (struct sockaddr *)&c_addr, &l)) < 0)
243 die(1, "Error: system call 'accept'\n");
244 ip = (unsigned char *)(&c_addr.sin_addr.s_addr);
245 switch (handle(clientfd, ip)) {
246 case BROKEN_PIPE:
247 die(-1, "Error: handling request 'broken pipe'\n");
248 break;
249 }
250 close(clientfd);
251 }
252}
253
254int
255main(int argc, char **argv)
256{
257 static struct sockaddr_in s_addr;
258 int port, filefd;
259 int reuse = 1;
260
261 /* validate arguments */
262 if (argc != 3 || !strcmp(argv[1], "-h"))
263 die(0, usage);
264 if ((port = toint(argv[1])) < 1 || port > 65535)
265 die(1, "Invalid port number: %s\n", argv[1]);
266 if ((filefd = open(argv[2], O_RDONLY)) == -1)
267 die(1, "Cannot open file: %s\n", argv[2]);
268 close(filefd);
269
270 /* define server socket address */
271 s_addr.sin_family = AF_INET;
272 s_addr.sin_addr.s_addr = htonl(INADDR_ANY);
273 s_addr.sin_port = htons(port);
274
275 /* setup the network socket */
276 if ((serverfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
277 die(1, "Error: system call 'socket'\n");
278
279 /* reuse address and port */
280 if (setsockopt(serverfd, SOL_SOCKET, SO_REUSEADDR,
281 (const char*)&reuse, sizeof(reuse)) < 0)
282 die(1, "Error: system call 'setsockopt(SO_REUSEADDR)'\n");
283 #ifdef SO_REUSEPORT
284 if (setsockopt(serverfd, SOL_SOCKET, SO_REUSEPORT,
285 (const char*)&reuse, sizeof(reuse)) < 0)
286 die(1, "Error: system call 'setsockopt(SO_REUSEPORT)'\n");
287 #endif
288
289 /* bind and listen */
290 if (bind(serverfd, (struct sockaddr *)&s_addr, sizeof(s_addr)) < 0)
291 die(1, "Error: system call 'bind'\n");
292 if (listen(serverfd, 64) < 0)
293 die(1, "Error: system call 'listen'\n");
294
295 path = argv[2];
296
297 die(-1, "Serving '%s' on 0.0.0.0:%d...\n\n", path, port);
298 serve(serverfd);
299
300 return 0;
301}