1 /*
2  * Copyright (c) 1997, 1998, 1999, 2000, 2001, 2005, 2008, 2009, 2010, 2011,
3  *               2012, 2013, 2014, 2021
4  *      Inferno Nettverk A/S, Norway.  All rights reserved.
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions
8  * are met:
9  * 1. The above copyright notice, this list of conditions and the following
10  *    disclaimer must appear in all copies of the software, derivative works
11  *    or modified versions, and any portions thereof, aswell as in all
12  *    supporting documentation.
13  * 2. All advertising materials mentioning features or use of this software
14  *    must display the following acknowledgement:
15  *      This product includes software developed by
16  *      Inferno Nettverk A/S, Norway.
17  * 3. The name of the author may not be used to endorse or promote products
18  *    derived from this software without specific prior written permission.
19  *
20  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
21  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
22  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
23  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
24  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
25  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
29  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30  *
31  * Inferno Nettverk A/S requests users of this software to return to
32  *
33  *  Software Distribution Coordinator  or  sdc@inet.no
34  *  Inferno Nettverk A/S
35  *  Oslo Research Park
36  *  Gaustadall�en 21
37  *  NO-0349 Oslo
38  *  Norway
39  *
40  * any improvements or extensions that they make and grant Inferno Nettverk A/S
41  * the rights to redistribute these changes.
42  *
43  */
44 
45 #include "common.h"
46 
47 static const char rcsid[] =
48 "$Id: httpproxy.c,v 1.73.4.2.6.2 2021/02/02 19:34:11 karls Exp $";
49 
50 int
httpproxy_negotiate(s,packet,emsg,emsglen)51 httpproxy_negotiate(s, packet, emsg, emsglen)
52    int s;
53    socks_t *packet;
54    char *emsg;
55    size_t emsglen;
56 {
57    const char *function = "httpproxy_negotiate()";
58    char buf[MAXHOSTNAMELEN + 512] /* The + 512 is for http babble. */,
59         visbuf[sizeof(buf) * 4 + 1], *p;
60    char host[MAXSOCKSHOSTSTRING];
61    int checked, eof;
62    ssize_t rc;
63    size_t len, readsofar;
64    struct sockaddr_storage addr;
65    socklen_t addrlen;
66 
67    slog(LOG_DEBUG, "%s", function);
68 
69    sockshost2string(&packet->req.host, host, sizeof(host));
70 
71    /*
72     * replace the dot that sockshost2string uses to separate port from host
73     * with http's ':'.
74     */
75    if ((p = strrchr(host, '.')) == NULL) {
76       snprintf(emsg, emsglen,
77                "did not find portnumber separator ('.') in string \"%s\"",
78                host);
79 
80       swarnx("%s: %s", function, emsg);
81       return -1;
82    }
83 
84    *p = ':';
85 
86    len = snprintf(buf, sizeof(buf),
87                   "CONNECT %s %s\r\n"
88                   "User-agent: %s/client v%s\r\n"
89                   "\r\n",
90                   host,
91                   proxyprotocol2string(packet->req.version),
92                   PRODUCT,
93                   VERSION);
94 
95    slog(LOG_NEGOTIATE, "%s: sending to server: %s",
96         function, str2vis(buf, len, visbuf, sizeof(visbuf)));
97 
98    if ((rc = socks_sendton(s, buf, len, len, 0, NULL, 0, NULL, NULL))
99    != (ssize_t)len) {
100       snprintf(emsg, emsglen,
101                "could not send request to proxy server.  Sent %ld/%lu: %s",
102                (long)rc, (unsigned long)len, strerror(errno));
103 
104       return -1;
105    }
106 
107    /*
108     * read til we get the eof response so there's no junk left in buffer
109     * for client, then return the response code.
110     */
111    eof = checked = readsofar = 0;
112    do {
113       const char *eofresponse_str = "\r\n\r\n"; /*
114                                                  * the CRLF terminating the
115                                                  * line, and the CRLF
116                                                  * terminating the entity body.
117                                                  */
118       const char *eol_str = "\r\n";
119       char *eol, *bufp;
120       size_t linelen;
121 
122       if ((rc = read(s, &buf[readsofar], sizeof(buf) - readsofar - 1)) <= 0) {
123          snprintf(emsg, emsglen,
124                   "could not read response from proxy server.  "
125                   "read(2) returned %ld after having read %lu bytes",
126                   (long)rc, (unsigned long)readsofar);
127          return -1;
128       }
129 
130       len = (size_t)rc;
131 
132       buf[readsofar + len] = NUL;
133 
134       slog(LOG_NEGOTIATE, "%s: read from server: %s",
135            function, str2vis(&buf[readsofar], len, visbuf, sizeof(visbuf)));
136 
137       readsofar += len;
138 
139       if ((strstr(buf, eofresponse_str)) == NULL)
140          continue; /* don't bother to start parsing til we've got it all. */
141       else
142          eof = 1;
143 
144       bufp = buf;
145       while ((eol = strstr(bufp, eol_str)) != NULL) {
146          /* check each line for the response we are looking for. */
147          *eol   = NUL;
148          linelen = eol - bufp;
149 
150          slog(LOG_DEBUG, "%s: checking line \"%s\"",
151               function, str2vis(bufp, linelen, visbuf, sizeof(visbuf)));
152 
153          if (!checked) {
154             int error = 0;
155 
156             switch (packet->req.version) {
157                case PROXY_HTTP_10:
158                case PROXY_HTTP_11: {
159                   const char *ver_str
160                   = proxyprotocol2string(packet->req.version);
161                   size_t offset = strlen(ver_str);
162 
163                   if (linelen < offset + strlen(" 200")) {
164                      snprintf(emsg, emsglen,
165                               "response from proxy server is too short to"
166                               "indicate success: \"%s\"",
167                               visbuf);
168 
169                      error = 1;
170                      break;
171                   }
172 
173                   if (strncmp(bufp, ver_str, offset) != 0) {
174                      snprintf(emsg, emsglen,
175                               "HTTP version (\"%s\") in response from proxy "
176                               "server does not match expected (\"%s\").  "
177                               "Continuing anyway and hoping for the best ...",
178                               visbuf, ver_str);
179                   }
180 
181                   while (isspace((unsigned char)bufp[offset]))
182                         ++offset;
183 
184                   if (!isdigit((unsigned char)bufp[offset])) {
185                      char tmp[sizeof(visbuf)];
186 
187                      snprintf(emsg, emsglen,
188                               "response from proxy server does not match.  "
189                               "Expected a number at offset %lu, but got \"%s\"",
190                               (unsigned long)offset,
191                               str2vis(&bufp[offset],
192                                       linelen - offset,
193                                       tmp,
194                                       sizeof(tmp)));
195                      error = 1;
196                      break;
197                   }
198 
199                   packet->res.version = packet->req.version;
200 
201                   /* not really a portnumber, but close enough. */
202                   if ((rc = string2portnumber(&bufp[offset], emsg, emsglen))
203                   == -1) {
204                      swarn("%s: could not find response code in http "
205                            "response (\"%s\"): %s",
206                            function, visbuf, emsg);
207 
208                      rc = HTTP_SUCCESS; /* hope for the best. */
209                   }
210                   else {
211                      snprintf(emsg, emsglen,
212                               "response code %ld from http server indicates "
213                               "%s: \"%s\"",
214                               (long)rc,
215                               rc == HTTP_SUCCESS ? "success" : "failure",
216                               visbuf);
217 
218                      slog(LOG_DEBUG, "%s: %s", function, emsg);
219                   }
220 
221                   socks_set_responsevalue(&packet->res, (unsigned int)rc);
222 
223                   /*
224                    * we have no idea what address the server will use on
225                    * our behalf, so set it to what we use.  Better than
226                    * nothing, perhaps. :-/
227                    */
228                   addrlen = sizeof(addr);
229                   if (getsockname(s, TOSA(&addr), &addrlen) != 0)
230                      SWARN(s);
231 
232                   sockaddr2sockshost(&addr, &packet->res.host);
233 
234                   checked = 1;
235                   break;
236                }
237 
238                default:
239                   SERRX(packet->req.version);
240             }
241 
242             if (error) {
243                snprintf(emsg, emsglen,
244                         "unknown response from proxy server: \"%s\"",
245                         str2vis(bufp, linelen, visbuf, sizeof(visbuf)));
246                return -1;
247             }
248          }
249 
250          /* shift out the line we just parsed, nothing of interest there. */
251          bufp += linelen;
252       }
253    } while (!eof);
254 
255    if (checked)
256       return socks_get_responsevalue(&packet->res) == HTTP_SUCCESS ? 0 : -1;
257 
258    slog(LOG_INFO, "%s: didn't get status code from proxy", function);
259    return -1;
260 }
261