1 /* ====================================================================
2 * The Kannel Software License, Version 1.0
3 *
4 * Copyright (c) 2001-2014 Kannel Group
5 * Copyright (c) 1998-2001 WapIT Ltd.
6 * All rights reserved.
7 *
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions
10 * are met:
11 *
12 * 1. Redistributions of source code must retain the above copyright
13 * notice, this list of conditions and the following disclaimer.
14 *
15 * 2. Redistributions in binary form must reproduce the above copyright
16 * notice, this list of conditions and the following disclaimer in
17 * the documentation and/or other materials provided with the
18 * distribution.
19 *
20 * 3. The end-user documentation included with the redistribution,
21 * if any, must include the following acknowledgment:
22 * "This product includes software developed by the
23 * Kannel Group (http://www.kannel.org/)."
24 * Alternately, this acknowledgment may appear in the software itself,
25 * if and wherever such third-party acknowledgments normally appear.
26 *
27 * 4. The names "Kannel" and "Kannel Group" must not be used to
28 * endorse or promote products derived from this software without
29 * prior written permission. For written permission, please
30 * contact org@kannel.org.
31 *
32 * 5. Products derived from this software may not be called "Kannel",
33 * nor may "Kannel" appear in their name, without prior written
34 * permission of the Kannel Group.
35 *
36 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
37 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
38 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
39 * DISCLAIMED. IN NO EVENT SHALL THE KANNEL GROUP OR ITS CONTRIBUTORS
40 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
41 * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
42 * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
43 * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
44 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
45 * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
46 * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
47 * ====================================================================
48 *
49 * This software consists of voluntary contributions made by many
50 * individuals on behalf of the Kannel Group. For more information on
51 * the Kannel Group, please see <http://www.kannel.org/>.
52 *
53 * Portions of this software are based upon software originally written at
54 * WapIT Ltd., Helsinki, Finland for the Kannel project.
55 */
56
57 /*
58 * test_http.c - a simple program to test the http library, server end
59 *
60 * Lars Wirzenius
61 */
62
63 #include <string.h>
64 #include <stdlib.h>
65 #include <unistd.h>
66 #include <signal.h>
67
68 #include "gwlib/gwlib.h"
69 #include "gwlib/http.h"
70
71 #define MAX_THREADS 1024
72
73 Octstr *whitelist, *blacklist;
74 Octstr *reply_text = NULL;
75
76 int verbose, port;
77 int ssl = 0; /* indicate if SSL-enabled server should be used */
78 static volatile sig_atomic_t run;
79 static List *extra_headers = NULL;
80
split_headers(Octstr * headers,List ** split)81 static void split_headers(Octstr *headers, List **split)
82 {
83 long start;
84 long pos;
85
86 *split = gwlist_create();
87 start = 0;
88 for (pos = 0; pos < octstr_len(headers); pos++) {
89 if (octstr_get_char(headers, pos) == '\n') {
90 Octstr *line;
91
92 if (pos == start) {
93 /* Skip empty lines */
94 start = pos + 1;
95 continue;
96 }
97 line = octstr_copy(headers, start, pos - start);
98 start = pos + 1;
99 gwlist_append(*split, line);
100 }
101 }
102 }
103
client_thread(void * arg)104 static void client_thread(void *arg)
105 {
106 HTTPClient *client;
107 Octstr *body, *url, *ip;
108 List *headers, *resph, *cgivars;
109 HTTPCGIVar *v;
110 Octstr *reply_body, *reply_type;
111 unsigned long n = 0;
112 int status, i;
113
114 while (run) {
115 client = http_accept_request(port, &ip, &url, &headers, &body, &cgivars);
116
117 n++;
118 if (client == NULL)
119 break;
120
121 info(0, "Request for <%s> from <%s>",
122 octstr_get_cstr(url), octstr_get_cstr(ip));
123 if (verbose)
124 debug("test.http", 0, "CGI vars were");
125
126 /*
127 * Don't use gwlist_extract() here, otherwise we don't have a chance
128 * to re-use the cgivars later on.
129 */
130 for (i = 0; i < gwlist_len(cgivars); i++) {
131 if ((v = gwlist_get(cgivars, i)) != NULL && verbose) {
132 octstr_dump(v->name, 0);
133 octstr_dump(v->value, 0);
134 }
135 }
136
137 if (arg == NULL) {
138 reply_body = octstr_duplicate(reply_text);
139 reply_type = octstr_create("Content-Type: text/plain; "
140 "charset=\"UTF-8\"");
141 } else {
142 reply_body = octstr_duplicate(arg);
143 reply_type = octstr_create("Content-Type: text/vnd.wap.wml");
144 }
145
146 resph = gwlist_create();
147 gwlist_append(resph, reply_type);
148
149 status = HTTP_OK;
150
151 /* check for special URIs and handle those */
152 if (octstr_compare(url, octstr_imm("/quit")) == 0) {
153 run = 0;
154 } else if (octstr_compare(url, octstr_imm("/whitelist")) == 0) {
155 octstr_destroy(reply_body);
156 if (whitelist != NULL) {
157 if (verbose) {
158 debug("test.http.server", 0, "we send a white list");
159 octstr_dump(whitelist, 0);
160 }
161 reply_body = octstr_duplicate(whitelist);
162 } else {
163 reply_body = octstr_imm("");
164 }
165 } else if (octstr_compare(url, octstr_imm("/blacklist")) == 0) {
166 octstr_destroy(reply_body);
167 if (blacklist != NULL) {
168 if (verbose) {
169 debug("test.http.server", 0, "we send a blacklist");
170 octstr_dump(blacklist, 0);
171 }
172 reply_body = octstr_duplicate(blacklist);
173 } else {
174 reply_body = octstr_imm("");
175 }
176 } else if (octstr_compare(url, octstr_imm("/save")) == 0) {
177 /* safe the body into a temporary file */
178 pid_t pid = getpid();
179 FILE *f = fopen(octstr_get_cstr(octstr_format("/tmp/body.%ld.%ld", pid, n)), "w");
180 octstr_print(f, body);
181 fclose(f);
182 } else if (octstr_compare(url, octstr_imm("/redirect/")) == 0) {
183 /* provide us with a HTTP 302 redirection response
184 * will return /redirect/<pid> for the location header
185 * and will return /redirect/ if cgivar loop is set to allow looping
186 */
187 Octstr *redirect_header, *scheme, *uri, *l;
188 pid_t pid = getpid();
189
190 uri = ((l = http_cgi_variable(cgivars, "loop")) != NULL) ?
191 octstr_format("%s?loop=%s", octstr_get_cstr(url),
192 octstr_get_cstr(l)) :
193 octstr_format("%s%ld", octstr_get_cstr(url), pid);
194
195 octstr_destroy(reply_body);
196 reply_body = octstr_imm("Here you got a redirection URL that you should follow.");
197 scheme = ssl ? octstr_imm("https://") : octstr_imm("http://");
198 redirect_header = octstr_format("Location: %s%s%s",
199 octstr_get_cstr(scheme),
200 octstr_get_cstr(http_header_value(headers, octstr_imm("Host"))),
201 octstr_get_cstr(uri));
202 gwlist_append(resph, redirect_header);
203 status = HTTP_FOUND; /* will provide 302 */
204 octstr_destroy(uri);
205 } else if (octstr_compare(url, octstr_imm("/mmsc")) == 0) {
206 /* fake a M-Send.conf PDU which is using MMSEncapsulation as body */
207 pid_t pid = getpid();
208 FILE *f;
209 gwlist_destroy(resph, octstr_destroy_item);
210 octstr_destroy(reply_body);
211 reply_type = octstr_create("Content-Type: application/vnd.wap.mms-message");
212 reply_body = octstr_create("");
213 octstr_append_from_hex(reply_body,
214 "8c81" /* X-Mms-Message-Type: m-send-conf */
215 "98632d3862343300" /* X-Mms-Transaction-ID: c-8b43 */
216 "8d90" /* X-Mms-MMS-Version: 1.0 */
217 "9280" /* Response-status: Ok */
218 "8b313331373939353434393639383434313731323400"
219 ); /* Message-Id: 13179954496984417124 */
220 resph = gwlist_create();
221 gwlist_append(resph, reply_type);
222 /* safe the M-Send.req body into a temporary file */
223 f = fopen(octstr_get_cstr(octstr_format("/tmp/mms-body.%ld.%ld", pid, n)), "w");
224 octstr_print(f, body);
225 fclose(f);
226 }
227
228 if (verbose) {
229 debug("test.http", 0, "request headers were");
230 http_header_dump(headers);
231 if (body != NULL) {
232 debug("test.http", 0, "request body was");
233 octstr_dump(body, 0);
234 }
235 }
236
237 if (extra_headers != NULL)
238 http_header_combine(resph, extra_headers);
239
240 /* return response to client */
241 http_send_reply(client, status, resph, reply_body);
242
243 octstr_destroy(ip);
244 octstr_destroy(url);
245 octstr_destroy(body);
246 octstr_destroy(reply_body);
247 http_destroy_cgiargs(cgivars);
248 gwlist_destroy(headers, octstr_destroy_item);
249 gwlist_destroy(resph, octstr_destroy_item);
250 }
251
252 octstr_destroy(whitelist);
253 octstr_destroy(blacklist);
254 debug("test.http", 0, "Working thread 'client_thread' terminates");
255 http_close_all_ports();
256 }
257
help(void)258 static void help(void) {
259 info(0, "Usage: test_http_server [options...]");
260 info(0, "where options are:");
261 info(0, "-t number");
262 info(0, " set number of working threads to use (default: 1)");
263 info(0, "-v number");
264 info(0, " set log level for stderr logging (default: 0 - debug)");
265 info(0, "-l logfile");
266 info(0, " log all output to a file");
267 info(0, "-f file");
268 info(0, " use a specific file content for the response body");
269 info(0, "-r reply_text");
270 info(0, " defines which static text to use for replies");
271 info(0, "-h");
272 info(0, " provides this usage help information");
273 info(0, "-q");
274 info(0, " don't be too verbose with output");
275 info(0, "-p port");
276 info(0, " bind server to a specific port");
277 info(0, "-s");
278 info(0, " be an SSL-enabled server");
279 info(0, "-c ssl_cert");
280 info(0, " file of the SSL certificate to use");
281 info(0, "-k ssl_key");
282 info(0, " file of the SSL private key to use");
283 info(0, "-w white_list");
284 info(0, " file that is used for whitelist");
285 info(0, "-b black_list");
286 info(0, " file that is used for blacklist");
287 info(0, "-H filename");
288 info(0, " read HTTP headers from file 'filename' and add them to");
289 info(0, " the request for url 'url'");
290 info(0, "specific URIs with special functions are:");
291 info(0, " /quite - shutdown the HTTP server");
292 info(0, " /whitelist - provides the -w whitelist as response");
293 info(0, " /blacklist - provides the -b blacklist as response");
294 info(0, " /save - save a HTTP POST request body to a file /tmp/body.<pid>.<n>");
295 info(0, " where <pid> is the process id and <n> is the received request number");
296 info(0, " /redirect/ - respond with HTTP 302 and the location /redirect/<pid>");
297 info(0, " where <pid> is the process id. if a cgivar loop=<something> is given");
298 info(0, " then HTTP reponses will end up in a loop.");
299 info(0, " /mmsc - fake a MMSC HTTP interface for M-Send.req PDUs send by a");
300 info(0, " mobile MMS-capable device, responds with a M-Send.conf PDU and");
301 info(0, " saves the M-Send.req body to a file /tmp/mms-body.<pid>.<n> in");
302 info(0, " MMSEncapsulation encoded binary format");
303
304 }
305
sigterm(int signo)306 static void sigterm(int signo) {
307 run = 0;
308 debug("test.gwlib", 0, "Signal %d received, quitting.", signo);
309 }
310
main(int argc,char ** argv)311 int main(int argc, char **argv) {
312 int i, opt, use_threads;
313 struct sigaction act;
314 char *filename;
315 Octstr *log_filename;
316 Octstr *file_contents;
317 #ifdef HAVE_LIBSSL
318 Octstr *ssl_server_cert_file = NULL;
319 Octstr *ssl_server_key_file = NULL;
320 #endif
321 char *whitelist_name;
322 char *blacklist_name;
323 int white_asked,
324 black_asked;
325 long threads[MAX_THREADS];
326 FILE *fp;
327
328 gwlib_init();
329
330 act.sa_handler = sigterm;
331 sigemptyset(&act.sa_mask);
332 act.sa_flags = 0;
333 sigaction(SIGTERM, &act, NULL);
334 sigaction(SIGINT, &act, NULL);
335
336 port = 8080;
337 use_threads = 1;
338 verbose = 1;
339 run = 1;
340 filename = NULL;
341 log_filename = NULL;
342 blacklist_name = NULL;
343 whitelist_name = NULL;
344 white_asked = 0;
345 black_asked = 0;
346
347 reply_text = octstr_create("Sent.");
348
349 while ((opt = getopt(argc, argv, "hqv:p:t:f:l:sc:k:b:w:r:H:")) != EOF) {
350 switch (opt) {
351 case 'v':
352 log_set_output_level(atoi(optarg));
353 break;
354
355 case 'q':
356 verbose = 0;
357 break;
358
359 case 'h':
360 help();
361 exit(0);
362
363 case 'p':
364 port = atoi(optarg);
365 break;
366
367 case 't':
368 use_threads = atoi(optarg);
369 if (use_threads > MAX_THREADS)
370 use_threads = MAX_THREADS;
371 break;
372
373 case 'c':
374 #ifdef HAVE_LIBSSL
375 octstr_destroy(ssl_server_cert_file);
376 ssl_server_cert_file = octstr_create(optarg);
377 #endif
378 break;
379
380 case 'k':
381 #ifdef HAVE_LIBSSL
382 octstr_destroy(ssl_server_key_file);
383 ssl_server_key_file = octstr_create(optarg);
384 #endif
385 break;
386
387 case 's':
388 #ifdef HAVE_LIBSSL
389 ssl = 1;
390 #endif
391 break;
392
393 case 'f':
394 filename = optarg;
395 break;
396
397 case 'l':
398 octstr_destroy(log_filename);
399 log_filename = octstr_create(optarg);
400 break;
401
402 case 'w':
403 whitelist_name = optarg;
404 if (whitelist_name == NULL)
405 whitelist_name = "";
406 white_asked = 1;
407 break;
408
409 case 'b':
410 blacklist_name = optarg;
411 if (blacklist_name == NULL)
412 blacklist_name = "";
413 black_asked = 1;
414 break;
415
416 case 'r':
417 octstr_destroy(reply_text);
418 reply_text = octstr_create(optarg);
419 break;
420
421 case 'H': {
422 Octstr *cont;
423
424 fp = fopen(optarg, "a");
425 if (fp == NULL)
426 panic(0, "Cannot open header text file %s", optarg);
427 cont = octstr_read_file(optarg);
428 if (cont == NULL)
429 panic(0, "Cannot read header text");
430 debug("", 0, "headers are");
431 octstr_dump(cont, 0);
432 split_headers(cont, &extra_headers);
433 fclose(fp);
434 octstr_destroy(cont);
435 break;
436 }
437
438 case '?':
439 default:
440 error(0, "Invalid option %c", opt);
441 help();
442 panic(0, "Stopping.");
443 }
444 }
445
446 if (log_filename != NULL) {
447 log_open(octstr_get_cstr(log_filename), GW_DEBUG, GW_NON_EXCL);
448 octstr_destroy(log_filename);
449 }
450
451 if (filename == NULL)
452 file_contents = NULL;
453 else
454 file_contents = octstr_read_file(filename);
455
456 if (white_asked) {
457 whitelist = octstr_read_file(whitelist_name);
458 if (whitelist == NULL)
459 panic(0, "Cannot read the whitelist");
460 }
461
462 if (black_asked) {
463 blacklist = octstr_read_file(blacklist_name);
464 if (blacklist == NULL)
465 panic(0, "Cannot read the blacklist");
466 }
467
468 #ifdef HAVE_LIBSSL
469 /*
470 * check if we are doing a SSL-enabled server version here
471 * load the required cert and key file
472 */
473 if (ssl) {
474 if (ssl_server_cert_file != NULL && ssl_server_key_file != NULL) {
475 use_global_server_certkey_file(ssl_server_cert_file, ssl_server_key_file);
476 octstr_destroy(ssl_server_cert_file);
477 octstr_destroy(ssl_server_key_file);
478 } else {
479 panic(0, "certificate and public key need to be given!");
480 }
481 }
482 #endif
483
484 if (http_open_port(port, ssl) == -1)
485 panic(0, "http_open_server failed");
486
487 /*
488 * Do the real work in a separate thread so that the main
489 * thread can catch signals safely.
490 */
491 for (i = 0; i < use_threads; ++i)
492 threads[i] = gwthread_create(client_thread, file_contents);
493
494 /* wait for all working threads */
495 for (i = 0; i < use_threads; ++i)
496 gwthread_join(threads[i]);
497
498 octstr_destroy(reply_text);
499 gwlist_destroy(extra_headers, octstr_destroy_item);
500
501 debug("test.http", 0, "Program exiting normally.");
502 gwlib_shutdown();
503 return 0;
504 }
505
506
507
508
509