|
| 1 | +/* |
| 2 | + * coraza_httpd - minimal HTTP server with Coraza WAF |
| 3 | + * Uses libmicrohttpd + libcoraza for standalone FTW testing |
| 4 | + */ |
| 5 | + |
| 6 | +#include <stdio.h> |
| 7 | +#include <stdlib.h> |
| 8 | +#include <string.h> |
| 9 | +#include <signal.h> |
| 10 | +#include <microhttpd.h> |
| 11 | +#include <coraza/coraza.h> |
| 12 | + |
| 13 | +static coraza_waf_t waf = 0; |
| 14 | +static volatile int running = 1; |
| 15 | + |
| 16 | +static void handle_signal(int sig) { |
| 17 | + (void)sig; |
| 18 | + running = 0; |
| 19 | +} |
| 20 | + |
| 21 | +struct connection_info { |
| 22 | + char *data; |
| 23 | + size_t data_len; |
| 24 | +}; |
| 25 | + |
| 26 | +static enum MHD_Result header_iterator(void *cls, |
| 27 | + enum MHD_ValueKind kind, const char *key, const char *value) |
| 28 | +{ |
| 29 | + (void)kind; |
| 30 | + coraza_transaction_t tx = (coraza_transaction_t)(uintptr_t)cls; |
| 31 | + if (key && value) { |
| 32 | + coraza_add_request_header(tx, (char*)key, strlen(key), (char*)value, strlen(value)); |
| 33 | + } |
| 34 | + return MHD_YES; |
| 35 | +} |
| 36 | + |
| 37 | +static enum MHD_Result handle_request(void *cls, |
| 38 | + struct MHD_Connection *connection, |
| 39 | + const char *url, const char *method, const char *version, |
| 40 | + const char *upload_data, size_t *upload_data_size, |
| 41 | + void **con_cls) |
| 42 | +{ |
| 43 | + (void)cls; |
| 44 | + |
| 45 | + /* First call: set up connection info */ |
| 46 | + if (*con_cls == NULL) { |
| 47 | + struct connection_info *ci = calloc(1, sizeof(struct connection_info)); |
| 48 | + *con_cls = ci; |
| 49 | + return MHD_YES; |
| 50 | + } |
| 51 | + |
| 52 | + struct connection_info *ci = *con_cls; |
| 53 | + |
| 54 | + /* Accumulate POST data */ |
| 55 | + if (*upload_data_size > 0) { |
| 56 | + ci->data = realloc(ci->data, ci->data_len + *upload_data_size + 1); |
| 57 | + memcpy(ci->data + ci->data_len, upload_data, *upload_data_size); |
| 58 | + ci->data_len += *upload_data_size; |
| 59 | + ci->data[ci->data_len] = '\0'; |
| 60 | + *upload_data_size = 0; |
| 61 | + return MHD_YES; |
| 62 | + } |
| 63 | + |
| 64 | + /* All data received, process with Coraza */ |
| 65 | + coraza_transaction_t tx = coraza_new_transaction(waf); |
| 66 | + |
| 67 | + /* Phase 0: connection */ |
| 68 | + coraza_process_connection(tx, "127.0.0.1", 12345, "127.0.0.1", 80); |
| 69 | + |
| 70 | + /* Strip HTTP/ prefix for version */ |
| 71 | + const char *proto = "1.1"; |
| 72 | + if (version && strncmp(version, "HTTP/", 5) == 0) |
| 73 | + proto = version + 5; |
| 74 | + coraza_process_uri(tx, (char*)url, (char*)method, (char*)proto); |
| 75 | + coraza_intervention_t *it = coraza_intervention(tx); |
| 76 | + int denied = (it && it->status >= 400); |
| 77 | + int deny_status = it ? it->status : 0; |
| 78 | + if (it) coraza_free_intervention(it); |
| 79 | + |
| 80 | + /* Phase 1: request headers */ |
| 81 | + if (!denied) { |
| 82 | + MHD_get_connection_values(connection, MHD_HEADER_KIND, |
| 83 | + header_iterator, (void*)(uintptr_t)tx); |
| 84 | + |
| 85 | + coraza_process_request_headers(tx); |
| 86 | + it = coraza_intervention(tx); |
| 87 | + if (it && it->status >= 400) { denied = 1; deny_status = it->status; } |
| 88 | + if (it) coraza_free_intervention(it); |
| 89 | + } |
| 90 | + |
| 91 | + /* Phase 2: request body */ |
| 92 | + if (!denied && ci->data) { |
| 93 | + coraza_append_request_body(tx, (unsigned char*)ci->data, ci->data_len); |
| 94 | + } |
| 95 | + if (!denied) { |
| 96 | + coraza_process_request_body(tx); |
| 97 | + it = coraza_intervention(tx); |
| 98 | + if (it && it->status >= 400) { denied = 1; deny_status = it->status; } |
| 99 | + if (it) coraza_free_intervention(it); |
| 100 | + } |
| 101 | + |
| 102 | + /* Phase 3: response headers */ |
| 103 | + if (!denied) { |
| 104 | + coraza_add_response_header(tx, "Content-Type", 12, "text/plain", 10); |
| 105 | + coraza_process_response_headers(tx, 200, "HTTP/1.1"); |
| 106 | + it = coraza_intervention(tx); |
| 107 | + if (it && it->status >= 400) { denied = 1; deny_status = it->status; } |
| 108 | + if (it) coraza_free_intervention(it); |
| 109 | + } |
| 110 | + |
| 111 | + /* Phase 4: response body */ |
| 112 | + const char *resp_body; |
| 113 | + size_t resp_len; |
| 114 | + if (denied) { |
| 115 | + resp_body = "Access Denied\n"; |
| 116 | + resp_len = 14; |
| 117 | + } else if (strcmp(url, "/reflect") == 0 && ci->data) { |
| 118 | + resp_body = ci->data; |
| 119 | + resp_len = ci->data_len; |
| 120 | + coraza_append_response_body(tx, (unsigned char*)resp_body, resp_len); |
| 121 | + } else { |
| 122 | + resp_body = "OK\n"; |
| 123 | + resp_len = 3; |
| 124 | + } |
| 125 | + |
| 126 | + if (!denied) { |
| 127 | + coraza_process_response_body(tx); |
| 128 | + it = coraza_intervention(tx); |
| 129 | + if (it && it->status >= 400) { denied = 1; deny_status = it->status; } |
| 130 | + if (it) coraza_free_intervention(it); |
| 131 | + } |
| 132 | + |
| 133 | + /* Phase 5: logging */ |
| 134 | + coraza_process_logging(tx); |
| 135 | + it = coraza_intervention(tx); |
| 136 | + if (it) coraza_free_intervention(it); |
| 137 | + |
| 138 | + /* Send response */ |
| 139 | + int status = denied ? deny_status : 200; |
| 140 | + if (denied) { |
| 141 | + resp_body = "Access Denied\n"; |
| 142 | + resp_len = 14; |
| 143 | + } |
| 144 | + |
| 145 | + struct MHD_Response *response = MHD_create_response_from_buffer( |
| 146 | + resp_len, (void*)resp_body, MHD_RESPMEM_MUST_COPY); |
| 147 | + enum MHD_Result ret = MHD_queue_response(connection, status, response); |
| 148 | + MHD_destroy_response(response); |
| 149 | + |
| 150 | + coraza_free_transaction(tx); |
| 151 | + |
| 152 | + return ret; |
| 153 | +} |
| 154 | + |
| 155 | +static void request_completed(void *cls, struct MHD_Connection *connection, |
| 156 | + void **con_cls, enum MHD_RequestTerminationCode toe) |
| 157 | +{ |
| 158 | + (void)cls; (void)connection; (void)toe; |
| 159 | + struct connection_info *ci = *con_cls; |
| 160 | + if (ci) { |
| 161 | + free(ci->data); |
| 162 | + free(ci); |
| 163 | + } |
| 164 | +} |
| 165 | + |
| 166 | +int main(int argc, char **argv) { |
| 167 | + const char *config_file = "coraza_includes.conf"; |
| 168 | + int port = 8080; |
| 169 | + |
| 170 | + if (argc > 1) config_file = argv[1]; |
| 171 | + if (argc > 2) port = atoi(argv[2]); |
| 172 | + |
| 173 | + /* Create WAF */ |
| 174 | + coraza_waf_config_t config = coraza_new_waf_config(); |
| 175 | + if (coraza_rules_add_file(config, (char*)config_file) < 0) { |
| 176 | + fprintf(stderr, "Failed to load rules from %s\n", config_file); |
| 177 | + coraza_free_waf_config(config); |
| 178 | + return 1; |
| 179 | + } |
| 180 | + |
| 181 | + char *err = NULL; |
| 182 | + waf = coraza_new_waf(config, &err); |
| 183 | + coraza_free_waf_config(config); |
| 184 | + if (waf == 0) { |
| 185 | + fprintf(stderr, "Failed to create WAF: %s\n", err ? err : "unknown"); |
| 186 | + return 1; |
| 187 | + } |
| 188 | + printf("WAF loaded, %d rules\n", coraza_rules_count(waf)); |
| 189 | + |
| 190 | + /* Start HTTP server */ |
| 191 | + struct MHD_Daemon *daemon = MHD_start_daemon( |
| 192 | + MHD_USE_INTERNAL_POLLING_THREAD, |
| 193 | + port, NULL, NULL, |
| 194 | + &handle_request, NULL, |
| 195 | + MHD_OPTION_NOTIFY_COMPLETED, &request_completed, NULL, |
| 196 | + MHD_OPTION_CLIENT_DISCIPLINE_LVL, (int)-1, |
| 197 | + MHD_OPTION_CONNECTION_TIMEOUT, (unsigned int)5, |
| 198 | + MHD_OPTION_END); |
| 199 | + |
| 200 | + if (!daemon) { |
| 201 | + fprintf(stderr, "Failed to start HTTP server\n"); |
| 202 | + coraza_free_waf(waf); |
| 203 | + return 1; |
| 204 | + } |
| 205 | + |
| 206 | + signal(SIGINT, handle_signal); |
| 207 | + signal(SIGTERM, handle_signal); |
| 208 | + |
| 209 | + printf("Coraza HTTP server listening on port %d\n", port); |
| 210 | + |
| 211 | + while (running) sleep(1); |
| 212 | + |
| 213 | + MHD_stop_daemon(daemon); |
| 214 | + coraza_free_waf(waf); |
| 215 | + return 0; |
| 216 | +} |
0 commit comments