add missing file exists check
[project/uhttpd.git] / main.c
1 /*
2  * uhttpd - Tiny single-threaded httpd
3  *
4  *   Copyright (C) 2010-2012 Jo-Philipp Wich <xm@subsignal.org>
5  *   Copyright (C) 2012 Felix Fietkau <nbd@openwrt.org>
6  *
7  *  Licensed under the Apache License, Version 2.0 (the "License");
8  *  you may not use this file except in compliance with the License.
9  *  You may obtain a copy of the License at
10  *
11  *      http://www.apache.org/licenses/LICENSE-2.0
12  *
13  *  Unless required by applicable law or agreed to in writing, software
14  *  distributed under the License is distributed on an "AS IS" BASIS,
15  *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16  *  See the License for the specific language governing permissions and
17  *  limitations under the License.
18  */
19
20 #define _BSD_SOURCE
21 #define _GNU_SOURCE
22 #define _XOPEN_SOURCE   700
23 #include <sys/types.h>
24 #include <sys/socket.h>
25 #include <netinet/in.h>
26
27 #include <getopt.h>
28 #include <errno.h>
29 #include <netdb.h>
30 #include <signal.h>
31
32 #include <libubox/usock.h>
33
34 #include "uhttpd.h"
35 #include "tls.h"
36
37 char uh_buf[4096];
38
39 static int run_server(void)
40 {
41         uloop_init();
42         uh_setup_listeners();
43         uh_plugin_post_init();
44         uloop_run();
45
46         return 0;
47 }
48
49 static void uh_config_parse(void)
50 {
51         const char *path = conf.file;
52         FILE *c;
53         char line[512];
54         char *col1;
55         char *col2;
56         char *eol;
57
58         if (!path)
59                 path = "/etc/httpd.conf";
60
61         c = fopen(path, "r");
62         if (!c)
63                 return;
64
65         memset(line, 0, sizeof(line));
66
67         while (fgets(line, sizeof(line) - 1, c)) {
68                 if ((line[0] == '/') && (strchr(line, ':') != NULL)) {
69                         if (!(col1 = strchr(line, ':')) || (*col1++ = 0) ||
70                                 !(col2 = strchr(col1, ':')) || (*col2++ = 0) ||
71                                 !(eol = strchr(col2, '\n')) || (*eol++  = 0))
72                                 continue;
73
74                         uh_auth_add(line, col1, col2);
75                 } else if (!strncmp(line, "I:", 2)) {
76                         if (!(col1 = strchr(line, ':')) || (*col1++ = 0) ||
77                                 !(eol = strchr(col1, '\n')) || (*eol++  = 0))
78                                 continue;
79
80                         uh_index_add(strdup(col1));
81                 } else if (!strncmp(line, "E404:", 5)) {
82                         if (!(col1 = strchr(line, ':')) || (*col1++ = 0) ||
83                                 !(eol = strchr(col1, '\n')) || (*eol++  = 0))
84                                 continue;
85
86                         conf.error_handler = strdup(col1);
87                 }
88                 else if ((line[0] == '*') && (strchr(line, ':') != NULL)) {
89                         if (!(col1 = strchr(line, '*')) || (*col1++ = 0) ||
90                                 !(col2 = strchr(col1, ':')) || (*col2++ = 0) ||
91                                 !(eol = strchr(col2, '\n')) || (*eol++  = 0))
92                                 continue;
93
94                         uh_interpreter_add(col1, col2);
95                 }
96         }
97
98         fclose(c);
99 }
100
101 static int add_listener_arg(char *arg, bool tls)
102 {
103         char *host = NULL;
104         char *port = arg;
105         char *s;
106
107         s = strrchr(arg, ':');
108         if (s) {
109                 host = arg;
110                 port = s + 1;
111                 *s = 0;
112         }
113
114         return uh_socket_bind(host, port, tls);
115 }
116
117 static int usage(const char *name)
118 {
119         fprintf(stderr,
120                 "Usage: %s -p [addr:]port -h docroot\n"
121                 "       -f              Do not fork to background\n"
122                 "       -c file         Configuration file, default is '/etc/httpd.conf'\n"
123                 "       -p [addr:]port  Bind to specified address and port, multiple allowed\n"
124 #ifdef HAVE_TLS
125                 "       -s [addr:]port  Like -p but provide HTTPS on this port\n"
126                 "       -C file         ASN.1 server certificate file\n"
127                 "       -K file         ASN.1 server private key file\n"
128 #endif
129                 "       -h directory    Specify the document root, default is '.'\n"
130                 "       -E string       Use given virtual URL as 404 error handler\n"
131                 "       -I string       Use given filename as index for directories, multiple allowed\n"
132                 "       -S              Do not follow symbolic links outside of the docroot\n"
133                 "       -D              Do not allow directory listings, send 403 instead\n"
134                 "       -R              Enable RFC1918 filter\n"
135                 "       -n count        Maximum allowed number of concurrent requests\n"
136 #ifdef HAVE_LUA
137                 "       -l string       URL prefix for Lua handler, default is '/lua'\n"
138                 "       -L file         Lua handler script, omit to disable Lua\n"
139 #endif
140 #ifdef HAVE_UBUS
141                 "       -u string       URL prefix for HTTP/JSON handler\n"
142                 "       -U file         Override ubus socket path\n"
143 #endif
144                 "       -x string       URL prefix for CGI handler, default is '/cgi-bin'\n"
145                 "       -i .ext=path    Use interpreter at path for files with the given extension\n"
146                 "       -t seconds      CGI, Lua and UBUS script timeout in seconds, default is 60\n"
147                 "       -T seconds      Network timeout in seconds, default is 30\n"
148                 "       -d string       URL decode given string\n"
149                 "       -r string       Specify basic auth realm\n"
150                 "       -m string       MD5 crypt given string\n"
151                 "\n", name
152         );
153         return 1;
154 }
155
156 static void init_defaults(void)
157 {
158         conf.script_timeout = 60;
159         conf.network_timeout = 30;
160         conf.http_keepalive = 20;
161         conf.max_requests = 3;
162         conf.realm = "Protected Area";
163         conf.cgi_prefix = "/cgi-bin";
164         conf.cgi_path = "/sbin:/usr/sbin:/bin:/usr/bin";
165
166         uh_index_add("index.html");
167         uh_index_add("index.htm");
168         uh_index_add("default.html");
169         uh_index_add("default.htm");
170 }
171
172 static void fixup_prefix(char *str)
173 {
174         int len;
175
176         if (!str || !str[0])
177                 return;
178
179         len = strlen(str) - 1;
180
181         while (len > 0 && str[len] == '/')
182                 len--;
183
184         str[len + 1] = 0;
185 }
186
187 int main(int argc, char **argv)
188 {
189         const char *tls_key = NULL, *tls_crt = NULL;
190         bool nofork = false;
191         char *port;
192         int opt, ch;
193         int cur_fd;
194         int bound = 0;
195         int n_tls = 0;
196
197         BUILD_BUG_ON(sizeof(uh_buf) < PATH_MAX);
198
199         uh_dispatch_add(&cgi_dispatch);
200         init_defaults();
201         signal(SIGPIPE, SIG_IGN);
202
203         while ((ch = getopt(argc, argv, "fSDRC:K:E:I:p:s:h:c:l:L:d:r:m:n:x:i:t:T:A:u:U:")) != -1) {
204                 bool tls = false;
205
206                 switch(ch) {
207                 case 's':
208                         n_tls++;
209                         tls = true;
210                         /* fall through */
211                 case 'p':
212                         bound += add_listener_arg(optarg, tls);
213                         break;
214
215                 case 'h':
216                         if (!realpath(optarg, uh_buf)) {
217                                 fprintf(stderr, "Error: Invalid directory %s: %s\n",
218                                                 optarg, strerror(errno));
219                                 exit(1);
220                         }
221                         conf.docroot = strdup(uh_buf);
222                         break;
223
224                 case 'E':
225                         if (optarg[0] != '/') {
226                                 fprintf(stderr, "Error: Invalid error handler: %s\n",
227                                                 optarg);
228                                 exit(1);
229                         }
230                         conf.error_handler = optarg;
231                         break;
232
233                 case 'I':
234                         if (optarg[0] == '/') {
235                                 fprintf(stderr, "Error: Invalid index page: %s\n",
236                                                 optarg);
237                                 exit(1);
238                         }
239                         uh_index_add(optarg);
240                         break;
241
242                 case 'S':
243                         conf.no_symlinks = 1;
244                         break;
245
246                 case 'D':
247                         conf.no_dirlists = 1;
248                         break;
249
250                 case 'R':
251                         conf.rfc1918_filter = 1;
252                         break;
253
254                 case 'n':
255                         conf.max_requests = atoi(optarg);
256                         break;
257
258                 case 'x':
259                         fixup_prefix(optarg);
260                         conf.cgi_prefix = optarg;
261                         break;
262
263                 case 'i':
264                         port = strchr(optarg, '=');
265                         if (optarg[0] != '.' || !port) {
266                                 fprintf(stderr, "Error: Invalid interpreter: %s\n",
267                                                 optarg);
268                                 exit(1);
269                         }
270
271                         *port++ = 0;
272                         uh_interpreter_add(optarg, port);
273                         break;
274
275                 case 't':
276                         conf.script_timeout = atoi(optarg);
277                         break;
278
279                 case 'T':
280                         conf.network_timeout = atoi(optarg);
281                         break;
282
283                 case 'A':
284                         conf.tcp_keepalive = atoi(optarg);
285                         break;
286
287                 case 'f':
288                         nofork = 1;
289                         break;
290
291                 case 'd':
292                         port = alloca(strlen(optarg) + 1);
293                         if (!port)
294                                 return -1;
295
296                         /* "decode" plus to space to retain compat */
297                         for (opt = 0; optarg[opt]; opt++)
298                                 if (optarg[opt] == '+')
299                                         optarg[opt] = ' ';
300
301                         /* opt now contains strlen(optarg) -- no need to re-scan */
302                         if (uh_urldecode(port, opt, optarg, opt) < 0) {
303                                 fprintf(stderr, "uhttpd: invalid encoding\n");
304                                 return -1;
305                         }
306
307                         printf("%s", port);
308                         break;
309
310                 /* basic auth realm */
311                 case 'r':
312                         conf.realm = optarg;
313                         break;
314
315                 /* md5 crypt */
316                 case 'm':
317                         printf("%s\n", crypt(optarg, "$1$"));
318                         return 0;
319                         break;
320
321                 /* config file */
322                 case 'c':
323                         conf.file = optarg;
324                         break;
325
326                 case 'C':
327                         tls_crt = optarg;
328                         break;
329
330                 case 'K':
331                         tls_key = optarg;
332                         break;
333 #ifdef HAVE_LUA
334                 case 'l':
335                         conf.lua_prefix = optarg;
336                         break;
337
338                 case 'L':
339                         conf.lua_handler = optarg;
340                         break;
341 #endif
342 #ifdef HAVE_UBUS
343                 case 'u':
344                         conf.ubus_prefix = optarg;
345                         break;
346
347                 case 'U':
348                         conf.ubus_socket = optarg;
349                         break;
350 #endif
351                 default:
352                         return usage(argv[0]);
353                 }
354         }
355
356         uh_config_parse();
357
358         if (!bound) {
359                 fprintf(stderr, "Error: No sockets bound, unable to continue\n");
360                 return 1;
361         }
362
363         if (n_tls) {
364                 if (!tls_crt || !tls_key) {
365                         fprintf(stderr, "Please specify a certificate and "
366                                         "a key file to enable SSL support\n");
367                         return 1;
368                 }
369
370 #ifdef HAVE_TLS
371                 if (uh_tls_init(tls_key, tls_crt))
372                     return 1;
373 #else
374                 fprintf(stderr, "Error: TLS support not compiled in.\n");
375                 return 1;
376 #endif
377         }
378
379 #ifdef HAVE_LUA
380         if (conf.lua_handler || conf.lua_prefix) {
381                 if (!conf.lua_handler || !conf.lua_prefix) {
382                         fprintf(stderr, "Need handler and prefix to enable Lua support\n");
383                         return 1;
384                 }
385                 if (uh_plugin_init("uhttpd_lua.so"))
386                         return 1;
387         }
388 #endif
389 #ifdef HAVE_UBUS
390         if (conf.ubus_prefix && uh_plugin_init("uhttpd_ubus.so"))
391                 return 1;
392 #endif
393
394         /* fork (if not disabled) */
395         if (!nofork) {
396                 switch (fork()) {
397                 case -1:
398                         perror("fork()");
399                         exit(1);
400
401                 case 0:
402                         /* daemon setup */
403                         if (chdir("/"))
404                                 perror("chdir()");
405
406                         cur_fd = open("/dev/null", O_WRONLY);
407                         if (cur_fd > 0) {
408                                 dup2(cur_fd, 0);
409                                 dup2(cur_fd, 1);
410                                 dup2(cur_fd, 2);
411                         }
412
413                         break;
414
415                 default:
416                         exit(0);
417                 }
418         }
419
420         return run_server();
421 }