release 0.92.3
[cntlm.git] / scanner.c
blob56287fc04424ffda68c404527c4c277db8310e58
1 /*
2 * CNTLM is free software; you can redistribute it and/or modify it under the
3 * terms of the GNU General Public License as published by the Free Software
4 * Foundation; either version 2 of the License, or (at your option) any later
5 * version.
7 * CNTLM is distributed in the hope that it will be useful, but WITHOUT ANY
8 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
9 * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
10 * details.
12 * You should have received a copy of the GNU General Public License along with
13 * this program; if not, write to the Free Software Foundation, Inc., 51 Franklin
14 * St, Fifth Floor, Boston, MA 02110-1301, USA.
16 * Copyright (c) 2007 David Kubicek
20 #include <sys/types.h>
21 #include <pthread.h>
22 #include <stdio.h>
23 #include <stdlib.h>
24 #include <unistd.h>
25 #include <ctype.h>
26 #include <string.h>
27 #include <strings.h>
28 #include <fnmatch.h>
30 #include "utils.h"
31 #include "socket.h"
32 #include "http.h"
33 #include "globals.h"
34 #include "forward.h"
35 #include "scanner.h"
38 * This code is a piece of shit, but it works. Cannot rewrite it now, because
39 * I don't have ISA AV filter anymore - wouldn't be able to test it.
41 int scanner_hook(rr_data_t request, rr_data_t response, struct auth_s *credentials, int cd, int *sd, long maxKBs) {
42 char *buf, *line, *pos, *tmp, *pat, *post, *isaid, *uurl;
43 int bsize, lsize, size, len, i, w, nc;
44 rr_data_t newreq, newres;
45 plist_t list;
47 int ok = 1;
48 int done = 0;
49 int headers_initiated = 0;
50 long c, progress = 0, filesize = 0;
53 * Let's limit the responses we examine to an absolute minimum
55 if (!request->req || response->code != 200
56 || http_has_body(request, response) != -1
57 || hlist_subcmp(response->headers, "Transfer-Encoding", "chunked")
58 || !hlist_subcmp(response->headers, "Proxy-Connection", "close"))
59 return PLUG_SENDHEAD | PLUG_SENDDATA;
61 tmp = hlist_get(request->headers, "User-Agent");
62 if (tmp) {
63 tmp = lowercase(strdup(tmp));
64 list = scanner_agent_list;
65 while (list) {
66 pat = lowercase(strdup(list->aux));
67 if (debug)
68 printf("scanner_hook: matching U-A header (%s) to %s\n", tmp, pat);
69 if (!fnmatch(pat, tmp, 0)) {
70 if (debug)
71 printf("scanner_hook: positive match!\n");
72 maxKBs = 0;
73 free(pat);
74 break;
76 free(pat);
77 list = list->next;
79 free(tmp);
82 bsize = SAMPLE;
83 buf = new(bsize);
85 len = 0;
86 do {
87 size = read(*sd, buf + len, SAMPLE - len - 1);
88 if (debug)
89 printf("scanner_hook: read %d of %d\n", size, SAMPLE - len);
90 if (size > 0)
91 len += size;
92 } while (size > 0 && len < SAMPLE - 1);
94 if (strstr(buf, "<title>Downloading status</title>") && (pos=strstr(buf, "ISAServerUniqueID=")) && (pos = strchr(pos, '"'))) {
95 pos++;
96 c = strlen(pos);
97 for (i = 0; i < c && pos[i] != '"'; ++i);
99 if (pos[i] == '"') {
100 isaid = substr(pos, 0, i);
101 if (debug)
102 printf("scanner_hook: ISA id = %s\n", isaid);
104 lsize = BUFSIZE;
105 line = new(lsize);
106 do {
107 i = so_recvln(*sd, &line, &lsize);
109 c = strlen(line);
110 if (len + c >= bsize) {
111 bsize *= 2;
112 tmp = realloc(buf, bsize);
113 if (tmp == NULL)
114 break;
115 else
116 buf = tmp;
119 strcat(buf, line);
120 len += c;
122 if (i >= 0 && (
123 ((pos = strstr(line, "UpdatePage("))
124 && isdigit(pos[11]))
126 ((pos = strstr(line, "DownloadFinished("))
127 && isdigit(pos[17])
128 && (done = 1)) )) {
129 if (debug)
130 printf("scanner_hook: %s", line);
132 if ((pos = strstr(line, "To be downloaded"))) {
133 filesize = atol(pos+16);
134 if (debug) {
135 if (filesize > 0) {
136 printf("scanner_hook: file size detected: %ld KiBs (max: %ld)\n", filesize/1024, maxKBs);
137 } else {
138 printf("scanner_hook: file size unknown -- quitting\n");
139 break;
143 if (maxKBs && (maxKBs == 1 || filesize/1024 > maxKBs))
144 break;
147 * We have to send HTTP protocol ID so we can send the notification
148 * headers during downloading. Once we've done that, it cannot appear
149 * again, which it would if we returned PLUG_SENDHEAD, so we must
150 * remember to not include it.
152 headers_initiated = 1;
153 tmp = new(MINIBUF_SIZE);
154 snprintf(tmp, MINIBUF_SIZE, "%s 200 OK\r\n", request->http);
155 w = write(cd, tmp, strlen(tmp));
156 free(tmp);
159 if (!headers_initiated) {
160 if (debug)
161 printf("scanner_hook: Giving up, \"To be downloaded\" line not found!\n");
162 break;
166 * Send a notification header to the client, just so it doesn't timeout
168 if (!done) {
169 tmp = new(MINIBUF_SIZE);
170 progress = atol(line+12);
171 snprintf(tmp, MINIBUF_SIZE, "ISA-Scanner: %ld of %ld\r\n", progress, filesize);
172 w = write(cd, tmp, strlen(tmp));
173 free(tmp);
177 * If download size is unknown beforehand, stop when downloaded amount is over ISAScannerSize
179 if (!filesize && maxKBs && maxKBs != 1 && progress/1024 > maxKBs)
180 break;
182 } while (i > 0 && !done);
184 if (i >= 0 && done && (pos = strstr(line, "\",\"")+3) && (c = strchr(pos, '"') - pos) > 0) {
185 tmp = substr(pos, 0, c);
186 pos = urlencode(tmp);
187 free(tmp);
189 uurl = urlencode(request->url);
191 post = new(BUFSIZE);
192 snprintf(post, BUFSIZE-1, "%surl=%s&%sSaveToDisk=YES&%sOrig=%s", isaid, pos, isaid, isaid, uurl);
194 if (debug)
195 printf("scanner_hook: Getting file with URL data = %s\n", request->url);
197 tmp = new(MINIBUF_SIZE);
198 snprintf(tmp, MINIBUF_SIZE, "%d", (int)strlen(post));
200 newres = new_rr_data();
201 newreq = dup_rr_data(request);
203 free(newreq->method);
204 newreq->method = strdup("POST");
205 hlist_mod(newreq->headers, "Referer", request->url, 1);
206 hlist_mod(newreq->headers, "Content-Type", "application/x-www-form-urlencoded", 1);
207 hlist_mod(newreq->headers, "Content-Length", tmp, 1);
208 free(tmp);
210 nc = proxy_connect(credentials);
211 c = proxy_authenticate(&nc, newreq, newres, credentials);
212 if (c && newres->code == 407) {
213 if (debug)
214 printf("scanner_hook: Authentication OK, getting the file...\n");
215 } else {
216 if (debug)
217 printf("scanner_hook: Authentication failed or refused!\n");
218 close(nc);
219 nc = 0;
223 * The POST request for the real file
225 reset_rr_data(newres);
226 if (nc && headers_send(nc, newreq) && write(nc, post, strlen(post)) && headers_recv(nc, newres)) {
227 if (debug)
228 hlist_dump(newres->headers);
231 * We always know the filesize here. Send it to the client, because ISA doesn't!!!
232 * The clients progress bar doesn't work without it and it stinks!
234 if (filesize || progress) {
235 tmp = new(20);
236 snprintf(tmp, 20, "%ld", filesize ? filesize : progress);
237 newres->headers = hlist_mod(newres->headers, "Content-Length", tmp, 1);
241 * Here we remember if previous code already sent some headers
242 * to the client. In such case, do not include the HTTP/1.x ID.
244 newres->skip_http = headers_initiated;
245 copy_rr_data(response, newres);
246 close(*sd);
247 *sd = nc;
249 len = 0;
250 ok = PLUG_SENDHEAD | PLUG_SENDDATA;
251 } else if (debug)
252 printf("scanner_hook: New request failed\n");
254 free(newreq);
255 free(newres);
256 free(post);
257 free(uurl);
260 free(line);
261 free(isaid);
262 } else if (debug)
263 printf("scanner_hook: ISA id not found\n");
266 if (len) {
267 if (debug) {
268 printf("scanner_hook: flushing %d original bytes\n", len);
269 hlist_dump(response->headers);
272 if (!headers_send(cd, response)) {
273 if (debug)
274 printf("scanner_hook: failed to send headers\n");
275 free(buf);
276 return PLUG_ERROR;
279 size = write(cd, buf, len);
280 if (size > 0)
281 ok = PLUG_SENDDATA;
282 else
283 ok = PLUG_ERROR;
286 if (debug)
287 printf("scanner_hook: ending with %d\n", ok);
289 free(buf);
290 return ok;