1 /**
2  * Browser instance
3  *
4  * Copyright (C) 2016 by
5  * Jeffrey Fulmer - <jeff@joedog.org>, et al.
6  * This file is distributed as part of Siege
7  *
8  * This program is free software; you can redistribute it and/or modify
9  * it under the terms of the GNU General Public License as published by
10  * the Free Software Foundation; either version 2 of the License, or
11  * (at your option) any later version.
12  *
13  * This program is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  * GNU General Public License for more details.
17  *
18  * You should have received a copy of the GNU General Public License along
19  * with this program; if not, write to the Free Software Foundation, Inc.
20  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
21  *--
22  */
23 #ifdef  HAVE_CONFIG_H
24 # include <config.h>
25 #endif/*HAVE_CONFIG_H*/
26 
27 #include <setup.h>
28 #include <signal.h>
29 #include <sock.h>
30 #include <ssl.h>
31 #include <ftp.h>
32 #include <http.h>
33 #include <hash.h>
34 #include <array.h>
35 #include <util.h>
36 #include <parser.h>
37 #include <perl.h>
38 #include <response.h>
39 #include <memory.h>
40 #include <notify.h>
41 #include <browser.h>
42 
43 #if defined(hpux) || defined(__hpux) || defined(WINDOWS)
44 # define SIGNAL_CLIENT_PLATFORM
45 #endif
46 
47 #ifdef SIGNAL_CLIENT_PLATFORM
48 static pthread_once_t once = PTHREAD_ONCE_INIT;
49 #endif/*SIGNAL_CLIENT_PLATFORM*/
50 
51 float __himark = 0;
52 float __lomark = -1;
53 
54 struct BROWSER_T
55 {
56   int      id;
57   size_t   tid;
58   ARRAY    urls;
59   ARRAY    parts;
60   HASH     cookies;
61   CONN *   conn;
62 #ifdef SIGNAL_CLIENT_PLATFORM
63   sigset_t sigs;
64 #else
65   int      type;
66   int      state;
67 #endif
68   float    total;
69   float    available;
70   float    lowest;
71   float    highest;
72   float    elapsed;
73   float    time;
74   float    himark;
75   float    lomark;
76   clock_t  start;
77   clock_t  stop;
78   struct   tms  t_start;
79   struct   tms  t_stop;
80   struct {
81     DCHLG *wchlg;
82     DCRED *wcred;
83     int    www;
84     DCHLG *pchlg;
85     DCRED *pcred;
86     int  proxy;
87     struct {
88       int  www;
89       int  proxy;
90     } bids;
91     struct {
92       TYPE www;
93       TYPE proxy;
94     } type;
95   } auth;
96   unsigned int  code;
97   unsigned int  count;
98   unsigned int  okay;
99   unsigned int  fail;
100   unsigned long hits;
101   unsigned long long bytes;
102   unsigned int  rseed;
103 };
104 
105 size_t BROWSERSIZE = sizeof(struct BROWSER_T);
106 
107 private BOOLEAN __init_connection(BROWSER this, URL U);
108 private BOOLEAN __request(BROWSER this, URL U);
109 private BOOLEAN __http(BROWSER this, URL U);
110 private BOOLEAN __ftp(BROWSER this, URL U);
111 private BOOLEAN __no_follow(const char *hostname);
112 private void    __increment_failures();
113 private int     __select_color(int code);
114 private void    __display_result(BROWSER this, RESPONSE resp, URL U, unsigned long bytes, float etime);
115 
116 
117 #ifdef  SIGNAL_CLIENT_PLATFORM
118 private void    __signal_handler(int sig);
119 private void    __signal_init();
120 #else/*CANCEL_CLIENT_PLATFORM*/
121 private void    __signal_cleanup();
122 #endif/*SIGNAL_CLIENT_PLATFORM*/
123 
124 
125 BROWSER
new_browser(int id)126 new_browser(int id)
127 {
128   BROWSER this;
129 
130   this = calloc(BROWSERSIZE,1);
131   this->id        = id;
132   this->total     = 0.0;
133   this->available = 0.0;
134   this->count     = 0.0;
135   this->okay      = 0;
136   this->fail      = 0;
137   this->lowest    =  -1;
138   this->highest   = 0.0;
139   this->elapsed   = 0.0;
140   this->bytes     = 0.0;
141   this->urls      = NULL;
142   this->parts     = new_array();
143   this->rseed     = urandom();
144   return this;
145 }
146 
147 BROWSER
browser_destroy(BROWSER this)148 browser_destroy(BROWSER this)
149 {
150   if (this != NULL) {
151     /**
152      * NOTE: this->urls is a reference to main.c:urls It was
153      * never instantiated in this class.  We'll reclaim that
154      * memory when we deconstruct main.c:urls
155      */
156 
157     if (this->parts != NULL) {
158       URL u;
159       while ((u = (URL)array_pop(this->parts)) != NULL) {
160         u = url_destroy(u);
161       }
162       this->parts = array_destroy(this->parts);
163     }
164     xfree(this);
165   }
166   this = NULL;
167   return this;
168 }
169 
170 unsigned long
browser_get_hits(BROWSER this)171 browser_get_hits(BROWSER this)
172 {
173   return this->hits;
174 }
175 
176 unsigned long long
browser_get_bytes(BROWSER this)177 browser_get_bytes(BROWSER this)
178 {
179   return this->bytes;
180 }
181 
182 float
browser_get_time(BROWSER this)183 browser_get_time(BROWSER this)
184 {
185   return this->time;
186 }
187 
188 unsigned int
browser_get_code(BROWSER this)189 browser_get_code(BROWSER this)
190 {
191   return this->code;
192 }
193 
194 unsigned int
browser_get_okay(BROWSER this)195 browser_get_okay(BROWSER this)
196 {
197   return this->okay;
198 }
199 
200 unsigned int
browser_get_fail(BROWSER this)201 browser_get_fail(BROWSER this)
202 {
203   return this->fail;
204 }
205 
206 float
browser_get_himark(BROWSER this)207 browser_get_himark(BROWSER this)
208 {
209   return this->himark;
210 }
211 
212 float
browser_get_lomark(BROWSER this)213 browser_get_lomark(BROWSER this)
214 {
215   return this->lomark;
216 }
217 
218 void *
start(BROWSER this)219 start(BROWSER this)
220 {
221   int x;
222   int y;
223   int max_y;
224   int ret;
225   int len;
226   this->conn  = NULL;
227   this->conn = xcalloc(sizeof(CONN), 1);
228   this->conn->sock       = -1;
229   this->conn->page       = new_page("");
230   this->conn->cache      = new_cache();
231 
232 #ifdef SIGNAL_CLIENT_PLATFORM
233   pthread_once(&this->once, __signal_init);
234   sigemptyset(&this->sigs);
235   sigaddset(&this->sigs, SIGUSR1);
236   pthread_sigmask(SIG_UNBLOCK, &this->sigs, NULL);
237 #else/*CANCEL_CLIENT_PLATFORM*/
238   #if defined(_AIX)
239     pthread_cleanup_push((void(*)(void*))__signal_cleanup, NULL);
240   #else
241     pthread_cleanup_push((void*)__signal_cleanup, this->conn);
242   #endif
243 
244   #if defined(sun)
245     pthread_setcanceltype (PTHREAD_CANCEL_DEFERRED, &this->type);
246   #elif defined(_AIX)
247     pthread_setcanceltype (PTHREAD_CANCEL_DEFERRED, &this->type);
248   #elif defined(hpux) || defined(__hpux)
249     pthread_setcanceltype (PTHREAD_CANCEL_ASYNCHRONOUS, &this->type);
250   #else
251     pthread_setcanceltype (PTHREAD_CANCEL_ASYNCHRONOUS, &this->type);
252   #endif
253   pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, &this->state);
254 #endif/*SIGNAL_CLIENT_PLATFORM*/
255 
256   if (my.login == TRUE) {
257     URL tmp = new_url(array_next(my.lurl));
258     url_set_ID(tmp, 0);
259     __request(this, tmp);
260   }
261 
262   len = (my.reps == -1) ? (int)array_length(this->urls) : my.reps;
263   y   = (my.reps == -1) ? 0 : this->id * (my.length / my.cusers);
264   max_y = (int)array_length(this->urls);
265   for (x = 0; x < len; x++, y++) {
266     x = ((my.secs > 0) && ((my.reps <= 0)||(my.reps == MAXREPS))) ? 0 : x;
267     if (my.internet == TRUE) {
268       y = (unsigned int) (((double)pthread_rand_np(&(this->rseed)) /
269                           ((double)RAND_MAX + 1) * my.length ) + .5);
270       y = (y >= my.length)?my.length-1:y;
271       y = (y < 0)?0:y;
272     } else {
273       /**
274        * URLs accessed sequentially; when reaching the end, start over
275        * with clean slate, ie. reset (delete) cookies (eg. to let a new
276        * session start)
277        */
278       if (y >= max_y) {
279         y = 0;
280         if (my.expire) {
281           cookies_delete_all(my.cookies);
282         }
283       }
284     }
285     if (y >= max_y || y < 0) {
286       y = 0;
287     }
288 
289     /**
290      * This is the initial request from the command line
291      * or urls.txt file. If it is text/html then it will
292      * be parsed in __http request function.
293      */
294     URL tmp = array_get(this->urls, y);
295     if (tmp != NULL && url_get_hostname(tmp) != NULL) {
296       this->auth.bids.www = 0; /* reset */
297       if ((ret = __request(this, tmp))==FALSE) {
298         __increment_failures();
299       }
300     }
301 
302     /**
303      * If we parsed http resources, we'll request them here
304      */
305     if (my.parser == TRUE && this->parts != NULL) {
306       URL  u;
307       while ((u = (URL)array_pop(this->parts)) != NULL) {
308         if (url_get_scheme(u) == UNSUPPORTED) {
309           ;;
310         } else if (my.cache && is_cached(this->conn->cache, u)) {
311           RESPONSE r = new_response();
312           response_set_code(r, "HTTP/1.1 200 OK");
313           response_set_from_cache(r, TRUE);
314           __display_result(this, r, u, 0, 0.00);
315           r = response_destroy(r);
316           ;;
317         } else {
318           this->auth.bids.www = 0;
319           // We'll only request files on the same host as the page
320           if (! __no_follow(url_get_hostname(u))) {
321             if ((ret = __request(this, u))==FALSE) {
322               __increment_failures();
323             }
324           }
325         }
326         u = url_destroy(u);
327       }
328     }
329 
330     /**
331      * Delay between interactions -D num /--delay=num
332      */
333     if (my.delay >= 1) {
334       pthread_sleep_np(
335        (unsigned int) (((double)pthread_rand_np(&(this->rseed)) /
336                        ((double)RAND_MAX + 1) * my.delay ) + .5)
337       );
338     } else if (my.delay >= .001) {
339       pthread_usleep_np(
340        (unsigned int) (((double)pthread_rand_np(&(this->rseed)) /
341                        ((double)RAND_MAX + 1) * my.delay * 1000000 ) + .0005)
342       );
343     }
344 
345     if (my.failures > 0 && my.failed >= my.failures) {
346       break;
347     }
348   }
349 
350 #ifdef SIGNAL_CLIENT_PLATFORM
351 #else/*CANCEL_CLIENT_PLATFORM*/
352   // XXX: every cleanup must have a pop
353   pthread_cleanup_pop(0);
354 #endif/*SIGNAL_CLIENT_PLATFORM*/
355 
356   if (this->conn->sock >= 0){
357     this->conn->connection.reuse = 0;
358     socket_close(this->conn);
359   }
360   this->conn->page  = page_destroy(this->conn->page);
361   this->conn->cache = cache_destroy(this->conn->cache); //XXX: do we want to persist this?
362   xfree(this->conn);
363   this->conn = NULL;
364 
365   return NULL;
366 }
367 
368 void
browser_set_urls(BROWSER this,ARRAY urls)369 browser_set_urls(BROWSER this, ARRAY urls)
370 {
371   this->urls = urls;
372 }
373 
374 void
browser_set_cookies(BROWSER this,HASH cookies)375 browser_set_cookies(BROWSER this, HASH cookies)
376 {
377   int i = 0;
378 
379   this->cookies = cookies;
380 
381   if (this->cookies != NULL) {
382     char **keys = hash_get_keys(this->cookies);
383     for (i = 0; i < hash_get_entries(this->cookies); i ++){
384       /**
385        * We need a local copy of the variable to pass to cookies_add
386        */
387       char *tmp;
388       int   len = strlen(hash_get(this->cookies, keys[i]));
389       tmp = xmalloc(len+2);
390       memset(tmp, '\0', len+2);
391       snprintf(tmp, len+1, "%s", (char*)hash_get(this->cookies, keys[i]));
392       cookies_add(my.cookies, tmp, ".");
393       xfree(tmp);
394     }
395   }
396 }
397 
398 private BOOLEAN
__request(BROWSER this,URL U)399 __request(BROWSER this, URL U) {
400   this->conn->scheme = url_get_scheme(U);
401 
402   switch (this->conn->scheme) {
403     case FTP:
404       return __ftp(this, U);
405     case HTTP:
406     case HTTPS:
407     default:
408       return __http(this, U);
409   }
410 }
411 
412 /**
413  * HTTP client request.
414  * The protocol is executed in http.c
415  * This function invoked functions inside that module
416  * and it gathers statistics about the request.
417  */
418 private BOOLEAN
__http(BROWSER this,URL U)419 __http(BROWSER this, URL U)
420 {
421   unsigned long bytes  = 0;
422   int      code, okay, fail;
423   float    etime;
424   clock_t  start, stop;
425   struct   tms t_start, t_stop;
426   RESPONSE resp;
427   char     *meta = NULL;
428 #ifdef  HAVE_LOCALTIME_R
429   struct   tm keepsake;
430 #endif/*HAVE_LOCALTIME_R*/
431   time_t   now;
432   struct   tm *tmp;
433   size_t   len;
434   char     fmtime[65];
435   URL      redirect_url = NULL;
436 
437   page_clear(this->conn->page);
438 
439   if (my.csv) {
440     now = time(NULL);
441     #ifdef HAVE_LOCALTIME_R
442     tmp = (struct tm *)localtime_r(&now, &keepsake);
443     #else
444     tmp = localtime(&now);
445     #endif/*HAVE_LOCALTIME_R*/
446     if (tmp) {
447       len = strftime(fmtime, 64, "%Y-%m-%d %H:%M:%S", tmp);
448       if (len == 0) {
449         memset(fmtime, '\0', 64);
450         snprintf(fmtime, 64, "n/a");
451       }
452     } else {
453       snprintf(fmtime, 64, "n/a");
454     }
455   }
456 
457   if (url_get_scheme(U) == UNSUPPORTED) {
458     if (my.verbose && !my.get && !my.print) {
459       NOTIFY (
460         ERROR,
461         "%s %d %6.2f secs: %7d bytes ==> %s\n",
462         "UNSPPRTD", 501, 0.00, 0, "PROTOCOL NOT SUPPORTED BY SIEGE"
463       );
464     } /* end if my.verbose */
465     return FALSE;
466   }
467 
468   /* record transaction start time */
469   start = times(&t_start);
470   if (! __init_connection(this, U)) return FALSE;
471 
472   /**
473    * write to socket with a GET/POST/PUT/DELETE/HEAD
474    */
475   if (url_get_method(U) == POST   || url_get_method(U) == PUT || url_get_method(U) == PATCH ||
476       url_get_method(U) == DELETE || url_get_method(U) == OPTIONS) {
477     if ((http_post(this->conn, U)) == FALSE) {
478       this->conn->connection.reuse = 0;
479       socket_close(this->conn);
480       return FALSE;
481     }
482   } else {
483     if ((http_get(this->conn, U)) == FALSE) {
484       this->conn->connection.reuse = 0;
485       socket_close(this->conn);
486       return FALSE;
487     }
488   }
489 
490   /**
491    * read from socket and collect statistics.
492    */
493   if ((resp = http_read_headers(this->conn, U))==NULL) {
494     this->conn->connection.reuse = 0;
495     socket_close(this->conn);
496     echo ("%s:%d NULL headers", __FILE__, __LINE__);
497     return FALSE;
498   }
499 
500   code = response_get_code(resp);
501 
502   if (code == 418) {
503     /**
504      * I don't know what server we're talking to but I
505      * know what it's not. It's not an HTTP server....
506      */
507     this->conn->connection.reuse = 0;
508     socket_close(this->conn);
509     stop  =  times(&t_stop);
510     etime =  elapsed_time(stop - start);
511     this->hits ++;
512     this->time += etime;
513     this->fail += 1;
514 
515     __display_result(this, resp, U, 0, etime);
516     resp = response_destroy(resp);
517     return FALSE;
518   }
519 
520   bytes = http_read(this->conn, resp);
521   if (my.print) {
522     printf("%s\n", page_value(this->conn->page));
523   }
524 
525   if (my.parser == TRUE) {
526     if (strmatch(response_get_content_type(resp), "text/html") && code < 300) {
527       int   i;
528       html_parser(this->parts, U, page_value(this->conn->page));
529       for (i = 0; i < (int)array_length(this->parts); i++) {
530         URL url  = (URL)array_get(this->parts, i);
531         if (url_is_redirect(url)) {
532           URL tmp = (URL)array_remove(this->parts, i);
533           meta    = xstrdup(url_get_absolute(tmp));
534           tmp     = url_destroy(tmp);
535         }
536       }
537     }
538   }
539 
540   if (!my.zero_ok && (bytes < 1)) {
541     this->conn->connection.reuse = 0;
542     socket_close(this->conn);
543     resp = response_destroy(resp);
544     echo ("%s:%d zero bytes back from server", __FILE__, __LINE__);
545     return FALSE;
546   }
547   stop     =  times(&t_stop);
548   etime    =  elapsed_time(stop - start);
549   okay     =  response_success(resp);
550   fail     =  response_failure(resp);
551   /**
552    * quantify the statistics for this client.
553    */
554   this->bytes += bytes;
555   this->time  += etime;
556   this->code  += okay;
557   this->fail  += fail;
558   if (code == 200) {
559     this->okay++;
560   }
561 
562   /**
563    * check to see if this transaction is the longest or shortest
564    */
565   if (etime > __himark) {
566     __himark = etime;
567   }
568   if ((__lomark < 0) || (etime < __lomark)) {
569     __lomark = etime;
570   }
571   this->himark = __himark;
572   this->lomark = __lomark;
573 
574   /**
575    * verbose output, print statistics to stdout
576    */
577   __display_result(this, resp, U, bytes, etime);
578 
579   /**
580    * close the socket and free memory.
581    */
582   if (!my.keepalive) {
583     socket_close(this->conn);
584   }
585 
586   switch (code) {
587     case 200:
588       if (meta != NULL && strlen(meta) > 2) {
589         /**
590          * <meta http-equiv="refresh" content="0; url=https://www.joedog.org/haha.html" />
591          */
592         redirect_url = url_normalize(U, meta);
593         xfree(meta);
594         meta = NULL;
595         page_clear(this->conn->page);
596         if (empty(url_get_hostname(redirect_url))) {
597           url_set_hostname(redirect_url, url_get_hostname(U));
598         }
599         url_set_redirect(U, FALSE);
600         url_set_redirect(redirect_url, FALSE);
601         if ((__request(this, redirect_url)) == FALSE) {
602           redirect_url = url_destroy(redirect_url);
603           return FALSE;
604         }
605         redirect_url = url_destroy(redirect_url);
606       }
607       break;
608     case 201:
609       if (my.follow && response_get_location(resp) != NULL) {
610         redirect_url = url_normalize(U, response_get_location(resp));
611         if (empty(url_get_hostname(redirect_url))) {
612           url_set_hostname(redirect_url, url_get_hostname(U));
613         }
614         if ((__request(this, redirect_url)) == FALSE) {
615           redirect_url = url_destroy(redirect_url);
616           return FALSE;
617         }
618       }
619       break;
620     case 301:
621     case 302:
622     case 303:
623     case 307:
624       if (my.follow && response_get_location(resp) != NULL) {
625         /**
626          * XXX: What if the server sends us
627          * Location: path/file.htm
628          *  OR
629          * Location: /path/file.htm
630          */
631         redirect_url = url_normalize(U, response_get_location(resp));
632 
633         if (empty(url_get_hostname(redirect_url))) {
634           url_set_hostname(redirect_url, url_get_hostname(U));
635         }
636         if (code == 307) {
637           url_set_conttype(redirect_url,url_get_conttype(U));
638           url_set_method(redirect_url, url_get_method(U));
639 
640           if (url_get_method(redirect_url) == POST  || url_get_method(redirect_url) == PUT ||
641               url_get_method(redirect_url) == PATCH || url_get_method(U) == DELETE         ||
642               url_get_method(U) == OPTIONS) {
643             url_set_postdata(redirect_url, url_get_postdata(U), url_get_postlen(U));
644           }
645         }
646         if ((__request(this, redirect_url)) == FALSE) {
647           redirect_url = url_destroy(redirect_url);
648           return FALSE;
649         }
650       }
651       redirect_url = url_destroy(redirect_url);
652       break;
653     case 401:
654       /**
655        * WWW-Authenticate challenge from the WWW server
656        */
657       this->auth.www = (this->auth.www==0) ? 1 : this->auth.www;
658       if ((this->auth.bids.www++) < my.bids - 1) {
659         BOOLEAN b;
660         if (response_get_www_auth_type(resp) == DIGEST) {
661           this->auth.type.www = DIGEST;
662           b = auth_set_digest_header(
663             my.auth, &(this->auth.wchlg), &(this->auth.wcred), &(this->rseed),
664             response_get_www_auth_realm(resp), response_get_www_auth_challenge(resp)
665           );
666           if (b == FALSE) {
667             fprintf(stderr, "ERROR: Unable to respond to an authorization challenge\n");
668             fprintf(stderr, "       in the following realm: '%s'\n", response_get_www_auth_realm(resp));
669             fprintf(stderr, "       Did you set login credentials in the conf file?\n");
670             resp = response_destroy(resp);
671             return FALSE;
672           }
673         }
674         if (response_get_www_auth_type(resp) == NTLM) {
675           this->auth.type.www =  NTLM;
676           b = auth_set_ntlm_header (
677             my.auth, HTTP, response_get_www_auth_challenge(resp), response_get_www_auth_realm(resp)
678           );
679         }
680         if (response_get_www_auth_type(resp) == BASIC) {
681           this->auth.type.www =  BASIC;
682           auth_set_basic_header(my.auth, HTTP, response_get_www_auth_realm(resp));
683         }
684         if ((__request(this, U)) == FALSE) {
685           fprintf(stderr, "ERROR from http_request\n");
686           return FALSE;
687         }
688       }
689       break;
690     case 407:
691       /**
692        * Proxy-Authenticate challenge from the proxy server.
693        */
694       this->auth.proxy = (this->auth.proxy==0) ? 1 : this->auth.proxy;
695       if ((this->auth.bids.proxy++) < my.bids - 1) {
696         if (response_get_proxy_auth_type(resp) == DIGEST) {
697           BOOLEAN b;
698           this->auth.type.proxy =  DIGEST;
699           b = auth_set_digest_header (
700             my.auth, &(this->auth.pchlg), &(this->auth.pcred), &(this->rseed),
701             response_get_proxy_auth_realm(resp), response_get_proxy_auth_challenge(resp)
702           );
703           if (b == FALSE) {
704             fprintf(stderr, "ERROR: Unable to respond to a proxy authorization challenge\n");
705             fprintf(stderr, "       in the following HTTP realm: '%s'\n", response_get_proxy_auth_realm(resp));
706             fprintf(stderr, "       Did you set proxy-login credentials in the conf file?\n");
707             resp = response_destroy(resp);
708             return FALSE;
709           }
710         }
711         if (response_get_proxy_auth_type(resp) == BASIC) {
712           this->auth.type.proxy = BASIC;
713           auth_set_basic_header(my.auth, PROXY, response_get_proxy_auth_realm(resp));
714         }
715         if ((__request(this, U)) == FALSE)
716           return FALSE;
717       }
718       break;
719     case 408:
720     case 500:
721     case 501:
722     case 502:
723     case 503:
724     case 504:
725     case 505:
726     case 506:
727     case 507:
728     case 508:
729     case 509:
730       return FALSE;
731     default:
732       break;
733   }
734 
735   this->hits++;
736   resp = response_destroy(resp);
737 
738   return TRUE;
739 }
740 
741 /**
742  * HTTP client request.
743  * The protocol is executed in http.c
744  * This function invoked functions inside that module
745  * and it gathers statistics about the request.
746  */
747 private BOOLEAN
__ftp(BROWSER this,URL U)748 __ftp(BROWSER this, URL U)
749 {
750   int     pass;
751   int     fail;
752   int     code = 0;      // capture the relevent return code
753   float   etime;         // elapsed time
754   CONN    *D    = NULL;  // FTP data connection
755   size_t  bytes = 0;     // bytes from server
756   clock_t start, stop;
757   struct  tms t_start, t_stop;
758 
759   D = xcalloc(sizeof(CONN), 1);
760   D->sock = -1;
761 
762   if (! __init_connection(this, U)) {
763     NOTIFY (
764       ERROR, "%s:%d connection failed %s:%d",
765       __FILE__, __LINE__, url_get_hostname(U), url_get_port(U)
766     );
767     xfree(D);
768     return FALSE;
769   }
770 
771   start = times(&t_start);
772   if (this->conn->sock < 0) {
773     NOTIFY (
774       ERROR, "%s:%d connection failed %s:%d",
775       __FILE__, __LINE__, url_get_hostname(U), url_get_port(U)
776     );
777     socket_close(this->conn);
778     xfree(D);
779     return FALSE;
780   }
781 
782   if (url_get_username(U) == NULL || strlen(url_get_username(U)) < 1) {
783     url_set_username(U, auth_get_ftp_username(my.auth, url_get_hostname(U)));
784   }
785 
786   if (url_get_password(U) == NULL || strlen(url_get_password(U)) < 1) {
787     url_set_password(U, auth_get_ftp_password(my.auth, url_get_hostname(U)));
788   }
789 
790   if (ftp_login(this->conn, U) == FALSE) {
791     if (my.verbose) {
792       int  color = __select_color(this->conn->ftp.code);
793       DISPLAY (
794         color, "FTP/%d %6.2f secs: %7lu bytes ==> %-6s %s",
795         this->conn->ftp.code, 0.0, bytes, url_get_method_name(U), url_get_request(U)
796       );
797     }
798     xfree(D);
799     this->fail += 1;
800     return FALSE;
801   }
802 
803   ftp_pasv(this->conn);
804   if (this->conn->ftp.pasv == TRUE) {
805     debug("Connecting to: %s:%d", this->conn->ftp.host, this->conn->ftp.port);
806     D->sock = new_socket(D, this->conn->ftp.host, this->conn->ftp.port);
807     if (D->sock < 0) {
808       debug (
809         "%s:%d connection failed. error %d(%s)",__FILE__, __LINE__, errno,strerror(errno)
810       );
811       this->fail += 1;
812       socket_close(D);
813       xfree(D);
814       return FALSE;
815     }
816   }
817   if (url_get_method(U) == POST   || url_get_method(U) == PUT || url_get_method(U) == PATCH ||
818       url_get_method(U) == DELETE || url_get_method(U) == OPTIONS) {
819     ftp_stor(this->conn, U);
820     bytes = ftp_put(D, U);
821     code  = this->conn->ftp.code;
822   } else {
823     if (ftp_size(this->conn, U) == TRUE) {
824       if (ftp_retr(this->conn, U) == TRUE) {
825         bytes = ftp_get(D, U, this->conn->ftp.size);
826       }
827     }
828     code = this->conn->ftp.code;
829   }
830   socket_close(D);
831   ftp_quit(this->conn);
832 
833   pass  = (bytes == this->conn->ftp.size) ? 1 : 0;
834   fail  = (pass  == 0) ? 1 : 0;
835   stop  =  times(&t_stop);
836   etime =  elapsed_time(stop - start);
837   this->bytes += bytes;
838   this->time  += etime;
839   this->code  += pass;
840   this->fail  += fail;
841 
842   /**
843    * check to see if this transaction is the longest or shortest
844    */
845   if (etime > __himark) {
846     __himark = etime;
847   }
848   if ((__lomark < 0) || (etime < __lomark)) {
849     __lomark = etime;
850   }
851   this->himark = __himark;
852   this->lomark = __lomark;
853 
854   if (my.verbose) {
855     int  color = (my.color == TRUE) ? __select_color(code) : -1;
856     DISPLAY (
857       color, "FTP/%d %6.2f secs: %7lu bytes ==> %-6s %s",
858       code, etime, bytes, url_get_method_name(U), url_get_request(U)
859     );
860   }
861   this->hits++;
862   xfree(D);
863   return TRUE;
864 }
865 
866 private BOOLEAN
__init_connection(BROWSER this,URL U)867 __init_connection(BROWSER this, URL U)
868 {
869   this->conn->pos_ini              = 0;
870   this->conn->inbuffer             = 0;
871   this->conn->content.transfer     = NONE;
872   this->conn->content.length       = (size_t)~0L;// VL - issue #2, 0 is a legit.value
873   this->conn->connection.keepalive = (this->conn->connection.max==1)?0:my.keepalive;
874   this->conn->connection.reuse     = (this->conn->connection.max==1)?0:my.keepalive;
875   this->conn->connection.tested    = (this->conn->connection.tested==0)?1:this->conn->connection.tested;
876   this->conn->auth.www             = this->auth.www;
877   this->conn->auth.wchlg           = this->auth.wchlg;
878   this->conn->auth.wcred           = this->auth.wcred;
879   this->conn->auth.proxy           = this->auth.proxy;
880   this->conn->auth.pchlg           = this->auth.pchlg;
881   this->conn->auth.pcred           = this->auth.pcred;
882   this->conn->auth.type.www        = this->auth.type.www;
883   this->conn->auth.type.proxy      = this->auth.type.proxy;
884   memset(this->conn->buffer, 0, sizeof(this->conn->buffer));
885 
886   debug (
887     "%s:%d attempting connection to %s:%d",
888     __FILE__, __LINE__,
889     (auth_get_proxy_required(my.auth))?auth_get_proxy_host(my.auth):url_get_hostname(U),
890     (auth_get_proxy_required(my.auth))?auth_get_proxy_port(my.auth):url_get_port(U)
891   );
892 
893   if (!this->conn->connection.reuse || this->conn->connection.status == 0) {
894     if (auth_get_proxy_required(my.auth)) {
895       debug (
896         "%s:%d creating new socket:     %s:%d",
897         __FILE__, __LINE__, auth_get_proxy_host(my.auth), auth_get_proxy_port(my.auth)
898       );
899       this->conn->sock = new_socket(this->conn, auth_get_proxy_host(my.auth), auth_get_proxy_port(my.auth));
900     } else {
901       debug (
902         "%s:%d creating new socket:     %s:%d",
903         __FILE__, __LINE__, url_get_hostname(U), url_get_port(U)
904       );
905       this->conn->sock = new_socket(this->conn, url_get_hostname(U), url_get_port(U));
906     }
907   }
908 
909   if (my.keepalive) {
910     this->conn->connection.reuse = TRUE;
911   }
912 
913   if (this->conn->sock < 0) {
914     debug (
915       "%s:%d connection failed. error %d(%s)",__FILE__, __LINE__, errno,strerror(errno)
916     );
917     socket_close(this->conn);
918     return FALSE;
919   }
920 
921   debug (
922     "%s:%d good socket connection:  %s:%d",
923     __FILE__, __LINE__,
924     (auth_get_proxy_required(my.auth))?auth_get_proxy_host(my.auth):url_get_hostname(U),
925     (auth_get_proxy_required(my.auth))?auth_get_proxy_port(my.auth):url_get_port(U)
926   );
927 
928   if (url_get_scheme(U) == HTTPS) {
929     if (auth_get_proxy_required(my.auth)) {
930       https_tunnel_request(this->conn, url_get_hostname(U), url_get_port(U));
931       https_tunnel_response(this->conn);
932     }
933     this->conn->encrypt = TRUE;
934     if (SSL_initialize(this->conn, url_get_hostname(U))==FALSE) {
935       return FALSE;
936     }
937   }
938   return TRUE;
939 }
940 
941 private void
__display_result(BROWSER this,RESPONSE resp,URL U,unsigned long bytes,float etime)942 __display_result(BROWSER this, RESPONSE resp, URL U, unsigned long bytes, float etime)
943 {
944   char   fmtime[65];
945   #ifdef  HAVE_LOCALTIME_R
946   struct tm keepsake;
947   #endif/*HAVE_LOCALTIME_R*/
948   time_t now;
949   struct tm * tmp;
950   size_t len;
951 
952 
953   if (my.csv) {
954     now = time(NULL);
955     #ifdef HAVE_LOCALTIME_R
956     tmp = (struct tm *)localtime_r(&now, &keepsake);
957     #else
958     tmp = localtime(&now);
959     #endif/*HAVE_LOCALTIME_R*/
960     if (tmp) {
961       len = strftime(fmtime, 64, "%Y-%m-%d %H:%M:%S", tmp);
962       if (len == 0) {
963         memset(fmtime, '\0', 64);
964         snprintf(fmtime, 64, "n/a");
965       }
966     } else {
967       snprintf(fmtime, 64, "n/a");
968     }
969   }
970 
971   /**
972    * verbose output, print statistics to stdout
973    */
974   if ((my.verbose && !my.get && !my.print) && (!my.debug)) {
975     int  color   = (my.color == TRUE) ? __select_color(response_get_code(resp)) : -1;
976     DATE date    = new_date(NULL);
977     char *stamp  = (my.timestamp)?date_stamp(date):"";
978     char *cached = response_get_from_cache(resp) ? "(C)":"   ";
979     if (my.color && response_get_from_cache(resp) == TRUE) {
980       color = GREEN;
981     }
982 
983     if (my.csv) {
984       if (my.display)
985         DISPLAY(color, "%s%s%s%4d,%s,%d,%6.2f,%7lu,%s,%d,%s",
986         stamp, (my.mark)?my.markstr:"", (my.mark)?",":"", this->id, response_get_protocol(resp),
987         response_get_code(resp), etime, bytes, url_get_display(U), url_get_ID(U), fmtime
988       );
989       else
990         DISPLAY(color, "%s%s%s%s,%d,%6.2f,%7lu,%s,%d,%s",
991           stamp, (my.mark)?my.markstr:"", (my.mark)?",":"", response_get_protocol(resp),
992           response_get_code(resp), etime, bytes, url_get_display(U), url_get_ID(U), fmtime
993         );
994     } else {
995       if (my.display)
996         DISPLAY(
997           color, "%4d) %s %d %6.2f secs: %7lu bytes ==> %-4s %s",
998           this->id, response_get_protocol(resp), response_get_code(resp),
999           etime, bytes, url_get_method_name(U), url_get_display(U)
1000         );
1001       else
1002         DISPLAY (
1003           color, "%s%s %d%s %5.2f secs: %7lu bytes ==> %-4s %s",
1004           stamp, response_get_protocol(resp), response_get_code(resp), cached,
1005           etime, bytes, url_get_method_name(U), url_get_display(U)
1006         );
1007     } /* else not my.csv */
1008     date = date_destroy(date);
1009   }
1010   return;
1011 }
1012 
1013 private void
__increment_failures()1014 __increment_failures()
1015 {
1016   pthread_mutex_lock(&(my.lock));
1017   my.failed++;
1018   pthread_mutex_unlock(&(my.lock));
1019   pthread_testcancel();
1020 }
1021 
1022 private BOOLEAN
__no_follow(const char * hostname)1023 __no_follow(const char *hostname)
1024 {
1025   int i;
1026 
1027   for (i = 0; i < my.nomap->index; i++) {
1028     if (stristr(hostname, my.nomap->line[i]) != NULL) {
1029       return TRUE;
1030     }
1031   }
1032   return FALSE;
1033 }
1034 
1035 
1036 private int
__select_color(int code)1037 __select_color(int code)
1038 {
1039   switch(code) {
1040     case 150:
1041     case 200:
1042     case 201:
1043     case 202:
1044     case 203:
1045     case 204:
1046     case 205:
1047     case 206:
1048     case 226:
1049       return BLUE;
1050     case 300:
1051     case 301:
1052     case 302:
1053     case 303:
1054     case 304:
1055     case 305:
1056     case 306:
1057     case 307:
1058       return CYAN;
1059     case 400:
1060     case 401:
1061     case 402:
1062     case 403:
1063     case 404:
1064     case 405:
1065     case 406:
1066     case 407:
1067     case 408:
1068     case 409:
1069     case 410:
1070     case 411:
1071     case 412:
1072     case 413:
1073     case 414:
1074     case 415:
1075     case 416:
1076     case 417:
1077       return MAGENTA;
1078     case 500:
1079     case 501:
1080     case 502:
1081     case 503:
1082     case 504:
1083     case 505:
1084     default: // WTF?
1085       return RED;
1086   }
1087   return RED;
1088 }
1089 
1090 #ifdef SIGNAL_CLIENT_PLATFORM
1091 private void
__signal_handler(int sig)1092 __signal_handler(int sig)
1093 {
1094   pthread_exit(&sig);
1095 }
1096 
1097 private void
__signal_init()1098 __signal_init()
1099 {
1100   struct sigaction sa;
1101 
1102   sa.sa_handler = signal_handler;
1103   sigemptyset(&sa.sa_mask);
1104   sa.sa_flags = 0;
1105   sigaction(SIGUSR1, &sa, NULL);
1106 }
1107 #else/*CANCEL_CLIENT_PLATFORM*/
1108 private void
__signal_cleanup()1109 __signal_cleanup()
1110 {
1111   return;
1112 }
1113 #endif
1114