Demonstration release of the principles underpinning krsd.
[krsd] / src / process / main.c
1 /*
2  * rs-serve - (c) 2013 Niklas E. Cathor
3  *
4  * This program is distributed in the hope that it will be useful,
5  * but WITHOUT ANY WARRANTY; without even the implied warranty of
6  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
7  * GNU Affero General Public License for more details.
8  *
9  * You should have received a copy of the GNU Affero General Public License
10  * along with this program. If not, see <http://www.gnu.org/licenses/>.
11  */
12
13 #include "rs-serve.h"
14
15 #define ASSERT(cond, desc) {                    \
16     if(!(cond)) {                               \
17       perror(#desc " failed");                  \
18       exit(EXIT_FAILURE);                       \
19     }                                           \
20   }
21 #define ASSERT_NOT_NULL(var, desc) ASSERT((var) != NULL, desc)
22 #define ASSERT_ZERO(var, desc)     ASSERT((var) == 0, desc)
23 #define ASSERT_NOT_EQ(a, b, desc)  ASSERT((a) != (b), desc)
24
25 unsigned int request_count;
26
27 static const char * method_strmap[] = {
28     "GET",
29     "HEAD",
30     "POST",
31     "PUT",
32     "DELETE",
33     "MKCOL",
34     "COPY",
35     "MOVE",
36     "OPTIONS",
37     "PROPFIND",
38     "PROPATCH",
39     "LOCK",
40     "UNLOCK",
41     "TRACE",
42     "CONNECT",
43     "PATCH",
44 };
45
46 void handle_signal(evutil_socket_t fd, short events, void *arg) {
47   struct signalfd_siginfo siginfo;
48   if(read(fd, &siginfo, sizeof(siginfo)) < 0) {
49     log_error("Failed to read signal: %s", strerror(errno));
50     return;
51   }
52   switch(siginfo.ssi_signo) {
53   case SIGINT:
54     log_info("SIGINT caught, exiting.");
55     exit(EXIT_SUCCESS);
56     break;
57   case SIGTERM:
58     log_info("SIGTERM caught, exiting.");
59     exit(EXIT_SUCCESS);
60     break;
61   default:
62     log_error("Unhandled signal caught: %s", strsignal(siginfo.ssi_signo));
63   }
64 }
65
66 struct event_base *rs_event_base = NULL;
67
68 void log_event_base_message(int severity, const char *msg) {
69   char *format = "(from libevent) %s";
70   switch(severity) {
71   case EVENT_LOG_DEBUG:
72     log_debug(format, msg);
73     break;
74   case EVENT_LOG_MSG:
75     log_info(format, msg);
76     break;
77   case EVENT_LOG_ERR:
78     log_error(format, msg);
79     break;
80   case EVENT_LOG_WARN:
81     log_warn(format, msg);
82     break;
83   }
84 }
85
86 static evhtp_res finish_request(evhtp_request_t *req, void *arg) {
87   request_count--;
88   log_info("[rc=%d] %s %s -> %d (fini: %d)", request_count, method_strmap[req->method], req->uri->path->full, req->status, req->finished);
89   return 0;
90 }
91
92 static void handle_storage(evhtp_request_t *req, void *arg) {
93   request_count++;
94   log_info("[rc=%d] (start) %s %s", request_count, method_strmap[req->method], req->uri->path->full, req->status, req->finished);
95   dispatch_storage(req, arg);
96 }
97
98 static void handle_static(evhtp_request_t *req, void *arg) {
99   log_info("static URI path \"%s\"", req->uri->path->full);
100   if (req->method != htp_method_GET) {
101     req->status = EVHTP_RES_METHNALLOWED;
102   } else if ((req->uri->path->full [0] != '/') || strstr (req->uri->path->full, "/..")) {
103     req->status = EVHTP_RES_SERVERR;
104   } else {
105     char *path = malloc (RS_STATIC_DIR_LEN + strlen (req->uri->path->full) + 10 + 1);
106     if (!path) {
107       log_error("malloc() failed");
108       req->status = EVHTP_RES_SERVERR;
109     } else {
110       int fh;
111       char *mime_type = NULL;
112       int free_mime_type = 0;
113
114       sprintf (path, "%s%s", RS_STATIC_DIR, req->uri->path->full);
115       if (path [strlen (path) - 1] == '/') {
116         strcat (path, "index.html");
117       }
118       log_debug("static filename \"%s\"", path);
119     
120       // mime type is either passed in ... (such as for directory listings)
121       if(mime_type == NULL) {
122         // ... or detected based on xattr
123         mime_type = content_type_from_xattr(path);
124         if(mime_type == NULL) {
125 #if 0
126           // ... or guessed by libmagic
127           log_debug("mime type not given, detecting...");
128           mime_type = magic_file(magic_cookie, path);
129 #endif
130           if(mime_type == NULL) {
131             // ... or defaulted to "application/octet-stream"
132             log_error("magic failed: %s", magic_error(magic_cookie));
133             mime_type = "application/octet-stream; charset=binary";
134           }
135         } else {
136           // xattr detected mime type and allocated memory for it
137           free_mime_type = 1;
138         }
139       }
140     
141       if (mime_type) {
142         log_info("setting Content-Type of %s: %s", req->uri->path->full, mime_type);
143         ADD_RESP_HEADER_CP(req, "Content-Type", mime_type);
144       }
145     
146       if(free_mime_type) {
147         free((char*)mime_type);
148       }
149
150       fh = open (path, O_RDONLY);
151       free(path);
152       if (fh < 0) {
153         req->status = EVHTP_RES_NOTFOUND;
154       } else {
155         char buf [1024];
156         size_t rdlen;
157         while ((rdlen = read (fh, buf, 1024)) > 0) {
158           evbuffer_add (req->buffer_out, buf, rdlen);
159         }
160         close (fh);
161         req->status = EVHTP_RES_OK;
162       }
163     }
164   }
165   evhtp_send_reply(req, req->status);
166 }
167
168 static int dummy_ssl_verify_callback(int ok, X509_STORE_CTX * x509_store) {
169   return 1;
170 }
171
172 static int dummy_check_issued_cb(X509_STORE_CTX * ctx, X509 * x, X509 * issuer) {
173   return 1;
174 }
175
176
177 magic_t magic_cookie;
178
179 int main(int argc, char **argv) {
180
181   init_config(argc, argv);
182
183   open_authorizations("r");
184
185   init_webfinger();
186
187   /** OPEN MAGIC DATABASE **/
188
189   magic_cookie = magic_open(MAGIC_MIME);
190   if(magic_load(magic_cookie, RS_MAGIC_DATABASE) != 0) {
191     log_error("Failed to load magic database: %s", magic_error(magic_cookie));
192     exit(EXIT_FAILURE);
193   }
194
195   log_info("starting process: main");
196
197   if(prctl(PR_SET_NAME, "rs-serve [main]", 0, 0, 0) != 0) {
198     log_error("Failed to set process name: %s", strerror(errno));
199   }
200
201   /** SETUP EVENT BASE **/
202
203   rs_event_base = event_base_new();
204   ASSERT_NOT_NULL(rs_event_base, "event_base_new()");
205   log_debug("libevent method: %s", event_base_get_method(rs_event_base));
206   event_set_log_callback(log_event_base_message);
207
208   // TODO: add error cb to base
209
210
211   /** SETUP LISTENER **/
212
213   struct sockaddr_in6 sin;
214   memset(&sin, 0, sizeof(struct sockaddr_in6));
215   sin.sin6_family = AF_INET6;
216   //ALREADY_DONE// sin.sin6_addr.s_addr = htonl(0);
217   sin.sin6_port = htons(RS_PORT);
218
219   evhtp_t *server = evhtp_new(rs_event_base, NULL);
220
221   if(RS_USE_SSL) {
222     evhtp_ssl_cfg_t ssl_config = {
223       .pemfile = RS_SSL_CERT_PATH,
224       .privfile = RS_SSL_KEY_PATH,
225       .cafile = RS_SSL_CA_PATH,
226       // what's this???
227       .capath = NULL,
228       .ciphers = "RC4+RSA:HIGH:+MEDIUM:+LOW",
229       .ssl_opts = SSL_OP_NO_SSLv2,
230       .ssl_ctx_timeout = 60*60*48,
231       .verify_peer = SSL_VERIFY_PEER,
232       .verify_depth = 42,
233       .x509_verify_cb = dummy_ssl_verify_callback,
234       .x509_chk_issued_cb = dummy_check_issued_cb,
235       .scache_type = evhtp_ssl_scache_type_internal,
236       .scache_size = 1024,
237       .scache_timeout = 1024,
238       .scache_init = NULL,
239       .scache_add = NULL,
240       .scache_get = NULL,
241       .scache_del = NULL
242     };
243
244     if(evhtp_ssl_init(server, &ssl_config) != 0) {
245       log_error("evhtp_ssl_init() failed");
246       exit(EXIT_FAILURE);
247     }
248   }
249
250   /* WEBFINGER */
251
252   evhtp_callback_cb webfinger_cb = (RS_WEBFINGER_ENABLED ?
253                                     handle_webfinger : reject_webfinger);
254   evhtp_set_cb(server, "/.well-known/webfinger", webfinger_cb, NULL);
255   // support legacy webfinger clients (we don't support XRD though):
256   evhtp_set_cb(server, "/.well-known/host-meta", webfinger_cb, NULL);
257   evhtp_set_cb(server, "/.well-known/host-meta.json", webfinger_cb, NULL);
258
259   /* REMOTESTORAGE */
260
261   evhtp_callback_t *storage_cb = evhtp_set_regex_cb(server, "^/storage/([^/]+/[^/]+)/.*$", handle_storage, NULL);
262
263   evhtp_set_hook(&storage_cb->hooks, evhtp_hook_on_request_fini, finish_request, NULL);
264
265   /* STATIC CONTENT */
266
267   evhtp_set_gencb(server, handle_static, NULL);
268
269   if(evhtp_bind_sockaddr(server, (struct sockaddr*)&sin, sizeof(sin), 1024) != 0) {
270     log_error("evhtp_bind_sockaddr() failed: %s", strerror(errno));
271     exit(EXIT_FAILURE);
272   }
273
274   /** SETUP SIGNALS **/
275
276   sigset_t sigmask;
277   sigemptyset(&sigmask);
278   sigaddset(&sigmask, SIGINT);
279   sigaddset(&sigmask, SIGTERM);
280   sigaddset(&sigmask, SIGCHLD);
281   ASSERT_ZERO(sigprocmask(SIG_BLOCK, &sigmask, NULL), "sigprocmask()");
282   int sfd = signalfd(-1, &sigmask, SFD_NONBLOCK);
283   ASSERT_NOT_EQ(sfd, -1, "signalfd()");
284
285   struct event *signal_event = event_new(rs_event_base, sfd, EV_READ | EV_PERSIST,
286                                          handle_signal, NULL);
287   event_add(signal_event, NULL);
288
289   /** RUN EVENT LOOP **/
290
291   if(RS_DETACH) {
292     int pid = fork();
293     if(pid == 0) {
294       event_reinit(rs_event_base);
295
296       if(RS_LOG_FILE == stdout) {
297         log_warn("No --log-file option given. Future output will be lost.");
298         freopen("/dev/null", "r", stdout);
299         freopen("/dev/null", "r", stderr);
300       }
301
302       return event_base_dispatch(rs_event_base);
303     } else {
304       printf("rs-serve detached with pid %d\n", pid);
305       if(RS_PID_FILE) {
306         fprintf(RS_PID_FILE, "%d", pid);
307         fflush(RS_PID_FILE);
308       }
309       _exit(EXIT_SUCCESS);
310     }
311   } else {
312     if(RS_PID_FILE) {
313       fprintf(RS_PID_FILE, "%d", getpid());
314       fflush(RS_PID_FILE);
315     }
316     return event_base_dispatch(rs_event_base);
317   }
318 }