@@ -43,105 +43,329 @@ static bool read_line(bufchain *input, strbuf *output, bool is_header)
4343
4444typedef struct HttpProxyNegotiator {
4545 int crLine ;
46- strbuf * line ;
46+ strbuf * response , * header , * token ;
47+ int http_status_pos ;
48+ size_t header_pos ;
49+ strbuf * username , * password ;
50+ int http_status ;
51+ bool connection_close ;
52+ prompts_t * prompts ;
53+ int username_prompt_index , password_prompt_index ;
54+ size_t content_length ;
4755 ProxyNegotiator pn ;
4856} HttpProxyNegotiator ;
4957
5058static ProxyNegotiator * proxy_http_new (const ProxyNegotiatorVT * vt )
5159{
5260 HttpProxyNegotiator * s = snew (HttpProxyNegotiator );
61+ memset (s , 0 , sizeof (* s ));
5362 s -> pn .vt = vt ;
54- s -> crLine = 0 ;
55- s -> line = strbuf_new ();
63+ s -> response = strbuf_new ();
64+ s -> header = strbuf_new ();
65+ s -> token = strbuf_new ();
66+ s -> username = strbuf_new ();
67+ s -> password = strbuf_new_nm ();
5668 return & s -> pn ;
5769}
5870
5971static void proxy_http_free (ProxyNegotiator * pn )
6072{
6173 HttpProxyNegotiator * s = container_of (pn , HttpProxyNegotiator , pn );
62- strbuf_free (s -> line );
74+ strbuf_free (s -> response );
75+ strbuf_free (s -> header );
76+ strbuf_free (s -> token );
77+ strbuf_free (s -> username );
78+ strbuf_free (s -> password );
79+ if (s -> prompts )
80+ free_prompts (s -> prompts );
6381 sfree (s );
6482}
6583
84+ #define HTTP_HEADER_LIST (X ) \
85+ X(HDR_CONNECTION, "Connection") \
86+ X(HDR_CONTENT_LENGTH, "Content-Length") \
87+ X(HDR_PROXY_AUTHENTICATE, "Proxy-Authenticate") \
88+ /* end of list */
89+
90+ typedef enum HttpHeader {
91+ #define ENUM_DEF(id, string) id,
92+ HTTP_HEADER_LIST (ENUM_DEF )
93+ #undef ENUM_DEF
94+ HDR_UNKNOWN
95+ } HttpHeader ;
96+
97+ static inline bool is_whitespace (char c )
98+ {
99+ return (c == ' ' || c == '\t' || c == '\n' );
100+ }
101+
102+ static inline bool is_separator (char c )
103+ {
104+ return (c == '(' || c == ')' || c == '<' || c == '>' || c == '@' ||
105+ c == ',' || c == ';' || c == ':' || c == '\\' || c == '"' ||
106+ c == '/' || c == '[' || c == ']' || c == '?' || c == '=' ||
107+ c == '{' || c == '}' );
108+ }
109+
110+ #define HTTP_SEPARATORS
111+
112+ static bool get_token (HttpProxyNegotiator * s )
113+ {
114+ size_t pos = s -> header_pos ;
115+
116+ while (pos < s -> header -> len && is_whitespace (s -> header -> s [pos ]))
117+ pos ++ ;
118+
119+ if (pos == s -> header -> len )
120+ return false; /* end of string */
121+
122+ if (is_separator (s -> header -> s [pos ]))
123+ return false;
124+
125+ strbuf_clear (s -> token );
126+ while (pos < s -> header -> len &&
127+ !is_whitespace (s -> header -> s [pos ]) &&
128+ !is_separator (s -> header -> s [pos ]))
129+ put_byte (s -> token , s -> header -> s [pos ++ ]);
130+
131+ s -> header_pos = pos ;
132+ return true;
133+ }
134+
135+ static bool get_separator (HttpProxyNegotiator * s , char sep )
136+ {
137+ size_t pos = s -> header_pos ;
138+
139+ while (pos < s -> header -> len && is_whitespace (s -> header -> s [pos ]))
140+ pos ++ ;
141+
142+ if (pos == s -> header -> len )
143+ return false; /* end of string */
144+
145+ if (s -> header -> s [pos ] != sep )
146+ return false;
147+
148+ s -> header_pos = ++ pos ;
149+ return true;
150+ }
151+
66152static void proxy_http_process_queue (ProxyNegotiator * pn )
67153{
68154 HttpProxyNegotiator * s = container_of (pn , HttpProxyNegotiator , pn );
69155
70156 crBegin (s -> crLine );
71157
72158 /*
73- * Standard prefix for the HTTP CONNECT request .
159+ * Initialise our username and password strbufs from the Conf .
74160 */
75- {
76- char dest [512 ];
77- sk_getaddr (pn -> ps -> remote_addr , dest , lenof (dest ));
78- put_fmt (pn -> output ,
79- "CONNECT %s:%d HTTP/1.1\r\n"
80- "Host: %s:%d\r\n" ,
81- dest , pn -> ps -> remote_port , dest , pn -> ps -> remote_port );
82- }
161+ put_dataz (s -> username , conf_get_str (pn -> ps -> conf , CONF_proxy_username ));
162+ put_dataz (s -> password , conf_get_str (pn -> ps -> conf , CONF_proxy_password ));
83163
84- /*
85- * Optionally send an HTTP Basic auth header with the username and
86- * password.
87- */
88- {
89- const char * username = conf_get_str (pn -> ps -> conf , CONF_proxy_username );
90- const char * password = conf_get_str (pn -> ps -> conf , CONF_proxy_password );
91- if (username [0 ] || password [0 ]) {
92- put_datalit (pn -> output , "Proxy-Authorization: Basic " );
93-
94- char * base64_input = dupcat (username , ":" , password );
95- char base64_output [4 ];
96- for (size_t i = 0 , e = strlen (base64_input ); i < e ; i += 3 ) {
97- base64_encode_atom ((const unsigned char * )base64_input + i ,
98- e - i > 3 ? 3 : e - i , base64_output );
99- put_data (pn -> output , base64_output , 4 );
164+ while (true) {
165+ /*
166+ * Standard prefix for the HTTP CONNECT request.
167+ */
168+ {
169+ char dest [512 ];
170+ sk_getaddr (pn -> ps -> remote_addr , dest , lenof (dest ));
171+ put_fmt (pn -> output ,
172+ "CONNECT %s:%d HTTP/1.1\r\n"
173+ "Host: %s:%d\r\n" ,
174+ dest , pn -> ps -> remote_port , dest , pn -> ps -> remote_port );
175+ }
176+
177+ /*
178+ * Optionally send an HTTP Basic auth header with the username and
179+ * password.
180+ */
181+ {
182+ if (s -> username -> len || s -> password -> len ) {
183+ put_datalit (pn -> output , "Proxy-Authorization: Basic " );
184+
185+ strbuf * base64_input = strbuf_new_nm ();
186+ put_datapl (base64_input , ptrlen_from_strbuf (s -> username ));
187+ put_byte (base64_input , ':' );
188+ put_datapl (base64_input , ptrlen_from_strbuf (s -> password ));
189+
190+ char base64_output [4 ];
191+ for (size_t i = 0 , e = base64_input -> len ; i < e ; i += 3 ) {
192+ base64_encode_atom (base64_input -> u + i ,
193+ e - i > 3 ? 3 : e - i , base64_output );
194+ put_data (pn -> output , base64_output , 4 );
195+ }
196+ strbuf_free (base64_input );
197+ smemclr (base64_output , sizeof (base64_output ));
198+ put_datalit (pn -> output , "\r\n" );
100199 }
101- burnstr (base64_input );
102- smemclr (base64_output , sizeof (base64_output ));
103- put_datalit (pn -> output , "\r\n" );
104200 }
105- }
106201
107- /*
108- * Blank line to terminate the HTTP request.
109- */
110- put_datalit (pn -> output , "\r\n" );
111- crReturnV ;
202+ /*
203+ * Blank line to terminate the HTTP request.
204+ */
205+ put_datalit (pn -> output , "\r\n" );
206+ crReturnV ;
112207
113- /*
114- * Read and parse the HTTP status line, and check if it's a 2xx
115- * for success.
116- */
117- strbuf_clear (s -> line );
118- crMaybeWaitUntilV (read_line (pn -> input , s -> line , false));
119- {
120- int maj_ver , min_ver , status_pos = -1 ;
121- sscanf (s -> line -> s , "HTTP/%d.%d %n" , & maj_ver , & min_ver , & status_pos );
122-
123- /* If status_pos is still -1 then the sscanf didn't get right
124- * to the end of the string */
125- if (status_pos == -1 ) {
126- pn -> error = dupstr ("HTTP response was absent or malformed" );
127- crStopV ;
208+ s -> content_length = 0 ;
209+ s -> connection_close = false;
210+
211+ /*
212+ * Read and parse the HTTP status line, and check if it's a 2xx
213+ * for success.
214+ */
215+ strbuf_clear (s -> response );
216+ crMaybeWaitUntilV (read_line (pn -> input , s -> response , false));
217+ {
218+ int maj_ver , min_ver , n_scanned ;
219+ n_scanned = sscanf (
220+ s -> response -> s , "HTTP/%d.%d %n%d" ,
221+ & maj_ver , & min_ver , & s -> http_status_pos , & s -> http_status );
222+
223+ if (n_scanned < 3 ) {
224+ pn -> error = dupstr ("HTTP response was absent or malformed" );
225+ crStopV ;
226+ }
227+
228+ if (maj_ver < 1 && (maj_ver == 1 && min_ver < 1 )) {
229+ /* Before HTTP/1.1, connections close by default */
230+ s -> connection_close = true;
231+ }
128232 }
129233
130- if (s -> line -> s [status_pos ] != '2' ) {
131- pn -> error = dupprintf ("HTTP response %s" , s -> line -> s + status_pos );
234+ /*
235+ * Read the HTTP response header section.
236+ */
237+ do {
238+ strbuf_clear (s -> header );
239+ crMaybeWaitUntilV (read_line (pn -> input , s -> header , true));
240+ s -> header_pos = 0 ;
241+
242+ if (!get_token (s )) {
243+ /* Possibly we ought to panic if we see an HTTP header
244+ * we can't make any sense of at all? But whatever,
245+ * ignore it and hope the next one makes more sense */
246+ continue ;
247+ }
248+
249+ /* Parse the header name */
250+ HttpHeader hdr = HDR_UNKNOWN ;
251+ {
252+ #define CHECK_HEADER (id , string ) \
253+ if (!stricmp(s->token->s, string)) hdr = id;
254+ HTTP_HEADER_LIST (CHECK_HEADER );
255+ #undef CHECK_HEADER
256+ }
257+
258+ if (!get_separator (s , ':' ))
259+ continue ;
260+
261+ if (hdr == HDR_CONTENT_LENGTH ) {
262+ if (!get_token (s ))
263+ continue ;
264+ s -> content_length = strtoumax (s -> token -> s , NULL , 10 );
265+ } else if (hdr == HDR_CONNECTION ) {
266+ if (!get_token (s ))
267+ continue ;
268+ if (!stricmp (s -> token -> s , "close" ))
269+ s -> connection_close = true;
270+ else if (!stricmp (s -> token -> s , "keep-alive" ))
271+ s -> connection_close = false;
272+ } else if (hdr == HDR_PROXY_AUTHENTICATE ) {
273+ if (!get_token (s ))
274+ continue ;
275+
276+ if (!stricmp (s -> token -> s , "Basic" )) {
277+ /* fine, we know how to do Basic auth */
278+ } else {
279+ pn -> error = dupprintf ("HTTP proxy asked for unsupported "
280+ "authentication type '%s'" ,
281+ s -> token -> s );
282+ crStopV ;
283+ }
284+ }
285+ } while (s -> header -> len > 0 );
286+
287+ /* Read and ignore the entire response document */
288+ crMaybeWaitUntilV (bufchain_try_consume (
289+ pn -> input , s -> content_length ));
290+
291+ if (200 <= s -> http_status && s -> http_status < 300 ) {
292+ /* Any 2xx HTTP response means we're done */
293+ goto authenticated ;
294+ } else if (s -> http_status == 407 ) {
295+ /* 407 is Proxy Authentication Required, which we may be
296+ * able to do something about. */
297+ if (s -> connection_close ) {
298+ pn -> error = dupprintf ("HTTP proxy closed connection after "
299+ "asking for authentication" );
300+ crStopV ;
301+ }
302+
303+ /* Either we never had a password in the first place, or
304+ * the one we already presented was rejected. We can only
305+ * proceed from here if we have a way to ask the user
306+ * questions. */
307+ if (!pn -> itr ) {
308+ pn -> error = dupprintf ("HTTP proxy requested authentication "
309+ "which we do not have" );
310+ crStopV ;
311+ }
312+
313+ /*
314+ * Send some prompts to the user. We'll assume the
315+ * password is always required (since it's just been
316+ * rejected, even if we did send one before), and we'll
317+ * prompt for the username only if we don't have one from
318+ * the Conf.
319+ */
320+ s -> prompts = proxy_new_prompts (pn -> ps );
321+ s -> prompts -> to_server = true;
322+ s -> prompts -> from_server = false;
323+ s -> prompts -> name = dupstr ("HTTP proxy authentication" );
324+ if (!s -> username -> len ) {
325+ s -> username_prompt_index = s -> prompts -> n_prompts ;
326+ add_prompt (s -> prompts , dupstr ("Proxy username: " ), true);
327+ } else {
328+ s -> username_prompt_index = -1 ;
329+ }
330+
331+ s -> password_prompt_index = s -> prompts -> n_prompts ;
332+ add_prompt (s -> prompts , dupstr ("Proxy password: " ), false);
333+
334+ while (true) {
335+ int prompt_result = seat_get_userpass_input (
336+ interactor_announce (pn -> itr ), s -> prompts );
337+ if (prompt_result > 0 ) {
338+ break ;
339+ } else if (prompt_result == 0 ) {
340+ pn -> aborted = true;
341+ crStopV ;
342+ }
343+ crReturnV ;
344+ }
345+
346+ if (s -> username_prompt_index != -1 ) {
347+ strbuf_clear (s -> username );
348+ put_dataz (s -> username ,
349+ prompt_get_result_ref (
350+ s -> prompts -> prompts [s -> username_prompt_index ]));
351+ }
352+
353+ strbuf_clear (s -> password );
354+ put_dataz (s -> password ,
355+ prompt_get_result_ref (
356+ s -> prompts -> prompts [s -> password_prompt_index ]));
357+
358+ free_prompts (s -> prompts );
359+ s -> prompts = NULL ;
360+ } else {
361+ /* Any other HTTP response is treated as permanent failure */
362+ pn -> error = dupprintf ("HTTP response %s" ,
363+ s -> response -> s + s -> http_status_pos );
132364 crStopV ;
133365 }
134366 }
135367
136- /*
137- * Read and skip the rest of the HTTP response headers, terminated
138- * by a blank line.
139- */
140- do {
141- strbuf_clear (s -> line );
142- crMaybeWaitUntilV (read_line (pn -> input , s -> line , true));
143- } while (s -> line -> len > 0 );
144-
368+ authenticated :
145369 /*
146370 * Success! Hand over to the main connection.
147371 */
0 commit comments