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