4 * DEBUG: section -- WWW Client
5 * AUTHOR: Harvest Derived
7 * SQUID Web Proxy Cache http://www.squid-cache.org/
8 * ----------------------------------------------------------
10 * Squid is the result of efforts by numerous individuals from
11 * the Internet community; see the CONTRIBUTORS file for full
12 * details. Many organizations have provided support for Squid's
13 * development; see the SPONSORS file for full details. Squid is
14 * Copyrighted (C) 2001 by the Regents of the University of
15 * California; see the COPYRIGHT file for full details. Squid
16 * incorporates software developed and/or copyrighted by other
17 * sources; see the CREDITS file for full details.
19 * This program is free software; you can redistribute it and/or modify
20 * it under the terms of the GNU General Public License as published by
21 * the Free Software Foundation; either version 2 of the License, or
22 * (at your option) any later version.
24 * This program is distributed in the hope that it will be useful,
25 * but WITHOUT ANY WARRANTY; without even the implied warranty of
26 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
27 * GNU General Public License for more details.
29 * You should have received a copy of the GNU General Public License
30 * along with this program; if not, write to the Free Software
31 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA.
37 #include "ip/Address.h"
39 #include "SquidTime.h"
42 /** \cond AUTODOCS-IGNORE */
43 using namespace Squid
;
54 #include <sys/socket.h>
78 #include <netinet/in.h>
85 #if HAVE_GSSAPI_GSSAPI_H
86 #include <gssapi/gssapi.h>
89 #endif /* HAVE_GSSAPI_GSSAPI_H/HAVE_GSSAPI_H */
90 #if !HAVE_HEIMDAL_KERBEROS
91 #if HAVE_GSSAPI_GSSAPI_KRB5_H
92 #include <gssapi/gssapi_krb5.h>
94 #if HAVE_GSSAPI_GSSAPI_GENERIC_H
95 #include <gssapi/gssapi_generic.h>
97 #if HAVE_GSSAPI_GSSAPI_EXT_H
98 #include <gssapi/gssapi_ext.h>
102 #ifndef gss_nt_service_name
103 #define gss_nt_service_name GSS_C_NT_HOSTBASED_SERVICE
106 #ifndef gss_mech_spnego
107 static gss_OID_desc _gss_mech_spnego
= {6, (void *) "\x2b\x06\x01\x05\x05\x02"};
108 gss_OID gss_mech_spnego
= &_gss_mech_spnego
;
110 #endif /* HAVE_GSSAPI */
116 #define MESSAGELEN 65536
119 #define HEADERLEN 65536
122 typedef void SIGHDLR(int sig
);
124 /* Local functions */
125 static int client_comm_bind(int, const Ip::Address
&);
127 static int client_comm_connect(int, const Ip::Address
&, struct timeval
*);
128 static void usage(const char *progname
);
130 static int Now(struct timeval
*);
132 SIGHDLR pipe_handler
;
133 static void set_our_signal(void);
134 static ssize_t
myread(int fd
, void *buf
, size_t len
);
135 static ssize_t
mywrite(int fd
, void *buf
, size_t len
);
138 static int check_gss_err(OM_uint32 major_status
, OM_uint32 minor_status
, const char *function
);
139 static char *GSSAPI_token(const char *server
);
143 static char *put_file
= NULL
;
145 static struct stat sb
;
147 int io_timeout
= 120;
151 Win32SockCleanup(void)
159 usage(const char *progname
)
163 "Usage: %s [-arsv] [-A 'string'] [-g count] [-h remote host] [-H 'string'] [-i IMS] [-I ping-interval] [-j 'Host-header']"
164 "[-k] [-l local-host] [-m method] "
168 "[-p port] [-P file] [-t count] [-T timeout] [-u proxy-user] [-U www-user] "
169 "[-V version] [-w proxy-password] [-W www-password] url\n"
172 " -a Do NOT include Accept: header.\n"
173 " -A User-Agent: header. Use \"\" to omit.\n"
174 " -g count Ping mode, perform \"count\" iterations (0 to loop until interrupted).\n"
175 " -h host Retrieve URL from cache on hostname. Default is localhost.\n"
176 " -H 'string' Extra headers to send. Use '\\n' for new lines.\n"
177 " -i IMS If-Modified-Since time (in Epoch seconds).\n"
178 " -I interval Ping interval in seconds (default 1 second).\n"
179 " -j hosthdr Host header content\n"
180 " -k Keep the connection active. Default is to do only one request then close.\n"
181 " -l host Specify a local IP address to bind to. Default is none.\n"
182 " -m method Request method, default is GET.\n"
184 " -n Proxy Negotiate(Kerberos) authentication\n"
185 " -N WWW Negotiate(Kerberos) authentication\n"
187 " -p port Port number of cache. Default is %d.\n"
188 " -P file PUT request. Using the named file\n"
189 " -r Force cache to reload URL.\n"
190 " -s Silent. Do not print data to stdout.\n"
191 " -t count Trace count cache-hops\n"
192 " -T timeout Timeout value (seconds) for read/write operations.\n"
193 " -u user Proxy authentication username\n"
194 " -U user WWW authentication username\n"
195 " -v Verbose. Print outgoing message to stderr.\n"
196 " -V version HTTP Version. Use '-' for HTTP/0.9 omitted case\n"
197 " -w password Proxy authentication password\n"
198 " -W password WWW authentication password\n",
199 VERSION
, progname
, CACHE_HTTP_PORT
);
203 static int interrupted
= 0;
205 main(int argc
, char *argv
[])
207 int conn
, c
, len
, bytesWritten
;
208 int port
, to_stdout
, reload
;
211 int opt_noaccept
= 0;
212 bool opt_verbose
= false;
214 int www_neg
= 0, proxy_neg
= 0;
216 const char *hostname
, *localhost
;
218 char url
[BUFSIZ
], msg
[MESSAGELEN
], buf
[BUFSIZ
];
219 char extra_hdrs
[HEADERLEN
];
220 const char *method
= "GET";
223 int max_forwards
= -1;
225 struct timeval tv1
, tv2
;
228 long ping_min
= 0, ping_max
= 0, ping_sum
= 0, ping_mean
= 0;
229 const char *proxy_user
= NULL
;
230 const char *proxy_password
= NULL
;
231 const char *www_user
= NULL
;
232 const char *www_password
= NULL
;
233 const char *host
= NULL
;
234 const char *version
= "1.0";
235 const char *useragent
= NULL
;
237 /* set the defaults */
238 hostname
= "localhost";
240 extra_hdrs
[0] = '\0';
241 port
= CACHE_HTTP_PORT
;
249 usage(argv
[0]); /* need URL */
250 } else if (argc
>= 2) {
251 strncpy(url
, argv
[argc
- 1], BUFSIZ
);
252 url
[BUFSIZ
- 1] = '\0';
257 while ((c
= getopt(argc
, argv
, "aA:h:j:V:l:P:i:km:p:rsvt:g:p:I:H:T:u:U:w:W:nN?")) != -1)
259 while ((c
= getopt(argc
, argv
, "aA:h:j:V:l:P:i:km:p:rsvt:g:p:I:H:T:u:U:w:W:?")) != -1)
272 case 'h': /* remote host */
286 case 'l': /* local host */
291 case 's': /* silent */
295 case 'k': /* backward compat */
299 case 'r': /* reload */
303 case 'p': /* port number */
304 sscanf(optarg
, "%d", &port
);
306 port
= CACHE_HTTP_PORT
; /* default */
310 put_file
= xstrdup(optarg
);
314 ims
= (time_t) atoi(optarg
);
318 method
= xstrdup(optarg
);
322 method
= xstrdup("TRACE");
323 max_forwards
= atoi(optarg
);
328 pcount
= atoi(optarg
);
333 if ((ping_int
= atoi(optarg
) * 1000) <= 0)
338 if (strlen(optarg
)) {
340 strncpy(extra_hdrs
, optarg
, sizeof(extra_hdrs
));
341 while ((t
= strstr(extra_hdrs
, "\\n")))
342 *t
= '\r', *(t
+ 1) = '\n';
347 io_timeout
= atoi(optarg
);
355 proxy_password
= optarg
;
363 www_password
= optarg
;
376 /* undocumented: may increase verb-level by giving more -v's */
380 case '?': /* usage */
390 WSAStartup(2, &wsaData
);
391 atexit(Win32SockCleanup
);
394 /* Build the HTTP request */
395 if (strncmp(url
, "mgr:", 4) == 0) {
396 char *t
= xstrdup(url
+ 4);
397 const char *at
= NULL
;
398 if (!strrchr(t
, '@')) { // ignore any -w password if @ is explicit already.
401 // embed the -w proxy password into old-style cachemgr URLs
403 snprintf(url
, BUFSIZ
, "cache_object://%s/%s@%s", hostname
, t
, at
);
405 snprintf(url
, BUFSIZ
, "cache_object://%s/%s", hostname
, t
);
409 put_fd
= open(put_file
, O_RDONLY
);
413 fprintf(stderr
, "%s: can't open file (%s)\n", argv
[0],
418 setmode(put_fd
, O_BINARY
);
425 char *newhost
= strstr(url
, "://");
429 newhost
= xstrdup(newhost
);
430 t
= newhost
+ strcspn(newhost
, "@/?");
433 t
= newhost
+ strcspn(newhost
, "@/?");
440 if (version
[0] == '-' || !version
[0]) {
441 /* HTTP/0.9, no headers, no version */
442 snprintf(msg
, BUFSIZ
, "%s %s\r\n", method
, url
);
444 if (!xisdigit(version
[0])) // not HTTP/n.n
445 snprintf(msg
, BUFSIZ
, "%s %s %s\r\n", method
, url
, version
);
447 snprintf(msg
, BUFSIZ
, "%s %s HTTP/%s\r\n", method
, url
, version
);
450 snprintf(buf
, BUFSIZ
, "Host: %s\r\n", host
);
454 if (useragent
== NULL
) {
455 snprintf(buf
, BUFSIZ
, "User-Agent: squidclient/%s\r\n", VERSION
);
457 } else if (useragent
[0] != '\0') {
458 snprintf(buf
, BUFSIZ
, "User-Agent: %s\r\n", useragent
);
463 snprintf(buf
, BUFSIZ
, "Cache-Control: no-cache\r\n");
467 snprintf(buf
, BUFSIZ
, "Content-length: %" PRId64
"\r\n", (int64_t) sb
.st_size
);
470 if (opt_noaccept
== 0) {
471 snprintf(buf
, BUFSIZ
, "Accept: */*\r\n");
475 snprintf(buf
, BUFSIZ
, "If-Modified-Since: %s\r\n", mkrfc1123(ims
));
478 if (max_forwards
> -1) {
479 snprintf(buf
, BUFSIZ
, "Max-Forwards: %d\r\n", max_forwards
);
483 const char *user
= proxy_user
;
484 const char *password
= proxy_password
;
487 password
= getpass("Proxy password: ");
490 fprintf(stderr
, "ERROR: Proxy password missing\n");
493 snprintf(buf
, BUFSIZ
, "%s:%s", user
, password
);
494 snprintf(buf
, BUFSIZ
, "Proxy-Authorization: Basic %s\r\n", old_base64_encode(buf
));
498 const char *user
= www_user
;
499 const char *password
= www_password
;
502 password
= getpass("WWW password: ");
505 fprintf(stderr
, "ERROR: WWW password missing\n");
508 snprintf(buf
, BUFSIZ
, "%s:%s", user
, password
);
509 snprintf(buf
, BUFSIZ
, "Authorization: Basic %s\r\n", old_base64_encode(buf
));
515 snprintf(buf
, BUFSIZ
, "Authorization: Negotiate %s\r\n", GSSAPI_token(host
));
518 fprintf(stderr
, "ERROR: server host missing\n");
522 snprintf(buf
, BUFSIZ
, "Proxy-Authorization: Negotiate %s\r\n", GSSAPI_token(hostname
));
525 fprintf(stderr
, "ERROR: proxy server host missing\n");
529 /* HTTP/1.0 may need keep-alive explicitly */
530 if (strcmp(version
, "1.0") == 0 && keep_alive
)
531 strcat(msg
, "Connection: keep-alive\r\n");
533 /* HTTP/1.1 may need close explicitly */
535 strcat(msg
, "Connection: close\r\n");
537 strcat(msg
, extra_hdrs
);
542 fprintf(stderr
, "Request:'%s'\n", msg
);
547 struct sigaction sa
, osa
;
549 if (sigaction(SIGINT
, NULL
, &osa
) == 0 && osa
.sa_handler
== SIG_DFL
) {
550 sa
.sa_handler
= catchSignal
;
552 sigemptyset(&sa
.sa_mask
);
553 (void) sigaction(SIGINT
, &sa
, NULL
);
558 if ((osig
= signal(SIGINT
, catchSignal
)) != SIG_DFL
)
559 (void) signal(SIGINT
, osig
);
564 loops
= ping
? pcount
: 1;
566 for (i
= 0; loops
== 0 || i
< loops
; ++i
) {
568 struct addrinfo
*AI
= NULL
;
571 fprintf(stderr
, "Resolving... %s\n", hostname
);
573 /* Connect to the server */
576 if ( !iaddr
.GetHostByName(localhost
) ) {
577 fprintf(stderr
, "client: ERROR: Cannot resolve %s: Host unknown.\n", localhost
);
581 /* Process the remote host name to locate the Protocol required
582 in case we are being asked to link to another version of squid */
583 if ( !iaddr
.GetHostByName(hostname
) ) {
584 fprintf(stderr
, "client: ERROR: Cannot resolve %s: Host unknown.\n", hostname
);
589 iaddr
.GetAddrInfo(AI
);
590 if ((conn
= socket(AI
->ai_family
, AI
->ai_socktype
, 0)) < 0) {
591 perror("client: socket");
592 iaddr
.FreeAddrInfo(AI
);
595 iaddr
.FreeAddrInfo(AI
);
597 if (localhost
&& client_comm_bind(conn
, iaddr
) < 0) {
598 perror("client: bind");
603 if ( !iaddr
.GetHostByName(hostname
) ) {
604 fprintf(stderr
, "client: ERROR: Cannot resolve %s: Host unknown.\n", hostname
);
611 char ipbuf
[MAX_IPSTRLEN
];
612 fprintf(stderr
, "Connecting... %s(%s)\n", hostname
, iaddr
.NtoA(ipbuf
, MAX_IPSTRLEN
));
615 if (client_comm_connect(conn
, iaddr
, ping
? &tv1
: NULL
) < 0) {
616 char hostnameBuf
[MAX_IPSTRLEN
];
617 iaddr
.ToURL(hostnameBuf
, MAX_IPSTRLEN
);
619 fprintf(stderr
, "client: ERROR: Cannot connect to %s: Host unknown.\n", hostnameBuf
);
622 snprintf(tbuf
, BUFSIZ
, "client: ERROR: Cannot connect to %s", hostnameBuf
);
628 char ipbuf
[MAX_IPSTRLEN
];
629 fprintf(stderr
, "Connected to: %s (%s)\n", hostname
, iaddr
.NtoA(ipbuf
, MAX_IPSTRLEN
));
632 /* Send the HTTP request */
633 bytesWritten
= mywrite(conn
, msg
, strlen(msg
));
635 if (bytesWritten
< 0) {
636 perror("client: ERROR: write");
638 } else if ((unsigned) bytesWritten
!= strlen(msg
)) {
639 fprintf(stderr
, "client: ERROR: Cannot send request?: %s\n", msg
);
645 lseek(put_fd
, 0, SEEK_SET
);
646 while ((x
= read(put_fd
, buf
, sizeof(buf
))) > 0) {
648 x
= mywrite(conn
, buf
, x
);
657 fprintf(stderr
, "client: ERROR: Cannot send file.\n");
662 setmode(1, O_BINARY
);
665 while ((len
= myread(conn
, buf
, sizeof(buf
))) > 0) {
668 if (to_stdout
&& fwrite(buf
, len
, 1, stdout
) != 1)
669 perror("client: ERROR writing to stdout");
676 (void) close(conn
); /* done with socket */
688 elapsed_msec
= tvSubMsec(tv1
, tv2
);
690 tmp
= localtime(&t2s
);
691 fprintf(stderr
, "%d-%02d-%02d %02d:%02d:%02d [%d]: %ld.%03ld secs, %f KB/s\n",
692 tmp
->tm_year
+ 1900, tmp
->tm_mon
+ 1, tmp
->tm_mday
,
693 tmp
->tm_hour
, tmp
->tm_min
, tmp
->tm_sec
, i
+ 1,
694 elapsed_msec
/ 1000, elapsed_msec
% 1000,
695 elapsed_msec
? (double) fsize
/ elapsed_msec
: -1.0);
697 if (i
== 0 || elapsed_msec
< ping_min
)
698 ping_min
= elapsed_msec
;
700 if (i
== 0 || elapsed_msec
> ping_max
)
701 ping_max
= elapsed_msec
;
703 ping_sum
+= elapsed_msec
;
705 /* Delay until next "ping_int" boundary */
706 if ((loops
== 0 || i
+ 1 < loops
) && elapsed_msec
< ping_int
) {
709 long msec_left
= ping_int
- elapsed_msec
;
711 tvs
.tv_sec
= msec_left
/ 1000;
712 tvs
.tv_usec
= (msec_left
% 1000) * 1000;
713 select(0, NULL
, NULL
, NULL
, &tvs
);
719 ping_mean
= ping_sum
/ i
;
720 fprintf(stderr
, "%d requests, round-trip (secs) min/avg/max = "
721 "%ld.%03ld/%ld.%03ld/%ld.%03ld\n", i
,
722 ping_min
/ 1000, ping_min
% 1000, ping_mean
/ 1000, ping_mean
% 1000,
723 ping_max
/ 1000, ping_max
% 1000);
731 client_comm_bind(int sock
, const Ip::Address
&addr
)
736 static struct addrinfo
*AI
= NULL
;
738 /* Set up the source socket address from which to send. */
740 addr
.GetAddrInfo(AI
);
742 res
= bind(sock
, AI
->ai_addr
, AI
->ai_addrlen
);
744 addr
.FreeAddrInfo(AI
);
750 client_comm_connect(int sock
, const Ip::Address
&addr
, struct timeval
*tvp
)
753 static struct addrinfo
*AI
= NULL
;
755 /* Set up the destination socket address for message to send to. */
757 addr
.GetAddrInfo(AI
);
759 res
= connect(sock
, AI
->ai_addr
, AI
->ai_addrlen
);
761 addr
.FreeAddrInfo(AI
);
770 Now(struct timeval
*tp
)
772 #if GETTIMEOFDAY_NO_TZP
773 return gettimeofday(tp
);
776 return gettimeofday(tp
, NULL
);
784 fprintf(stderr
, "Interrupted.\n");
788 pipe_handler(int sig
)
790 fprintf(stderr
, "SIGPIPE received.\n");
799 sa
.sa_handler
= pipe_handler
;
800 sa
.sa_flags
= SA_RESTART
;
801 sigemptyset(&sa
.sa_mask
);
803 if (sigaction(SIGPIPE
, &sa
, NULL
) < 0) {
804 fprintf(stderr
, "Cannot set PIPE signal.\n");
808 signal(SIGPIPE
, pipe_handler
);
815 myread(int fd
, void *buf
, size_t len
)
818 return recv(fd
, buf
, len
, 0);
821 return read(fd
, buf
, len
);
826 mywrite(int fd
, void *buf
, size_t len
)
829 return send(fd
, buf
, len
, 0);
832 return write(fd
, buf
, len
);
838 * Check return valuse major_status, minor_status for error and print error description
839 * in case of an error.
840 * Returns 1 in case of gssapi error
841 * 0 in case of no gssapi error
843 #define BUFFER_SIZE 8192
845 check_gss_err(OM_uint32 major_status
, OM_uint32 minor_status
, const char *function
)
847 if (GSS_ERROR(major_status
)) {
848 OM_uint32 maj_stat
, min_stat
;
849 OM_uint32 msg_ctx
= 0;
850 gss_buffer_desc status_string
;
851 char buf
[BUFFER_SIZE
];
857 /* convert major status code (GSS-API error) to text */
858 maj_stat
= gss_display_status(&min_stat
, major_status
,
861 &msg_ctx
, &status_string
);
862 if (maj_stat
== GSS_S_COMPLETE
) {
863 snprintf(buf
+ len
, BUFFER_SIZE
-len
, "%s", (char *) status_string
.value
);
864 len
+= status_string
.length
;
865 gss_release_buffer(&min_stat
, &status_string
);
868 gss_release_buffer(&min_stat
, &status_string
);
870 snprintf(buf
+ len
, BUFFER_SIZE
-len
, "%s", ". ");
874 /* convert minor status code (underlying routine error) to text */
875 maj_stat
= gss_display_status(&min_stat
, minor_status
,
878 &msg_ctx
, &status_string
);
879 if (maj_stat
== GSS_S_COMPLETE
) {
880 snprintf(buf
+ len
, BUFFER_SIZE
-len
,"%s", (char *) status_string
.value
);
881 len
+= status_string
.length
;
882 gss_release_buffer(&min_stat
, &status_string
);
885 gss_release_buffer(&min_stat
, &status_string
);
887 fprintf(stderr
, "%s failed: %s\n", function
, buf
);
894 * Get gssapi token for service HTTP/<server>
895 * User has to initiate a kinit user@DOMAIN on commandline first for the
896 * function to be successful
897 * Returns base64 encoded token if successful
898 * string "ERROR" if unsuccessful
901 GSSAPI_token(const char *server
)
903 OM_uint32 major_status
, minor_status
;
904 gss_ctx_id_t gss_context
= GSS_C_NO_CONTEXT
;
905 gss_name_t server_name
= GSS_C_NO_NAME
;
906 gss_buffer_desc service
= GSS_C_EMPTY_BUFFER
;
907 gss_buffer_desc input_token
= GSS_C_EMPTY_BUFFER
;
908 gss_buffer_desc output_token
= GSS_C_EMPTY_BUFFER
;
911 setbuf(stdout
, NULL
);
915 fprintf(stderr
, "Error: No server name\n");
916 return (char *)"ERROR";
918 service
.value
= xmalloc(strlen("HTTP") + strlen(server
) + 2);
919 snprintf((char *) service
.value
, strlen("HTTP") + strlen(server
) + 2, "%s@%s", "HTTP", server
);
920 service
.length
= strlen((char *) service
.value
);
922 major_status
= gss_import_name(&minor_status
, &service
,
923 gss_nt_service_name
, &server_name
);
925 if (!check_gss_err(major_status
, minor_status
, "gss_import_name()")) {
927 major_status
= gss_init_sec_context(&minor_status
,
934 GSS_C_NO_CHANNEL_BINDINGS
,
941 if (!check_gss_err(major_status
, minor_status
, "gss_init_sec_context()")) {
943 if (output_token
.length
)
944 token
= (char *) base64_encode_bin((const char *) output_token
.value
, output_token
.length
);
948 if (!output_token
.length
)
949 token
= (char *) "ERROR";
950 gss_delete_sec_context(&minor_status
, &gss_context
, NULL
);
951 gss_release_buffer(&minor_status
, &service
);
952 gss_release_buffer(&minor_status
, &input_token
);
953 gss_release_buffer(&minor_status
, &output_token
);
954 gss_release_name(&minor_status
, &server_name
);