root/src/noit_rest.c

Revision 8f70c06674bf6638d637075656f61e43ea438325, 17.6 kB (checked in by Theo Schlossnagle <jesus@omniti.com>, 4 years ago)

fixes erroneous 404 for empty / zero byte files, refs #235

  • Property mode set to 100644
Line 
1 /*
2  * Copyright (c) 2007, OmniTI Computer Consulting, Inc.
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions are
7  * met:
8  *
9  *     * Redistributions of source code must retain the above copyright
10  *       notice, this list of conditions and the following disclaimer.
11  *     * Redistributions in binary form must reproduce the above
12  *       copyright notice, this list of conditions and the following
13  *       disclaimer in the documentation and/or other materials provided
14  *       with the distribution.
15  *     * Neither the name OmniTI Computer Consulting, Inc. nor the names
16  *       of its contributors may be used to endorse or promote products
17  *       derived from this software without specific prior written
18  *       permission.
19  *
20  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
23  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
24  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
25  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
26  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
27  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
28  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
30  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31  */
32
33 #include "noit_defines.h"
34 #include "noit_listener.h"
35 #include "noit_http.h"
36 #include "noit_rest.h"
37 #include "noit_conf.h"
38
39 #include <pcre.h>
40 #include <unistd.h>
41 #include <fcntl.h>
42 #include <sys/mman.h>
43 #include <errno.h>
44
45 struct rest_xml_payload {
46   char *buffer;
47   xmlDocPtr indoc;
48   int len;
49   int allocd;
50   int complete;
51 };
52
53 struct rest_url_dispatcher {
54   char *method;
55   pcre *expression;
56   pcre_extra *extra;
57   rest_request_handler handler;
58   rest_authorize_func_t auth;
59   /* Chain to the next one */
60   struct rest_url_dispatcher *next;
61 };
62
63 struct rule_container {
64   char *base;
65   struct rest_url_dispatcher *rules;
66   struct rest_url_dispatcher *rules_endptr;
67 };
68 noit_hash_table dispatch_points = NOIT_HASH_EMPTY;
69
70 struct noit_rest_acl_rule {
71   noit_boolean allow;
72   pcre *url;
73   pcre *cn;
74   struct noit_rest_acl_rule *next;
75 };
76 struct noit_rest_acl {
77   noit_boolean allow;
78   pcre *url;
79   pcre *cn;
80   struct noit_rest_acl_rule *rules;
81   struct noit_rest_acl *next;
82 };
83
84 static struct noit_rest_acl *global_rest_acls = NULL;
85
86 static int
87 noit_http_rest_permission_denied(noit_http_rest_closure_t *restc,
88                                  int npats, char **pats) {
89   noit_http_session_ctx *ctx = restc->http_ctx;
90   noit_http_response_standard(ctx, 403, "DENIED", "text/xml");
91   noit_http_response_end(ctx);
92   return 0;
93 }
94 static rest_request_handler
95 noit_http_get_handler(noit_http_rest_closure_t *restc) {
96   struct rule_container *cont = NULL;
97   struct rest_url_dispatcher *rule;
98   char *eoq, *eob;
99   eoq = strchr(restc->http_ctx->req.uri_str, '?');
100   if(!eoq)
101     eoq = restc->http_ctx->req.uri_str + strlen(restc->http_ctx->req.uri_str);
102   eob = eoq - 1;
103
104   /* find the right base */
105   while(1) {
106     void *vcont;
107     while(eob >= restc->http_ctx->req.uri_str && *eob != '/') eob--;
108     if(eob < restc->http_ctx->req.uri_str) break; /* off the front */
109     if(noit_hash_retrieve(&dispatch_points, restc->http_ctx->req.uri_str,
110                           eob - restc->http_ctx->req.uri_str + 1, &vcont)) {
111       cont = vcont;
112       eob++; /* move past the determined base */
113       break;
114     }
115     eob--;
116   }
117   if(!cont) return NULL;
118   for(rule = cont->rules; rule; rule = rule->next) {
119     int ovector[30];
120     int cnt;
121     if(strcmp(rule->method, restc->http_ctx->req.method_str)) continue;
122     if((cnt = pcre_exec(rule->expression, rule->extra, eob, eoq - eob, 0, 0,
123                         ovector, sizeof(ovector)/sizeof(*ovector))) > 0) {
124       /* We match, set 'er up */
125       restc->fastpath = rule->handler;
126       restc->nparams = cnt - 1;
127       if(restc->nparams) {
128         restc->params = calloc(restc->nparams, sizeof(*restc->params));
129         for(cnt = 0; cnt < restc->nparams; cnt++) {
130           int start = ovector[(cnt+1)*2];
131           int end = ovector[(cnt+1)*2+1];
132           restc->params[cnt] = malloc(end - start + 1);
133           memcpy(restc->params[cnt], eob + start, end - start);
134           restc->params[cnt][end - start] = '\0';
135         }
136       }
137       if(rule->auth && !rule->auth(restc, restc->nparams, restc->params))
138         return noit_http_rest_permission_denied;
139       return restc->fastpath;
140     }
141   }
142   return NULL;
143 }
144 noit_boolean
145 noit_http_rest_client_cert_auth(noit_http_rest_closure_t *restc,
146                                 int npats, char **pats) {
147   struct noit_rest_acl *acl;
148   struct noit_rest_acl_rule *rule;
149   int ovector[30];
150
151   if(!restc->remote_cn || !strlen(restc->remote_cn)) return noit_false;
152   for(acl = global_rest_acls; acl; acl = acl->next) {
153     if(acl->cn && pcre_exec(acl->cn, NULL, restc->remote_cn,
154                             strlen(restc->remote_cn), 0, 0,
155                             ovector, sizeof(ovector)/sizeof(*ovector)) <= 0)
156       continue;
157     if(acl->url && pcre_exec(acl->url, NULL, restc->http_ctx->req.uri_str,
158                              strlen(restc->http_ctx->req.uri_str), 0, 0,
159                              ovector, sizeof(ovector)/sizeof(*ovector)) <= 0)
160       continue;
161     for(rule = acl->rules; rule; rule = rule->next) {
162       if(rule->cn && pcre_exec(rule->cn, NULL, restc->remote_cn,
163                                strlen(restc->remote_cn), 0, 0,
164                                ovector, sizeof(ovector)/sizeof(*ovector)) <= 0)
165         continue;
166       if(rule->url && pcre_exec(rule->url, NULL, restc->http_ctx->req.uri_str,
167                                 strlen(restc->http_ctx->req.uri_str), 0, 0,
168                                 ovector, sizeof(ovector)/sizeof(*ovector)) <= 0)
169         continue;
170       return rule->allow;
171     }
172     return acl->allow;
173   }
174   return noit_false;
175 }
176 int
177 noit_http_rest_register(const char *method, const char *base,
178                         const char *expr, rest_request_handler f) {
179   return noit_http_rest_register_auth(method, base, expr, f, NULL);
180 }
181 int
182 noit_http_rest_register_auth(const char *method, const char *base,
183                              const char *expr, rest_request_handler f,
184                              rest_authorize_func_t auth) {
185   void *vcont;
186   struct rule_container *cont;
187   struct rest_url_dispatcher *rule;
188   const char *error;
189   int erroffset;
190   pcre *pcre_expr;
191   int blen = strlen(base);
192   /* base must end in a /, 'cause I said so */
193   if(blen == 0 || base[blen-1] != '/') return -1;
194   pcre_expr = pcre_compile(expr, 0, &error, &erroffset, NULL);
195   if(!pcre_expr) {
196     noitL(noit_error, "Error in rest expr(%s) '%s'@%d: %s\n",
197           base, expr, erroffset, error);
198     return -1;
199   }
200   rule = calloc(1, sizeof(*rule));
201   rule->method = strdup(method);
202   rule->expression = pcre_expr;
203   rule->extra = pcre_study(rule->expression, 0, &error);
204   rule->handler = f;
205   rule->auth = auth;
206
207   /* Make sure we have a container */
208   if(!noit_hash_retrieve(&dispatch_points, base, strlen(base), &vcont)) {
209     cont = calloc(1, sizeof(*cont));
210     cont->base = strdup(base);
211     noit_hash_store(&dispatch_points, cont->base, strlen(cont->base), cont);
212   }
213   else cont = vcont;
214
215   /* Append the rule */
216   if(cont->rules_endptr) {
217     cont->rules_endptr->next = rule;
218     cont->rules_endptr = cont->rules_endptr->next;
219   }
220   else
221     cont->rules = cont->rules_endptr = rule;
222   return 0;
223 }
224
225 static noit_http_rest_closure_t *
226 noit_http_rest_closure_alloc() {
227   noit_http_rest_closure_t *restc;
228   restc = calloc(1, sizeof(*restc));
229   return restc;
230 }
231 static void
232 noit_http_rest_clean_request(noit_http_rest_closure_t *restc) {
233   int i;
234   if(restc->nparams) {
235     for(i=0;i<restc->nparams;i++) free(restc->params[i]);
236     free(restc->params);
237   }
238   if(restc->call_closure_free) restc->call_closure_free(restc->call_closure);
239   restc->call_closure_free = NULL;
240   restc->call_closure = NULL;
241   restc->nparams = 0;
242   restc->params = NULL;
243   restc->fastpath = NULL;
244 }
245 void
246 noit_http_rest_closure_free(noit_http_rest_closure_t *restc) {
247   free(restc->remote_cn);
248   noit_http_rest_clean_request(restc);
249   free(restc);
250 }
251
252 int
253 noit_rest_request_dispatcher(noit_http_session_ctx *ctx) {
254   noit_http_rest_closure_t *restc = ctx->dispatcher_closure;
255   rest_request_handler handler = restc->fastpath;
256   if(!handler) handler = noit_http_get_handler(restc);
257   if(handler) {
258     int rv;
259     rv = handler(restc, restc->nparams, restc->params);
260     if(ctx->res.closed) noit_http_rest_clean_request(restc);
261     return rv;
262   }
263   noit_http_response_status_set(ctx, 404, "NOT FOUND");
264   noit_http_response_option_set(ctx, NOIT_HTTP_CHUNKED);
265   noit_http_rest_clean_request(restc);
266   noit_http_response_end(ctx);
267   return 0;
268 }
269
270 int
271 noit_http_rest_handler(eventer_t e, int mask, void *closure,
272                        struct timeval *now) {
273   int newmask = EVENTER_READ | EVENTER_EXCEPTION;
274   acceptor_closure_t *ac = closure;
275   noit_http_rest_closure_t *restc = ac->service_ctx;
276
277   if(mask & EVENTER_EXCEPTION || (restc && restc->wants_shutdown)) {
278 socket_error:
279     /* Exceptions cause us to simply snip the connection */
280     eventer_remove_fd(e->fd);
281     e->opset->close(e->fd, &newmask, e);
282     if(restc) noit_http_rest_closure_free(restc);
283     if(ac) acceptor_closure_free(ac);
284     return 0;
285   }
286
287   if(!ac->service_ctx) {
288     const char *primer = "";
289     ac->service_ctx = restc = noit_http_rest_closure_alloc();
290     restc->ac = ac;
291     restc->remote_cn = strdup(ac->remote_cn ? ac->remote_cn : "");
292     restc->http_ctx =
293         noit_http_session_ctx_new(noit_rest_request_dispatcher,
294                                   restc, e);
295    
296     switch(ac->cmd) {
297       case NOIT_CONTROL_DELETE:
298         primer = "DELE";
299         break;
300       case NOIT_CONTROL_GET:
301         primer = "GET ";
302         break;
303       case NOIT_CONTROL_HEAD:
304         primer = "HEAD";
305         break;
306       case NOIT_CONTROL_POST:
307         primer = "POST";
308         break;
309       case NOIT_CONTROL_PUT:
310         primer = "PUT ";
311         break;
312       default:
313         goto socket_error;
314     }
315     noit_http_session_prime_input(restc->http_ctx, primer, 4);
316   }
317   return restc->http_ctx->drive(e, mask, restc->http_ctx, now);
318 }
319
320 int
321 noit_http_rest_raw_handler(eventer_t e, int mask, void *closure,
322                            struct timeval *now) {
323   int newmask = EVENTER_READ | EVENTER_EXCEPTION;
324   acceptor_closure_t *ac = closure;
325   noit_http_rest_closure_t *restc = ac->service_ctx;
326
327   if(mask & EVENTER_EXCEPTION || (restc && restc->wants_shutdown)) {
328     /* Exceptions cause us to simply snip the connection */
329     eventer_remove_fd(e->fd);
330     e->opset->close(e->fd, &newmask, e);
331     if(restc) noit_http_rest_closure_free(restc);
332     if(ac) acceptor_closure_free(ac);
333     return 0;
334   }
335   if(!ac->service_ctx) {
336     ac->service_ctx = restc = noit_http_rest_closure_alloc();
337     restc->ac = ac;
338     restc->http_ctx =
339         noit_http_session_ctx_new(noit_rest_request_dispatcher,
340                                   restc, e);
341   }
342   return restc->http_ctx->drive(e, mask, restc->http_ctx, now);
343 }
344
345 static void
346 rest_xml_payload_free(void *f) {
347   struct rest_xml_payload *xmlin = f;
348   if(xmlin->buffer) free(xmlin->buffer);
349   if(xmlin->indoc) xmlFreeDoc(xmlin->indoc);
350 }
351
352 xmlDocPtr
353 rest_get_xml_upload(noit_http_rest_closure_t *restc,
354                     int *mask, int *complete) {
355   struct rest_xml_payload *rxc;
356   if(restc->call_closure == NULL) {
357     rxc = restc->call_closure = calloc(1, sizeof(*rxc));
358     restc->call_closure_free = rest_xml_payload_free;
359   }
360   rxc = restc->call_closure;
361   while(!rxc->complete) {
362     int len;
363     if(rxc->len == rxc->allocd) {
364       char *b;
365       rxc->allocd += 32768;
366       b = rxc->buffer ? realloc(rxc->buffer, rxc->allocd) :
367                         malloc(rxc->allocd);
368       if(!b) {
369         *complete = 1;
370         return NULL;
371       }
372       rxc->buffer = b;
373     }
374     len = noit_http_session_req_consume(restc->http_ctx,
375                                         rxc->buffer + rxc->len,
376                                         rxc->allocd - rxc->len,
377                                         mask);
378     if(len > 0) rxc->len += len;
379     if(len < 0 && errno == EAGAIN) return NULL;
380     else if(len < 0) {
381       *complete = 1;
382       return NULL;
383     }
384     if(rxc->len == restc->http_ctx->req.content_length) {
385       rxc->indoc = xmlParseMemory(rxc->buffer, rxc->len);
386       rxc->complete = 1;
387     }
388   }
389
390   *complete = 1;
391   return rxc->indoc;
392 }
393 int
394 noit_rest_simple_file_handler(noit_http_rest_closure_t *restc,
395                               int npats, char **pats) {
396   int drlen = 0;
397   const char *document_root = NULL;
398   const char *index_file = NULL;
399   noit_http_session_ctx *ctx = restc->http_ctx;
400   char file[PATH_MAX], rfile[PATH_MAX];
401   struct stat st;
402   int fd;
403   void *contents;
404
405   if(npats != 1 ||
406      !noit_hash_retr_str(restc->ac->config,
407                          "document_root", strlen("document_root"),
408                          &document_root)) {
409     goto not_found;
410   }
411   if(!noit_hash_retr_str(restc->ac->config,
412                          "index_file", strlen("index_file"),
413                          &index_file)) {
414     index_file = "index.html";
415   }
416   drlen = strlen(document_root);
417   snprintf(file, sizeof(file), "%s/%s", document_root, pats[0]);
418   if(file[strlen(file) - 1] == '/') {
419     snprintf(file + strlen(file), sizeof(file) - strlen(file),
420              "%s", index_file);
421   }
422   /* resolve */
423   if(realpath(file, rfile) == NULL) goto not_found;
424   /* restrict */
425   if(strncmp(rfile, document_root, drlen)) goto denied;
426   if(rfile[drlen] != '/' && rfile[drlen + 1] != '/') goto denied;
427   /* stat */
428   if(stat(rfile, &st) != 0) {
429     switch (errno) {
430       case EACCES: goto denied;
431       default: goto not_found;
432     }
433   }
434   /* open */
435   if(st.st_size > 0) {
436     fd = open(rfile, O_RDONLY);
437     if(fd < 0) goto not_found;
438     contents = mmap(NULL, st.st_size, PROT_READ, MAP_SHARED, fd, 0);
439     close(fd);
440     if(contents == (void *)-1) goto not_found;
441   }
442   noit_http_response_ok(ctx, "text/html");
443   if(st.st_size > 0) {
444     noit_http_response_append(ctx, contents, st.st_size);
445     munmap(contents, st.st_size);
446   }
447   noit_http_response_end(ctx);
448   return 0;
449
450  denied:
451   noit_http_response_denied(ctx, "text/html");
452   noit_http_response_end(ctx);
453   return 0;
454  not_found:
455   noit_http_response_not_found(ctx, "text/html");
456   noit_http_response_end(ctx);
457   return 0;
458 }
459
460 void noit_http_rest_load_rules() {
461   int ai, cnt = 0;
462   noit_conf_section_t *acls;
463   char path[256];
464   struct noit_rest_acl *newhead = NULL, *oldacls, *remove_acl;
465   struct noit_rest_acl_rule *remove_rule;
466
467   snprintf(path, sizeof(path), "//rest/acl");
468   acls = noit_conf_get_sections(NULL, path, &cnt);
469   noitL(noit_stderr, "Found %d acl stanzas\n", cnt);
470   for(ai = cnt-1; ai>=0; ai--) {
471     char tbuff[32];
472     struct noit_rest_acl *newacl;
473     int ri, rcnt = 0;
474     noit_boolean default_allow = noit_false;
475     noit_conf_section_t *rules;
476
477     newacl = calloc(1, sizeof(*newacl));
478     newacl->next = newhead;
479     newhead = newacl;
480     if(noit_conf_get_stringbuf(acls[ai], "@type", tbuff, sizeof(tbuff)) &&
481        !strcmp(tbuff, "allow"))
482       newacl->allow = noit_true;
483
484 #define compile_re(node, cont, name) do { \
485   char buff[256]; \
486   if(noit_conf_get_stringbuf(node, "@" #name, buff, sizeof(buff))) { \
487     const char *error; \
488     int erroffset; \
489     cont->name = pcre_compile(buff, 0, &error, &erroffset, NULL); \
490   } \
491 } while(0)
492
493     newacl->allow = default_allow;
494     compile_re(acls[ai], newacl, cn);
495     compile_re(acls[ai], newacl, url);
496     rules = noit_conf_get_sections(acls[ai], "rule", &rcnt);
497     for(ri = rcnt - 1; ri >= 0; ri--) {
498       struct noit_rest_acl_rule *newacl_rule;
499       newacl_rule = calloc(1, sizeof(*newacl_rule));
500       newacl_rule->next = newacl->rules;
501       newacl->rules = newacl_rule;
502       if(noit_conf_get_stringbuf(rules[ri], "@type", tbuff, sizeof(tbuff)) &&
503          !strcmp(tbuff, "allow"))
504         newacl_rule->allow = noit_true;
505       compile_re(rules[ri], newacl_rule, cn);
506       compile_re(rules[ri], newacl_rule, url);
507     }
508     free(rules);
509   }
510   free(acls);
511
512   oldacls = global_rest_acls;
513   global_rest_acls = newhead;
514
515   while(oldacls) {
516     remove_acl = oldacls->next;
517     while(oldacls->rules) {
518       remove_rule = oldacls->rules->next;
519       if(oldacls->rules->cn) pcre_free(oldacls->rules->cn);
520       if(oldacls->rules->url) pcre_free(oldacls->rules->url);
521       free(oldacls->rules);
522       oldacls->rules = remove_rule;
523     }
524     if(oldacls->cn) pcre_free(oldacls->cn);
525     if(oldacls->url) pcre_free(oldacls->url);
526     free(oldacls);
527     oldacls = remove_acl;
528   }
529 }
530 void noit_http_rest_init() {
531   eventer_name_callback("noit_wire_rest_api/1.0", noit_http_rest_handler);
532   eventer_name_callback("http_rest_api", noit_http_rest_raw_handler);
533
534   noit_http_rest_load_rules();
535
536   noit_control_dispatch_delegate(noit_control_dispatch,
537                                  NOIT_CONTROL_DELETE,
538                                  noit_http_rest_handler);
539   noit_control_dispatch_delegate(noit_control_dispatch,
540                                  NOIT_CONTROL_GET,
541                                  noit_http_rest_handler);
542   noit_control_dispatch_delegate(noit_control_dispatch,
543                                  NOIT_CONTROL_HEAD,
544                                  noit_http_rest_handler);
545   noit_control_dispatch_delegate(noit_control_dispatch,
546                                  NOIT_CONTROL_POST,
547                                  noit_http_rest_handler);
548   noit_control_dispatch_delegate(noit_control_dispatch,
549                                  NOIT_CONTROL_PUT,
550                                  noit_http_rest_handler);
551 }
552
Note: See TracBrowser for help on using the browser.