diff -Nru cntlm-0.35.1/acl.c cntlm-0.91~rc6/acl.c --- cntlm-0.35.1/acl.c 2007-10-31 18:48:15.000000000 +0000 +++ cntlm-0.91~rc6/acl.c 2010-03-25 01:24:22.000000000 +0000 @@ -19,10 +19,11 @@ * */ +#include +#include #include #include #include -#include #include #include diff -Nru cntlm-0.35.1/auth.c cntlm-0.91~rc6/auth.c --- cntlm-0.35.1/auth.c 2007-10-31 18:48:15.000000000 +0000 +++ cntlm-0.91~rc6/auth.c 2010-03-25 01:24:22.000000000 +0000 @@ -29,13 +29,16 @@ struct auth_s *new_auth(void) { struct auth_s *tmp; - tmp = (struct auth_s *)new(sizeof(struct auth_s)); - tmp->user = new(MINIBUF_SIZE); - tmp->domain = new(MINIBUF_SIZE); - tmp->workstation = new(MINIBUF_SIZE); - tmp->passntlm2 = 0; - tmp->passnt = 0; - tmp->passlm = 0; + tmp = (struct auth_s *)malloc(sizeof(struct auth_s)); + if (tmp == NULL) + return NULL; + + memset(tmp->user, 0, MINIBUF_SIZE); + memset(tmp->domain, 0, MINIBUF_SIZE); + memset(tmp->workstation, 0, MINIBUF_SIZE); + memset(tmp->passntlm2, 0, MINIBUF_SIZE); + memset(tmp->passnt, 0, MINIBUF_SIZE); + memset(tmp->passlm, 0, MINIBUF_SIZE); tmp->hashntlm2 = 1; tmp->hashnt = 0; tmp->hashlm = 0; @@ -44,67 +47,48 @@ return tmp; } -struct auth_s *dup_auth(struct auth_s *creds, int fullcopy) { - struct auth_s *tmp; - - tmp = new_auth(); +struct auth_s *copy_auth(struct auth_s *dst, struct auth_s *src, int fullcopy) { + dst->hashntlm2 = src->hashntlm2; + dst->hashnt = src->hashnt; + dst->hashlm = src->hashlm; + dst->flags = src->flags; - tmp->domain = new(MINIBUF_SIZE); - strlcpy(tmp->domain, creds->domain, MINIBUF_SIZE); - - tmp->workstation = new(MINIBUF_SIZE); - strlcpy(tmp->workstation, creds->workstation, MINIBUF_SIZE); - - tmp->hashntlm2 = creds->hashntlm2; - tmp->hashnt = creds->hashnt; - tmp->hashlm = creds->hashlm; - tmp->flags = creds->flags; + strlcpy(dst->domain, src->domain, MINIBUF_SIZE); + strlcpy(dst->workstation, src->workstation, MINIBUF_SIZE); if (fullcopy) { - tmp->user = new(MINIBUF_SIZE); - strlcpy(tmp->user, creds->user, MINIBUF_SIZE); - - if (creds->passntlm2) { - tmp->passntlm2 = new(MINIBUF_SIZE); - memcpy(tmp->passntlm2, creds->passntlm2, MINIBUF_SIZE); - } - - if (creds->passnt) { - tmp->passnt = new(MINIBUF_SIZE); - memcpy(tmp->passnt, creds->passnt, MINIBUF_SIZE); - } - - if (creds->passlm) { - tmp->passlm = new(MINIBUF_SIZE); - memcpy(tmp->passlm, creds->passlm, MINIBUF_SIZE); - } + strlcpy(dst->user, src->user, MINIBUF_SIZE); + if (src->passntlm2) + memcpy(dst->passntlm2, src->passntlm2, MINIBUF_SIZE); + if (src->passnt) + memcpy(dst->passnt, src->passnt, MINIBUF_SIZE); + if (src->passlm) + memcpy(dst->passlm, src->passlm, MINIBUF_SIZE); + } else { + memset(dst->user, 0, MINIBUF_SIZE); + memset(dst->passntlm2, 0, MINIBUF_SIZE); + memset(dst->passnt, 0, MINIBUF_SIZE); + memset(dst->passlm, 0, MINIBUF_SIZE); } - return tmp; + return dst; } -void free_auth(struct auth_s *creds) { - if (!creds) - return; +struct auth_s *dup_auth(struct auth_s *creds, int fullcopy) { + struct auth_s *tmp; + + tmp = new_auth(); + if (tmp == NULL) + return NULL; - free(creds->domain); - free(creds->workstation); - if (creds->user) - free(creds->user); - if (creds->passntlm2) - free(creds->passntlm2); - if (creds->passnt) - free(creds->passnt); - if (creds->passlm) - free(creds->passlm); - free(creds); + return copy_auth(tmp, creds, fullcopy); } void dump_auth(struct auth_s *creds) { char *tmp; printf("Credentials structure dump:\n"); - if (!creds) { + if (creds == NULL) { printf("Struct is not allocated!\n"); return; } diff -Nru cntlm-0.35.1/auth.h cntlm-0.91~rc6/auth.h --- cntlm-0.35.1/auth.h 2007-10-31 18:48:15.000000000 +0000 +++ cntlm-0.91~rc6/auth.h 2010-03-25 01:24:22.000000000 +0000 @@ -24,13 +24,20 @@ #include +#include "utils.h" + +/* + * Although I always prefer structs with pointer refs, I need direct storage + * here to be able to alloc/free it in one go. It is used in a plist_t which + * frees its items, but not recursively. + */ struct auth_s { - char *user; - char *domain; - char *workstation; - char *passlm; - char *passnt; - char *passntlm2; + char user[MINIBUF_SIZE]; + char domain[MINIBUF_SIZE]; + char workstation[MINIBUF_SIZE]; + char passlm[MINIBUF_SIZE]; + char passnt[MINIBUF_SIZE]; + char passntlm2[MINIBUF_SIZE]; int hashntlm2; int hashnt; int hashlm; @@ -39,29 +46,21 @@ #define auth_strcpy(creds, var, value) \ if ((creds) && (value)) { \ - if (!((creds)->var)) \ - ((creds)->var) = new(MINIBUF_SIZE); \ strlcpy(((creds)->var), (value), MINIBUF_SIZE); \ } -#define auth_strncpy(creds, var, value, len) \ - if ((creds) && (value)) { \ - if (!((creds)->var)) \ - ((creds)->var) = new(MINIBUF_SIZE); \ - strlcpy(((creds)->var), (value), MIN(len, MINIBUF_SIZE)); \ - } - #define auth_memcpy(creds, var, value, len) \ if ((creds) && (value)) { \ - if (!((creds)->var)) \ - ((creds)->var) = new(MINIBUF_SIZE); \ memcpy(((creds)->var), (value), MIN(len, MINIBUF_SIZE)); \ } - +/* + * No free_auth() required, just use free() + * new_auth() is also just a convenience malloc/memset() wrapper + */ extern struct auth_s *new_auth(void); +extern struct auth_s *copy_auth(struct auth_s *dst, struct auth_s *src, int fullcopy); extern struct auth_s *dup_auth(struct auth_s *creds, int fullcopy); -extern void free_auth(struct auth_s *creds); extern void dump_auth(struct auth_s *creds); #endif /* _AUTH_H */ diff -Nru cntlm-0.35.1/config.c cntlm-0.91~rc6/config.c --- cntlm-0.35.1/config.c 2007-10-31 18:48:15.000000000 +0000 +++ cntlm-0.91~rc6/config.c 2010-04-13 00:02:53.000000000 +0000 @@ -24,68 +24,124 @@ #include #include +#include "globals.h" #include "config.h" #include "utils.h" -#define alnum(c) (isalpha(c) || isdigit(c)) -#define blank(c) ((c) == ' ' || (c) == '\t' || (c) == '\r' || (c) == '\n') +static const char *globals[] = { + "Allow", + "Deny", + "Gateway", + "Listen", + "SOCKS5Proxy", + "SOCKS5User", + "NTLMToBasic", + "Tunnel" }; config_t config_open(const char *fname) { - config_t ret; + config_t rc; FILE *fp; - char *buf, *key, *value; - int i, j, len; + char *buf, *tmp, *key, *value; + char section[MINIBUF_SIZE] = "global"; + int i, j, slen, len, quote; + + //printf("sizeof = %d\n", sizeof(globals) / sizeof(char *)); fp = fopen(fname, "r"); if (!fp) return NULL; buf = new(BUFSIZE); - ret = (config_t)new(sizeof(struct config_s)); - ret->options = NULL; + rc = (config_t)new(sizeof(struct config_s)); + rc->options = NULL; while (!feof(fp)) { - fgets(buf, BUFSIZE, fp); - len = MIN(BUFSIZE, strlen(buf)); + quote = 0; + tmp = fgets(buf, BUFSIZE, fp); + if (!tmp) + break; + len = MIN(BUFSIZE, strlen(buf)); if (!len || feof(fp)) continue; - i = j = 0; - while (j < len && blank(buf[j])) - j++; - - if (j >= len || buf[j] == '#') + /* + * Find first non-empty character + */ + for (i = j = 0; j < len && isspace(buf[j]); ++j); + + /* + * Comment? + */ + if (j >= len || buf[j] == '#' || buf[j] == ';') continue; - i = j; - while (j < len && alnum(buf[j])) - j++; + /* + * Find end of keyword + */ + for (i = j; j < len && isalnum(buf[j]); ++j); + + /* + * Malformed? + */ + if (j >= len) + continue; - if (j >= len || !blank(buf[j])) + /* + * Is it a section? + */ + if (buf[j] == '[') { + for (++j; j < len && isspace(buf[j]); ++j); + for (slen = j; j < len && j-slen < MINIBUF_SIZE-1 && buf[j] != ']' && !isspace(buf[j]); ++j); + if (j-slen > 0) { + strlcpy(section, buf+slen, j-slen+1); + } continue; + } + /* + * It's an OK keyword + */ key = substr(buf, i, j-i); - i = j; - while (j < len && blank(buf[j])) - j++; - if (j >= len || buf[j] == '#') + /* + * Find next non-empty character + */ + for (i = j; j < len && isspace(buf[j]); ++j); + if (j >= len || buf[j] == '#' || buf[j] == ';') continue; - value = substr(buf, j, len-j); - i = strcspn(value, "#"); - if (i != strlen(value)) - value[i] = 0; - - trimr(value); - ret->options = hlist_add(ret->options, key, value, 0, 0); + /* + * Is value quoted? + */ + if (buf[j] == '"') { + quote = 1; + for (i = ++j; j < len && buf[i] != '"'; ++i); + if (i >= len) + continue; + } else + i = len; + + /* + * Get value as quoted or until EOL/comment + */ + value = substr(buf, j, i-j); + if (!quote) { + i = strcspn(value, "#"); + if (i != strlen(value)) + value[i] = 0; + trimr(value); + } + + if (debug) + printf("section: %s, %s = '%s'\n", section, key, value); + rc->options = hlist_add(rc->options, key, value, HLIST_NOALLOC, HLIST_NOALLOC); } free(buf); fclose(fp); - return ret; + return rc; } void config_set(config_t cf, char *option, char *value) { diff -Nru cntlm-0.35.1/configure cntlm-0.91~rc6/configure --- cntlm-0.35.1/configure 2007-11-21 00:19:00.000000000 +0000 +++ cntlm-0.91~rc6/configure 2008-07-08 13:08:46.000000000 +0000 @@ -6,19 +6,25 @@ # # To prevent ugly Makefile extensions, underscore and chars following it # in the name XXX are automatically removed before locating relevant -# Makefile. This is why compiler "xlc_r" has automatic Makefile extension -# just "xlc". This can be disabled if neccessary. +# Makefile. This is why compiler "xlc_r" has Makefile extension "xlc". +# This can be disabled if neccessary. # CCS="xlc_r gcc" +# +# Look for supported compilers +# for c in $CCS; do - if CCPATH=`which $c 2>&1`; then + if CCPATH=`which $c 2>&1` && [ `expr substr "$CCPATH" 1 1` = "/" ]; then CC="$c" break fi done +# +# Make a link to a proper Makefile.* +# if [ -z "$CC" ]; then echo "Unable to find GNU GCC or IBM XL C/C++. Fix your PATH!" exit 1 diff -Nru cntlm-0.35.1/COPYRIGHT cntlm-0.91~rc6/COPYRIGHT --- cntlm-0.35.1/COPYRIGHT 2007-10-31 18:48:15.000000000 +0000 +++ cntlm-0.91~rc6/COPYRIGHT 2010-03-19 15:40:54.000000000 +0000 @@ -1,6 +1,5 @@ - CNTLM - Authenticating HTTP Proxy - Copyright (C) 2007 David Kubicek + Copyright (C) 2007-2010 David Kubicek This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -18,3 +17,5 @@ If you would like to negotiate alternate licensing terms, you may do so by contacting: David Kubicek , + + diff -Nru cntlm-0.35.1/debian/changelog cntlm-0.91~rc6/debian/changelog --- cntlm-0.35.1/debian/changelog 2010-09-27 12:48:25.000000000 +0000 +++ cntlm-0.91~rc6/debian/changelog 2010-09-27 12:44:11.000000000 +0000 @@ -1,3 +1,11 @@ +cntlm (0.91~rc6-0~sr1) lucid; urgency=low + + * New Upstream pre-release. + * Switched to source format 3.0 (quilt) + * Updated watch file to rc location. + + -- Stefano Rivera Wed, 08 Sep 2010 11:39:35 +0200 + cntlm (0.35.1-5) unstable; urgency=low * Fix lintian errors in debian/copyright diff -Nru cntlm-0.35.1/debian/source/format cntlm-0.91~rc6/debian/source/format --- cntlm-0.35.1/debian/source/format 1970-01-01 00:00:00.000000000 +0000 +++ cntlm-0.91~rc6/debian/source/format 2010-09-27 12:48:26.000000000 +0000 @@ -0,0 +1 @@ +3.0 (quilt) diff -Nru cntlm-0.35.1/debian/watch cntlm-0.91~rc6/debian/watch --- cntlm-0.35.1/debian/watch 2010-09-27 12:48:25.000000000 +0000 +++ cntlm-0.91~rc6/debian/watch 2010-09-27 12:44:11.000000000 +0000 @@ -1,2 +1,3 @@ version=3 -http://sf.net/cntlm/cntlm-([\.\d]+)\.tar\.gz +opts=dversionmangle=s/~rc/rc/ \ +http://ftp.awk.cz/cntlm/cntlm-(.+)\.tar\.gz diff -Nru cntlm-0.35.1/direct.c cntlm-0.91~rc6/direct.c --- cntlm-0.35.1/direct.c 1970-01-01 00:00:00.000000000 +0000 +++ cntlm-0.91~rc6/direct.c 2010-04-29 11:18:58.000000000 +0000 @@ -0,0 +1,450 @@ +/* + * CNTLM is free software; you can redistribute it and/or modify it under the + * terms of the GNU General Public License as published by the Free Software + * Foundation; either version 2 of the License, or (at your option) any later + * version. + * + * CNTLM is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., 51 Franklin + * St, Fifth Floor, Boston, MA 02110-1301, USA. + * + * Copyright (c) 2007 David Kubicek + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "utils.h" +#include "globals.h" +#include "auth.h" +#include "http.h" +#include "socket.h" +#include "ntlm.h" +#include "direct.h" +#include "pages.h" + +int host_connect(const char *hostname, int port) { + struct in_addr addr; + + errno = 0; + if (!so_resolv(&addr, hostname)) { + if (debug) + printf("so_resolv: %s failed\n", hostname); + return -1; + } + + return so_connect(addr, port); + +} + +int www_authenticate(int sd, rr_data_t request, rr_data_t response, struct auth_s *creds) { + char *tmp, *buf, *challenge; + rr_data_t auth; + int len; + + int rc = 0; + + buf = new(BUFSIZE); + + strcpy(buf, "NTLM "); + len = ntlm_request(&tmp, creds); + if (len) { + to_base64(MEM(buf, unsigned char, 5), MEM(tmp, unsigned char, 0), len, BUFSIZE-5); + free(tmp); + } + + auth = dup_rr_data(request); + auth->headers = hlist_mod(auth->headers, "Connection", "keep-alive", 1); + auth->headers = hlist_mod(auth->headers, "Authorization", buf, 1); + auth->headers = hlist_mod(auth->headers, "Content-Length", "0", 1); + auth->headers = hlist_del(auth->headers, "Transfer-Encoding"); + + /* + * Drop whatever error page server returned + */ + if (!http_body_drop(sd, response)) + goto bailout; + + if (debug) { + printf("\nSending WWW auth request...\n"); + hlist_dump(auth->headers); + } + + if (!headers_send(sd, auth)) + goto bailout; + + if (debug) + printf("\nReading WWW auth response...\n"); + + /* + * Get NTLM challenge + */ + reset_rr_data(auth); + if (!headers_recv(sd, auth)) { + goto bailout; + } + + if (debug) + hlist_dump(auth->headers); + + /* + * Auth required? + */ + if (auth->code == 401) { + if (!http_body_drop(sd, auth)) + goto bailout; + + tmp = hlist_get(auth->headers, "WWW-Authenticate"); + if (tmp && strlen(tmp) > 6 + 8) { + challenge = new(strlen(tmp) + 5 + 1); + len = from_base64(challenge, tmp + 5); + if (len > NTLM_CHALLENGE_MIN) { + len = ntlm_response(&tmp, challenge, len, creds); + if (len > 0) { + strcpy(buf, "NTLM "); + to_base64(MEM(buf, unsigned char, 5), MEM(tmp, unsigned char, 0), len, BUFSIZE-5); + request->headers = hlist_mod(request->headers, "Authorization", buf, 1); + free(tmp); + } else { + syslog(LOG_ERR, "No target info block. Cannot do NTLMv2!\n"); + response->errmsg = "Invalid NTLM challenge from web server"; + free(challenge); + goto bailout; + } + } else { + syslog(LOG_ERR, "Server returning invalid challenge!\n"); + response->errmsg = "Invalid NTLM challenge from web server"; + free(challenge); + goto bailout; + } + + free(challenge); + } else { + syslog(LOG_WARNING, "No challenge in WWW-Authenticate!\n"); + response->errmsg = "Web server reply missing NTLM challenge"; + goto bailout; + } + } else { + goto bailout; + } + + if (debug) + printf("\nSending WWW auth...\n"); + + if (!headers_send(sd, request)) { + goto bailout; + } + + if (debug) + printf("\nReading final server response...\n"); + + reset_rr_data(auth); + if (!headers_recv(sd, auth)) { + goto bailout; + } + + rc = 1; + + if (debug) + hlist_dump(auth->headers); + +bailout: + if (rc) + response = copy_rr_data(response, auth); + free_rr_data(auth); + free(buf); + + return rc; +} + +rr_data_t direct_request(void *cdata, rr_data_t request) { + rr_data_t data[2], rc = NULL; + struct auth_s *tcreds = NULL; + int *rsocket[2], *wsocket[2]; + int w, loop, sd; + char *tmp; + + char *hostname = NULL; + int port = 0; + int conn_alive = 0; + + int cd = ((struct thread_arg_s *)cdata)->fd; + struct sockaddr_in caddr = ((struct thread_arg_s *)cdata)->addr; + + if (debug) + printf("Direct thread processing...\n"); + + sd = host_connect(request->hostname, request->port); + if (sd < 0) { + syslog(LOG_WARNING, "Connection failed for %s:%d (%s)", request->hostname, request->port, strerror(errno)); + tmp = gen_502_page(request->http, strerror(errno)); + w = write(cd, tmp, strlen(tmp)); + free(tmp); + + rc = (void *)-1; + goto bailout; + } + + /* + * Now save NTLM credentials for purposes of this thread. + * If web auth fails, we'll rewrite them like with NTLM-to-Basic in proxy mode. + */ + tcreds = dup_auth(g_creds, /* fullcopy */ 1); + + if (request->hostname) { + hostname = strdup(request->hostname); + port = request->port; + } else { + tmp = gen_502_page(request->http, "Invalid request URL"); + w = write(cd, tmp, strlen(tmp)); + free(tmp); + + rc = (void *)-1; + goto bailout; + } + + do { + if (request) { + data[0] = dup_rr_data(request); + request = NULL; + } else { + data[0] = new_rr_data(); + } + data[1] = new_rr_data(); + + rsocket[0] = wsocket[1] = &cd; + rsocket[1] = wsocket[0] = &sd; + + conn_alive = 0; + + for (loop = 0; loop < 2; ++loop) { + if (data[loop]->empty) { // Isn't this the first loop with request supplied by caller? + if (debug) { + printf("\n******* Round %d C: %d, S: %d *******\n", loop+1, cd, sd); + printf("Reading headers (%d)...\n", *rsocket[loop]); + } + if (!headers_recv(*rsocket[loop], data[loop])) { + free_rr_data(data[0]); + free_rr_data(data[1]); + rc = (void *)-1; + goto bailout; + } + } + + /* + * Check whether this new request still talks to the same server as previous. + * If no, return request to caller, he must decide on forward or direct + * approach. + */ + if (loop == 0 && hostname && data[0]->hostname + && (strcasecmp(hostname, data[0]->hostname) || port != data[0]->port)) { + if (debug) + printf("\n******* D RETURN: %s *******\n", data[0]->url); + + rc = dup_rr_data(data[0]); + free_rr_data(data[0]); + free_rr_data(data[1]); + goto bailout; + } + + if (debug) + hlist_dump(data[loop]->headers); + + if (loop == 0 && data[0]->req) { + syslog(LOG_DEBUG, "%s %s %s", inet_ntoa(caddr.sin_addr), data[0]->method, data[0]->url); + + /* + * Convert full proxy request URL into a relative URL + * Host header is already inserted by headers_recv() + */ + if (data[0]->rel_url) { + if (data[0]->url) + free(data[0]->url); + data[0]->url = strdup(data[0]->rel_url); + } + + data[0]->headers = hlist_mod(data[0]->headers, "Connection", "keep-alive", 1); + data[0]->headers = hlist_del(data[0]->headers, "Proxy-Authorization"); + + /* + * Try to get auth from client if present + */ + if (http_parse_basic(data[0]->headers, "Authorization", tcreds) > 0 && debug) + printf("NTLM-to-basic: Credentials parsed: %s\\%s at %s\n", tcreds->domain, tcreds->user, tcreds->workstation); + } + + /* + * Is this a CONNECT request? + */ + if (loop == 0 && CONNECT(data[0])) { + if (debug) + printf("CONNECTing...\n"); + + data[1]->empty = 0; + data[1]->req = 0; + data[1]->code = 200; + data[1]->msg = strdup("Connection established"); + data[1]->http = strdup(data[0]->http); + + if (headers_send(cd, data[1])) + tunnel(cd, sd); + + free_rr_data(data[0]); + free_rr_data(data[1]); + rc = (void *)-1; + goto bailout; + } + + if (loop == 1 && data[1]->code == 401 && hlist_subcmp_all(data[1]->headers, "WWW-Authenticate", "NTLM")) { + /* + * Server closing the connection after 401? + * Should never happen. + */ + if (hlist_subcmp(data[1]->headers, "Connection", "close")) { + if (debug) + printf("Reconnect before WWW auth\n"); + close(sd); + sd = host_connect(data[0]->hostname, data[0]->port); + if (sd < 0) { + tmp = gen_502_page(data[0]->http, "WWW authentication reconnect failed"); + w = write(cd, tmp, strlen(tmp)); + free(tmp); + + rc = (void *)-1; + goto bailout; + } + } + if (!www_authenticate(*wsocket[0], data[0], data[1], tcreds)) { + if (debug) + printf("WWW auth connection error.\n"); + + tmp = gen_502_page(data[1]->http, data[1]->errmsg ? data[1]->errmsg : "Error during WWW-Authenticate"); + w = write(cd, tmp, strlen(tmp)); + free(tmp); + + free_rr_data(data[0]); + free_rr_data(data[1]); + + rc = (void *)-1; + goto bailout; + } else if (data[1]->code == 401) { + /* + * Server giving 401 after auth? + * Request basic auth + */ + tmp = gen_401_page(data[1]->http, data[0]->hostname, data[0]->port); + w = write(cd, tmp, strlen(tmp)); + free(tmp); + + free_rr_data(data[0]); + free_rr_data(data[1]); + + rc = (void *)-1; + goto bailout; + } + } + + /* + * Check if we should loop for another request. Required for keep-alive + * connections, client might really need a non-interrupted conversation. + * + * We default to keep-alive server connections, unless server explicitly + * flags closing the connection or we detect a body with unknown size + * (end marked by server closing). + */ + if (loop == 1) { + conn_alive = !hlist_subcmp(data[1]->headers, "Connection", "close") + && http_has_body(data[0], data[1]) != -1; + if (conn_alive) { + data[1]->headers = hlist_mod(data[1]->headers, "Proxy-Connection", "keep-alive", 1); + data[1]->headers = hlist_mod(data[1]->headers, "Connection", "keep-alive", 1); + } else { + data[1]->headers = hlist_mod(data[1]->headers, "Proxy-Connection", "close", 1); + rc = (void *)-1; + } + } + + if (debug) + printf("Sending headers (%d)...\n", *wsocket[loop]); + + /* + * Send headers + */ + if (!headers_send(*wsocket[loop], data[loop])) { + free_rr_data(data[0]); + free_rr_data(data[1]); + rc = (void *)-1; + goto bailout; + } + + if (!http_body_send(*wsocket[loop], *rsocket[loop], data[0], data[1])) { + free_rr_data(data[0]); + free_rr_data(data[1]); + rc = (void *)-1; + goto bailout; + } + } + + free_rr_data(data[0]); + free_rr_data(data[1]); + + } while (conn_alive && !so_closed(sd) && !so_closed(cd) && !serialize); + +bailout: + if (tcreds) + free(tcreds); + if (hostname) + free(hostname); + + close(sd); + + return rc; +} + +void direct_tunnel(void *thread_data) { + int sd, port = 0; + char *pos, *hostname; + + int cd = ((struct thread_arg_s *)thread_data)->fd; + char *thost = ((struct thread_arg_s *)thread_data)->target; + struct sockaddr_in caddr = ((struct thread_arg_s *)thread_data)->addr; + + hostname = strdup(thost); + if ((pos = strchr(hostname, ':')) != NULL) { + *pos = 0; + port = atoi(++pos); + } + + sd = host_connect(hostname, port); + if (sd <= 0) + goto bailout; + + syslog(LOG_DEBUG, "%s FORWARD %s", inet_ntoa(caddr.sin_addr), thost); + if (debug) + printf("Portforwarding to %s for client %d...\n", thost, cd); + + tunnel(cd, sd); + +bailout: + free(hostname); + close(sd); + close(cd); + + return; +} + diff -Nru cntlm-0.35.1/direct.h cntlm-0.91~rc6/direct.h --- cntlm-0.35.1/direct.h 1970-01-01 00:00:00.000000000 +0000 +++ cntlm-0.91~rc6/direct.h 2010-04-21 11:56:47.000000000 +0000 @@ -0,0 +1,29 @@ +/* + * CNTLM is free software; you can redistribute it and/or modify it under the + * terms of the GNU General Public License as published by the Free Software + * Foundation; either version 2 of the License, or (at your option) any later + * version. + * + * CNTLM is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., 51 Franklin + * St, Fifth Floor, Boston, MA 02110-1301, USA. + * + * Copyright (c) 2007 David Kubicek + * + */ + +#ifndef _DIRECT_H +#define _DIRECT_H + +#include "utils.h" + +extern int host_connect(const char *hostname, int port); +extern rr_data_t direct_request(void *cdata, rr_data_t request); +extern void direct_tunnel(void *thread_data); + +#endif /* _DIRECT_H */ diff -Nru cntlm-0.35.1/doc/cntlm.1 cntlm-0.91~rc6/doc/cntlm.1 --- cntlm-0.35.1/doc/cntlm.1 2010-09-27 12:48:25.000000000 +0000 +++ cntlm-0.91~rc6/doc/cntlm.1 2010-04-13 00:02:53.000000000 +0000 @@ -1,4 +1,4 @@ -.TH CNTLM 1 "Nov 2007" "cntlm 0.35" "Accelerating NTLM/NTLMv2 Authentication Proxy" +.TH CNTLM 1 "Nov 2010" "cntlm 0.90" "Accelerating NTLM/NTLMv2 Authentication Proxy" .SH NAME \fBcntlm\fP - authenticating HTTP(S) proxy with TCP/IP tunneling and acceleration @@ -9,43 +9,45 @@ ] [ \fIhost1\fP \fIport1\fP | \fIhost1\fP:\fIport1\fP ] ... \fIhostN\fP \fIportN\fP .SH DESCRIPTION -\fBCntlm\fP is an NTLM/NTLMv2 authenticating HTTP proxy. It takes the address of your proxy or proxies -(\fIhost1..N\fP and \fIport1..N\fP) and opens a listening socket, forwarding each request to the parent proxy -(moving in a circular list if the active parent stops working). Along the way, a connection to the parent is -created anew and authenticated or, if available, previously cached connection is reused to achieve higher -efficiency and faster responses. When the chain is set up, \fBcntlm\fP should be used as a proxy in your -applications. \fBCntlm\fP also integrates transparent TCP/IP port forwarding (tunneling) through the parent -(incl. authentication). Each tunnel opens a new listening socket on the defined local port and forwards all -connections to the given host:port behind the secondary proxy. Manual page explains how to setup \fBcntlm\fP -properly using configuration file or command-line arguments. +\fBCntlm\fP is an NTLM/NTLM SR/NTLMv2 authenticating HTTP proxy. It stands between your applications and the +corporate proxy, adding NTLM authentication on-the-fly. You can specify several "parent" proxies and Cntlm +will try one after another until one works. All auth'd connections are cached and reused to achieve high +efficiency. Just point your apps proxy settings at Cntlm, fill in cntlm.conf (cntlm.ini) and you're ready to +do. This is useful on Windows, but essential for non-Microsoft OS's. Proxy IP addresses can be specified via +CLI (\fIhost1:port1\fP to \fIhostN:portN\fP) or the configuration file. + +Another option is to have \fBcntlm\fP authenticate your local web connections without any parent proxies. It +can work in a stand-alone mode, just like Squid or ISA. By default, all requests are forwarded to parent +proxies, but the user can set a "NoProxy" list, a list of URL matching wild-card patterns, that route between +direct and forward modes. \fBCntlm\fP can also recognize when all your corporate proxies are unavailable and +switch to stand-alone mode automatically (and then back again). Aside from \fIWWW\fP and \fIPROXY\fP +authentication, \fBcntlm\fP provides a useful feature enabling users migrate their laptops between work and +home without changing proxy settings in their applications (using \fBcntlm\fP all the time). \fBCntlm\fP also +integrates transparent TCP/IP port forwarding (tunneling). Each tunnel opens a new listening socket on local +machine and and forwards all connections to the target host behind the parent proxy. Instead of these SSH-like +tunnels, user can also choose a limited SOCKS5 interface. .PP -\fBCntlm\fP works similarly to NTLMAPS, plus full NTLM support, a bucket of new features and none of its -shortcomings and inefficiencies. It adds support for real keep-alive (on both sides) and it caches all -authenticated connections for reuse in subsequent requests. It can be restarted without TIME_WAIT delay, uses -just a fraction of memory compared to NTLMAPS and by orders of magnitude less CPU. Each thread is completely -independent and one cannot block another. \fBCntlm\fP has many security/privacy features like \fBNTLMv2\fP -support and password protection - it is possible to substitute password hashes (which can be obtained -using\ \fB-H\fP) for the actual password or to enter the password interactively. If plaintext password is -used, it is automatically hashed during the startup and all its traces are removed from the process memory. +Core \fBcntlm\fP function had been similar to the late NTLMAPS, but today, \fBcntlm\fP has evolved way beyond +anything any other application of this type can offer. The feature list below speaks for itself. \fBCntlm\fP +has many security/privacy features like \fBNTLMv2\fP support and password protection - it is possible to +substitute password hashes (which can be obtained using\ \fB-H\fP) in place of the actual password or to enter +the password interactively (on start-up or via "basic" HTTP auth translation). If plaintext password is used, +it is automatically hashed during the startup and all traces of it are removed from the process memory. .PP -In addition to lower usage of system resources, \fBcntlm\fP achieves higher throughput on a given link. By +In addition to minimal use of system resources, \fBcntlm\fP achieves higher throughput on a given link. By caching authenticated connections, it acts as an HTTP accelerator; This way, the 5-way auth handshake for -each connection is transparently eliminated, providing direct access most of the time. NTLMAPS doesn't -authenticate in parallel with the request - instead, it first connects, sends a probe and disconnects. No -sooner than that it connects again and initiates NTLM handshake. \fBCntlm\fP also doesn't read the whole -request including HTTP body into memory, in fact, no traffic is generated except for the exchange of headers -until the client <-> server connection is fully negotiated. Only then are the request and response bodies -forwarded, directly between client and server sockets. This way, \fBcntlm\fP avoids most of the TCP/IP -overhead of similar proxies. Along with the fact that \fBcntlm\fP is written in optimized C, it achieves up to -fifteen times faster responses. The slower the line, the more impact \fBcntlm\fP has on download speeds. +each connection is transparently eliminated, providing immediate access most of the time. \fBCntlm\fP never +caches a request/reply body in memory, in fact, no traffic is generated except for the exchange of auth headers +until the client <-> server connection is fully negotiated. Only then real data transfer takes place. +\fBCntlm\fP is written in optimized C and easily achieves fifteen times faster responses than others. .PP -An example of \fBcntlm\fP compared to NTLMAPS under the same conditions: \fBcntlm\fP gave avg 76 kB/s with -peak CPU usage of 0.3% whereas with NTLMAPS it was avg 48 kB/s with peak CPU at 98% (Pentium M 1.8 GHz). The -extreme difference in resource usage is one of many important benefits for laptop use. Peak memory -consumption (several complex sites, 50 paralell connections/threads; values are in KiB): +An example of \fBcntlm\fP compared to NTLMAPS: \fBcntlm\fP gave avg 76 kB/s with peak CPU usage of 0.3% +whereas with NTLMAPS it was avg 48 kB/s with peak CPU at 98% (Pentium M 1.8 GHz). The extreme difference in +resource usage is one of many important benefits for laptop use. Peak memory consumption (several complex +sites, 50 paralell connections/threads; values are in KiB): .nf .ft C @@ -59,14 +61,14 @@ .PP Inherent part of the development is profiling and memory management screening using Valgrind. The source distribution contains a file called \fIvalgrind.txt\fP, where you can see the report confirming zero leaks, no -access to unallocated memory, no usage of uninitialized data - all tracked down to each CPU instruction -emulated in Valgrind's virtual CPU during a typical production lifetime of the proxy. +access to unallocated memory, no usage of uninitialized data - all traced down to each instruction emulated in +Valgrind's virtual CPU during a typical production lifetime of the proxy. .SH OPTIONS Most options can be pre-set in a configuration file. Specifying an option more than once is not an error, but \fBcntlm\fP ignores all occurences except the last one. This does not apply to options like\ \fB-L\fP, each of which creates a new instance of some feature. \fBCntlm\fP can be built with a hardcoded configuration file -(e.g. /etc/cntlm.conf), which is always loaded, if possible. See\ \fB-c\fP option on how to override some or +(e.g. /etc/cntlm.conf), which is always loaded, if possible. See\ \fB-c\fP option on how to override some or all of its settings. Use \fB-h\fP to see available options with short description. @@ -77,7 +79,7 @@ to have this in a configuration file, but \fBCntlm\fP follows the premise that you can do the same on the command-line as you can using the config file. When \fBCntlm\fP receives a connection request, it decides whether to allow or deny it. All ACL rules are stored in a list in the same order as specified. \fBCntlm\fP -then walks the list and the first \fIIP/mask\fP rule that matches the request source address is applied. The +then walks the list and the first \fIIP/mask\fP rule that matches the request source address is applied. The \fImask\fP can be any number from 0 to 32, where 32 is the default (that is exact IP match). This notation is also known as CIDR. If you want to match everything, use \fB0/0\fP or an asterix. ACLs on the command-line take precedence over those in the config file. In such case, you will see info about that in the log (among @@ -152,7 +154,7 @@ .ne 6 .TP .B -G \ \ \ \ (ISAScannerAgent) -User-Agent matching (case insensitive) for trans-isa-scan plugin (see \fB-S\fP for explanation). Positive +User-Agent matching (case insensitive) for trans-isa-scan plugin (see \fB-S\fP for explanation). Positive match identifies requests (applications) for which the plugin should be enabled without considering the size of the download (see \fB-S\fP). You can use shell wildcard characters, namely "*", "?" and "[]". If used without \fB-S\fP or \fBISAScannerSize\fP, the \fImax_size_in_kb\fP is internally set to infinity, so the @@ -188,7 +190,7 @@ .TP .B -L [:]::\ \ \ \ (Tunnel) -Tunnel specification. The syntax is the same as in OpenSSH's local forwarding (\fB-L\fP), with a new optional +Tunnel definition. The syntax is the same as in OpenSSH's local forwarding (\fB-L\fP), with a new optional prefix, \fIsaddr\fP - the source IP address to bind the \fIlport\fP to. \fBCntlm\fP will listen for incomming connections on the local port \fIlport\fP, forwarding every new connection through the parent proxy to the \fIrhost\fP:\fIrport\fP (authenticating on the go). This option can be used multiple times for unlimited @@ -219,8 +221,8 @@ .ne 6 You can choose to run the proxy service on more than one port, in such case just use this option as many times -as necessary. But unlike tunnel specification, \fBcntlm\fP fails to start if it cannot bind all of the proxy -service ports. Proxy service port can also be bound selectively. Use \fIsaddr\fP to pick source IP address to +as necessary. But unlike tunnel definition, \fBcntlm\fP fails to start if it cannot bind all of the proxy +service ports. Proxy service port can also be bound selectively. Use \fIsaddr\fP to pick source IP address to bind the \fIlport\fP to. This allows you, for example, to run the service on different ports for subnet A and B and make it invisible for subnet C. See \fB-g\fP for the details concerning local port binding when \fIsaddr\fP is not used. @@ -233,6 +235,13 @@ how to use \fBAuth\fP, \fBFlags\fP and password-hash options, you have to configure at least your credentials and proxy address first. You can use \fB-I\fP to enter your password interactively. +.ne 5 +.TP +.B -N [,:]\ \ \ \ (SOCKS5Proxy) @@ -259,8 +268,8 @@ .TP .B -p \ \ \ \ (Password, PassNT, ...) Proxy account password. \fBCntlm\fP deletes the password from the memory, to make it invisible in /proc or -with inspection tools like \fBps(1)\fP, but the preferable way of specifying password is the configuration -file. To that end, you can use \fBPassword\fP option (for plaintext, human readable format), or "encrypt" your +with inspection tools like \fBps(1)\fP, but the preferable way of setting password is the configuration file. +To that end, you can use \fBPassword\fP option (for plaintext, human readable format), or "encrypt" your password via \fB-H\fP and then use \fBPassNTLMv2\fP, \fBPassNT\fP and/or \fBPassLM\fP. .ne 3 @@ -273,7 +282,7 @@ .B -S \ \ \ \ (ISAScannerSize) Enables the plugin for transparent handling of the dreaded ISA AV scanner, which returns an interactive HTTP page (displaying the scanning progress) instead of the file/data you've requested, every time it feels like -scanning the contents. This presumptuous behavior breaks every automated downloader, updater and basically +scanning the contents. This presumptuous behavior breaks every automated downloader, updater and basically EVERY application relying on downloads (e.g. wget, apt-get). .ne 6 @@ -323,7 +332,7 @@ .TP .B -T -Used in combination with \fB-v\fP to save the debug output into a trace file. It should be placed as the +Used in combination with \fB-v\fP to save the debug output into a trace file. It should be placed as the first parameter on the command line. To prevent data loss, it never overwrites an existing file. You have to pick a unique name or manually delete the old file. @@ -331,7 +340,7 @@ .TP .B -U When executed as root, do the stuff that needs such permissions (read config, bind ports, etc.) and then -immediately drop privileges and change to \fIuid\fP. This parameter can be either number or system username. +immediately drop privileges and change to \fIuid\fP. This parameter can be either number or system username. If you use a number, both uid and gid of the process will be set to this value; if you specify a username, uid and gid will be set according to that user's uid and primary gid as defined in \fI/etc/passwd\fP. You should use the latter, possibly using a dedicated \fBcntlm\fP account. As with any daemon, you are \fBstrongly\fP @@ -347,15 +356,24 @@ .TP .B -w \ \ \ \ (Workstation) -Workstation NetBIOS name. Do not use full domain name (FQDN) here. Just the first part. If not specified, -\fBcntlm\fP tries to get the system hostname and if that fails, uses "cntlm" - it's because some proxies -require this field non-empty. +Workstation NetBIOS name. Do not use full qualified domain name (FQDN) here. Just the first part. +If not specified, \fBcntlm\fP tries to get the system hostname and if that fails, uses "cntlm" - it's because +some proxies require this field non-empty. .SH CONFIGURATION -Configuration file has the same syntax as OpenSSH ssh_config. It comprises of whitespace delimited keywords -and values. Comment begins with a hash '#' and can begin anywhere in the file. Everything after the hash up -until the EOL is a comment. Values can contain any characters, including whitespace. Do not quote anything. -For detailed explanation of keywords, see appropriate command-line options. Following keywords are available: +Configuration file is basically an INI file, except there are no "=" between keys and values. It comprises of +whitespace delimited keyword and value pairs. Apart from that, there are sections as well, they have the usual +"[section_name]" syntax. Comment begins with a hash "#" or a semicolon ";" and can be anywhere in the file. +Everything after the mark up until EOL is a comment. Values can contain any characters, including whitespace. +You \fIcan\fP use double quotes to set a value ending/beginning with a "space" character or containing a +comment mark, but otherwise it's not necessary. There are no escape sequences or characters. + +There are two types of keywords, \fIlocal\fP and \fIglobal\fP. Local options specify authentication details +per domain (or location). Global keywords apply to all sections and proxies. They should be placed before all +sections, but it's not necessary. They are: \fCAllow, Deny, Gateway, Listen, SOCKS5Proxy, SOCKS5User, +NTLMToBasic, Tunnel\fP. + +All available keywords are listed here, full descriptions are in the OPTIONS section: .TP .B Allow [/] @@ -428,11 +446,18 @@ .TP .B Proxy Parent proxy, which requires authentication. The same as proxy on the command-line, can be used more than -once to specify unlimited number of proxies. Should one proxy fail, \fBcntlm\fP automatically moves on to the +once to specify an arbitrary number of proxies. Should one proxy fail, \fBcntlm\fP automatically moves on to the next one. The connect request fails only if the whole list of proxies is scanned and (for each request) and found to be invalid. Command-line takes precedence over the configuration file. .TP +.B NoProxy , , ... +Avoid parent proxy for these host names. All matching URL's will be proxied \fIdirectly\fP by \fBcntlm\fP as a +stand-alone proxy. \fBCntlm\fP supports WWW authentication in this mode, thus allowing you to access local +intranet sites with corporate NTLM authentication. Hopefully, you won't need that virtualized MSIE any more. :) +See \fB-N\fP for more. + +.TP .B SOCKS5Proxy [:] Enable SOCKS5 proxy. See \fB-O\fP for more. @@ -446,7 +471,7 @@ .TP .B Tunnel [:]:: -Tunnel specification. See \fB-L\fP for more. +Tunnel definition. See \fB-L\fP for more. .TP .B Username @@ -460,7 +485,7 @@ .ne 7 .SH FILES The optional location of the configuration file is defined in the Makefile, with the default for 1) deb/rpm -package, 2) traditional "make; make install" and 3) Windows installer being: +package, 2) traditional "make; make install" and 3) Windows installer, respectively, being: .nf .ft C @@ -471,35 +496,24 @@ .fi .SH PORTING -\fBCntlm\fP has been successfully compiled and tested on both little and big endian machines (Linux/i386 and -AIX/PowerPC). For compilation details, see README in the source distribution. Porting to any POSIX conforming -OS shouldn't be more than a matter of the Makefile rearrangement. \fBCntlm\fP uses strictly POSIX.1-2001 -interfaces with ISO C99 libc (\fBsnprintf(3)\fP), it is also compliant with SUSv3. Since version 0.33, -\fBcntlm\fP supports Windows using POSIX emulation layer Cygwin. - -.SH TODO -In the much needed NTLM-proxy departement, \fBcntlm\fP aims to be a drop-in replacement for NTLMAPS. But -please note that NTLM WWW auth (that is auth to HTTP servers), when it is running without any parent proxy as -a standalone proxy server in itself, won't probaly be implemented ever. Even though the tasks share common -NTLM authentication, they are different things. Also, I've never seen any access-protected HTTP server -requiring solely NTLM without any alternative. Such a narrow-spectrum tool can be written in Perl in a few -minutes. I strive to keep the code of \fBcntlm\fP simple and efficient. +\fBCntlm\fP is being used on many platforms, little and big endian machines, so users should not have any +problems with compilation. Nowadays, \fBcntlm\fP is a standard tool in most Linux distributions and there are +various repositories for other UNIX-like systems. Personally, I release Debian Linux (deb), RedHat Linux (rpm) +and Windows (exe) binaries, but most people get \fBcntlm\fP from their OS distributor. -.SH BUGS -This software is still BETA, so there are probably many bugs for you to uncloak even though I'm testing every -new piece of code AMAP and use \fBcntlm\fP daily. I'll be happy to fix all of them, but if you can manage, -patches would be better. ;) +.ne 2 +For compilation details, see README in the source distribution. Porting to any POSIX conforming OS shouldn't +be more than a matter of a Makefile rearrangement. \fBCntlm\fP uses strictly POSIX.1-2001 interfaces with +ISO C99 libc and is also compliant with SUSv3. Since version 0.33, \fBcntlm\fP supports Windows using a POSIX +emulation layer called \fBCygwin\fP. +.SH BUGS \fBTo report a bug\fP, enable the debug output, save it to a file and submit on-line along with a detailed -description of the problem and how to reproduce it. The link can be found on the homepage. - -To generate the debug tracefile correctly, first run \fBcntlm\fP from the shell (command line) and make sure -you can reproduce the bug. When you will have verfied that, stop \fBcntlm\fP (hit Ctrl-C) and insert the -following parameters at the beginning of the command line, preserving their order. Example: +description of the problem and how to reproduce it. Visit the home page for more. .nf .ft C - cntlm[.exe] \-T cntlmtrace.log \-v \-s ... the rest ... + cntlm \-T cntlmtrace.log \-v \-s ... the rest ... .ft P .fi @@ -509,6 +523,6 @@ Homepage: http://cntlm.sourceforge.net/ .SH COPYRIGHT -Copyright \(co 2007 David Kubicek +Copyright \(co 2007-2010 David Kubicek .br -\fBCntlm\fP uses DES, MD4, MD5 and HMAC-MD5 routines from gnulib and Base64 routines from \fBmutt(1)\fP. +\fBCntlm\fP uses DES, MD4, MD5 and HMAC-MD5 routines from \fBgnulib\fP and Base64 routines from \fBmutt(1)\fP. diff -Nru cntlm-0.35.1/doc/cntlm.conf cntlm-0.91~rc6/doc/cntlm.conf --- cntlm-0.35.1/doc/cntlm.conf 2007-11-02 01:07:11.000000000 +0000 +++ cntlm-0.91~rc6/doc/cntlm.conf 2010-03-22 09:51:25.000000000 +0000 @@ -7,20 +7,42 @@ Username testuser Domain corp-uk -Password password # Use hashes instead (-H) -#Workstation netbios_hostname # Should be auto-guessed - -Proxy 10.217.112.41:8080 -Proxy 10.217.112.42:8080 - -# -# This is the port number where Cntlm will listen +Password password +# NOTE: Use plaintext password only at your own risk +# Use hashes instead. You can use a "cntlm -M" and "cntlm -H" +# command sequence to get the right config for your environment. +# See cntlm man page +# Example secure config shown below. +# PassLM 1AD35398BE6565DDB5C4EF70C0593492 +# PassNT 77B9081511704EE852F94227CF48A793 +### Only for user 'testuser', domain 'corp-uk' +# PassNTLMv2 D5826E9C665C37C80B53397D5C07BBCB + +# Specify the netbios hostname cntlm will send to the parent +# proxies. Normally the value is auto-guessed. +# +# Workstation netbios_hostname + +# List of parent proxies to use. More proxies can be defined +# one per line in format : +# +Proxy 10.0.0.41:8080 +Proxy 10.0.0.42:8080 + +# List addresses you do not want to pass to parent proxies +# * and ? wildcards can be used +# +NoProxy localhost, 127.0.0.*, 10.*, 192.168.* + +# Specify the port cntlm will listen on +# You can bind cntlm to specific interface by specifying +# the appropriate IP address also in format : +# Cntlm listens on 127.0.0.1:3128 by default # Listen 3128 -# # If you wish to use the SOCKS5 proxy feature as well, uncomment -# the following option, SOCKS5. It can be used several times +# the following option. It can be used several times # to have SOCKS5 on more than one port or on different network # interfaces (specify explicit source address for that). # @@ -31,7 +53,6 @@ #SOCKS5Proxy 8010 #SOCKS5User dave:password -# # Use -M first to detect the best NTLM settings for your proxy. # Default is to use the only secure hash, NTLMv2, but it is not # as available as the older stuff. @@ -43,32 +64,29 @@ #Auth LM #Flags 0x06820000 -# # Enable to allow access from other computers # #Gateway yes -# # Useful in Gateway mode to allow/restrict certain IPs +# Specifiy individual IPs or subnets one rule per line. # #Allow 127.0.0.1 #Deny 0/0 -# # GFI WebMonitor-handling plugin parameters, disabled by default # -#ISAScannerSize 1024 -#ISAScannerAgent Wget/ -#ISAScannerAgent APT-HTTP/ -#ISAScannerAgent Yum/ +#ISAScannerSize 1024 +#ISAScannerAgent Wget/ +#ISAScannerAgent APT-HTTP/ +#ISAScannerAgent Yum/ -# # Headers which should be replaced if present in the request # #Header User-Agent: Mozilla/4.0 (compatible; MSIE 5.5; Windows 98) -# -# Tunnels mapping local port to a machine behind the proxy +# Tunnels mapping local port to a machine behind the proxy. +# The format is :: # #Tunnel 11443:remote.com:443 diff -Nru cntlm-0.35.1/doc/files.txt cntlm-0.91~rc6/doc/files.txt --- cntlm-0.35.1/doc/files.txt 2007-11-20 22:36:34.000000000 +0000 +++ cntlm-0.91~rc6/doc/files.txt 1970-01-01 00:00:00.000000000 +0000 @@ -1,38 +0,0 @@ -./COPYRIGHT -./LICENSE -./Makefile -./Makefile.xlc -./README -./VERSION -./acl.c -./acl.h -./auth.c -./auth.h -./config.c -./config.h -./config/endian.c -./config/gethostname.c -./config/socklen_t.c -./config/strdup.c -./configure -./http.c -./http.h -./ntlm.c -./ntlm.h -./pages.c -./proxy.c -./socket.c -./socket.h -./swap.h -./utils.c -./utils.h -./xcrypt.c -./xcrypt.h -./doc/cntlm.1 -./doc/cntlm.conf -./doc/files.txt -./doc/valgrind.txt -./redhat/cntlm.init -./redhat/cntlm.spec -./redhat/cntlm.sysconfig -./redhat/rules diff -Nru cntlm-0.35.1/forward.c cntlm-0.91~rc6/forward.c --- cntlm-0.35.1/forward.c 1970-01-01 00:00:00.000000000 +0000 +++ cntlm-0.91~rc6/forward.c 2010-04-29 11:18:58.000000000 +0000 @@ -0,0 +1,933 @@ +/* + * CNTLM is free software; you can redistribute it and/or modify it under the + * terms of the GNU General Public License as published by the Free Software + * Foundation; either version 2 of the License, or (at your option) any later + * version. + * + * CNTLM is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., 51 Franklin + * St, Fifth Floor, Boston, MA 02110-1301, USA. + * + * Copyright (c) 2007 David Kubicek + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "utils.h" +#include "globals.h" +#include "auth.h" +#include "http.h" +#include "socket.h" +#include "ntlm.h" +#include "forward.h" +#include "scanner.h" +#include "pages.h" + +int parent_curr = 0; +pthread_mutex_t parent_mtx = PTHREAD_MUTEX_INITIALIZER; + +/* + * Connect to the selected proxy. If the request fails, pick next proxy + * in the line. Each request scans the whole list until all items are tried + * or a working proxy is found, in which case it is selected and used by + * all threads until it stops working. Then the search starts again. + * + * Writes required credentials into passed auth_s structure + */ +int proxy_connect(struct auth_s *credentials) { + proxy_t *aux; + int i, prev; + plist_t list, tmp; + int loop = 0; + + prev = parent_curr; + pthread_mutex_lock(&parent_mtx); + if (parent_curr == 0) { + aux = (proxy_t *)plist_get(parent_list, ++parent_curr); + syslog(LOG_INFO, "Using proxy %s:%d\n", inet_ntoa(aux->host), aux->port); + } + pthread_mutex_unlock(&parent_mtx); + + do { + aux = (proxy_t *)plist_get(parent_list, parent_curr); + i = so_connect(aux->host, aux->port); + if (i <= 0) { + pthread_mutex_lock(&parent_mtx); + if (parent_curr >= parent_count) + parent_curr = 0; + aux = (proxy_t *)plist_get(parent_list, ++parent_curr); + pthread_mutex_unlock(&parent_mtx); + syslog(LOG_ERR, "Proxy connect failed, will try %s:%d\n", inet_ntoa(aux->host), aux->port); + } + } while (i <= 0 && ++loop < parent_count); + + if (i <= 0 && loop >= parent_count) + syslog(LOG_ERR, "No proxy on the list works. You lose.\n"); + + /* + * We have to invalidate the cached connections if we moved to a different proxy + */ + if (prev != parent_curr) { + pthread_mutex_lock(&connection_mtx); + list = connection_list; + while (list) { + tmp = list->next; + close(list->key); + list = tmp; + } + plist_free(connection_list); + pthread_mutex_unlock(&connection_mtx); + } + + if (i > 0 && credentials != NULL) + copy_auth(credentials, g_creds, /* fullcopy */ !ntlmbasic); + + return i; +} + +/* + * Send request, read reply, if it contains NTLM challenge, generate final + * NTLM auth message and insert it into the original client header, + * which is then processed by caller himself. + * + * If response is present, we fill in proxy's reply. Caller can tell + * if auth was required or not from response->code. If not, caller has + * a full reply to forward to client. + * + * Return 0 in case of network error, 1 when proxy replies + * + * Caller must init & free "request" and "response" (if supplied) + * + */ +int proxy_authenticate(int *sd, rr_data_t request, rr_data_t response, struct auth_s *credentials) { + char *tmp, *buf, *challenge; + rr_data_t auth; + int len; + + int pretend407 = 0; + int rc = 0; + + buf = new(BUFSIZE); + + strcpy(buf, "NTLM "); + len = ntlm_request(&tmp, credentials); + if (len) { + to_base64(MEM(buf, unsigned char, 5), MEM(tmp, unsigned char, 0), len, BUFSIZE-5); + free(tmp); + } + + auth = dup_rr_data(request); + auth->headers = hlist_mod(auth->headers, "Proxy-Authorization", buf, 1); + + if (HEAD(request) || http_has_body(request, response) != 0) { + /* + * There's a body - make this request just a probe. Do not send any body. If no auth + * is required, we let our caller send the reply directly to the client to avoid + * another duplicate request later (which traditionally finishes the 2nd part of + * NTLM handshake). Without auth, there's no need for the final request. + * + * However, if client has a body, we make this request without it and let caller do + * the second request in full. If we did it here, we'd have to cache the request + * body in memory (even chunked) and carry it around. Not practical. + * + * When caller sees 407, he makes the second request. That's why we pretend a 407 + * in this situation. Without it, caller wouldn't make it, sending the client a + * reply to our PROBE, not the real request. + * + * The same for HEAD requests - at least one ISA doesn't allow making auth + * request using HEAD!! + */ + if (debug) + printf("Will send just a probe request.\n"); + pretend407 = 1; + } + + /* + * For broken ISA's that don't accept HEAD in auth request + */ + if (HEAD(request)) { + free(auth->method); + auth->method = strdup("GET"); + } + + auth->headers = hlist_mod(auth->headers, "Content-Length", "0", 1); + auth->headers = hlist_del(auth->headers, "Transfer-Encoding"); + + if (debug) { + printf("\nSending PROXY auth request...\n"); + hlist_dump(auth->headers); + } + + if (!headers_send(*sd, auth)) { + goto bailout; + } + + if (debug) + printf("\nReading PROXY auth response...\n"); + + /* + * Return response if requested. "auth" is used to get it, + * so make it point to the caller's structure. + */ + if (response) { + free_rr_data(auth); + auth = response; + } + + reset_rr_data(auth); + if (!headers_recv(*sd, auth)) { + goto bailout; + } + + if (debug) + hlist_dump(auth->headers); + + rc = 1; + + /* + * Auth required? + */ + if (auth->code == 407) { + if (!http_body_drop(*sd, auth)) { // FIXME: if below fails, we should forward what we drop here... + rc = 0; + goto bailout; + } + tmp = hlist_get(auth->headers, "Proxy-Authenticate"); + if (tmp) { + challenge = new(strlen(tmp) + 5 + 1); + len = from_base64(challenge, tmp + 5); + if (len > NTLM_CHALLENGE_MIN) { + len = ntlm_response(&tmp, challenge, len, credentials); + if (len > 0) { + strcpy(buf, "NTLM "); + to_base64(MEM(buf, unsigned char, 5), MEM(tmp, unsigned char, 0), len, BUFSIZE-5); + request->headers = hlist_mod(request->headers, "Proxy-Authorization", buf, 1); + free(tmp); + } else { + syslog(LOG_ERR, "No target info block. Cannot do NTLMv2!\n"); + free(challenge); + goto bailout; + } + } else { + syslog(LOG_ERR, "Proxy returning invalid challenge!\n"); + free(challenge); + goto bailout; + } + + free(challenge); + } else { + syslog(LOG_WARNING, "No Proxy-Authenticate, NTLM not supported?\n"); + } + } else if (pretend407) { + if (debug) + printf("Client %s - forcing second request.\n", HEAD(request) ? "sent HEAD" : "has a body"); + if (response) + response->code = 407; // See explanation above + if (!http_body_drop(*sd, auth)) { + rc = 0; + goto bailout; + } + } + + /* + * Did proxy closed connection? It's our fault, reconnect for the caller. + */ + if (so_closed(*sd)) { + if (debug) + printf("Proxy closed on us, reconnect.\n"); + close(*sd); + *sd = proxy_connect(credentials); + if (*sd < 0) { + rc = 0; + goto bailout; + } + } + +bailout: + if (!response) + free_rr_data(auth); + + free(buf); + + return rc; +} + +/* + * Forwarding thread. Connect to the proxy, process auth then request. + * + * First read request, then call proxy_authenticate() which will send + * the request. If proxy returns 407, it will compute NTLM reply and + * return authenticated request to us. If proxy returns full response + * (no auth needed), it returns the full reply. Then we just forward + * the reply to client OR make the request again with properly auth'd + * headers provided by proxy_authenticate(). + * + * We loop while we see Connection: keep-alive, thus making sure clients + * can have uninterrupted conversations with a web server. Proxy-Connection + * is not our concern, it's handled in the caller, proxy_thread(). If it's + * present, however, we cache the auth'd proxy connection for reuse. + * + * Some proxies return Connection: keep-alive even when not requested and + * would make us loop indefinitely. Because of that, we remember which server + * we're talking to and if that changes, we return the request to be processed + * by our caller. + * + * Caller decides which URL's to forward and which to process directly, that's + * also why we return the request if the server name changes. + * + * We return NULL when we're finished or a pointer to another request. + * Returned request means server name has changed and needs to be checked + * agains NoProxy exceptions. + * + * thread_data is NOT freed + * request is NOT freed + */ +rr_data_t forward_request(void *thread_data, rr_data_t request) { + int i, w, loop, plugin, retry = 0; + int *rsocket[2], *wsocket[2]; + rr_data_t data[2], rc = NULL; + hlist_t tl; + char *tmp; + struct auth_s *tcreds = NULL; /* Per-thread credentials */ + char *hostname = NULL; + int proxy_alive; + int conn_alive; + int authok; + int noauth; + int was_cached; + + int sd; + int cd = ((struct thread_arg_s *)thread_data)->fd; + struct sockaddr_in caddr = ((struct thread_arg_s *)thread_data)->addr; + +beginning: + sd = was_cached = noauth = authok = conn_alive = proxy_alive = 0; + + rsocket[0] = wsocket[1] = &cd; + rsocket[1] = wsocket[0] = &sd; + + if (debug) { + printf("Thread processing%s...\n", retry ? " (retry)" : ""); + plist_dump(connection_list); + } + + /* + * NTLM credentials for purposes of this thread (tcreds) are given to + * us by proxy_connect() or retrieved from connection cache. + * + * Ultimately, the source for creds is always proxy_connect(), but when + * we cache a connection, we store creds associated with it in the + * cache as well, in case we'll need them. + */ + pthread_mutex_lock(&connection_mtx); + i = plist_pop(&connection_list, (void **)&tcreds); + pthread_mutex_unlock(&connection_mtx); + if (i) { + if (debug) + printf("Found autenticated connection %d!\n", i); + sd = i; + authok = 1; + was_cached = 1; + } else { + tcreds = new_auth(); + sd = proxy_connect(tcreds); + if (sd <= 0) { + tmp = gen_502_page(request->http, "Parent proxy unreacheable"); + w = write(cd, tmp, strlen(tmp)); + free(tmp); + rc = (void *)-1; + goto bailout; + } + } + + /* + * Each thread only serves req's for one hostname. If hostname changes, + * we return request to our caller for a new direct/forward decision. + */ + if (!hostname && request->hostname) { + hostname = strdup(request->hostname); + } + + do { + /* + * data[0] is for the first loop pass + * - first do {} loop iteration uses request passed from caller, + * in subsequent iterations we read the request headers from the client + * - if not already done, we try to authenticate the connection + * - we send the request headers to the proxy with HTTP body, if present + * + * data[1] is for the second pass + * - read proxy response + * - forward it to the client with HTTP body, if present + * + * There two goto's: + * - beginning: jump here to retry request (when cached connection timed out + * or we thought proxy was notauth, but got 407) + * - shortcut: jump here from 1st iter. of inner loop, when we detect + * that auth isn't required by proxy. We do loop++, make the jump and + * the reply to our auth attempt (containing valid response) is sent to + * client directly without us making a request a second time. + */ + if (request) { + if (retry) + data[0] = request; // Got from inside the loop = retry (must free ourselves) + else + data[0] = dup_rr_data(request); // Got from caller (make a dup, caller will free) + request = NULL; // Next time, just alloc empty structure + } else { + data[0] = new_rr_data(); + } + data[1] = new_rr_data(); + + retry = 0; + proxy_alive = 0; + conn_alive = 0; + + for (loop = 0; loop < 2; ++loop) { + if (data[loop]->empty) { // Isn't this the first loop with request supplied by caller? + if (debug) { + printf("\n******* Round %d C: %d, S: %d (authok=%d, noauth=%d) *******\n", loop+1, cd, sd, authok, noauth); + printf("Reading headers (%d)...\n", *rsocket[loop]); + } + if (!headers_recv(*rsocket[loop], data[loop])) { + free_rr_data(data[0]); + free_rr_data(data[1]); + rc = (void *)-1; + /* error page */ + goto bailout; + } + } + + /* + * Check whether this new request still talks to the same server as previous. + * If no, return request to caller, he must decide on forward or direct + * approach. + * + * If we're here, previous request loop must have been proxy keep-alive + * (we're looping only if proxy_alive) or this is the first loop since + * we were called. If former, set proxy_alive=1 to cache the connection. + */ + if (loop == 0 && hostname && data[0]->hostname + && strcasecmp(hostname, data[0]->hostname)) { + if (debug) + printf("\n******* F RETURN: %s *******\n", data[0]->url); + if (authok) + proxy_alive = 1; + + rc = dup_rr_data(data[0]); + free_rr_data(data[0]); + free_rr_data(data[1]); + goto bailout; + } + + if (debug) + hlist_dump(data[loop]->headers); + + if (loop == 0 && data[0]->req) + syslog(LOG_DEBUG, "%s %s %s", inet_ntoa(caddr.sin_addr), data[0]->method, data[0]->url); + +shortcut: + /* + * Modify request headers. + * + * Try to request keep-alive for every connection. We keep them in a pool + * for future reuse. + */ + if (loop == 0 && data[0]->req) { + /* + * NTLM-to-Basic + */ + if (http_parse_basic(data[loop]->headers, "Proxy-Authorization", tcreds) > 0) { + if (debug) + printf("NTLM-to-basic: Credentials parsed: %s\\%s at %s\n", tcreds->domain, tcreds->user, tcreds->workstation); + } else if (ntlmbasic) { + if (debug) + printf("NTLM-to-basic: Returning client auth request.\n"); + + tmp = gen_407_page(data[loop]->http); + w = write(cd, tmp, strlen(tmp)); + free(tmp); + + free_rr_data(data[0]); + free_rr_data(data[1]); + rc = (void *)-1; + goto bailout; + } + + /* + * Header replacement implementation + */ + tl = header_list; + while (tl) { + data[0]->headers = hlist_mod(data[0]->headers, tl->key, tl->value, 0); + tl = tl->next; + } + + /* + * Also remove runaway P-A from the client (e.g. Basic from N-t-B), which might + * cause some ISAs to deny us, even if the connection is already auth'd. + */ + data[0]->headers = hlist_mod(data[0]->headers, "Proxy-Connection", "keep-alive", 1); + + /* + * Remove all Proxy-Authorization headers from client + */ + while (hlist_get(data[loop]->headers, "Proxy-Authorization")) { + data[loop]->headers = hlist_del(data[loop]->headers, "Proxy-Authorization"); + } + } + + /* + * Got request from client and connection is not yet authenticated? + * This can happen only with non-cached connections. + */ + if (loop == 0 && data[0]->req && !authok && !noauth) { + if (!proxy_authenticate(wsocket[0], data[0], data[1], tcreds)) { + if (debug) + printf("Proxy auth connection error.\n"); + free_rr_data(data[0]); + free_rr_data(data[1]); + rc = (void *)-1; + /* error page */ + goto bailout; + } + + /* + * !!! data[1] is now filled by proxy_authenticate() !!! + * !!! with proxy's reply to our first (auth) req. !!! + * !!! that's why we reset data[1] below !!! + * + * Reply to auth request wasn't 407? Then auth is not required, + * let's jump into the next loop and forward it to client + * Also just forward if proxy doesn't reply with keep-alive, + * because without it, NTLM auth wouldn't work anyway. + * + * Let's decide proxy doesn't want any auth if it returns a + * non-error reply. Next rounds will be faster. + */ + if (data[1]->code != 407) { // || !hlist_subcmp(data[1]->headers, "Proxy-Connection", "keep-alive")) { + if (debug) + printf("Proxy auth not requested - just forwarding.\n"); + if (data[1]->code < 400) + noauth = 1; + loop = 1; + goto shortcut; + } + + /* + * If we're continuing normally, we have to free possible + * auth response from proxy_authenticate() in data[1] + */ + reset_rr_data(data[1]); + } + + /* + * Is final reply from proxy still 407 denied? If this is a chached + * connection or we thougth proxy was noauth (so we didn't auth), make a new + * connect and try to auth. + */ + if (loop == 1 && data[1]->code == 407 && (was_cached || noauth)) { + if (debug) + printf("\nFinal reply is 407 - retrying (cached=%d, noauth=%d).\n", was_cached, noauth); + if (tcreds) + free(tcreds); + + retry = 1; + request = data[0]; + free_rr_data(data[1]); + close(sd); + goto beginning; + } + + /* + * Was the request first and did we authenticate with proxy? + * Remember not to authenticate this connection any more. + */ + if (loop == 1 && !noauth && data[1]->code != 407) + authok = 1; + + /* + * This is to make the ISA AV scanner bullshit transparent. If the page + * returned is scan-progress-html-fuck instead of requested file/data, parse + * it, wait for completion, make a new request to ISA for the real data and + * substitute the result for the original response html-fuck response. + */ + plugin = PLUG_ALL; + if (loop == 1 && scanner_plugin) { + plugin = scanner_hook(data[0], data[1], tcreds, *wsocket[loop], rsocket[loop], scanner_plugin_maxsize); + } + + /* + * Check if we should loop for another request. Required for keep-alive + * connections, client might really need a non-interrupted conversation. + * + * We check only server reply for keep-alive, because client may want it, + * but it's not gonna happen unless server agrees. + */ + if (loop == 1) { + conn_alive = hlist_subcmp(data[1]->headers, "Connection", "keep-alive"); + if (!conn_alive) + data[1]->headers = hlist_mod(data[1]->headers, "Connection", "close", 1); + + /* + * Remove all Proxy-Authenticate headers from proxy + */ + while (hlist_get(data[loop]->headers, "Proxy-Authenticate")) { + data[loop]->headers = hlist_del(data[loop]->headers, "Proxy-Authenticate"); + } + + /* + * Are we returning 407 to the client? Substitute his request + * by our BASIC translation request. + */ + if (data[1]->code == 407) { + data[1]->headers = hlist_mod(data[1]->headers, "Proxy-Authenticate", "Basic realm=\"Cntlm for parent\"", 1); + } + } + + if (plugin & PLUG_SENDHEAD) { + if (debug) { + printf("Sending headers (%d)...\n", *wsocket[loop]); + if (loop == 0) + hlist_dump(data[loop]->headers); + } + + /* + * Forward client's headers to the proxy and vice versa; proxy_authenticate() + * might have by now prepared 1st and 2nd auth steps and filled our headers with + * the 3rd, final, NTLM message. + */ + if (!headers_send(*wsocket[loop], data[loop])) { + free_rr_data(data[0]); + free_rr_data(data[1]); + rc = (void *)-1; + /* error page */ + goto bailout; + } + } + + /* + * Was the request CONNECT and proxy agreed? + */ + if (loop == 1 && CONNECT(data[0]) && data[1]->code == 200) { + if (debug) + printf("Ok CONNECT response. Tunneling...\n"); + + tunnel(cd, sd); + free_rr_data(data[0]); + free_rr_data(data[1]); + rc = (void *)-1; + goto bailout; + } + + if (plugin & PLUG_SENDDATA) { + if (!http_body_send(*wsocket[loop], *rsocket[loop], data[0], data[1])) { + free_rr_data(data[0]); + free_rr_data(data[1]); + rc = (void *)-1; + goto bailout; + } + } + + /* + * Proxy-Connection: keep-alive is taken care of in our caller as I said, + * but we do return when we see proxy is closing. Next headers_recv() would + * fail and we'd exit anyway. + * + * This way, we also tell our caller that proxy keep-alive is impossible. + */ + if (loop == 1) { + proxy_alive = hlist_subcmp(data[loop]->headers, "Proxy-Connection", "keep-alive"); + if (!proxy_alive) { + if (debug) + printf("PROXY CLOSING CONNECTION\n"); + rc = (void *)-1; + } + } + } + + free_rr_data(data[0]); + free_rr_data(data[1]); + + /* + * Checking conn_alive && proxy_alive is sufficient, + * so_closed() just eliminates loops that we know would fail. + */ + } while (conn_alive && proxy_alive && !so_closed(sd) && !so_closed(cd) && !serialize); + +bailout: + if (hostname) + free(hostname); + + if (debug) { + printf("forward_request: palive=%d, authok=%d, ntlm=%d, closed=%d\n", proxy_alive, authok, ntlmbasic, so_closed(sd)); + printf("\nThread finished.\n"); + } + + if (proxy_alive && authok && !ntlmbasic && !so_closed(sd)) { + if (debug) + printf("Storing the connection for reuse (%d:%d).\n", cd, sd); + pthread_mutex_lock(&connection_mtx); + connection_list = plist_add(connection_list, sd, (void *)tcreds); + pthread_mutex_unlock(&connection_mtx); + } else { + free(tcreds); + close(sd); + } + + return rc; +} + +/* + * Auth connection "sd" and try to return negotiated CONNECT + * connection to a remote host:port (thost). + * + * Return 1 for success, 0 failure. + */ +int prepare_http_connect(int sd, struct auth_s *credentials, const char *thost) { + rr_data_t data1, data2; + int rc = 0; + hlist_t tl; + + if (!sd || !thost || !strlen(thost)) + return 0; + + data1 = new_rr_data(); + data2 = new_rr_data(); + + data1->req = 1; + data1->method = strdup("CONNECT"); + data1->url = strdup(thost); + data1->http = strdup("1"); + data1->headers = hlist_mod(data1->headers, "Proxy-Connection", "keep-alive", 1); + + /* + * Header replacement + */ + tl = header_list; + while (tl) { + data1->headers = hlist_mod(data1->headers, tl->key, tl->value, 1); + tl = tl->next; + } + + if (debug) + printf("Starting authentication...\n"); + + if (proxy_authenticate(&sd, data1, data2, credentials)) { + /* + * Let's try final auth step, possibly changing data2->code + */ + if (data2->code == 407) { + if (debug) { + printf("Sending real request:\n"); + hlist_dump(data1->headers); + } + if (!headers_send(sd, data1)) { + printf("Sending request failed!\n"); + goto bailout; + } + + if (debug) + printf("\nReading real response:\n"); + reset_rr_data(data2); + if (!headers_recv(sd, data2)) { + if (debug) + printf("Reading response failed!\n"); + goto bailout; + } + if (debug) + hlist_dump(data2->headers); + } + + if (data2->code == 200) { + if (debug) + printf("Ok CONNECT response. Tunneling...\n"); + rc = 1; + } else if (data2->code == 407) { + syslog(LOG_ERR, "Authentication for tunnel %s failed!\n", thost); + } else { + syslog(LOG_ERR, "Request for CONNECT to %s denied!\n", thost); + } + } else + syslog(LOG_ERR, "Tunnel requests failed!\n"); + +bailout: + free_rr_data(data1); + free_rr_data(data2); + + return rc; +} + +void forward_tunnel(void *thread_data) { + struct auth_s *tcreds; + int sd; + + int cd = ((struct thread_arg_s *)thread_data)->fd; + char *thost = ((struct thread_arg_s *)thread_data)->target; + struct sockaddr_in caddr = ((struct thread_arg_s *)thread_data)->addr; + + tcreds = new_auth(); + sd = proxy_connect(tcreds); + + if (sd <= 0) + goto bailout; + + syslog(LOG_DEBUG, "%s TUNNEL %s", inet_ntoa(caddr.sin_addr), thost); + if (debug) + printf("Tunneling to %s for client %d...\n", thost, cd); + + if (prepare_http_connect(sd, tcreds, thost)) + tunnel(cd, sd); + +bailout: + close(sd); + close(cd); + free(tcreds); + + return; +} + +#define MAGIC_TESTS 4 + +void magic_auth_detect(const char *url) { + int i, nc, c, ign = 0, found = -1; + rr_data_t req, res; + char *tmp, *pos, *host = NULL; + + struct auth_s *tcreds; + char *authstr[5] = { "NTLMv2", "NTLM2SR", "NT", "NTLM", "LM" }; + int prefs[MAGIC_TESTS][5] = { + /* NT, LM, NTLMv2, Flags, index to authstr[] */ + { 0, 0, 1, 0, 0 }, + { 1, 1, 0, 0, 3 }, + { 0, 1, 0, 0, 4 }, + { 2, 0, 0, 0, 1 } + }; + + tcreds = new_auth(); + copy_auth(tcreds, g_creds, /* fullcopy */ 1); + + if (!tcreds->passnt || !tcreds->passlm || !tcreds->passntlm2) { + printf("Cannot detect NTLM dialect - password or all its hashes must be defined, try -I\n"); + exit(1); + } + + pos = strstr(url, "://"); + if (pos) { + tmp = strchr(pos+3, '/'); + host = substr(pos+3, 0, tmp ? tmp-pos-3 : 0); + } else { + fprintf(stderr, "Invalid URL (%s)\n", url); + return; + } + + for (i = 0; i < MAGIC_TESTS; ++i) { + res = new_rr_data(); + req = new_rr_data(); + + req->req = 1; + req->method = strdup("GET"); + req->url = strdup(url); + req->http = strdup("1"); + req->headers = hlist_add(req->headers, "Proxy-Connection", "keep-alive", HLIST_ALLOC, HLIST_ALLOC); + if (host) + req->headers = hlist_add(req->headers, "Host", host, HLIST_ALLOC, HLIST_ALLOC); + + tcreds->hashnt = prefs[i][0]; + tcreds->hashlm = prefs[i][1]; + tcreds->hashntlm2 = prefs[i][2]; + tcreds->flags = prefs[i][3]; + + printf("Config profile %2d/%d... ", i+1, MAGIC_TESTS); + + nc = proxy_connect(NULL); + if (nc <= 0) { + printf("\nConnection to proxy failed, bailing out\n"); + free_rr_data(res); + free_rr_data(req); + close(nc); + if (host) + free(host); + return; + } + + c = proxy_authenticate(&nc, req, res, tcreds); + if (c && res->code != 407) { + ign++; + printf("Auth not required (HTTP code: %d)\n", res->code); + free_rr_data(res); + free_rr_data(req); + close(nc); + continue; + } + + reset_rr_data(res); + if (!headers_send(nc, req) || !headers_recv(nc, res)) { + printf("Connection closed\n"); + } else { + if (res->code == 407) { + if (hlist_subcmp_all(res->headers, "Proxy-Authenticate", "NTLM") || hlist_subcmp_all(res->headers, "Proxy-Authenticate", "BASIC")) { + printf("Credentials rejected\n"); + } else { + printf("Proxy doesn't offer NTLM or BASIC\n"); + break; + } + } else { + printf("OK (HTTP code: %d)\n", res->code); + if (found < 0) { + found = i; + free_rr_data(res); + free_rr_data(req); + close(nc); + break; + } + } + } + + free_rr_data(res); + free_rr_data(req); + close(nc); + } + + if (found > -1) { + printf("----------------------------[ Profile %2d ]------\n", found); + printf("Auth %s\n", authstr[prefs[found][4]]); + if (prefs[found][3]) + printf("Flags 0x%x\n", prefs[found][3]); + if (prefs[found][0]) { + printf("PassNT %s\n", tmp=printmem(tcreds->passnt, 16, 8)); + free(tmp); + } + if (prefs[found][1]) { + printf("PassLM %s\n", tmp=printmem(tcreds->passlm, 16, 8)); + free(tmp); + } + if (prefs[found][2]) { + printf("PassNTLMv2 %s\n", tmp=printmem(tcreds->passntlm2, 16, 8)); + free(tmp); + } + printf("------------------------------------------------\n"); + } else if (ign == MAGIC_TESTS) { + printf("\nYour proxy is open, you don't need another proxy.\n"); + } else + printf("\nWrong credentials, invalid URL or proxy doesn't support NTLM nor BASIC.\n"); + + if (host) + free(host); +} + diff -Nru cntlm-0.35.1/forward.h cntlm-0.91~rc6/forward.h --- cntlm-0.35.1/forward.h 1970-01-01 00:00:00.000000000 +0000 +++ cntlm-0.91~rc6/forward.h 2010-04-21 11:56:47.000000000 +0000 @@ -0,0 +1,33 @@ +/* + * CNTLM is free software; you can redistribute it and/or modify it under the + * terms of the GNU General Public License as published by the Free Software + * Foundation; either version 2 of the License, or (at your option) any later + * version. + * + * CNTLM is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., 51 Franklin + * St, Fifth Floor, Boston, MA 02110-1301, USA. + * + * Copyright (c) 2007 David Kubicek + * + */ + +#ifndef _FORWARD_H +#define _FORWARD_H + +#include "utils.h" +#include "auth.h" + +extern int proxy_connect(struct auth_s *credentials); +extern int proxy_authenticate(int *sd, rr_data_t request, rr_data_t response, struct auth_s *creds); +extern int prepare_http_connect(int sd, struct auth_s *credentials, const char *thost); +extern rr_data_t forward_request(void *cdata, rr_data_t request); +extern void forward_tunnel(void *thread_data); +extern void magic_auth_detect(const char *url); + +#endif /* _FORWARD_H */ diff -Nru cntlm-0.35.1/globals.h cntlm-0.91~rc6/globals.h --- cntlm-0.35.1/globals.h 1970-01-01 00:00:00.000000000 +0000 +++ cntlm-0.91~rc6/globals.h 2010-03-28 23:58:51.000000000 +0000 @@ -0,0 +1,65 @@ +/* + * CNTLM is free software; you can redistribute it and/or modify it under the + * terms of the GNU General Public License as published by the Free Software + * Foundation; either version 2 of the License, or (at your option) any later + * version. + * + * CNTLM is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., 51 Franklin + * St, Fifth Floor, Boston, MA 02110-1301, USA. + * + * Copyright (c) 2007 David Kubicek + * + */ + +/* + * These are globals, mostly run-time options, defined and setup in main module + * proxy.c + */ + +#ifndef _GLOBALS_H +#define _GLOBALS_H + +#include + +#include "utils.h" +#include "auth.h" + +extern int debug; + +extern struct auth_s *g_creds; /* global NTLM credentials */ + +extern int ntlmbasic; /* forward_request() */ +extern int serialize; +extern int scanner_plugin; +extern long scanner_plugin_maxsize; + +extern plist_t threads_list; +extern pthread_mutex_t threads_mtx; + +extern plist_t connection_list; +extern pthread_mutex_t connection_mtx; + +extern int parent_count; +extern plist_t parent_list; + +/* + * just malloc/free sizeof(proxy_t) + */ +typedef struct { + struct auth_s creds; + struct in_addr host; + int port; +} proxy_t; + +extern hlist_t header_list; /* forward_request() */ +extern hlist_t users_list; /* socks5_thread() */ +extern plist_t scanner_agent_list; /* scanner_hook() */ +extern plist_t noproxy_list; /* proxy_thread() */ + +#endif /* _GLOBALS_H */ diff -Nru cntlm-0.35.1/http.c cntlm-0.91~rc6/http.c --- cntlm-0.35.1/http.c 2007-10-31 18:48:15.000000000 +0000 +++ cntlm-0.91~rc6/http.c 2010-04-20 12:21:39.000000000 +0000 @@ -19,41 +19,82 @@ * */ +#include +#include +#include +#include #include #include #include #include #include #include -#include -#include -#include #include "utils.h" #include "socket.h" +#include "ntlm.h" +#include "http.h" #define BLOCK 2048 extern int debug; /* + * Ture if src is a header. This is just a basic check + * for the colon delimiter. Might eventually become more + * sophisticated. :) + */ +int is_http_header(const char *src) { + return strcspn(src, ":") != strlen(src); +} + +/* + * Extract the header name from the source. + */ +char *get_http_header_name(const char *src) { + int i; + + i = strcspn(src, ":"); + if (i != strlen(src)) + return substr(src, 0, i); + else + return NULL; +} + +/* + * Extract the header value from the source. + */ +char *get_http_header_value(const char *src) { + char *sub; + + if ((sub = strchr(src, ':'))) { + sub++; + while (*sub == ' ') + sub++; + + return strdup(sub); + } else + return NULL; +} + +/* * Receive HTTP request/response from the given socket. Fill in pre-allocated * rr_data_t structure. * Returns: 1 if OK, 0 in case of socket EOF or other error */ int headers_recv(int fd, rr_data_t data) { - char *tok, *s3 = 0; + int i, bsize; int len; char *buf; + char *tok, *s3 = 0; + char *orig = NULL; char *ccode = NULL; char *host = NULL; - int i, bsize; bsize = BUFSIZE; buf = new(bsize); i = so_recvln(fd, &buf, &bsize); - if (i <= 0) goto bailout; @@ -64,10 +105,12 @@ * Are we reading HTTP request (from client) or response (from server)? */ trimr(buf); + orig = strdup(buf); len = strlen(buf); tok = strtok_r(buf, " ", &s3); if (!strncasecmp(buf, "HTTP/", 5) && tok) { data->req = 0; + data->empty = 0; data->http = NULL; data->msg = NULL; @@ -88,14 +131,17 @@ data->msg = strdup(""); if (!ccode || strlen(ccode) != 3 || (data->code = atoi(ccode)) == 0 || !data->http) { - i = -1; + i = -2; goto bailout; } - } else if (tok) { + } else if (strstr(orig, " HTTP/") && tok) { data->req = 1; + data->empty = 0; data->method = NULL; data->url = NULL; + data->rel_url = NULL; data->http = NULL; + data->hostname = NULL; data->method = strdup(tok); @@ -108,19 +154,29 @@ data->http = substr(tok, 7, 1); if (!data->url || !data->http) { - i = -1; + i = -3; goto bailout; } - tok = strstr(data->url, "://"); - if (tok) { - s3 = strchr(tok+3, '/'); - host = substr(tok+3, 0, s3 ? s3-tok-3 : 0); + if ((tok = strstr(data->url, "://"))) { + tok += 3; + } else { + tok = data->url; } + + s3 = strchr(tok, '/'); + if (s3) { + host = substr(tok, 0, s3-tok); + data->rel_url = strdup(s3); + } else { + host = substr(tok, 0, strlen(tok)); + data->rel_url = strdup("/"); + } + } else { if (debug) - printf("headers_recv: Unknown header (%s).\n", buf); - i = -1; + printf("headers_recv: Unknown header (%s).\n", orig); + i = -4; goto bailout; } @@ -130,22 +186,54 @@ do { i = so_recvln(fd, &buf, &bsize); trimr(buf); - if (i > 0 && head_ok(buf)) { - data->headers = hlist_add(data->headers, head_name(buf), head_value(buf), 0, 0); + if (i > 0 && is_http_header(buf)) { + data->headers = hlist_add(data->headers, get_http_header_name(buf), get_http_header_value(buf), HLIST_NOALLOC, HLIST_NOALLOC); } } while (strlen(buf) != 0 && i > 0); - if (host && !hlist_in(data->headers, "Host")) - data->headers = hlist_add(data->headers, "Host", host, 1, 1); + if (data->req) { + /* + * Fix requests, make sure the Host: header is present + */ + if (host && strlen(host)) { + data->hostname = strdup(host); + if (!hlist_get(data->headers, "Host")) + data->headers = hlist_add(data->headers, "Host", host, HLIST_ALLOC, HLIST_ALLOC); + } else { + if (debug) + printf("headers_recv: no host name (%s)\n", orig); + i = -6; + goto bailout; + } + + /* + * Remove port number from internal host name variable + */ + if (data->hostname && (tok = strchr(data->hostname, ':'))) { + *tok = 0; + data->port = atoi(tok+1); + } else if (data->url) { + if (!strncasecmp(data->url, "https", 5)) + data->port = 443; + else + data->port = 80; + } + + if (!strlen(data->hostname) || !data->port) { + i = -5; + goto bailout; + } + } bailout: + if (orig) free(orig); if (ccode) free(ccode); if (host) free(host); free(buf); if (i <= 0) { if (debug) - printf("headers_recv: fd %d warning %d (connection closed)\n", fd, i); + printf("headers_recv: fd %d error %d\n", fd, i); return 0; } @@ -223,35 +311,9 @@ } /* - * Connection cleanup - discard "size" of incomming data. - */ -int data_drop(int src, int size) { - char *buf; - int i, block, c = 0; - - if (!size) - return 1; - - buf = new(BLOCK); - do { - block = (size-c > BLOCK ? BLOCK : size-c); - i = read(src, buf, block); - c += i; - } while (i > 0 && c < size); - - free(buf); - if (i <= 0) { - if (debug) - printf("data_drop: fd %d warning %d (connection closed)\n", src, i); - return 0; - } - - return 1; -} - -/* * Forward "size" of data from "src" to "dst". If size == -1 then keep * forwarding until src reaches EOF. + * If dst == -1, data is discarded. */ int data_send(int dst, int src, int size) { char *buf; @@ -271,17 +333,16 @@ if (i > 0) c += i; - if (debug) + if (dst >= 0 && debug) printf("data_send: read %d of %d / %d of %d (errno = %s)\n", i, block, c, size, i < 0 ? strerror(errno) : "ok"); - if (so_closed(dst)) { + if (dst >= 0 && so_closed(dst)) { i = -999; break; } - if (i > 0) { + if (dst >= 0 && i > 0) { j = write(dst, buf, i); - if (debug) printf("data_send: wrote %d of %d\n", j, i); } @@ -304,11 +365,12 @@ /* * Forward chunked HTTP body from "src" descriptor to "dst". + * If dst == -1, data is discarded. */ int chunked_data_send(int dst, int src) { char *buf; int bsize; - int i, csize; + int i, w, csize; char *err = NULL; @@ -325,35 +387,18 @@ return 0; } - if (debug) - printf("Line: %s", buf); - - /* - printf("*buf = "); - for (i = 0; i < 100; i++) { - printf("%02x ", buf[i]); - if (i % 8 == 7) - printf("\n "); - } - printf("\n"); - */ - csize = strtol(buf, &err, 16); - if (debug) - printf("strtol: %d (%x) - err: %s\n", csize, csize, err); - - if (*err != '\r' && *err != '\n' && *err != ';' && *err != ' ' && *err != '\t') { + if (!isspace(*err) && *err != ';') { if (debug) printf("chunked_data_send: aborting, chunk size format error\n"); free(buf); return 0; } - if (debug && !csize) - printf("last chunk: %d\n", csize); + if (dst >= 0) + i = write(dst, buf, strlen(buf)); - write(dst, buf, strlen(buf)); if (csize) if (!data_send(dst, src, csize+2)) { if (debug) @@ -362,16 +407,13 @@ free(buf); return 0; } - } while (csize != 0); /* Take care of possible trailer */ do { i = so_recvln(src, &buf, &bsize); - if (debug) - printf("Trailer header(i=%d): %s\n", i, buf); - if (i > 0) - write(dst, buf, strlen(buf)); + if (dst >= 0 && i > 0) + w = write(dst, buf, strlen(buf)); } while (i > 0 && buf[0] != '\r' && buf[0] != '\n'); free(buf); @@ -410,7 +452,7 @@ ret = read(from, buf, BUFSIZE); if (ret > 0) { ret = write(to, buf, ret); - } if (ret <= 0) { + } else { free(buf); return (ret == 0); } @@ -424,3 +466,182 @@ return 1; } +/* + * Return 0 if no body, -1 if body until EOF, number if size known + * One of request/response can be NULL + */ +int http_has_body(rr_data_t request, rr_data_t response) { + rr_data_t current; + int length, nobody; + char *tmp; + + /* + * Are we checking a complete req+res conversation or just the + * request body? + */ + current = (!response || response->empty ? request : response); + + /* + * HTTP body length decisions. There MUST NOT be any body from + * server if the request was HEAD or reply is 1xx, 204 or 304. + * No body can be in GET request if direction is from client. + */ + if (current == response) { + nobody = (HEAD(request) || + (response->code >= 100 && response->code < 200) || + response->code == 204 || + response->code == 304); + } else { + nobody = GET(request) || HEAD(request); + } + + /* + * Otherwise consult Content-Length. If present, we forward exaclty + * that many bytes. + * + * If not present, but there is Transfer-Encoding or Content-Type + * (or a request to close connection, that is, end of data is signaled + * by remote close), we will forward until EOF. + * + * No C-L, no T-E, no C-T == no body. + */ + tmp = hlist_get(current->headers, "Content-Length"); + if (!nobody && tmp == NULL && (hlist_in(current->headers, "Content-Type") + || hlist_in(current->headers, "Transfer-Encoding") + || hlist_subcmp(current->headers, "Connection", "close"))) { + // || (response->code == 200) + if (hlist_in(current->headers, "Transfer-Encoding") + && hlist_subcmp(current->headers, "Transfer-Encoding", "chunked")) + length = 1; + else + length = -1; + } else + length = (tmp == NULL || nobody ? 0 : atol(tmp)); + + return length; +} + +/* + * Send a HTTP body (if any) between descriptors readfd and writefd + */ +int http_body_send(int writefd, int readfd, rr_data_t request, rr_data_t response) { + int bodylen; + int rc = 1; + rr_data_t current; + + /* + * Are we checking a complete req+res conversation or just the + * request body? + */ + current = (response->empty ? request : response); + + /* + * Ok, so do we expect any body? + */ + bodylen = http_has_body(request, response); + if (bodylen) { + /* + * Check for supported T-E. + */ + if (hlist_subcmp(current->headers, "Transfer-Encoding", "chunked")) { + if (debug) + printf("Chunked body included.\n"); + + rc = chunked_data_send(writefd, readfd); + if (debug) + printf(rc ? "Chunked body sent.\n" : "Could not chunk send whole body\n"); + } else { + if (debug) + printf("Body included. Lenght: %d\n", bodylen); + + rc = data_send(writefd, readfd, bodylen); + if (debug) + printf(rc ? "Body sent.\n" : "Could not send whole body\n"); + } + } else if (debug) + printf("No body.\n"); + + return rc; +} + +/* + * Connection cleanup - C-L or chunked body + * Return 0 if connection closed or EOF, 1 if OK to continue + */ +int http_body_drop(int fd, rr_data_t response) { + int bodylen, rc = 1; + + bodylen = http_has_body(NULL, response); + if (bodylen) { + if (hlist_subcmp(response->headers, "Transfer-Encoding", "chunked")) { + if (debug) + printf("Discarding chunked body.\n"); + rc = chunked_data_send(-1, fd); + } else { + if (debug) + printf("Discarding %d bytes.\n", bodylen); + rc = data_send(-1, fd, bodylen); + } + } + + return rc; +} + +/* + * Parse headers for BASIC auth credentials + * + * Return 1 = creds parsed OK, 0 = no creds, -1 = invalid creds + */ +int http_parse_basic(hlist_t headers, const char *header, struct auth_s *tcreds) { + char *tmp = NULL, *pos = NULL, *buf = NULL, *dom = NULL; + int i; + + if (!hlist_subcmp(headers, header, "basic")) + return 0; + + tmp = hlist_get(headers, header); + buf = new(strlen(tmp) + 1); + i = 5; + while (i < strlen(tmp) && tmp[++i] == ' '); + from_base64(buf, tmp+i); + pos = strchr(buf, ':'); + + if (pos == NULL) { + memset(buf, 0, strlen(buf)); /* clean password memory */ + free(buf); + return -1; + } else { + *pos = 0; + dom = strchr(buf, '\\'); + if (dom == NULL) { + auth_strcpy(tcreds, user, buf); + } else { + *dom = 0; + auth_strcpy(tcreds, domain, buf); + auth_strcpy(tcreds, user, dom+1); + } + + if (tcreds->hashntlm2) { + tmp = ntlm2_hash_password(tcreds->user, tcreds->domain, pos+1); + auth_memcpy(tcreds, passntlm2, tmp, 16); + free(tmp); + } + + if (tcreds->hashnt) { + tmp = ntlm_hash_nt_password(pos+1); + auth_memcpy(tcreds, passnt, tmp, 21); + free(tmp); + } + + if (tcreds->hashlm) { + tmp = ntlm_hash_lm_password(pos+1); + auth_memcpy(tcreds, passlm, tmp, 21); + free(tmp); + } + + memset(buf, 0, strlen(buf)); + free(buf); + } + + return 1; +} diff -Nru cntlm-0.35.1/http.h cntlm-0.91~rc6/http.h --- cntlm-0.35.1/http.h 2007-10-31 18:48:15.000000000 +0000 +++ cntlm-0.91~rc6/http.h 2010-04-20 12:21:39.000000000 +0000 @@ -23,12 +23,24 @@ #define _HTTP_H #include "utils.h" +#include "auth.h" +/* + * A couple of shortcuts for if statements + */ +#define CONNECT(data) ((data) && (data)->req && !strcasecmp("CONNECT", (data)->method)) +#define HEAD(data) ((data) && (data)->req && !strcasecmp("HEAD", (data)->method)) +#define GET(data) ((data) && (data)->req && !strcasecmp("GET", (data)->method)) + +extern int is_http_header(const char *src); +extern char *get_http_header_name(const char *src); +extern char *get_http_header_value(const char *src); +extern int http_parse_basic(hlist_t headers, const char *header, struct auth_s *tcreds); extern int headers_recv(int fd, rr_data_t data); extern int headers_send(int fd, rr_data_t data); -extern int data_drop(int src, int size); -extern int data_send(int dst, int src, int size); -extern int chunked_data_send(int dst, int src); extern int tunnel(int cd, int sd); +extern int http_has_body(rr_data_t request, rr_data_t response); +extern int http_body_send(int writefd, int readfd, rr_data_t request, rr_data_t response); +extern int http_body_drop(int fd, rr_data_t response); #endif /* _HTTP_H */ diff -Nru cntlm-0.35.1/main.c cntlm-0.91~rc6/main.c --- cntlm-0.35.1/main.c 1970-01-01 00:00:00.000000000 +0000 +++ cntlm-0.91~rc6/main.c 2010-04-21 11:56:47.000000000 +0000 @@ -0,0 +1,1612 @@ +/* + * This is the main module of the CNTLM + * + * CNTLM is free software; you can redistribute it and/or modify it under the + * terms of the GNU General Public License as published by the Free Software + * Foundation; either version 2 of the License, or (at your option) any later + * version. + * + * CNTLM is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., 51 Franklin + * St, Fifth Floor, Boston, MA 02110-1301, USA. + * + * Copyright (c) 2007 David Kubicek + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * Some helping routines like linked list manipulation substr(), memory + * allocation, NTLM authentication routines, etc. + */ +#include "config/config.h" +#include "socket.h" +#include "utils.h" +#include "ntlm.h" +#include "swap.h" +#include "config.h" +#include "acl.h" +#include "auth.h" +#include "http.h" +#include "globals.h" +#include "pages.h" +#include "forward.h" /* code serving via parent proxy */ +#include "direct.h" /* code serving directly without proxy */ + +#define STACK_SIZE sizeof(void *)*8*1024 + +/* + * Global "read-only" data initialized in main(). Comments list funcs. which use + * them. Having these global avoids the need to pass them to each thread and + * from there again a few times to inner calls. + */ +int debug = 0; /* all debug printf's and possibly external modules */ + +struct auth_s *g_creds = NULL; /* throughout the whole module */ + +int quit = 0; /* sighandler() */ +int ntlmbasic = 0; /* forward_request() */ +int serialize = 0; +int scanner_plugin = 0; +long scanner_plugin_maxsize = 0; + +/* + * List of finished threads. Each forward_request() thread adds itself to it when + * finished. Main regularly joins and removes all tid's in there. + */ +plist_t threads_list = NULL; +pthread_mutex_t threads_mtx = PTHREAD_MUTEX_INITIALIZER; + +/* + * List of cached connections. Accessed by each thread forward_request(). + */ +plist_t connection_list = NULL; +pthread_mutex_t connection_mtx = PTHREAD_MUTEX_INITIALIZER; + +/* + * List of available proxies and current proxy id for proxy_connect(). + */ +int parent_count = 0; +plist_t parent_list = NULL; + +/* + * List of custom header substitutions, SOCKS5 proxy users and + * UserAgents for the scanner plugin. + */ +hlist_t header_list = NULL; /* forward_request() */ +hlist_t users_list = NULL; /* socks5_thread() */ +plist_t scanner_agent_list = NULL; /* scanner_hook() */ +plist_t noproxy_list = NULL; /* proxy_thread() */ + +/* + * General signal handler. If in debug mode, quit immediately. + */ +void sighandler(int p) { + if (!quit) + syslog(LOG_INFO, "Signal %d received, issuing clean shutdown\n", p); + else + syslog(LOG_INFO, "Signal %d received, forcing shutdown\n", p); + + if (quit++ || debug) + quit++; +} + +/* + * Parse proxy parameter and add it to the global list. + */ +int parent_add(char *parent, int port) { + int len, i; + char *proxy; + proxy_t *aux; + struct in_addr host; + + /* + * Check format and parse it. + */ + proxy = strdup(parent); + len = strlen(proxy); + i = strcspn(proxy, ": "); + if (i != len) { + proxy[i++] = 0; + while (i < len && (proxy[i] == ' ' || proxy[i] == '\t')) + i++; + + if (i >= len) { + free(proxy); + return 0; + } + + port = atoi(proxy+i); + } + + /* + * No port argument and not parsed from proxy? + */ + if (!port) { + syslog(LOG_ERR, "Invalid proxy specification %s.\n", parent); + free(proxy); + myexit(1); + } + + /* + * Try to resolve proxy address + */ + if (debug) + syslog(LOG_INFO, "Resolving proxy %s...\n", proxy); + if (!so_resolv(&host, proxy)) { + syslog(LOG_ERR, "Cannot resolve proxy %s, discarding.\n", parent); + free(proxy); + return 0; + } + + aux = (proxy_t *)new(sizeof(proxy_t)); + aux->host = host; + aux->port = port; + parent_list = plist_add(parent_list, ++parent_count, (char *)aux); + + free(proxy); + return 1; +} + +/* + * Register and bind new proxy service port. + */ +void listen_add(const char *service, plist_t *list, char *spec, int gateway) { + struct in_addr source; + int i, p, len, port; + char *tmp; + + len = strlen(spec); + p = strcspn(spec, ":"); + if (p < len-1) { + tmp = substr(spec, 0, p); + if (!so_resolv(&source, tmp)) { + syslog(LOG_ERR, "Cannot resolve listen address %s\n", tmp); + myexit(1); + } + free(tmp); + port = atoi(tmp = spec+p+1); + } else { + source.s_addr = htonl(gateway ? INADDR_ANY : INADDR_LOOPBACK); + port = atoi(tmp = spec); + } + + if (!port) { + syslog(LOG_ERR, "Invalid listen port %s.\n", tmp); + myexit(1); + } + + i = so_listen(port, source); + if (i > 0) { + *list = plist_add(*list, i, NULL); + syslog(LOG_INFO, "%s listening on %s:%d\n", service, inet_ntoa(source), port); + } +} + +/* + * Register a new tunnel definition, bind service port. + */ +void tunnel_add(plist_t *list, char *spec, int gateway) { + struct in_addr source; + int i, len, count, pos, port; + char *field[4]; + char *tmp; + + spec = strdup(spec); + len = strlen(spec); + field[0] = spec; + for (count = 1, i = 0; i < len; ++i) + if (spec[i] == ':') { + spec[i] = 0; + field[count++] = spec+i+1; + } + + pos = 0; + if (count == 4) { + if (!so_resolv(&source, field[pos])) { + syslog(LOG_ERR, "Cannot resolve tunel bind address: %s\n", field[pos]); + myexit(1); + } + pos++; + } else + source.s_addr = htonl(gateway ? INADDR_ANY : INADDR_LOOPBACK); + + if (count-pos == 3) { + port = atoi(field[pos]); + if (port == 0) { + syslog(LOG_ERR, "Invalid tunnel local port: %s\n", field[pos]); + myexit(1); + } + + if (!strlen(field[pos+1]) || !strlen(field[pos+2])) { + syslog(LOG_ERR, "Invalid tunnel target: %s:%s\n", field[pos+1], field[pos+2]); + myexit(1); + } + + tmp = new(strlen(field[pos+1]) + strlen(field[pos+2]) + 2 + 1); + strcpy(tmp, field[pos+1]); + strcat(tmp, ":"); + strcat(tmp, field[pos+2]); + + i = so_listen(port, source); + if (i > 0) { + *list = plist_add(*list, i, tmp); + syslog(LOG_INFO, "New tunnel from %s:%d to %s\n", inet_ntoa(source), port, tmp); + } else + free(tmp); + } else { + printf("Tunnel specification incorrect ([laddress:]lport:rserver:rport).\n"); + myexit(1); + } + + free(spec); +} + +/* + * Add no-proxy hostname/IP + */ +plist_t noproxy_add(plist_t list, char *spec) { + char *tok, *save; + + tok = strtok_r(spec, ", ", &save); + while ( tok != NULL ) { + if (debug) + printf("Adding no-proxy for: '%s'\n", tok); + list = plist_add(list, 0, strdup(tok)); + tok = strtok_r(NULL, ", ", &save); + } + + return list; +} + +int noproxy_match(const char *addr) { + plist_t list; + + list = noproxy_list; + while (list) { + if (list->aux && strlen(list->aux) + && fnmatch(list->aux, addr, 0) == 0) { + if (debug) + printf("MATCH: %s (%s)\n", addr, (char *)list->aux); + return 1; + } else if (debug) + printf(" NO: %s (%s)\n", addr, (char *)list->aux); + + list = list->next; + } + + return 0; +} + +/* + * Proxy thread - decide between direct and forward based on NoProxy + */ +void *proxy_thread(void *thread_data) { + rr_data_t request, ret; + int keep_alive; /* Proxy-Connection */ + + int cd = ((struct thread_arg_s *)thread_data)->fd; + + do { + ret = NULL; + keep_alive = 0; + + if (debug) { + printf("\n******* Round 1 C: %d *******\n", cd); + printf("Reading headers (%d)...\n", cd); + } + + request = new_rr_data(); + if (!headers_recv(cd, request)) { + free_rr_data(request); + break; + } + + do { + /* + * Are we being returned a request by forward_request or direct_request? + */ + if (ret) { + free_rr_data(request); + request = ret; + } + + keep_alive = hlist_subcmp(request->headers, "Proxy-Connection", "keep-alive"); + + if (noproxy_match(request->hostname)) + ret = direct_request(thread_data, request); + else + ret = forward_request(thread_data, request); + + if (debug) + printf("proxy_thread: request rc = %x\n", (int)ret); + } while (ret != NULL && ret != (void *)-1); + + free_rr_data(request); + /* + * If client asked for proxy keep-alive, loop unless the last server response + * requested (Proxy-)Connection: close. + */ + } while (keep_alive && ret != (void *)-1 && !serialize); + + /* + * Add ourselves to the "threads to join" list. + */ + if (!serialize) { + pthread_mutex_lock(&threads_mtx); + threads_list = plist_add(threads_list, (unsigned long)pthread_self(), NULL); + pthread_mutex_unlock(&threads_mtx); + } + + free(thread_data); + close(cd); + + return NULL; +} + +/* + * Tunnel/port forward thread - this method is obviously better solution than using extra + * tools like "corkscrew" which after all require us for authentication and tunneling + * their HTTP CONNECT in the first place. + */ +void *tunnel_thread(void *thread_data) { + char *hostname, *pos; + char *thost = ((struct thread_arg_s *)thread_data)->target; + + hostname = strdup(thost); + if ((pos = strchr(hostname, ':')) != NULL) + *pos = 0; + + if (noproxy_match(hostname)) + direct_tunnel(thread_data); + else + forward_tunnel(thread_data); + + free(hostname); + free(thread_data); + + /* + * Add ourself to the "threads to join" list. + */ + pthread_mutex_lock(&threads_mtx); + threads_list = plist_add(threads_list, (unsigned long)pthread_self(), NULL); + pthread_mutex_unlock(&threads_mtx); + + return NULL; +} + +/* + * SOCKS5 thread + */ +void *socks5_thread(void *thread_data) { + char *tmp, *thost, *tport, *uname, *upass; + unsigned short port; + int ver, r, c, i, w; + + struct auth_s *tcreds = NULL; + unsigned char *bs = NULL, *auths = NULL, *addr = NULL; + int found = -1; + int sd = -1; + int open = !hlist_count(users_list); + + int cd = ((struct thread_arg_s *)thread_data)->fd; + struct sockaddr_in caddr = ((struct thread_arg_s *)thread_data)->addr; + free(thread_data); + + /* + * Check client's version, possibly fuck'em + */ + bs = (unsigned char *)new(10); + thost = new(MINIBUF_SIZE); + tport = new(MINIBUF_SIZE); + r = read(cd, bs, 2); + if (r != 2 || bs[0] != 5) + goto bailout; + + /* + * Read offered auth schemes + */ + c = bs[1]; + auths = (unsigned char *)new(c+1); + r = read(cd, auths, c); + if (r != c) + goto bailout; + + /* + * Are we wide open and client is OK with no auth? + */ + if (open) { + for (i = 0; i < c && (auths[i] || (found = 0)); ++i); + } + + /* + * If not, accept plain auth if offered + */ + if (found < 0) { + for (i = 0; i < c && (auths[i] != 2 || !(found = 2)); ++i); + } + + /* + * If not open and no auth offered or open and auth requested, fuck'em + * and complete the handshake + */ + if (found < 0) { + bs[0] = 5; + bs[1] = 0xFF; + w = write(cd, bs, 2); + goto bailout; + } else { + bs[0] = 5; + bs[1] = found; + w = write(cd, bs, 2); + } + + /* + * Plain auth negotiated? + */ + if (found != 0) { + /* + * Check ver and read username len + */ + r = read(cd, bs, 2); + if (r != 2) { + bs[0] = 1; + bs[1] = 0xFF; /* Unsuccessful (not supported) */ + w = write(cd, bs, 2); + goto bailout; + } + c = bs[1]; + + /* + * Read username and pass len + */ + uname = new(c+1); + r = read(cd, uname, c+1); + if (r != c+1) { + free(uname); + goto bailout; + } + i = uname[c]; + uname[c] = 0; + c = i; + + /* + * Read pass + */ + upass = new(c+1); + r = read(cd, upass, c); + if (r != c) { + free(upass); + free(uname); + goto bailout; + } + upass[c] = 0; + + /* + * Check credentials against the list + */ + tmp = hlist_get(users_list, uname); + if (!hlist_count(users_list) || (tmp && !strcmp(tmp, upass))) { + bs[0] = 1; + bs[1] = 0; /* Success */ + } else { + bs[0] = 1; + bs[1] = 0xFF; /* Failed */ + } + + /* + * Send response + */ + w = write(cd, bs, 2); + free(upass); + free(uname); + + /* + * Fuck'em if auth failed + */ + if (bs[1]) + goto bailout; + } + + /* + * Read request type + */ + r = read(cd, bs, 4); + if (r != 4) + goto bailout; + + /* + * Is it connect for supported address type (IPv4 or DNS)? If not, fuck'em + */ + if (bs[1] != 1 || (bs[3] != 1 && bs[3] != 3)) { + bs[0] = 5; + bs[1] = 2; /* Not allowed */ + bs[2] = 0; + bs[3] = 1; /* Dummy IPv4 */ + memset(bs+4, 0, 6); + w = write(cd, bs, 10); + goto bailout; + } + + /* + * Ok, it's connect to a domain or IP + * Let's read dest address + */ + if (bs[3] == 1) { + ver = 1; /* IPv4, we know the length */ + c = 4; + } else if (bs[3] == 3) { + ver = 2; /* FQDN, get string length */ + r = read(cd, &c, 1); + if (r != 1) + goto bailout; + } else + goto bailout; + + addr = (unsigned char *)new(c+10 + 1); + r = read(cd, addr, c); + if (r != c) + goto bailout; + addr[c] = 0; + + /* + * Convert the address to character string + */ + if (ver == 1) { + sprintf(thost, "%d.%d.%d.%d", addr[0], addr[1], addr[2], addr[3]); /* It's in network byte order */ + } else { + strlcpy(thost, (char *)addr, MINIBUF_SIZE); + } + + /* + * Read port number and convert to host byte order int + */ + r = read(cd, &port, 2); + if (r != 2) + goto bailout; + + i = 0; + if (noproxy_match(thost)) { + sd = host_connect(thost, ntohs(port)); + i = (sd >= 0); + } else { + sprintf(tport, "%d", ntohs(port)); + strlcat(thost, ":", MINIBUF_SIZE); + strlcat(thost, tport, MINIBUF_SIZE); + + tcreds = new_auth(); + sd = proxy_connect(tcreds); + if (sd >= 0) + i = prepare_http_connect(sd, tcreds, thost); + } + + /* + * Direct or proxy connect? + */ + if (!i) { + /* + * Connect/tunnel failed, report + */ + bs[0] = 5; + bs[1] = 1; /* General failure */ + bs[2] = 0; + bs[3] = 1; /* Dummy IPv4 */ + memset(bs+4, 0, 6); + w = write(cd, bs, 10); + goto bailout; + } else { + /* + * All right + */ + bs[0] = 5; + bs[1] = 0; /* Success */ + bs[2] = 0; + bs[3] = 1; /* Dummy IPv4 */ + memset(bs+4, 0, 6); + w = write(cd, bs, 10); + } + + syslog(LOG_DEBUG, "%s SOCKS %s", inet_ntoa(caddr.sin_addr), thost); + + /* + * Let's give them bi-directional connection they asked for + */ + tunnel(cd, sd); + +bailout: + if (addr) + free(addr); + if (auths) + free(auths); + if (thost) + free(thost); + if (tport) + free(tport); + if (bs) + free(bs); + if (tcreds) + free(tcreds); + if (sd) + close(sd); + close(cd); + + return NULL; +} + +int main(int argc, char **argv) { + char *tmp, *head; + char *cpassword, *cpassntlm2, *cpassnt, *cpasslm; + char *cuser, *cdomain, *cworkstation, *cuid, *cpidfile, *cauth; + struct passwd *pw; + struct termios termold, termnew; + pthread_attr_t pattr; + pthread_t pthr; + hlist_t list; + int i, w; + + int cd = 0; + int help = 0; + int nuid = 0; + int ngid = 0; + int gateway = 0; + int tc = 0; + int tj = 0; + int interactivepwd = 0; + int interactivehash = 0; + int tracefile = 0; + int cflags = 0; + int asdaemon = 1; + plist_t tunneld_list = NULL; + plist_t proxyd_list = NULL; + plist_t socksd_list = NULL; + plist_t rules = NULL; + config_t cf = NULL; + char *magic_detect = NULL; + + g_creds = new_auth(); + cuser = new(MINIBUF_SIZE); + cdomain = new(MINIBUF_SIZE); + cpassword = new(MINIBUF_SIZE); + cpassntlm2 = new(MINIBUF_SIZE); + cpassnt = new(MINIBUF_SIZE); + cpasslm = new(MINIBUF_SIZE); + cworkstation = new(MINIBUF_SIZE); + cpidfile = new(MINIBUF_SIZE); + cuid = new(MINIBUF_SIZE); + cauth = new(MINIBUF_SIZE); + + openlog("cntlm", LOG_CONS, LOG_DAEMON); + +#if config_endian == 0 + syslog(LOG_INFO, "Starting cntlm version " VERSION " for BIG endian\n"); +#else + syslog(LOG_INFO, "Starting cntlm version " VERSION " for LITTLE endian\n"); +#endif + + while ((i = getopt(argc, argv, ":-:a:c:d:fghIl:p:r:su:vw:A:BD:F:G:HL:M:N:O:P:R:S:T:U:")) != -1) { + switch (i) { + case 'A': + case 'D': + if (!acl_add(&rules, optarg, (i == 'A' ? ACL_ALLOW : ACL_DENY))) + myexit(1); + break; + case 'a': + strlcpy(cauth, optarg, MINIBUF_SIZE); + break; + case 'B': + ntlmbasic = 1; + break; + case 'c': + if (!(cf = config_open(optarg))) { + syslog(LOG_ERR, "Cannot access specified config file: %s\n", optarg); + myexit(1); + } + break; + case 'd': + strlcpy(cdomain, optarg, MINIBUF_SIZE); + break; + case 'F': + cflags = swap32(strtoul(optarg, &tmp, 0)); + break; + case 'f': + asdaemon = 0; + break; + case 'G': + if (strlen(optarg)) { + scanner_plugin = 1; + if (!scanner_plugin_maxsize) + scanner_plugin_maxsize = 1; + i = strlen(optarg) + 3; + tmp = new(i); + snprintf(tmp, i, "*%s*", optarg); + scanner_agent_list = plist_add(scanner_agent_list, 0, tmp); + } + break; + case 'g': + gateway = 1; + break; + case 'H': + interactivehash = 1; + break; + case 'I': + interactivepwd = 1; + break; + case 'L': + /* + * Parse and validate the argument. + * Create a listening socket for tunneling. + */ + tunnel_add(&tunneld_list, optarg, gateway); + break; + case 'l': + /* + * Create a listening socket for proxy function. + */ + listen_add("Proxy", &proxyd_list, optarg, gateway); + break; + case 'M': + magic_detect = strdup(optarg); + break; + case 'N': + noproxy_list = noproxy_add(noproxy_list, tmp=strdup(optarg)); + free(tmp); + break; + case 'O': + listen_add("SOCKS5 proxy", &socksd_list, optarg, gateway); + break; + case 'P': + strlcpy(cpidfile, optarg, MINIBUF_SIZE); + break; + case 'p': + /* + * Overwrite the password parameter with '*'s to make it + * invisible in "ps", /proc, etc. + */ + strlcpy(cpassword, optarg, MINIBUF_SIZE); + for (i = strlen(optarg)-1; i >= 0; --i) + optarg[i] = '*'; + break; + case 'R': + tmp = strdup(optarg); + head = strchr(tmp, ':'); + if (!head) { + fprintf(stderr, "Invalid username:password format for -R: %s\n", tmp); + } else { + head[0] = 0; + users_list = hlist_add(users_list, tmp, head+1, + HLIST_ALLOC, HLIST_ALLOC); + } + break; + case 'r': + if (is_http_header(optarg)) + header_list = hlist_add(header_list, + get_http_header_name(optarg), + get_http_header_value(optarg), + HLIST_NOALLOC, HLIST_NOALLOC); + break; + case 'S': + scanner_plugin = 1; + scanner_plugin_maxsize = atol(optarg); + break; + case 's': + /* + * Do not use threads - for debugging purposes only + */ + serialize = 1; + break; + case 'T': + tracefile = open(optarg, O_CREAT | O_TRUNC | O_WRONLY, 0600); + if (tracefile < 0) { + fprintf(stderr, "Cannot create trace file.\n"); + myexit(1); + } else { + printf("Redirecting all output to %s\n", optarg); + dup2(tracefile, 1); + dup2(tracefile, 2); + printf("Cntlm debug trace, version " VERSION); +#ifdef __CYGWIN__ + printf(" windows/cygwin port"); +#endif + printf(".\nCommand line: "); + for (i = 0; i < argc; ++i) + printf("%s ", argv[i]); + printf("\n"); + } + break; + case 'U': + strlcpy(cuid, optarg, MINIBUF_SIZE); + break; + case 'u': + i = strcspn(optarg, "@"); + if (i != strlen(optarg)) { + strlcpy(cuser, optarg, MIN(MINIBUF_SIZE, i+1)); + strlcpy(cdomain, optarg+i+1, MINIBUF_SIZE); + } else { + strlcpy(cuser, optarg, MINIBUF_SIZE); + } + break; + case 'v': + debug = 1; + asdaemon = 0; + openlog("cntlm", LOG_CONS | LOG_PERROR, LOG_DAEMON); + break; + case 'w': + strlcpy(cworkstation, optarg, MINIBUF_SIZE); + break; + case 'h': + default: + help = 1; + } + } + + /* + * Help requested? + */ + if (help) { + printf("CNTLM - Accelerating NTLM Authentication Proxy version " VERSION "\n"); + printf("Copyright (c) 2oo7-2o1o David Kubicek\n\n" + "This program comes with NO WARRANTY, to the extent permitted by law. You\n" + "may redistribute copies of it under the terms of the GNU GPL Version 2 or\n" + "newer. For more information about these matters, see the file LICENSE.\n" + "For copyright holders of included encryption routines see headers.\n\n"); + + fprintf(stderr, "Usage: %s [-AaBcDdFfgHhILlMPpSsTUuvw] [:] ...\n", argv[0]); + fprintf(stderr, "\t-A
[/]\n" + "\t ACL allow rule. IP or hostname, net must be a number (CIDR notation)\n"); + fprintf(stderr, "\t-a ntlm | nt | lm\n" + "\t Authentication type - combined NTLM, just LM, or just NT. Default NTLM.\n" + "\t It is the most versatile setting and likely to work for you.\n"); + fprintf(stderr, "\t-B Enable NTLM-to-basic authentication.\n"); + fprintf(stderr, "\t-c \n" + "\t Configuration file. Other arguments can be used as well, overriding\n" + "\t config file settings.\n"); + fprintf(stderr, "\t-D
[/]\n" + "\t ACL deny rule. Syntax same as -A.\n"); + fprintf(stderr, "\t-d \n" + "\t Domain/workgroup can be set separately.\n"); + fprintf(stderr, "\t-f Run in foreground, do not fork into daemon mode.\n"); + fprintf(stderr, "\t-F \n" + "\t NTLM authentication flags.\n"); + fprintf(stderr, "\t-G \n" + "\t User-Agent matching for the trans-isa-scan plugin.\n"); + fprintf(stderr, "\t-g Gateway mode - listen on all interfaces, not only loopback.\n"); + fprintf(stderr, "\t-H Print password hashes for use in config file (NTLMv2 needs -u and -d).\n"); + fprintf(stderr, "\t-h Print this help info along with version number.\n"); + fprintf(stderr, "\t-I Prompt for the password interactively.\n"); + fprintf(stderr, "\t-L [:]::\n" + "\t Forwarding/tunneling a la OpenSSH. Same syntax - listen on lport\n" + "\t and forward all connections through the proxy to rhost:rport.\n" + "\t Can be used for direct tunneling without corkscrew, etc.\n"); + fprintf(stderr, "\t-l [:]\n" + "\t Main listening port for the NTLM proxy.\n"); + fprintf(stderr, "\t-M \n" + "\t Magic autodetection of proxy's NTLM dialect.\n"); + fprintf(stderr, "\t-N \"[, \"\n" + "\t List of URL's to serve direcly as stand-alone proxy (e.g. '*.local')\n"); + fprintf(stderr, "\t-O [:]\n" + "\t Enable SOCKS5 proxy on port lport (binding to address saddr)\n"); + fprintf(stderr, "\t-P \n" + "\t Create a PID file upon successful start.\n"); + fprintf(stderr, "\t-p \n" + "\t Account password. Will not be visible in \"ps\", /proc, etc.\n"); + fprintf(stderr, "\t-r \"HeaderName: value\"\n" + "\t Add a header substitution. All such headers will be added/replaced\n" + "\t in the client's requests.\n"); + fprintf(stderr, "\t-S \n" + "\t Enable automation of GFI WebMonitor ISA scanner for files < size_in_kb.\n"); + fprintf(stderr, "\t-s Do not use threads, serialize all requests - for debugging only.\n"); + fprintf(stderr, "\t-U \n" + "\t Run as uid. It is an important security measure not to run as root.\n"); + fprintf(stderr, "\t-u [@\n" + "\t Some proxies require correct NetBIOS hostname.\n\n"); + exit(1); + } + + /* + * More arguments on the command-line? Must be proxies. + */ + i = optind; + while (i < argc) { + tmp = strchr(argv[i], ':'); + parent_add(argv[i], !tmp && i+1 < argc ? atoi(argv[i+1]) : 0); + i += (!tmp ? 2 : 1); + } + + /* + * No configuration file yet? Load the default. + */ +#ifdef SYSCONFDIR + if (!cf) { +#ifdef __CYGWIN__ + tmp = getenv("PROGRAMFILES"); + if (tmp == NULL) { + tmp = "C:\\Program Files"; + } + + head = new(MINIBUF_SIZE); + strlcpy(head, tmp, MINIBUF_SIZE); + strlcat(head, "\\cntlm\\cntlm.ini", MINIBUF_SIZE); + cf = config_open(head); +#else + cf = config_open(SYSCONFDIR "/cntlm.conf"); +#endif + if (debug) { + if (cf) + printf("Default config file opened successfully\n"); + else + syslog(LOG_ERR, "Could not open default config file\n"); + } + } +#endif + + /* + * If any configuration file was successfully opened, parse it. + */ + if (cf) { + /* + * Check if gateway mode is requested before actually binding any ports. + */ + tmp = new(MINIBUF_SIZE); + CFG_DEFAULT(cf, "Gateway", tmp, MINIBUF_SIZE); + if (!strcasecmp("yes", tmp)) + gateway = 1; + free(tmp); + + /* + * Check for NTLM-to-basic settings + */ + tmp = new(MINIBUF_SIZE); + CFG_DEFAULT(cf, "NTLMToBasic", tmp, MINIBUF_SIZE); + if (!strcasecmp("yes", tmp)) + ntlmbasic = 1; + free(tmp); + + /* + * Setup the rest of tunnels. + */ + while ((tmp = config_pop(cf, "Tunnel"))) { + tunnel_add(&tunneld_list, tmp, gateway); + free(tmp); + } + + /* + * Bind the rest of proxy service ports. + */ + while ((tmp = config_pop(cf, "Listen"))) { + listen_add("Proxy", &proxyd_list, tmp, gateway); + free(tmp); + } + + /* + * Bind the rest of SOCKS5 service ports. + */ + while ((tmp = config_pop(cf, "SOCKS5Proxy"))) { + listen_add("SOCKS5 proxy", &socksd_list, tmp, gateway); + free(tmp); + } + + /* + * Accept only headers not specified on the command line. + * Command line has higher priority. + */ + while ((tmp = config_pop(cf, "Header"))) { + if (is_http_header(tmp)) { + head = get_http_header_name(tmp); + if (!hlist_in(header_list, head)) + header_list = hlist_add(header_list, head, get_http_header_value(tmp), + HLIST_ALLOC, HLIST_NOALLOC); + free(head); + } else + syslog(LOG_ERR, "Invalid header format: %s\n", tmp); + + free(tmp); + } + + /* + * Add the rest of parent proxies. + */ + while ((tmp = config_pop(cf, "Proxy"))) { + parent_add(tmp, 0); + free(tmp); + } + + /* + * No ACLs on the command line? Use config file. + */ + if (rules == NULL) { + list = cf->options; + while (list) { + if (!(i=strcasecmp("Allow", list->key)) || !strcasecmp("Deny", list->key)) + if (!acl_add(&rules, list->value, i ? ACL_DENY : ACL_ALLOW)) + myexit(1); + list = list->next; + } + + while ((tmp = config_pop(cf, "Allow"))) + free(tmp); + while ((tmp = config_pop(cf, "Deny"))) + free(tmp); + } + + /* + * Single options. + */ + CFG_DEFAULT(cf, "Auth", cauth, MINIBUF_SIZE); + CFG_DEFAULT(cf, "Domain", cdomain, MINIBUF_SIZE); + CFG_DEFAULT(cf, "Password", cpassword, MINIBUF_SIZE); + CFG_DEFAULT(cf, "PassNTLMv2", cpassntlm2, MINIBUF_SIZE); + CFG_DEFAULT(cf, "PassNT", cpassnt, MINIBUF_SIZE); + CFG_DEFAULT(cf, "PassLM", cpasslm, MINIBUF_SIZE); + CFG_DEFAULT(cf, "Username", cuser, MINIBUF_SIZE); + CFG_DEFAULT(cf, "Workstation", cworkstation, MINIBUF_SIZE); + + tmp = new(MINIBUF_SIZE); + CFG_DEFAULT(cf, "Flags", tmp, MINIBUF_SIZE); + if (!cflags) + cflags = swap32(strtoul(tmp, NULL, 0)); + free(tmp); + + tmp = new(MINIBUF_SIZE); + CFG_DEFAULT(cf, "ISAScannerSize", tmp, MINIBUF_SIZE); + if (!scanner_plugin_maxsize && strlen(tmp)) { + scanner_plugin = 1; + scanner_plugin_maxsize = atoi(tmp); + } + free(tmp); + + while ((tmp = config_pop(cf, "NoProxy"))) { + if (strlen(tmp)) { + noproxy_list = noproxy_add(noproxy_list, tmp); + } + free(tmp); + } + + while ((tmp = config_pop(cf, "SOCKS5Users"))) { + head = strchr(tmp, ':'); + if (!head) { + syslog(LOG_ERR, "Invalid username:password format for SOCKS5User: %s\n", tmp); + } else { + head[0] = 0; + users_list = hlist_add(users_list, tmp, head+1, HLIST_ALLOC, HLIST_ALLOC); + } + } + + + /* + * Add User-Agent matching patterns. + */ + while ((tmp = config_pop(cf, "ISAScannerAgent"))) { + scanner_plugin = 1; + if (!scanner_plugin_maxsize) + scanner_plugin_maxsize = 1; + + if ((i = strlen(tmp))) { + head = new(i + 3); + snprintf(head, i+3, "*%s*", tmp); + scanner_agent_list = plist_add(scanner_agent_list, 0, head); + } + free(tmp); + } + + /* + * Print out unused/unknown options. + */ + list = cf->options; + while (list) { + syslog(LOG_INFO, "Ignoring config file option: %s\n", list->key); + list = list->next; + } + } + + config_close(cf); + + if (!interactivehash && !parent_list) + croak("Parent proxy address missing.\n", interactivepwd || magic_detect); + + if (!interactivehash && !magic_detect && !proxyd_list) + croak("No proxy service ports were successfully opened.\n", interactivepwd); + + /* + * Set default value for the workstation. Hostname if possible. + */ + if (!strlen(cworkstation)) { +#if config_gethostname == 1 + gethostname(cworkstation, MINIBUF_SIZE); +#endif + if (!strlen(cworkstation)) + strlcpy(cworkstation, "cntlm", MINIBUF_SIZE); + + syslog(LOG_INFO, "Workstation name used: %s\n", cworkstation); + } + + /* + * Parse selected NTLM hash combination. + */ + if (strlen(cauth)) { + if (!strcasecmp("ntlm", cauth)) { + g_creds->hashnt = 1; + g_creds->hashlm = 1; + g_creds->hashntlm2 = 0; + } else if (!strcasecmp("nt", cauth)) { + g_creds->hashnt = 1; + g_creds->hashlm = 0; + g_creds->hashntlm2 = 0; + } else if (!strcasecmp("lm", cauth)) { + g_creds->hashnt = 0; + g_creds->hashlm = 1; + g_creds->hashntlm2 = 0; + } else if (!strcasecmp("ntlmv2", cauth)) { + g_creds->hashnt = 0; + g_creds->hashlm = 0; + g_creds->hashntlm2 = 1; + } else if (!strcasecmp("ntlm2sr", cauth)) { + g_creds->hashnt = 2; + g_creds->hashlm = 0; + g_creds->hashntlm2 = 0; + } else { + syslog(LOG_ERR, "Unknown NTLM auth combination.\n"); + myexit(1); + } + } + + if (socksd_list && !users_list) + syslog(LOG_WARNING, "SOCKS5 proxy will NOT require any authentication\n"); + + if (!magic_detect) + syslog(LOG_INFO, "Using following NTLM hashes: NTLMv2(%d) NT(%d) LM(%d)\n", + g_creds->hashntlm2, g_creds->hashnt, g_creds->hashlm); + + if (cflags) { + syslog(LOG_INFO, "Using manual NTLM flags: 0x%X\n", swap32(cflags)); + g_creds->flags = cflags; + } + + /* + * Last chance to get password from the user + */ + if (interactivehash || (interactivepwd && !ntlmbasic)) { + printf("Password: "); + tcgetattr(0, &termold); + termnew = termold; + termnew.c_lflag &= ~(ISIG | ECHO); + tcsetattr(0, TCSADRAIN, &termnew); + tmp = fgets(cpassword, MINIBUF_SIZE, stdin); + tcsetattr(0, TCSADRAIN, &termold); + i = strlen(cpassword)-1; + trimr(cpassword); + printf("\n"); + } + + /* + * Convert optional PassNT, PassLM and PassNTLMv2 strings to hashes + * unless plaintext pass was used, which has higher priority. + * + * If plain password is present, calculate its NT and LM hashes + * and remove it from the memory. + */ + if (!strlen(cpassword)) { + if (strlen(cpassntlm2)) { + tmp = scanmem(cpassntlm2, 8); + if (!tmp) { + syslog(LOG_ERR, "Invalid PassNTLMv2 hash, terminating\n"); + exit(1); + } + auth_memcpy(g_creds, passntlm2, tmp, 16); + free(tmp); + } + if (strlen(cpassnt)) { + tmp = scanmem(cpassnt, 8); + if (!tmp) { + syslog(LOG_ERR, "Invalid PassNT hash, terminating\n"); + exit(1); + } + auth_memcpy(g_creds, passnt, tmp, 16); + free(tmp); + } + if (strlen(cpasslm)) { + tmp = scanmem(cpasslm, 8); + if (!tmp) { + syslog(LOG_ERR, "Invalid PassLM hash, terminating\n"); + exit(1); + } + auth_memcpy(g_creds, passlm, tmp, 16); + free(tmp); + } + } else { + if (g_creds->hashnt || magic_detect || interactivehash) { + tmp = ntlm_hash_nt_password(cpassword); + auth_memcpy(g_creds, passnt, tmp, 21); + free(tmp); + } if (g_creds->hashlm || magic_detect || interactivehash) { + tmp = ntlm_hash_lm_password(cpassword); + auth_memcpy(g_creds, passlm, tmp, 21); + free(tmp); + } if (g_creds->hashntlm2 || magic_detect || interactivehash) { + tmp = ntlm2_hash_password(cuser, cdomain, cpassword); + auth_memcpy(g_creds, passntlm2, tmp, 16); + free(tmp); + } + memset(cpassword, 0, strlen(cpassword)); + } + + auth_strcpy(g_creds, user, cuser); + auth_strcpy(g_creds, domain, cdomain); + auth_strcpy(g_creds, workstation, cworkstation); + + free(cuser); + free(cdomain); + free(cworkstation); + free(cpassword); + free(cpassntlm2); + free(cpassnt); + free(cpasslm); + free(cauth); + + /* + * Try known NTLM auth combinations and print which ones work. + * User can pick the best (most secure) one as his config. + */ + if (magic_detect) { + magic_auth_detect(magic_detect); + goto bailout; + } + + if (interactivehash) { + if (g_creds->passlm) { + tmp = printmem(g_creds->passlm, 16, 8); + printf("PassLM %s\n", tmp); + free(tmp); + } + + if (g_creds->passnt) { + tmp = printmem(g_creds->passnt, 16, 8); + printf("PassNT %s\n", tmp); + free(tmp); + } + + if (g_creds->passntlm2) { + tmp = printmem(g_creds->passntlm2, 16, 8); + printf("PassNTLMv2 %s # Only for user '%s', domain '%s'\n", + tmp, g_creds->user, g_creds->domain); + free(tmp); + } + goto bailout; + } + + /* + * If we're going to need a password, check we really have it. + */ + if (!ntlmbasic && ( + (g_creds->hashnt && !g_creds->passnt) + || (g_creds->hashlm && !g_creds->passlm) + || (g_creds->hashntlm2 && !g_creds->passntlm2))) { + syslog(LOG_ERR, "Parent proxy account password (or required hashes) missing.\n"); + myexit(1); + } + + /* + * Ok, we are ready to rock. If daemon mode was requested, + * fork and die. The child will not be group leader anymore + * and can thus create a new session for itself and detach + * from the controlling terminal. + */ + if (asdaemon) { + if (debug) + printf("Forking into background as requested.\n"); + + i = fork(); + if (i == -1) { + perror("Fork into background failed"); /* fork failed */ + myexit(1); + } else if (i) + myexit(0); /* parent */ + + setsid(); + umask(0); + w = chdir("/"); + i = open("/dev/null", O_RDWR); + if (i >= 0) { + dup2(i, 0); + dup2(i, 1); + dup2(i, 2); + if (i > 2) + close(i); + } + } + + /* + * Reinit syslog logging to include our PID, after forking + * it is going to be OK + */ + if (asdaemon) { + openlog("cntlm", LOG_CONS | LOG_PID, LOG_DAEMON); + syslog(LOG_INFO, "Daemon ready"); + } else { + openlog("cntlm", LOG_CONS | LOG_PID | LOG_PERROR, LOG_DAEMON); + syslog(LOG_INFO, "Cntlm ready, staying in the foreground"); + } + + /* + * Check and change UID. + */ + if (strlen(cuid)) { + if (getuid() && geteuid()) { + syslog(LOG_WARNING, "No root privileges; keeping identity %d:%d\n", getuid(), getgid()); + } else { + if (isdigit(cuid[0])) { + nuid = atoi(cuid); + ngid = nuid; + if (nuid <= 0) { + syslog(LOG_ERR, "Numerical uid parameter invalid\n"); + myexit(1); + } + } else { + pw = getpwnam(cuid); + if (!pw || !pw->pw_uid) { + syslog(LOG_ERR, "Username %s in -U is invalid\n", cuid); + myexit(1); + } + nuid = pw->pw_uid; + ngid = pw->pw_gid; + } + setgid(ngid); + i = setuid(nuid); + syslog(LOG_INFO, "Changing uid:gid to %d:%d - %s\n", nuid, ngid, strerror(errno)); + if (i) { + syslog(LOG_ERR, "Terminating\n"); + myexit(1); + } + } + } + + /* + * PID file requested? Try to create one (it must not exist). + * If we fail, exit with error. + */ + if (strlen(cpidfile)) { + umask(0); + cd = open(cpidfile, O_WRONLY | O_CREAT | O_TRUNC, 0644); + if (cd < 0) { + syslog(LOG_ERR, "Error creating a new PID file\n"); + myexit(1); + } + + tmp = new(50); + snprintf(tmp, 50, "%d\n", getpid()); + w = write(cd, tmp, strlen(tmp)); + free(tmp); + close(cd); + } + + /* + * Change the handler for signals recognized as clean shutdown. + * When the handler is called (termination request), it signals + * this news by adding 1 to the global quit variable. + */ + signal(SIGPIPE, SIG_IGN); + signal(SIGINT, &sighandler); + signal(SIGTERM, &sighandler); + signal(SIGHUP, &sighandler); + + /* + * Initialize the random number generator + */ + srandom(time(NULL)); + + /* + * This loop iterates over every connection request on any of + * the listening ports. We keep the number of created threads. + * + * We also check the "finished threads" list, threads_list, here and + * free the memory of all inactive threads. Then, we update the + * number of finished threads. + * + * The loop ends, when we were "killed" and all threads created + * are finished, OR if we were killed more than once. This way, + * we have a "clean" shutdown (wait for all connections to finish + * after the first kill) and a "forced" one (user insists and + * killed us twice). + */ + while (quit < 1 || tc != tj) { + struct thread_arg_s *data; + struct sockaddr_in caddr; + struct timeval tv; + socklen_t clen; + fd_set set; + plist_t t; + int tid = 0; + + FD_ZERO(&set); + + /* + * Watch for proxy ports. + */ + t = proxyd_list; + while (t) { + FD_SET(t->key, &set); + t = t->next; + } + + /* + * Watch for SOCKS5 ports. + */ + t = socksd_list; + while (t) { + FD_SET(t->key, &set); + t = t->next; + } + + /* + * Watch for tunneled ports. + */ + t = tunneld_list; + while (t) { + FD_SET(t->key, &set); + t = t->next; + } + + tv.tv_sec = 1; + tv.tv_usec = 0; + + /* + * Wait here for data (connection request) on any of the listening + * sockets. When ready, establish the connection. For the main + * port, a new proxy_thread() thread is spawned to service the HTTP + * request. For tunneled ports, tunnel_thread() thread is created + * and for SOCKS port, socks5_thread() is created. + * + * All threads are defined in forward.c, except for local proxy_thread() + * which routes the request as forwarded or direct, depending on the + * URL host name and NoProxy settings. + */ + cd = select(FD_SETSIZE, &set, NULL, NULL, &tv); + if (cd > 0) { + for (i = 0; i < FD_SETSIZE; ++i) { + if (!FD_ISSET(i, &set)) + continue; + + clen = sizeof(caddr); + cd = accept(i, (struct sockaddr *)&caddr, (socklen_t *)&clen); + + if (cd < 0) { + syslog(LOG_ERR, "Serious error during accept: %s\n", strerror(errno)); + continue; + } + + /* + * Check main access control list. + */ + if (acl_check(rules, caddr.sin_addr) != ACL_ALLOW) { + syslog(LOG_WARNING, "Connection denied for %s:%d\n", + inet_ntoa(caddr.sin_addr), ntohs(caddr.sin_port)); + tmp = gen_denied_page(inet_ntoa(caddr.sin_addr)); + w = write(cd, tmp, strlen(tmp)); + free(tmp); + close(cd); + continue; + } + + /* + * Log peer IP if it's not localhost + * + * if (debug || (gateway && caddr.sin_addr.s_addr != htonl(INADDR_LOOPBACK))) + * syslog(LOG_INFO, "Connection accepted from %s:%d\n", + * inet_ntoa(caddr.sin_addr), ntohs(caddr.sin_port)); + */ + + pthread_attr_init(&pattr); + pthread_attr_setstacksize(&pattr, STACK_SIZE); +#ifndef __CYGWIN__ + pthread_attr_setguardsize(&pattr, 256); +#endif + + if (plist_in(proxyd_list, i)) { + data = (struct thread_arg_s *)new(sizeof(struct thread_arg_s)); + data->fd = cd; + data->addr = caddr; + if (!serialize) + tid = pthread_create(&pthr, &pattr, proxy_thread, (void *)data); + else + proxy_thread((void *)data); + } else if (plist_in(socksd_list, i)) { + data = (struct thread_arg_s *)new(sizeof(struct thread_arg_s)); + data->fd = cd; + data->addr = caddr; + tid = pthread_create(&pthr, &pattr, socks5_thread, (void *)data); + } else { + data = (struct thread_arg_s *)new(sizeof(struct thread_arg_s)); + data->fd = cd; + data->addr = caddr; + data->target = plist_get(tunneld_list, i); + tid = pthread_create(&pthr, &pattr, tunnel_thread, (void *)data); + } + + pthread_attr_destroy(&pattr); + + if (tid) + syslog(LOG_ERR, "Serious error during pthread_create: %d\n", tid); + else + tc++; + } + } else if (cd < 0 && !quit) + syslog(LOG_ERR, "Serious error during select: %s\n", strerror(errno)); + + if (threads_list) { + pthread_mutex_lock(&threads_mtx); + t = threads_list; + while (t) { + plist_t tmp = t->next; + tid = pthread_join((pthread_t)t->key, (void *)&i); + + if (!tid) { + tj++; + if (debug) + printf("Joining thread %lu; rc: %d\n", t->key, i); + } else + syslog(LOG_ERR, "Serious error during pthread_join: %d\n", tid); + + free(t); + t = tmp; + } + threads_list = NULL; + pthread_mutex_unlock(&threads_mtx); + } + } + +bailout: + if (strlen(cpidfile)) + unlink(cpidfile); + + syslog(LOG_INFO, "Terminating with %d active threads\n", tc - tj); + pthread_mutex_lock(&connection_mtx); + plist_free(connection_list); + pthread_mutex_unlock(&connection_mtx); + + hlist_free(header_list); + plist_free(scanner_agent_list); + plist_free(noproxy_list); + plist_free(tunneld_list); + plist_free(proxyd_list); + plist_free(socksd_list); + plist_free(rules); + + free(cuid); + free(cpidfile); + free(magic_detect); + free(g_creds); + + plist_free(parent_list); + + exit(0); +} + diff -Nru cntlm-0.35.1/Makefile cntlm-0.91~rc6/Makefile --- cntlm-0.35.1/Makefile 2010-09-27 12:48:25.000000000 +0000 +++ cntlm-0.91~rc6/Makefile 2010-04-29 11:18:58.000000000 +0000 @@ -2,6 +2,7 @@ # You can tweak these three variables to make things install where you # like, but do not touch more unless you know what you are doing. ;) # +DESTDIR= SYSCONFDIR=$(DESTDIR)/etc BINDIR=$(DESTDIR)/usr/sbin MANDIR=$(DESTDIR)/usr/share/man @@ -11,24 +12,25 @@ # __BSD_VISIBLE is for FreeBSD AF_* constants # _ALL_SOURCE is for AIX 5.3 LOG_PERROR constant # -CC=gcc -OBJS=utils.o ntlm.o xcrypt.o config.o socket.o acl.o auth.o http.o proxy.o -CFLAGS=$(FLAGS) -std=c99 -Wall -pedantic -O3 -D__BSD_VISIBLE -D_ALL_SOURCE -D_XOPEN_SOURCE=600 -D_POSIX_C_SOURCE=200112 -D_ISOC99_SOURCE -D_REENTRANT -DVERSION=\"`cat VERSION`\" -LDFLAGS=-lpthread NAME=cntlm +CC=gcc VER=`cat VERSION` -DIR=`pwd` +OBJS=utils.o ntlm.o xcrypt.o config.o socket.o acl.o auth.o http.o forward.o direct.o scanner.o pages.o main.o +CFLAGS=$(FLAGS) -std=c99 -Wall -pedantic -O3 -D__BSD_VISIBLE -D_ALL_SOURCE -D_XOPEN_SOURCE=600 -D_POSIX_C_SOURCE=200112 -D_ISOC99_SOURCE -D_REENTRANT -DVERSION=\"`cat VERSION`\" -g +OS=$(shell uname -s) +OSLDFLAGS=$(shell [ $(OS) = "SunOS" ] && echo "-lrt -lsocket -lnsl") +LDFLAGS:=-lpthread $(OSLDFLAGS) $(NAME): configure-stamp $(OBJS) @echo "Linking $@" @$(CC) $(CFLAGS) -o $@ $(OBJS) $(LDFLAGS) -proxy.o: proxy.c +main.o: main.c @echo "Compiling $<" @if [ -z "$(SYSCONFDIR)" ]; then \ - $(CC) $(CFLAGS) -c proxy.c -o $@; \ + $(CC) $(CFLAGS) -c main.c -o $@; \ else \ - $(CC) $(CFLAGS) -DSYSCONFDIR=\"$(SYSCONFDIR)\" -c proxy.c -o $@; \ + $(CC) $(CFLAGS) -DSYSCONFDIR=\"$(SYSCONFDIR)\" -c main.c -o $@; \ fi .c.o: @@ -38,61 +40,90 @@ install: $(NAME) # AIX? if [ -f /usr/bin/oslevel ]; then \ - install -O root -G system -M 755 -S -f $(BINDIR) $(NAME); \ - install -O root -G system -M 644 -f $(MANDIR)/man1 doc/$(NAME).1; \ - install -O root -G system -M 600 -c $(SYSCONFDIR) doc/$(NAME).conf; \ + install -M 755 -S -f $(BINDIR) $(NAME); \ + install -M 644 -f $(MANDIR)/man1 doc/$(NAME).1; \ + install -M 600 -c $(SYSCONFDIR) doc/$(NAME).conf; \ else \ - install -D -o root -g root -m 755 -s $(NAME) $(BINDIR)/$(NAME); \ - install -D -o root -g root -m 644 doc/$(NAME).1 $(MANDIR)/man1/$(NAME).1; \ + install -D -m 755 -s $(NAME) $(BINDIR)/$(NAME); \ + install -D -m 644 doc/$(NAME).1 $(MANDIR)/man1/$(NAME).1; \ [ -f $(SYSCONFDIR)/$(NAME).conf -o -z "$(SYSCONFDIR)" ] \ - || install -D -o root -g root -m 600 doc/$(NAME).conf $(SYSCONFDIR)/$(NAME).conf; \ + || install -D -m 600 doc/$(NAME).conf $(SYSCONFDIR)/$(NAME).conf; \ fi @echo; echo "Cntlm will look for configuration in $(SYSCONFDIR)/$(NAME).conf" -rpm: +tgz: + mkdir -p tmp + rm -rf tmp/$(NAME)-$(VER) + svn export . tmp/$(NAME)-$(VER) + tar zcvf $(NAME)-$(VER).tar.gz -C tmp/ $(NAME)-$(VER) + rm -rf tmp/$(NAME)-$(VER) + rmdir tmp 2>/dev/null || true + +tbz2: + mkdir -p tmp + rm -rf tmp/$(NAME)-$(VER) + svn export . tmp/$(NAME)-$(VER) + tar jcvf $(NAME)-$(VER).tar.bz2 -C tmp/ $(NAME)-$(VER) + rm -rf tmp/$(NAME)-$(VER) + rmdir tmp 2>/dev/null || true + +deb: builddeb +builddeb: + sed -i "s/^\(cntlm *\)([^)]*)/\1($(VER))/g" debian/changelog if [ `id -u` = 0 ]; then \ - redhat/rules binary; \ - redhat/rules clean; \ + debian/rules binary; \ + debian/rules clean; \ else \ - fakeroot redhat/rules binary; \ - fakeroot redhat/rules clean; \ + fakeroot debian/rules binary; \ + fakeroot debian/rules clean; \ fi + mv ../cntlm_$(VER)*.deb . -tgz: - mkdir -p tmp - rm -f tmp/$(NAME)-$(VER) - ln -s $(DIR) tmp/$(NAME)-$(VER) - sed "s/^\./$(NAME)-$(VER)/" doc/files.txt | tar zchf $(NAME)-$(VER).tar.gz --no-recursion -C tmp -T - - rm tmp/$(NAME)-$(VER) - rmdir tmp 2>/dev/null || true +rpm: buildrpm +buildrpm: + sed -i "s/^\(Version:[\t ]*\)\(.*\)/\1$(VER)/g" rpm/cntlm.spec + if [ `id -u` = 0 ]; then \ + rpm/rules binary; \ + rpm/rules clean; \ + else \ + fakeroot rpm/rules binary; \ + fakeroot rpm/rules clean; \ + fi -win: - groff -t -e -mandoc -Tps doc/cntlm.1 | ps2pdf - win32/cntlm_manual.pdf - cat doc/cntlm.conf | unix2dos > win32/cntlm.ini - cp /bin/cygwin1.dll /bin/cygrunsrv.exe win32/ - strip cntlm.exe - cp cntlm.exe win32/ - rm -f cntlm-install - ln -s win32 cntlm-install - zip -r cntlm-$(VER)-win32.zip cntlm-install -x *.svn/* - rm -f cntlm-install cntlm-$(VER)-win32.zip.sig +win: buildwin +buildwin: + @echo + @echo "* This build target must be run from a Cywgin shell on Windows *" + @echo "* and you also need InnoSetup installed *" + @echo + rm -f win/cntlm_manual.pdf + groff -t -e -mandoc -Tps doc/cntlm.1 | ps2pdf - win/cntlm_manual.pdf + cat doc/cntlm.conf | unix2dos > win/cntlm.ini + cat COPYRIGHT LICENSE | unix2dos > win/license.txt + sed "s/\$$VERSION/$(VER)/g" win/setup.iss.in > win/setup.iss + cp /bin/cygwin1.dll /bin/cygrunsrv.exe win/ + cp cntlm.exe win/ + strip win/cntlm.exe + @echo + @echo Now open folder "win", right-click "setup.iss", then "Compile". + @echo InnoSetup will generate a new installer cntlm-X.XX-setup.exe + @echo uninstall: rm -f $(BINDIR)/$(NAME) $(MANDIR)/man1/$(NAME).1 2>/dev/null || true clean: @rm -f *.o cntlm cntlm.exe configure-stamp build-stamp config/config.h 2>/dev/null - @rm -f cntlm-install win32/cyg* win32/cntlm* 2>/dev/null + @rm -f win/*.exe win/*.dll win/*.iss win/*.pdf win/cntlm.ini win/license.txt 2>/dev/null @rm -f config/endian config/gethostname config/strdup config/socklen_t config/*.exe @if [ -h Makefile ]; then rm -f Makefile; mv Makefile.gcc Makefile; fi -cleanp: clean - @rm -f *.deb *.tgz *.tar.gz *.rpm *.o tags cntlm pid massif* callgrind* 2>/dev/null - distclean: clean if [ `id -u` = 0 ]; then \ - redhat/rules clean; \ + debian/rules clean; \ + rpm/rules clean; \ else \ - fakeroot redhat/rules clean; \ + fakeroot debian/rules clean; \ + fakeroot rpm/rules clean; \ fi - + @rm -f *.exe *.deb *.rpm *.tgz *.tar.gz *.tar.bz2 tags ctags pid 2>/dev/null diff -Nru cntlm-0.35.1/Makefile.xlc cntlm-0.91~rc6/Makefile.xlc --- cntlm-0.35.1/Makefile.xlc 2007-11-21 00:20:04.000000000 +0000 +++ cntlm-0.91~rc6/Makefile.xlc 2010-03-20 22:08:14.000000000 +0000 @@ -9,7 +9,7 @@ # # CC=xlc_r -OBJS=utils.o ntlm.o xcrypt.o config.o socket.o acl.o auth.o http.o proxy.o +OBJS=utils.o ntlm.o xcrypt.o config.o socket.o acl.o auth.o http.o forward.o direct.o scanner.o pages.o main.o CFLAGS=$(FLAGS) -O3 -D_POSIX_C_SOURCE=200112 -D_ISOC99_SOURCE -D_REENTRANT -DVERSION=\"`cat VERSION`\" LDFLAGS=-lpthread NAME=cntlm @@ -17,16 +17,23 @@ $(NAME): $(OBJS) $(CC) $(CFLAGS) -o $@ $(OBJS) $(LDFLAGS) +main.o: main.c + if [ -z "$(SYSCONFDIR)" ]; then \ + $(CC) $(CFLAGS) -c main.c -o $@; \ + else \ + $(CC) $(CFLAGS) -DSYSCONFDIR=\"$(SYSCONFDIR)\" -c main.c -o $@; \ + fi + install: $(NAME) if [ -f /usr/bin/oslevel ]; then \ - install -O root -G system -M 0755 -S -f $(BINDIR) $(NAME); \ - install -O root -G system -M 0644 -f $(MANDIR)/man1 doc/$(NAME).1; \ - install -O root -G system -M 0600 -c $(SYSCONFDIR) doc/$(NAME).conf; \ + install -M 0755 -S -f $(BINDIR) $(NAME); \ + install -M 0644 -f $(MANDIR)/man1 doc/$(NAME).1; \ + install -M 0600 -c $(SYSCONFDIR) doc/$(NAME).conf; \ else \ - install -D -o root -g root -m 0755 -s $(NAME) $(BINDIR)/$(NAME); \ - install -D -o root -g root -m 0644 doc/$(NAME).1 $(MANDIR)/man1/$(NAME).1; \ + install -D -m 0755 -s $(NAME) $(BINDIR)/$(NAME); \ + install -D -m 0644 doc/$(NAME).1 $(MANDIR)/man1/$(NAME).1; \ [ -f $(SYSCONFDIR)/$(NAME).conf -o -z "$(SYSCONFDIR)" ] \ - || install -D -o root -g root -m 0600 doc/$(NAME).conf $(SYSCONFDIR)/$(NAME).conf; \ + || install -D -m 0600 doc/$(NAME).conf $(SYSCONFDIR)/$(NAME).conf; \ fi uninstall: @@ -34,13 +41,9 @@ clean: @rm -f *.o cntlm cntlm.exe configure-stamp build-stamp config/config.h 2>/dev/null - @rm -f cntlm-install win32/cyg* win32/cntlm* 2>/dev/null + @rm -f cntlm-install win/cyg* win/cntlm* 2>/dev/null @rm -f config/endian config/gethostname config/strdup config/socklen_t config/*.exe @if [ -h Makefile ]; then rm -f Makefile; mv Makefile.gcc Makefile; fi -proxy.o: proxy.c - if [ -z "$(SYSCONFDIR)" ]; then \ - $(CC) $(CFLAGS) -c proxy.c -o $@; \ - else \ - $(CC) $(CFLAGS) -DSYSCONFDIR=\"$(SYSCONFDIR)\" -c proxy.c -o $@; \ - fi +distclean: clean + @rm -f *.deb *.rpm *.tgz *.tar.gz *.tar.bz2 tags ctags pid 2>/dev/null diff -Nru cntlm-0.35.1/ntlm.c cntlm-0.91~rc6/ntlm.c --- cntlm-0.35.1/ntlm.c 2007-11-14 00:22:03.000000000 +0000 +++ cntlm-0.91~rc6/ntlm.c 2010-04-29 11:18:58.000000000 +0000 @@ -64,7 +64,7 @@ } static void ntlm2_calc_resp(char **nthash, int *ntlen, char **lmhash, int *lmlen, - char *username, char *domain, char *passnt2, char *challenge, int tbofs, int tblen) { + char *passnt2, char *challenge, int tbofs, int tblen) { char *tmp, *blob, *nonce, *buf; int64_t tw; int blen; @@ -73,9 +73,13 @@ VAL(nonce, uint64_t, 0) = ((uint64_t)random() << 32) | random(); tw = ((uint64_t)time(NULL) + 11644473600LLU) * 10000000LLU; - if (0 && debug) { + if (debug) { tmp = printmem(nonce, 8, 7); +#ifdef PRId64 + printf("NTLMv2:\n\t Nonce: %s\n\tTimestamp: %"PRId64"\n", tmp, tw); +#else printf("NTLMv2:\n\t Nonce: %s\n\tTimestamp: %lld\n", tmp, tw); +#endif free(tmp); } @@ -208,6 +212,7 @@ int dlen, hlen; uint32_t flags = 0xb206; + *dst = NULL; dlen = strlen(creds->domain); hlen = strlen(creds->workstation); @@ -222,8 +227,13 @@ flags = 0xb205; else if (creds->hashlm) flags = 0xb206; - else + else { + if (debug) { + printf("You're requesting with empty auth_s?!\n"); + dump_auth(creds); + } return 0; + } } else flags = creds->flags; @@ -349,7 +359,7 @@ } if (creds->hashntlm2) { - ntlm2_calc_resp(&nthash, &ntlen, &lmhash, &lmlen, creds->user, creds->domain, creds->passntlm2, challenge, tbofs, tblen); + ntlm2_calc_resp(&nthash, &ntlen, &lmhash, &lmlen, creds->passntlm2, challenge, tbofs, tblen); } if (creds->hashnt == 2) { diff -Nru cntlm-0.35.1/pages.c cntlm-0.91~rc6/pages.c --- cntlm-0.35.1/pages.c 2007-10-31 18:48:15.000000000 +0000 +++ cntlm-0.91~rc6/pages.c 2010-03-21 14:22:58.000000000 +0000 @@ -19,32 +19,67 @@ * */ +#ifndef _PAGES_H +#define _PAGES_H + #include "utils.h" #include "string.h" #include "stdio.h" -char *gen_auth_page(char *http) { +char *gen_407_page(const char *http) { char *tmp; - + if (http == NULL) + http = "0"; tmp = new(BUFSIZE); - snprintf(tmp, BUFSIZE, "HTTP/1.%s 407 Access denied.\r\n", http); - strcat(tmp, "Proxy-Authenticate: Basic realm=\"Cntlm Proxy\"\r\n"); - strcat(tmp, "Content-Type: text/html\r\n\r\n"); - strcat(tmp, "

Authentication error

Cntlm " - "proxy has NTLM-to-basic feature enabled. You have to enter correct credentials to continue " - "(try Ctrl-R or F5).

"); - + snprintf(tmp, BUFSIZE-1, + "HTTP/1.%s 407 Access denied\r\n" + "Proxy-Authenticate: Basic realm=\"Cntlm Proxy\"\r\n" + "Content-Type: text/html\r\n\r\n" + "

407 Access denied

Cntlm requests your credentials for proxy access.

", + http); return tmp; } - -char *gen_denied_page(char *ip) { +char *gen_401_page(const char *http, const char *host, int port) { char *tmp; + if (http == NULL) + http = "0"; + tmp = new(BUFSIZE); + snprintf(tmp, BUFSIZE-1, + "HTTP/1.%s 401 Access denied\r\n" + "WWW-Authenticate: Basic realm=\"%s:%d\"\r\n" + "Content-Type: text/html\r\n\r\n" + "

401 Access denied

Cntlm proxy requests your credentials for this URL.

", + http, host, port); + return tmp; +} +char *gen_denied_page(const char *ip) { + char *tmp; + if (ip == NULL) + ip = "client"; tmp = new(BUFSIZE); - snprintf(tmp, BUFSIZE, "HTTP/1.0 407 Access denied.\r\nContent-Type: text/html\r\n\r\n" - "

Access denied

Your request has been declined, %s " - "is not allowed to connect.

", ip); + snprintf(tmp, BUFSIZE-1, + "HTTP/1.0 407 Access denied\r\n" + "Content-Type: text/html\r\n\r\n" + "

Access denied

Your request has been declined, %s is not allowed to connect.

", + ip); + return tmp; +} +char *gen_502_page(const char *http, const char *msg) { + char *tmp; + if (http == NULL) + http = "0"; + if (msg == NULL) + msg = "Proxy error"; + tmp = new(BUFSIZE); + snprintf(tmp, BUFSIZE-1, + "HTTP/1.%s 502 %s\r\n" + "Content-Type: text/html\r\n\r\n" + "

502 %s

Cntlm proxy failed to complete the request.

", + http, msg, msg); return tmp; } + +#endif /* _PAGES_H */ diff -Nru cntlm-0.35.1/pages.h cntlm-0.91~rc6/pages.h --- cntlm-0.35.1/pages.h 1970-01-01 00:00:00.000000000 +0000 +++ cntlm-0.91~rc6/pages.h 2010-03-21 14:22:58.000000000 +0000 @@ -0,0 +1,27 @@ +/* + * CNTLM is free software; you can redistribute it and/or modify it under the + * terms of the GNU General Public License as published by the Free Software + * Foundation; either version 2 of the License, or (at your option) any later + * version. + * + * CNTLM is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., 51 Franklin + * St, Fifth Floor, Boston, MA 02110-1301, USA. + * + * Copyright (c) 2007 David Kubicek + * + */ + +#include "utils.h" +#include "string.h" +#include "stdio.h" + +extern char *gen_407_page(const char *http); +extern char *gen_401_page(const char *http, const char *host, int port); +extern char *gen_denied_page(const char *ip); +extern char *gen_502_page(const char *http, const char *msg); diff -Nru cntlm-0.35.1/proxy.c cntlm-0.91~rc6/proxy.c --- cntlm-0.35.1/proxy.c 2007-11-16 01:08:06.000000000 +0000 +++ cntlm-0.91~rc6/proxy.c 1970-01-01 00:00:00.000000000 +0000 @@ -1,2691 +0,0 @@ -/* - * This is the main module of the CNTLM - * - * CNTLM is free software; you can redistribute it and/or modify it under the - * terms of the GNU General Public License as published by the Free Software - * Foundation; either version 2 of the License, or (at your option) any later - * version. - * - * CNTLM is distributed in the hope that it will be useful, but WITHOUT ANY - * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more - * details. - * - * You should have received a copy of the GNU General Public License along with - * this program; if not, write to the Free Software Foundation, Inc., 51 Franklin - * St, Fifth Floor, Boston, MA 02110-1301, USA. - * - * Copyright (c) 2007 David Kubicek - * - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -/* - * Some helping routines like linked list manipulation substr(), memory - * allocation, NTLM authentication routines, etc. - */ -#include "config/config.h" -#include "socket.h" -#include "utils.h" -#include "ntlm.h" -#include "swap.h" -#include "config.h" -#include "acl.h" -#include "auth.h" -#include "http.h" -#include "pages.c" - -#define DEFAULT_PORT "3128" - -#define SAMPLE 4096 -#define STACK_SIZE sizeof(void *)*8*1024 - -#define PLUG_NONE 0x0000 -#define PLUG_SENDHEAD 0x0001 -#define PLUG_SENDDATA 0x0002 -#define PLUG_ERROR 0x8000 -#define PLUG_ALL 0x7FFF - -/* - * A couple of shortcuts for if statements - */ -#define CONNECT(data) (data && data->req && !strcasecmp("CONNECT", data->method)) -#define HEAD(data) (data && data->req && !strcasecmp("HEAD", data->method)) -#define GET(data) (data && data->req && !strcasecmp("GET", data->method)) - -/* - * Global "read-only" data initialized in main(). Comments list funcs. which use - * them. Having these global avoids the need to pass them to each thread and - * from there again a few times to inner calls. - */ -int debug = 0; /* all debug printf's and possibly external modules */ - -static struct auth_s *creds = NULL; /* throughout the whole module */ - -static int quit = 0; /* sighandler() */ -static int asdaemon = 1; /* myexit() */ -static int ntlmbasic = 0; /* proxy_thread() */ -static int serialize = 0; -static int scanner_plugin = 0; -static long scanner_plugin_maxsize = 0; - -static int precache = 0; -static int active_conns = 0; -static pthread_mutex_t active_mtx = PTHREAD_MUTEX_INITIALIZER; - -/* - * List of finished threads. Each thread proxy_thread() adds itself to it when - * finished. Main regularly joins and removes all tid's in there. - */ -static plist_t threads_list = NULL; -static pthread_mutex_t threads_mtx = PTHREAD_MUTEX_INITIALIZER; - -/* - * List of cached connections. Accessed by each thread proxy_thread(). - */ -static plist_t connection_list = NULL; -static pthread_mutex_t connection_mtx = PTHREAD_MUTEX_INITIALIZER; - -/* - * List of available proxies and current proxy id for proxy_connect(). - */ -static int parent_count = 0; -static int parent_curr = 0; -static pthread_mutex_t parent_mtx = PTHREAD_MUTEX_INITIALIZER; - -static plist_t parent_list = NULL; -typedef struct { - struct in_addr host; - int port; -} proxy_t; - -/* - * List of custom header substitutions, SOCKS5 proxy users and - * UserAgents for the scanner plugin. - */ -static hlist_t header_list = NULL; /* proxy_thread() */ -static hlist_t users_list = NULL; /* socks5_thread() */ -static plist_t scanner_agent_list = NULL; /* scanner_hook() */ - -/* - * General signal handler. If in debug mode, quit immediately. - */ -void sighandler(int p) { - if (!quit) - syslog(LOG_INFO, "Signal %d received, issuing clean shutdown\n", p); - else - syslog(LOG_INFO, "Signal %d received, forcing shutdown\n", p); - - if (quit++ || debug) - quit++; -} - -void myexit(int rc) { - if (rc) - fprintf(stderr, "Exitting with error. Check daemon logs or run with -v.\n"); - - exit(rc); -} - -/* - * Keep count of active connections (trasferring data) - */ -void update_active(int i) { - pthread_mutex_lock(&active_mtx); - active_conns += i; - pthread_mutex_unlock(&active_mtx); -} - -/* - * Retur/ count of active connections (trasferring data) - */ -int check_active(void) { - int r; - - pthread_mutex_lock(&active_mtx); - r = active_conns; - pthread_mutex_unlock(&active_mtx); - - return r; -} - -/* - * Connect to the selected proxy. If the request fails, pick next proxy - * in the line. Each request scans the whole list until all items are tried - * or a working proxy is found, in which case it is selected and used by - * all threads until it stops working. Then the search starts again. - */ -int proxy_connect(void) { - proxy_t *aux; - int i, prev; - plist_t list, tmp; - int loop = 0; - - prev = parent_curr; - pthread_mutex_lock(&parent_mtx); - if (parent_curr == 0) { - aux = (proxy_t *)plist_get(parent_list, ++parent_curr); - syslog(LOG_INFO, "Using proxy %s:%d\n", inet_ntoa(aux->host), aux->port); - } - pthread_mutex_unlock(&parent_mtx); - - do { - aux = (proxy_t *)plist_get(parent_list, parent_curr); - i = so_connect(aux->host, aux->port); - if (i <= 0) { - pthread_mutex_lock(&parent_mtx); - if (parent_curr >= parent_count) - parent_curr = 0; - aux = (proxy_t *)plist_get(parent_list, ++parent_curr); - pthread_mutex_unlock(&parent_mtx); - syslog(LOG_ERR, "Proxy connect failed, will try %s:%d\n", inet_ntoa(aux->host), aux->port); - } - } while (i <= 0 && ++loop < parent_count); - - if (i <= 0 && loop >= parent_count) - syslog(LOG_ERR, "No proxy on the list works. You lose.\n"); - - /* - * We have to invalidate the cached connections if we moved to a different proxy - */ - if (prev != parent_curr) { - pthread_mutex_lock(&connection_mtx); - list = connection_list; - while (list) { - tmp = list->next; - close(list->key); - list = tmp; - } - plist_free(connection_list); - pthread_mutex_unlock(&connection_mtx); - } - - - return i; -} - -/* - * Parse proxy parameter and add it to the global list. - */ -int parent_add(char *parent, int port) { - int len, i; - char *proxy; - proxy_t *aux; - struct in_addr host; - - /* - * Check format and parse it. - */ - proxy = strdup(parent); - len = strlen(proxy); - i = strcspn(proxy, ": "); - if (i != len) { - proxy[i++] = 0; - while (i < len && (proxy[i] == ' ' || proxy[i] == '\t')) - i++; - - if (i >= len) { - free(proxy); - return 0; - } - - port = atoi(proxy+i); - } - - /* - * No port argument and not parsed from proxy? - */ - if (!port) { - syslog(LOG_ERR, "Invalid proxy specification %s.\n", parent); - free(proxy); - myexit(1); - } - - /* - * Try to resolve proxy address - */ - if (debug) - syslog(LOG_INFO, "Resolving proxy %s...\n", proxy); - if (!so_resolv(&host, proxy)) { - syslog(LOG_ERR, "Cannot resolve proxy %s, discarding.\n", parent); - free(proxy); - return 0; - } - - aux = (proxy_t *)new(sizeof(proxy_t)); - aux->host = host; - aux->port = port; - parent_list = plist_add(parent_list, ++parent_count, (char *)aux); - - free(proxy); - return 1; -} - -/* - * Register and bind new proxy service port. - */ -void listen_add(const char *service, plist_t *list, char *spec, int gateway) { - struct in_addr source; - int i, p, len, port; - char *tmp; - - len = strlen(spec); - p = strcspn(spec, ":"); - if (p < len-1) { - tmp = substr(spec, 0, p); - if (!so_resolv(&source, tmp)) { - syslog(LOG_ERR, "Cannot resolve listen address %s\n", tmp); - myexit(1); - } - free(tmp); - port = atoi(tmp = spec+p+1); - } else { - source.s_addr = htonl(gateway ? INADDR_ANY : INADDR_LOOPBACK); - port = atoi(tmp = spec); - } - - if (!port) { - syslog(LOG_ERR, "Invalid listen port %s.\n", tmp); - myexit(1); - } - - i = so_listen(port, source); - if (i > 0) { - *list = plist_add(*list, i, NULL); - syslog(LOG_INFO, "%s listening on %s:%d\n", service, inet_ntoa(source), port); - } -} - -/* - * Register a new tunnel definition, bind service port. - */ -void tunnel_add(plist_t *list, char *spec, int gateway) { - struct in_addr source; - int i, len, count, pos, port; - char *field[4]; - char *tmp; - - spec = strdup(spec); - len = strlen(spec); - field[0] = spec; - for (count = 1, i = 0; i < len; ++i) - if (spec[i] == ':') { - spec[i] = 0; - field[count++] = spec+i+1; - } - - pos = 0; - if (count == 4) { - if (!so_resolv(&source, field[pos])) { - syslog(LOG_ERR, "Cannot resolve tunel listen address: %s\n", field[pos]); - myexit(1); - } - pos++; - } else - source.s_addr = htonl(gateway ? INADDR_ANY : INADDR_LOOPBACK); - - if (count-pos == 3) { - port = atoi(field[pos]); - if (port == 0) { - syslog(LOG_ERR, "Invalid tunnel local port: %s\n", field[pos]); - myexit(1); - } - - if (!strlen(field[pos+1]) || !strlen(field[pos+2])) { - syslog(LOG_ERR, "Invalid tunnel target: %s:%s\n", field[pos+1], field[pos+2]); - myexit(1); - } - - tmp = new(strlen(field[pos+1]) + strlen(field[pos+2]) + 2 + 1); - strcpy(tmp, field[pos+1]); - strcat(tmp, ":"); - strcat(tmp, field[pos+2]); - - i = so_listen(port, source); - if (i > 0) { - *list = plist_add(*list, i, tmp); - syslog(LOG_INFO, "New tunnel from %s:%d to %s\n", inet_ntoa(source), port, tmp); - } else - free(tmp); - } else { - printf("Tunnel specification incorrect ([laddress:]lport:rserver:rport).\n"); - myexit(1); - } - - free(spec); -} - -/* - * Duplicate client request headers, change requested method to HEAD - * (so we avoid any body transfers during NTLM negotiation), and add - * proxy authentication request headers. - * - * Read the reply, if it contains NTLM challenge, generate final - * NTLM auth message and insert it into the original client header, - * which is then processed by caller himself. - * - * If the proxy closes the connection for some reason, we notify our - * caller by setting closed to 1. Otherwise, it is set to 0. - * If closed == NULL, we do not signal anything. - */ -int authenticate(int sd, rr_data_t request, struct auth_s *creds, int *closed) { - char *tmp, *buf, *challenge; - rr_data_t auth; - int len, rc; - - if (closed) - *closed = 0; - - buf = new(BUFSIZE); - - strcpy(buf, "NTLM "); - len = ntlm_request(&tmp, creds); - to_base64(MEM(buf, unsigned char, 5), MEM(tmp, unsigned char, 0), len, BUFSIZE-5); - free(tmp); - - auth = dup_rr_data(request); - - /* - * If the request is CONNECT, we have to keep it unmodified - */ - if (!CONNECT(request)) { - free(auth->method); - auth->method = strdup("GET"); - } - auth->headers = hlist_mod(auth->headers, "Proxy-Authorization", buf, 1); - auth->headers = hlist_del(auth->headers, "Content-Length"); - - if (debug) { - printf("\nSending auth request...\n"); - hlist_dump(auth->headers); - } - - if (!headers_send(sd, auth)) { - rc = 0; - goto bailout; - } - - free_rr_data(auth); - auth = new_rr_data(); - - if (debug) { - printf("Reading auth response...\n"); - } - - if (!headers_recv(sd, auth)) { - rc = 0; - goto bailout; - } - - if (debug) - hlist_dump(auth->headers); - - tmp = hlist_get(auth->headers, "Content-Length"); - if (tmp && (len = atoi(tmp))) { - if (debug) - printf("Got %d too many bytes.\n", len); - data_drop(sd, len); - } - - /* - * Should auth continue? - */ - if (auth->code == 407) { - tmp = hlist_get(auth->headers, "Proxy-Authenticate"); - if (tmp) { - challenge = new(strlen(tmp)); - len = from_base64(challenge, tmp+5); - if (len > NTLM_CHALLENGE_MIN) { - len = ntlm_response(&tmp, challenge, len, creds); - if (len > 0) { - strcpy(buf, "NTLM "); - to_base64(MEM(buf, unsigned char, 5), MEM(tmp, unsigned char, 0), len, BUFSIZE-5); - request->headers = hlist_mod(request->headers, "Proxy-Authorization", buf, 1); - free(tmp); - } else { - syslog(LOG_ERR, "No target info block. Cannot do NTLMv2!\n"); - rc = 0; - free(challenge); - goto bailout; - } - } else { - syslog(LOG_ERR, "Proxy returning invalid challenge!\n"); - rc = 0; - free(challenge); - goto bailout; - } - - free(challenge); - } else { - syslog(LOG_WARNING, "No Proxy-Authenticate received! NTLM not supported?\n"); - } - } else if (auth->code >= 500 && auth->code <= 599) { - /* - * Proxy didn't like the request, close connection and don't try again. - */ - syslog(LOG_WARNING, "The request was denied!\n"); - - close(sd); - rc = 500; - - goto bailout; - } else { - /* - * No auth was neccessary, let the caller make the request again. - */ - if (closed) - *closed = 1; - } - - /* - * Does proxy intend to close the connection? E.g. it didn't require auth - * at all or there was some problem. If so, let caller know that it should - * reconnect! - */ - if (closed && hlist_subcmp(auth->headers, "Proxy-Connection", "close")) { - if (debug) - printf("Proxy signals it's closing the connection.\n"); - *closed = 1; - } - - rc = 1; - -bailout: - free_rr_data(auth); - free(buf); - - return rc; -} - -/* - * Auth connection "sd" and try to return negotiated CONNECT - * connection to a remote host:port (thost). - * - * Return 0 for success, -1 for proxy negotiation error and - * -HTTP_CODE in case the request failed. - */ -int make_connect(int sd, const char *thost) { - rr_data_t data1, data2; - int ret, closed; - - if (!sd || !thost || !strlen(thost)) - return -1; - - data1 = new_rr_data(); - data2 = new_rr_data(); - - data1->req = 1; - data1->method = strdup("CONNECT"); - data1->url = strdup(thost); - data1->http = strdup("0"); - data1->headers = hlist_mod(data1->headers, "Proxy-Connection", "Keep-Alive", 1); - - if (debug) - printf("Starting authentication...\n"); - - ret = authenticate(sd, data1, creds, &closed); - if (ret && ret != 500) { - if (closed || so_closed(sd)) { - close(sd); - sd = proxy_connect(); - if (sd <= 0) { - ret = -1; - goto bailout; - } - } - - if (debug) { - printf("Sending real request:\n"); - hlist_dump(data1->headers); - } - - if (headers_send(sd, data1)) { - if (debug) - printf("Reading real response:\n"); - - if (headers_recv(sd, data2)) { - if (debug) - hlist_dump(data2->headers); - if (data2->code == 200) { - if (debug) - printf("Ok CONNECT response. Tunneling...\n"); - ret = 0; - goto bailout; - } else if (data2->code == 407) { - syslog(LOG_ERR, "Authentication for tunnel %s failed!\n", thost); - } else { - syslog(LOG_ERR, "Request for CONNECT denied!\n"); - } - ret = -data2->code; - } else { - if (debug) - printf("Reading response failed!\n"); - ret = -1; - } - } else { - if (debug) - printf("Sending request failed!\n"); - ret = -1; - } - } else { - if (ret == 500) - syslog(LOG_ERR, "Tunneling to %s not allowed!\n", thost); - else - syslog(LOG_ERR, "Authentication requests failed!\n"); - ret = -500; - } - -bailout: - free_rr_data(data1); - free_rr_data(data2); - - return ret; -} - -/* - * Return 0 if no body, -1 if until EOF, number if size known - */ -int has_body(rr_data_t request, rr_data_t response) { - rr_data_t current; - int length, nobody; - char *tmp; - - /* - * Checking complete req+res conversation or just the - * first part when there's no response yet? - */ - current = (response->http ? response : request); - - /* - * HTTP body length decisions. There MUST NOT be any body from - * server if the request was HEAD or reply is 1xx, 204 or 304. - * No body can be in GET request if direction is from client. - */ - if (current == response) { - nobody = (HEAD(request) || - (response->code >= 100 && response->code < 200) || - response->code == 204 || - response->code == 304); - } else { - nobody = GET(request) || HEAD(request); - } - - /* - * Otherwise consult Content-Length. If present, we forward exaclty - * that many bytes. - * - * If not present, but there is Transfer-Encoding or Content-Type - * (or a request to close connection, that is, end of data is signaled - * by remote close), we will forward until EOF. - * - * No C-L, no T-E, no C-T == no body. - */ - tmp = hlist_get(current->headers, "Content-Length"); - if (!nobody && tmp == NULL && (hlist_in(current->headers, "Content-Type") - || hlist_in(current->headers, "Transfer-Encoding") - || (response->code == 200))) { - length = -1; - } else - length = (tmp == NULL || nobody ? 0 : atol(tmp)); - - return length; -} - -int scanner_hook(rr_data_t *request, rr_data_t *response, int *cd, int *sd, long maxKBs) { - char *buf, *line, *pos, *tmp, *pat, *post, *isaid, *uurl; - int bsize, lsize, size, len, i, nc; - rr_data_t newreq, newres; - plist_t list; - int ok = 1; - int done = 0; - int headers_initiated = 0; - long c, progress = 0, filesize = 0; - - if (!(*request)->method || !(*response)->http - || has_body(*request, *response) != -1 - || hlist_subcmp((*response)->headers, "Transfer-Encoding", "chunked") - || !hlist_subcmp((*response)->headers, "Proxy-Connection", "close")) - return PLUG_SENDHEAD | PLUG_SENDDATA; - - tmp = hlist_get((*request)->headers, "User-Agent"); - if (tmp) { - tmp = lowercase(strdup(tmp)); - list = scanner_agent_list; - while (list) { - pat = lowercase(strdup(list->aux)); - if (debug) - printf("scanner_hook: matching U-A header (%s) to %s\n", tmp, pat); - if (!fnmatch(pat, tmp, 0)) { - if (debug) - printf("scanner_hook: positive match!\n"); - maxKBs = 0; - free(pat); - break; - } - free(pat); - list = list->next; - } - free(tmp); - } - - bsize = SAMPLE; - buf = new(bsize); - - len = 0; - do { - size = read(*sd, buf + len, SAMPLE - len - 1); - if (debug) - printf("scanner_hook: read %d of %d\n", size, SAMPLE - len); - if (size > 0) - len += size; - } while (size > 0 && len < SAMPLE - 1); - - if (strstr(buf, "Downloading status") && (pos=strstr(buf, "ISAServerUniqueID=")) && (pos = strchr(pos, '"'))) { - pos++; - c = strlen(pos); - for (i = 0; i < c && pos[i] != '"'; ++i); - - if (pos[i] == '"') { - isaid = substr(pos, 0, i); - if (debug) - printf("scanner_hook: ISA id = %s\n", isaid); - - lsize = BUFSIZE; - line = new(lsize); - do { - i = so_recvln(*sd, &line, &lsize); - - c = strlen(line); - if (len + c >= bsize) { - bsize *= 2; - tmp = realloc(buf, bsize); - if (tmp == NULL) - break; - else - buf = tmp; - } - - strcat(buf, line); - len += c; - - if (i > 0 && (!strncmp(line, " UpdatePage(", 12) || (done=!strncmp(line, "DownloadFinished(", 17)))) { - if (debug) - printf("scanner_hook: %s", line); - - if ((pos=strstr(line, "To be downloaded"))) { - filesize = atol(pos+16); - if (debug) - printf("scanner_hook: file size detected: %ld KiBs (max: %ld)\n", filesize/1024, maxKBs); - - if (maxKBs && (maxKBs == 1 || filesize/1024 > maxKBs)) - break; - - /* - * We have to send HTTP protocol ID so we can send the notification - * headers during downloading. Once we've done that, it cannot appear - * again, which it would if we returned PLUG_SENDHEAD, so we must - * remember to not include it. - */ - headers_initiated = 1; - tmp = new(MINIBUF_SIZE); - snprintf(tmp, MINIBUF_SIZE, "HTTP/1.%s 200 OK\r\n", (*request)->http); - write(*cd, tmp, strlen(tmp)); - free(tmp); - } - - if (!headers_initiated) { - if (debug) - printf("scanner_hook: Giving up, \"To be downloaded\" line not found!\n"); - break; - } - - /* - * Send a notification header to the client, just so it doesn't timeout - */ - if (!done) { - tmp = new(MINIBUF_SIZE); - progress = atol(line+12); - snprintf(tmp, MINIBUF_SIZE, "ISA-Scanner: %ld of %ld\r\n", progress, filesize); - write(*cd, tmp, strlen(tmp)); - free(tmp); - } - - /* - * If download size is unknown beforehand, stop when downloaded amount is over ISAScannerSize - */ - if (!filesize && maxKBs && maxKBs != 1 && progress/1024 > maxKBs) - break; - } - } while (i > 0 && !done); - - if (i > 0 && done && (pos = strstr(line, "\",\"")+3) && (c = strchr(pos, '"')-pos) > 0) { - tmp = substr(pos, 0, c); - pos = urlencode(tmp); - free(tmp); - - uurl = urlencode((*request)->url); - - post = new(BUFSIZE); - snprintf(post, bsize, "%surl=%s&%sSaveToDisk=YES&%sOrig=%s", isaid, pos, isaid, isaid, uurl); - - if (debug) - printf("scanner_hook: Getting file with URL data = %s\n", (*request)->url); - - tmp = new(MINIBUF_SIZE); - snprintf(tmp, MINIBUF_SIZE, "%d", (int)strlen(post)); - - newres = new_rr_data(); - newreq = dup_rr_data(*request); - - free(newreq->method); - newreq->method = strdup("POST"); - hlist_mod(newreq->headers, "Referer", (*request)->url, 1); - hlist_mod(newreq->headers, "Content-Type", "application/x-www-form-urlencoded", 1); - hlist_mod(newreq->headers, "Content-Length", tmp, 1); - free(tmp); - - /* - * Try to use a cached connection or authenticate new. - */ - pthread_mutex_lock(&connection_mtx); - i = plist_pop(&connection_list); - pthread_mutex_unlock(&connection_mtx); - if (i) { - if (debug) - printf("scanner_hook: Found autenticated connection %d!\n", i); - nc = i; - } else { - nc = proxy_connect(); - c = authenticate(nc, newreq, creds, NULL); - if (c > 0 && c != 500) { - if (debug) - printf("scanner_hook: Authentication OK, getting the file...\n"); - } else { - if (debug) - printf("scanner_hook: Authentication failed\n"); - nc = 0; - } - } - - /* - * The POST request for the real file - */ - if (nc && headers_send(nc, newreq) && write(nc, post, strlen(post)) && headers_recv(nc, newres)) { - if (debug) - hlist_dump(newres->headers); - - free_rr_data(*response); - - /* - * We always know the filesize here. Send it to the client, because ISA doesn't!!! - * The clients progress bar doesn't work without it and it stinks! - */ - if (filesize || progress) { - tmp = new(20); - snprintf(tmp, 20, "%ld", filesize ? filesize : progress); - newres->headers = hlist_mod(newres->headers, "Content-Length", tmp, 1); - } - - /* - * Here we remember if previous code already sent some headers - * to the client. In such case, do not include the HTTP/1.x ID. - */ - newres->skip_http = headers_initiated; - *response = dup_rr_data(newres); - *sd = nc; - - len = 0; - ok = PLUG_SENDHEAD | PLUG_SENDDATA; - } else if (debug) - printf("scanner_hook: New request failed\n"); - - free(newreq); - free(newres); - free(post); - free(uurl); - } - - free(line); - free(isaid); - } else if (debug) - printf("scanner_hook: ISA id not found\n"); - } - - if (len) { - if (debug) { - printf("scanner_hook: flushing %d original bytes\n", len); - hlist_dump((*response)->headers); - } - - if (!headers_send(*cd, *response)) { - if (debug) - printf("scanner_hook: failed to send headers\n"); - free(buf); - return PLUG_ERROR; - } - - size = write(*cd, buf, len); - if (size > 0) - ok = PLUG_SENDDATA; - else - ok = PLUG_ERROR; - } - - if (debug) - printf("scanner_hook: ending with %d\n", ok); - - free(buf); - return ok; -} - -/* - * Thread starts here. Connect to the proxy, clear "already authenticated" flag. - * - * Then process the client request, authentication and proxy reply - * back to client. We loop here to allow proxy keep-alive connections) - * until the proxy closes. - */ -void *proxy_thread(void *client) { - int *rsocket[2], *wsocket[2]; - int i, loop, bodylen, keep, chunked, plugin, closed; - rr_data_t data[2], errdata; - hlist_t tl; - char *tmp, *buf, *pos, *dom; - struct auth_s *tcreds; /* Per-thread credentials; for NTLM-to-basic */ - - int cd = (int)client; - int authok = 0; - int sd = 0; - - if (debug) - printf("Thread processing...\n"); - - pthread_mutex_lock(&connection_mtx); - if (debug) - plist_dump(connection_list); - i = plist_pop(&connection_list); - pthread_mutex_unlock(&connection_mtx); - if (i) { - if (debug) - printf("Found autenticated connection %d!\n", i); - sd = i; - authok = 1; - } - - if (!sd) - sd = proxy_connect(); - - if (!ntlmbasic) { - tcreds = dup_auth(creds, 1); - } else { - tcreds = dup_auth(creds, 0); - } - - if (sd <= 0) - goto bailout; - - do { - /* - * data[0] is for the first loop pass - * - we read the request headers from the client - * - if not already done, we try to authenticate the connection - * - we send the request headers to the proxy with HTTP body, if present - * - * data[1] is for the second pass - * - read proxy response - * - forward it to the client with HTTP body, if present - */ - data[0] = new_rr_data(); - data[1] = new_rr_data(); - - rsocket[0] = wsocket[1] = &cd; - rsocket[1] = wsocket[0] = &sd; - - keep = 0; - - for (loop = 0; loop < 2; loop++) { - if (debug) { - printf("\n******* Round %d C: %d, S: %d *******!\n", loop+1, cd, sd); - printf("Reading headers...\n"); - } - - if (!headers_recv(*rsocket[loop], data[loop])) { - close(sd); - free_rr_data(data[0]); - free_rr_data(data[1]); - goto bailout; - } - - chunked = 0; - - if (debug) - hlist_dump(data[loop]->headers); - - /* - * NTLM-to-Basic implementation - * Switch to this mode automatically if the config-file - * supplied credentials don't work. - */ - if (!loop && ntlmbasic) { - tmp = hlist_get(data[loop]->headers, "Proxy-Authorization"); - pos = NULL; - buf = NULL; - - if (tmp) { - buf = new(strlen(tmp)); - i = 5; - while (tmp[++i] == ' '); - from_base64(buf, tmp+i); - if (debug) - printf("NTLM-to-basic: Received client credentials.\n"); - pos = strchr(buf, ':'); - } - - if (pos == NULL) { - if (debug && tmp != NULL) - printf("NTLM-to-basic: Could not parse given credentials.\n"); - if (debug) - printf("NTLM-to-basic: Sending the client auth request.\n"); - - tmp = gen_auth_page(data[loop]->http); - write(cd, tmp, strlen(tmp)); - free(tmp); - - if (buf) { - memset(buf, 0, strlen(buf)); - free(buf); - } - - close(sd); - free_rr_data(data[0]); - free_rr_data(data[1]); - goto bailout; - } else { - dom = strchr(buf, '\\'); - if (dom == NULL) { - auth_strncpy(tcreds, user, buf, MIN(MINIBUF_SIZE, pos-buf+1)); - } else { - auth_strncpy(tcreds, domain, buf, MIN(MINIBUF_SIZE, dom-buf+1)); - auth_strncpy(tcreds, user, dom+1, MIN(MINIBUF_SIZE, pos-dom)); - } - - if (tcreds->hashntlm2) { - tmp = ntlm2_hash_password(tcreds->user, tcreds->domain, pos+1); - auth_memcpy(tcreds, passntlm2, tmp, 16); - free(tmp); - } - - if (tcreds->hashnt) { - tmp = ntlm_hash_nt_password(pos+1); - auth_memcpy(tcreds, passnt, tmp, 21); - free(tmp); - } - - if (tcreds->hashlm) { - tmp = ntlm_hash_lm_password(pos+1); - auth_memcpy(tcreds, passlm, tmp, 21); - free(tmp); - } - - if (debug) { - printf("NTLM-to-basic: Credentials parsed: %s\\%s at %s\n", tcreds->domain, tcreds->user, tcreds->workstation); - } - - memset(buf, 0, strlen(buf)); - free(buf); - } - } - - /* - } else if (loop && data[loop]->code == 407) { - if (debug) - printf("NTLM-to-basic: Given credentials failed for proxy access.\n"); - - if (!ntlmbasic) - forced_basic = 1; - - tmp = gen_auth_page(data[loop]->http); - write(cd, tmp, strlen(tmp)); - free(tmp); - - close(sd); - free_rr_data(data[0]); - free_rr_data(data[1]); - goto bailout; - } - */ - - /* - * Try to request keep-alive for every connection, but first remember if client - * really asked for it. If not, disconnect from him after the request and keep - * the authenticated connection in a pool. - * - * This way, proxy doesn't (or rather shouldn't) close our connection after - * completing the request. We store this connection and when the client is done - * and disconnects, we have an authenticated connection ready for future clients. - * - * The connection pool is shared among all threads, allowing maximum reuse. - */ - if (!loop && data[loop]->req) { - if (hlist_subcmp(data[loop]->headers, "Proxy-Connection", "keep-alive")) - keep = 1; - - /* - * Header replacement implementation - */ - tl = header_list; - while (tl) { - data[loop]->headers = hlist_mod(data[loop]->headers, tl->key, tl->value, 0); - tl = tl->next; - } - - /* - * Also remove runaway P-A from the client (e.g. Basic from N-t-B), which might - * cause some ISAs to deny us, even if the connection is already auth'd. - */ - data[loop]->headers = hlist_mod(data[loop]->headers, "Proxy-Connection", "Keep-Alive", 1); - if (!CONNECT(data[loop])) - data[loop]->headers = hlist_mod(data[loop]->headers, "Connection", "Keep-Alive", 1); - data[loop]->headers = hlist_del(data[loop]->headers, "Proxy-Authorization"); - } - - /* - * Got request from client and connection is not yet authenticated? - */ - if (!loop && data[0]->req && !authok) { - errdata = NULL; - if (!(i = authenticate(*wsocket[0], data[0], tcreds, &closed))) - syslog(LOG_ERR, "Authentication requests failed. Will try without.\n"); - - if (!i || closed || so_closed(sd)) { - if (debug) - printf("Proxy closed connection (i=%d, closed=%d, so_closed=%d). Reconnecting...\n", i, closed, so_closed(sd)); - close(sd); - sd = proxy_connect(); - if (sd <= 0) { - free_rr_data(data[0]); - free_rr_data(data[1]); - goto bailout; - } - } - } - - /* - * Was the request first and did we authenticated with proxy? - * Remember not to authenticate this connection any more, should - * it be reused for future client requests. - */ - if (loop && !authok && data[1]->code != 407) - authok = 1; - - /* - * This is to make the ISA AV scanner bullshit transparent. - * If the page returned is scan-progress-html-fuck instead - * of requested file/data, parse it, wait for completion, - * make a new request to ISA for the real data and substitute - * the result for the original response html-fuck response. - */ - plugin = PLUG_ALL; - if (scanner_plugin && loop) { - plugin = scanner_hook(&data[0], &data[1], wsocket[loop], rsocket[loop], scanner_plugin_maxsize); - } - - bodylen = has_body(data[0], data[1]); - if (debug && bodylen == -1) { - printf("*************************\n"); - printf("CL: %s, C: %s, CT: %s, TE: %s\n", - hlist_get(data[loop]->headers, "Content-Length"), - hlist_get(data[loop]->headers, "Connection"), - hlist_get(data[loop]->headers, "Content-Type"), - hlist_get(data[loop]->headers, "Transfer-Encoding")); - } - - if (plugin & PLUG_SENDHEAD) { - if (debug) { - printf("Sending headers...\n"); - if (!loop) - hlist_dump(data[loop]->headers); - } - - /* - if (loop && serialize) { - hlist_mod(data[loop]->headers, "Proxy-Connection", "close", 1); - hlist_mod(data[loop]->headers, "Connection", "close", 1); - } - */ - - /* - * Forward client's headers to the proxy and vice versa; authenticate() - * might have by now prepared 1st and 2nd auth steps and filled our - * headers with the 3rd, final, NTLM message. - */ - if (!headers_send(*wsocket[loop], data[loop])) { - close(sd); - free_rr_data(data[0]); - free_rr_data(data[1]); - goto bailout; - } - } - - /* - * Was the request CONNECT and proxy agreed? - */ - if (loop && CONNECT(data[0]) && data[1]->code == 200) { - if (debug) - printf("Ok CONNECT response. Tunneling...\n"); - - tunnel(cd, sd); - close(sd); - free_rr_data(data[0]); - free_rr_data(data[1]); - goto bailout; - } - - if (plugin & PLUG_SENDDATA) { - /* - * Ok, so do we expect any body? - */ - if (bodylen) { - /* - * Check for supported T-E. - */ - if (hlist_subcmp(data[loop]->headers, "Transfer-Encoding", "chunked")) - chunked = 1; - - if (chunked) { - if (debug) - printf("Chunked body included.\n"); - - if (!chunked_data_send(*wsocket[loop], *rsocket[loop])) { - if (debug) - printf("Could not chunk send whole body\n"); - close(sd); - free_rr_data(data[0]); - free_rr_data(data[1]); - goto bailout; - } else if (debug) { - printf("Chunked body sent.\n"); - } - } else { - if (debug) - printf("Body included. Lenght: %d\n", bodylen); - - if (!bodylen || !data_send(*wsocket[loop], *rsocket[loop], bodylen)) { - if (debug) - printf("Could not send whole body\n"); - close(sd); - free_rr_data(data[0]); - free_rr_data(data[1]); - goto bailout; - } else if (debug) { - printf("Body sent.\n"); - } - } - } else if (debug) - printf("No body.\n"); - - /* - * Windows cannot detect remotely closed connection - * as accurately as UNIX. We look if the proxy explicitly - * tells us that it's closing the connection and if so, use - * it as fact that the connection is closed. - */ - if (hlist_subcmp(data[loop]->headers, "Proxy-Connection", "close")) { - if (debug) - printf("PROXY CLOSED CONNECTION\n"); - close(sd); - } - - } - } - - free_rr_data(data[0]); - free_rr_data(data[1]); - } while (!so_closed(sd) && !so_closed(cd) && !serialize && (keep || so_dataready(cd))); - -bailout: - free_auth(tcreds); - - if (debug) - printf("\nThread finished.\n"); - - close(cd); - if (!so_closed(sd) && authok) { - if (debug) - printf("Storing the connection for reuse (%d:%d).\n", cd, sd); - pthread_mutex_lock(&connection_mtx); - connection_list = plist_add(connection_list, sd, NULL); - pthread_mutex_unlock(&connection_mtx); - } else - close(sd); - - /* - * Add ourselves to the "threads to join" list. - */ - if (!serialize) { - pthread_mutex_lock(&threads_mtx); - threads_list = plist_add(threads_list, (unsigned long)pthread_self(), NULL); - pthread_mutex_unlock(&threads_mtx); - } - - return NULL; -} - -void *precache_thread(void *data) { - int i, sd, closed; - rr_data_t data1, data2; - char *tmp; - int new = 0; - - while (!quit) { - if (plist_count(connection_list) < precache && check_active() < 1) { - printf("precache_thread: creating new connection (active: %d)\n", active_conns); - sd = proxy_connect(); - - data1 = new_rr_data(); - data2 = new_rr_data(); - - data1->req = 1; - data1->method = strdup("GET"); - data1->url = strdup("http://www.google.com/"); - data1->http = strdup("1"); - - i = authenticate(sd, data1, creds, &closed); - if (i && i == 1 && !closed && !so_closed(sd) && headers_send(sd, data1) && headers_recv(sd, data2) && data2->code == 302) { - tmp = hlist_get(data2->headers, "Content-Length"); - if (tmp) - data_drop(sd, atoi(tmp)); - - new++; - pthread_mutex_lock(&connection_mtx); - connection_list = plist_add(connection_list, sd, NULL); - pthread_mutex_unlock(&connection_mtx); - } else { - if (debug) - printf("precache_thread: cooling down (i = %d, closed = %d, code = %d)...\n", i, so_closed(sd), data2->code); - sleep(60); - } - } else { - printf("SLEEPING\n"); - sleep(4); - if (active_conns > 0) { - new = 0; - printf("*************************************************************************\n"); - printf("precache_thread: connection cache full (%d), resting (active: %d)\n", plist_count(connection_list), active_conns); - } - sched_yield(); - } - } - - return NULL; -} - -/* - * Another thread-create function. This one does the tunneling/forwarding - * for the -L parameter. We receive malloced structure (pthreads pass only - * one pointer arg) containing accepted client socket and a string with - * remote server:port address. - * - * The -L is obviously better tunneling solution than using extra tools like - * "corkscrew" which after all require us for authentication and tunneling - * their HTTP CONNECT in the end. - */ -void *tunnel_thread(void *client) { - int cd = ((struct thread_arg_s *)client)->fd; - char *thost = ((struct thread_arg_s *)client)->target; - int sd; - - sd = proxy_connect(); - free(client); - - if (sd <= 0) { - close(cd); - return NULL; - } - - if (debug) - printf("Tunneling to %s for client %d...\n", thost, cd); - - if (!make_connect(sd, thost)) { - tunnel(cd, sd); - } - - close(sd); - close(cd); - - /* - * Add ourself to the "threads to join" list. - */ - pthread_mutex_lock(&threads_mtx); - threads_list = plist_add(threads_list, (unsigned long)pthread_self(), NULL); - pthread_mutex_unlock(&threads_mtx); - - return NULL; -} - -void *socks5_thread(void *client) { - int cd = (int)client; - char *tmp, *thost, *tport, *uname, *upass; - unsigned char *bs, *auths, *addr; - unsigned short port; - int ver, r, c, i; - - int found = -1; - int sd = 0; - int open = !hlist_count(users_list); - - /* - * Check client's version, possibly fuck'em - */ - bs = (unsigned char *)new(10); - thost = new(MINIBUF_SIZE); - tport = new(MINIBUF_SIZE); - r = read(cd, bs, 2); - if (r != 2 || bs[0] != 5) - goto bail1; - - /* - * Read offered auth schemes - */ - c = bs[1]; - auths = (unsigned char *)new(c+1); - r = read(cd, auths, c); - if (r != c) - goto bail2; - - /* - * Are we wide open and client is OK with no auth? - */ - if (open) { - for (i = 0; i < c && (auths[i] || (found = 0)); ++i); - } - - /* - * If not, accept plain auth if offered - */ - if (found < 0) { - for (i = 0; i < c && (auths[i] != 2 || !(found = 2)); ++i); - } - - /* - * If not open and no auth offered or open and auth requested, fuck'em - * and complete the handshake - */ - if (found < 0) { - bs[0] = 5; - bs[1] = 0xFF; - write(cd, bs, 2); - goto bail2; - } else { - bs[0] = 5; - bs[1] = found; - write(cd, bs, 2); - } - - /* - * Plain auth negotiated? - */ - if (found != 0) { - /* - * Check ver and read username len - */ - r = read(cd, bs, 2); - if (r != 2) { - bs[0] = 1; - bs[1] = 0xFF; /* Unsuccessful (not supported) */ - write(cd, bs, 2); - goto bail2; - } - c = bs[1]; - - /* - * Read username and pass len - */ - uname = new(c+1); - r = read(cd, uname, c+1); - if (r != c+1) { - free(uname); - goto bail2; - } - i = uname[c]; - uname[c] = 0; - c = i; - - /* - * Read pass - */ - upass = new(c+1); - r = read(cd, upass, c); - if (r != c) { - free(upass); - free(uname); - goto bail2; - } - upass[c] = 0; - - /* - * Check credentials against the list - */ - tmp = hlist_get(users_list, uname); - if (!hlist_count(users_list) || (tmp && !strcmp(tmp, upass))) { - bs[0] = 1; - bs[1] = 0; /* Success */ - } else { - bs[0] = 1; - bs[1] = 0xFF; /* Failed */ - } - - /* - * Send response - */ - write(cd, bs, 2); - free(upass); - free(uname); - - /* - * Fuck'em if auth failed - */ - if (bs[1]) - goto bail2; - } - - /* - * Read request type - */ - r = read(cd, bs, 4); - if (r != 4) - goto bail2; - - /* - * Is it connect for supported address type (IPv4 or DNS)? If not, fuck'em - */ - if (bs[1] != 1 || (bs[3] != 1 && bs[3] != 3)) { - bs[0] = 5; - bs[1] = 2; /* Not allowed */ - bs[2] = 0; - bs[3] = 1; /* Dummy IPv4 */ - memset(bs+4, 0, 6); - write(cd, bs, 10); - goto bail2; - } - - /* - * Ok, it's connect to a domain or IP - * Let's read dest address - */ - if (bs[3] == 1) { - ver = 1; /* IPv4, we know the length */ - c = 4; - } else if (bs[3] == 3) { - ver = 2; /* FQDN, get string length */ - r = read(cd, &c, 1); - if (r != 1) - goto bail2; - } else - goto bail2; - - addr = (unsigned char *)new(c+10 + 1); - r = read(cd, addr, c); - if (r != c) - goto bail3; - addr[c] = 0; - - /* - * Convert the address to character string - */ - if (ver == 1) { - sprintf(thost, "%d.%d.%d.%d", addr[0], addr[1], addr[2], addr[3]); /* It's in network byte order */ - } else { - strlcpy(thost, (char *)addr, MINIBUF_SIZE); - } - - /* - * Read port number and convert to host byte order int - */ - r = read(cd, &port, 2); - if (r != 2) - goto bail3; - sprintf(tport, "%d", ntohs(port)); - strlcat(thost, ":", MINIBUF_SIZE); - strlcat(thost, tport, MINIBUF_SIZE); - - /* - * Try connect to parent proxy - */ - sd = proxy_connect(); - if (sd <= 0 || (i=make_connect(sd, thost))) { - /* - * No such luck, report failure - */ - bs[0] = 5; - bs[1] = 1; /* General failure */ - bs[2] = 0; - bs[3] = 1; /* Dummy IPv4 */ - memset(bs+4, 0, 6); - write(cd, bs, 10); - goto bail3; - } else { - /* - * Proxy ok, auth worked - */ - bs[0] = 5; - bs[1] = 0; /* Success */ - bs[2] = 0; - bs[3] = 1; /* Dummy IPv4 */ - memset(bs+4, 0, 6); - write(cd, bs, 10); - } - - /* - * Let's give them bi-directional connection they asked for - */ - tunnel(cd, sd); - -bail3: - free(addr); -bail2: - free(auths); -bail1: - free(thost); - free(tport); - free(bs); - close(cd); - if (sd) - close(sd); - - return NULL; -} - -#define MAGIC_TESTS 11 - -void magic_auth_detect(const char *url) { - int i, nc, c, closed, found = -1; - rr_data_t req, res; - char *tmp, *pos, *host = NULL; - - char *authstr[5] = { "NTLMv2", "NTLM2SR", "NT", "NTLM", "LM" }; - int prefs[MAGIC_TESTS][5] = { - /* NT, LM, NTLMv2, Flags, Auth param equiv. */ - { 0, 0, 1, 0, 0 }, - { 0, 0, 1, 0xa208b207, 0 }, - { 0, 0, 1, 0xa2088207, 0 }, - { 2, 0, 0, 0, 1 }, - { 2, 0, 0, 0x88207, 1 }, - { 1, 0, 0, 0, 2 }, - { 1, 0, 0, 0x8205, 2 }, - { 1, 1, 0, 0, 3 }, - { 1, 1, 0, 0x8207, 3 }, - { 0, 1, 0, 0, 4 }, - { 0, 1, 0, 0x8206, 4 } - }; - - debug = 0; - - if (!creds->passnt || !creds->passlm || !creds->passntlm2) { - printf("Cannot detect NTLM dialect - password or its hashes must be defined, try -I\n"); - exit(1); - } - - pos = strstr(url, "://"); - if (pos) { - tmp = strchr(pos+3, '/'); - host = substr(pos+3, 0, tmp ? tmp-pos-3 : 0); - } else { - fprintf(stderr, "Invalid URL (%s)\n", url); - return; - } - - for (i = 0; i < MAGIC_TESTS; ++i) { - res = new_rr_data(); - req = new_rr_data(); - - req->req = 1; - req->method = strdup("GET"); - req->url = strdup(url); - req->http = strdup("1"); - req->headers = hlist_add(req->headers, "Proxy-Connection", "Keep-Alive", 1, 1); - if (host) - req->headers = hlist_add(req->headers, "Host", host, 1, 1); - - creds->hashnt = prefs[i][0]; - creds->hashlm = prefs[i][1]; - creds->hashntlm2 = prefs[i][2]; - creds->flags = prefs[i][3]; - - printf("Config profile %2d/%d... ", i+1, MAGIC_TESTS); - - nc = proxy_connect(); - if (nc <= 0) { - printf("\nConnection to proxy failed, bailing out\n"); - free_rr_data(res); - free_rr_data(req); - close(nc); - if (host) - free(host); - return; - } - - c = authenticate(nc, req, creds, &closed); - if (c <= 0 || c == 500 || closed) { - printf("Auth request ignored (HTTP code: %d)\n", c); - free_rr_data(res); - free_rr_data(req); - close(nc); - continue; - } - - if (!headers_send(nc, req) || !headers_recv(nc, res)) { - printf("Connection closed\n"); - } else { - if (res->code == 407) { - printf("Credentials rejected\n"); - } else { - printf("OK (HTTP code: %d)\n", res->code); - if (found < 0) { - found = i; - /* - * Following only for prod. version - */ - free_rr_data(res); - free_rr_data(req); - close(nc); - break; - } - } - } - - free_rr_data(res); - free_rr_data(req); - close(nc); - } - - if (found > -1) { - printf("----------------------------[ Profile %2d ]------\n", found); - printf("Auth %s\n", authstr[prefs[found][4]]); - if (prefs[found][3]) - printf("Flags 0x%x\n", prefs[found][3]); - if (prefs[found][0]) { - printf("PassNT %s\n", tmp=printmem(creds->passnt, 16, 8)); - free(tmp); - } - if (prefs[found][1]) { - printf("PassLM %s\n", tmp=printmem(creds->passlm, 16, 8)); - free(tmp); - } - if (prefs[found][2]) { - printf("PassNTLMv2 %s\n", tmp=printmem(creds->passntlm2, 16, 8)); - free(tmp); - } - printf("------------------------------------------------\n"); - } else - printf("You have used wrong credentials, bad URL or your proxy is quite insane,\nin which case try submitting a Support Request.\n"); - - if (host) - free(host); -} - -void carp(const char *msg, int console) { - if (console) - printf(msg); - else - syslog(LOG_ERR, msg); - - myexit(1); -} - -int main(int argc, char **argv) { - char *tmp, *head; - char *cpassword, *cpassntlm2, *cpassnt, *cpasslm, *cuser, *cdomain, *cworkstation, *cuid, *cpidfile, *cauth; - struct passwd *pw; - struct termios termold, termnew; - pthread_attr_t pattr; - pthread_t pthr; - hlist_t list; - int i; - - int cd = 0; - int help = 0; - int nuid = 0; - int ngid = 0; - int gateway = 0; - int tc = 0; - int tj = 0; - int interactivepwd = 0; - int interactivehash = 0; - int tracefile = 0; - int cflags = 0; - plist_t tunneld_list = NULL; - plist_t proxyd_list = NULL; - plist_t socksd_list = NULL; - plist_t rules = NULL; - config_t cf = NULL; - char *magic_detect = NULL; - - creds = new_auth(); - cuser = new(MINIBUF_SIZE); - cdomain = new(MINIBUF_SIZE); - cpassword = new(MINIBUF_SIZE); - cpassntlm2 = new(MINIBUF_SIZE); - cpassnt = new(MINIBUF_SIZE); - cpasslm = new(MINIBUF_SIZE); - cworkstation = new(MINIBUF_SIZE); - cpidfile = new(MINIBUF_SIZE); - cuid = new(MINIBUF_SIZE); - cauth = new(MINIBUF_SIZE); - - openlog("cntlm", LOG_CONS, LOG_DAEMON); - -#if config_endian == 0 - syslog(LOG_INFO, "Starting cntlm version " VERSION " for BIG endian\n"); -#else - syslog(LOG_INFO, "Starting cntlm version " VERSION " for LITTLE endian\n"); -#endif - - while ((i = getopt(argc, argv, ":-:a:c:d:fghIl:p:r:su:vw:A:BD:F:G:HL:M:O:P:R:S:T:U:")) != -1) { - switch (i) { - case 'A': - case 'D': - if (!acl_add(&rules, optarg, (i == 'A' ? ACL_ALLOW : ACL_DENY))) - myexit(1); - break; - case 'a': - strlcpy(cauth, optarg, MINIBUF_SIZE); - break; - case 'B': - ntlmbasic = 1; - break; - case 'c': - if (!(cf = config_open(optarg))) { - syslog(LOG_ERR, "Cannot access specified config file: %s\n", optarg); - myexit(1); - } - break; - case 'd': - strlcpy(cdomain, optarg, MINIBUF_SIZE); - break; - case 'F': - cflags = swap32(strtoul(optarg, &tmp, 0)); - break; - case 'f': - asdaemon = 0; - break; - case 'G': - if (strlen(optarg)) { - scanner_plugin = 1; - if (!scanner_plugin_maxsize) - scanner_plugin_maxsize = 1; - i = strlen(optarg) + 3; - tmp = new(i); - snprintf(tmp, i, "*%s*", optarg); - scanner_agent_list = plist_add(scanner_agent_list, 0, tmp); - } - break; - case 'g': - gateway = 1; - break; - case 'H': - interactivehash = 1; - break; - case 'I': - interactivepwd = 1; - break; - case 'L': - /* - * Parse and validate the argument. - * Create a listening socket for tunneling. - */ - tunnel_add(&tunneld_list, optarg, gateway); - break; - case 'l': - /* - * Create a listening socket for proxy function. - */ - listen_add("Proxy", &proxyd_list, optarg, gateway); - break; - case 'M': - magic_detect = strdup(optarg); - break; - case 'O': - listen_add("SOCKS5 proxy", &socksd_list, optarg, gateway); - break; - case 'P': - strlcpy(cpidfile, optarg, MINIBUF_SIZE); - break; - case 'p': - /* - * Overwrite the password parameter with '*'s to make it - * invisible in "ps", /proc, etc. - */ - strlcpy(cpassword, optarg, MINIBUF_SIZE); - for (i = strlen(optarg)-1; i >= 0; --i) - optarg[i] = '*'; - break; - case 'R': - tmp = strdup(optarg); - head = strchr(tmp, ':'); - if (!head) { - fprintf(stderr, "Invalid username:password format for -R: %s\n", tmp); - } else { - head[0] = 0; - users_list = hlist_add(users_list, tmp, head+1, 1, 1); - } - break; - case 'r': - if (head_ok(optarg)) - header_list = hlist_add(header_list, head_name(optarg), head_value(optarg), 0, 0); - break; - case 'S': - scanner_plugin = 1; - scanner_plugin_maxsize = atol(optarg); - break; - case 's': - /* - * Do not use threads - for debugging purposes only - */ - serialize = 1; - break; - case 'T': - tracefile = open(optarg, O_CREAT | O_EXCL | O_WRONLY, 0600); - if (tracefile < 0) { - fprintf(stderr, "Cannot create the trace file, make sure it doesn't already exist.\n"); - myexit(1); - } else { - printf("Redirecting all output to %s\n", optarg); - dup2(tracefile, 1); - dup2(tracefile, 2); - printf("Cntlm debug trace, version " VERSION); -#ifdef __CYGWIN__ - printf(" win32/cygwin port"); -#endif - printf(".\nCommand line: "); - for (i = 0; i < argc; ++i) - printf("%s ", argv[i]); - printf("\n"); - } - break; - case 'U': - strlcpy(cuid, optarg, MINIBUF_SIZE); - break; - case 'u': - i = strcspn(optarg, "@"); - if (i != strlen(optarg)) { - strlcpy(cuser, optarg, MIN(MINIBUF_SIZE, i+1)); - strlcpy(cdomain, optarg+i+1, MINIBUF_SIZE); - } else { - strlcpy(cuser, optarg, MINIBUF_SIZE); - } - break; - case 'v': - debug = 1; - asdaemon = 0; - openlog("cntlm", LOG_CONS | LOG_PERROR, LOG_DAEMON); - break; - case 'w': - strlcpy(cworkstation, optarg, MINIBUF_SIZE); - break; - case 'h': - default: - help = 1; - } - } - - /* - * Help requested? - */ - if (help) { - printf("CNTLM - Accelerating NTLM Authentication Proxy version " VERSION "\nCopyright (c) 2oo7 David Kubicek\n\n" - "This program comes with NO WARRANTY, to the extent permitted by law. You\n" - "may redistribute copies of it under the terms of the GNU GPL Version 2 or\n" - "newer. For more information about these matters, see the file LICENSE.\n" - "For copyright holders of included encryption routines see headers.\n\n"); - - fprintf(stderr, "Usage: %s [-AaBcDdFfgHhILlMPSsTUvw] -u [@] -p [:] ...\n", argv[0]); - fprintf(stderr, "\t-A
[/]\n" - "\t New ACL allow rule. Address can be an IP or a hostname, net must be a number (CIDR notation)\n"); - fprintf(stderr, "\t-a ntlm | nt | lm\n" - "\t Authentication parameter - combined NTLM, just LM, or just NT. Default is to,\n" - "\t send both, NTLM. It is the most versatile setting and likely to work for you.\n"); - fprintf(stderr, "\t-B Enable NTLM-to-basic authentication.\n"); - fprintf(stderr, "\t-c \n" - "\t Configuration file. Other arguments can be used as well, overriding\n" - "\t config file settings.\n"); - fprintf(stderr, "\t-D
[/]\n" - "\t New ACL deny rule. Syntax same as -A.\n"); - fprintf(stderr, "\t-d \n" - "\t Domain/workgroup can be set separately.\n"); - fprintf(stderr, "\t-f Run in foreground, do not fork into daemon mode.\n"); - fprintf(stderr, "\t-F \n" - "\t NTLM authentication flags.\n"); - fprintf(stderr, "\t-G \n" - "\t User-Agent matching for the trans-isa-scan plugin.\n"); - fprintf(stderr, "\t-g Gateway mode - listen on all interfaces, not only loopback.\n"); - fprintf(stderr, "\t-H Prompt for the password interactively, print its hashes and exit (NTLMv2 needs -u and -d).\n"); - fprintf(stderr, "\t-h Print this help info along with version number.\n"); - fprintf(stderr, "\t-I Prompt for the password interactively.\n"); - fprintf(stderr, "\t-L [:]::\n" - "\t Forwarding/tunneling a la OpenSSH. Same syntax - listen on lport\n" - "\t and forward all connections through the proxy to rhost:rport.\n" - "\t Can be used for direct tunneling without corkscrew, etc.\n"); - fprintf(stderr, "\t-l [:]\n" - "\t Main listening port for the NTLM proxy.\n"); - fprintf(stderr, "\t-M \n" - "\t Magic autodetection of proxy's NTLM dialect.\n"); - fprintf(stderr, "\t-O [:]\n" - "\t Enable SOCKS5 proxy and make it listen on the specified port (and address).\n"); - fprintf(stderr, "\t-P \n" - "\t Create a PID file upon successful start.\n"); - fprintf(stderr, "\t-p \n" - "\t Account password. Will not be visible in \"ps\", /proc, etc.\n"); - fprintf(stderr, "\t-r \"HeaderName: value\"\n" - "\t Add a header substitution. All such headers will be added/replaced\n" - "\t in the client's requests.\n"); - fprintf(stderr, "\t-S \n" - "\t Enable transparent handler of ISA AV scanner plugin for files up to size_in_kb KiB.\n"); - fprintf(stderr, "\t-s Do not use threads, serialize all requests - for debugging only.\n"); - fprintf(stderr, "\t-U \n" - "\t Run as uid. It is an important security measure not to run as root.\n"); - fprintf(stderr, "\t-u [@\n" - "\t Some proxies require correct NetBIOS hostname.\n\n"); - exit(1); - } - - /* - * More arguments on the command-line? Must be proxies. - */ - i = optind; - while (i < argc) { - tmp = strchr(argv[i], ':'); - parent_add(argv[i], !tmp && i+1 < argc ? atoi(argv[i+1]) : 0); - i += (!tmp ? 2 : 1); - } - - /* - * No configuration file yet? Load the default. - */ -#ifdef SYSCONFDIR - if (!cf) { -#ifdef __CYGWIN__ - tmp = getenv("PROGRAMFILES"); - if (tmp == NULL) { - tmp = "C:\\Program Files"; - } - - head = new(MINIBUF_SIZE); - strlcpy(head, tmp, MINIBUF_SIZE); - strlcat(head, "\\cntlm\\cntlm.ini", MINIBUF_SIZE); - cf = config_open(head); -#else - cf = config_open(SYSCONFDIR "/cntlm.conf"); -#endif - if (debug) { - if (cf) - printf("Default config file opened successfully\n"); - else - syslog(LOG_ERR, "Could not open default config file\n"); - } - } -#endif - - /* - * If any configuration file was successfully opened, parse it. - */ - if (cf) { - /* - * Check if gateway mode is requested before actually binding any ports. - */ - tmp = new(MINIBUF_SIZE); - CFG_DEFAULT(cf, "Gateway", tmp, MINIBUF_SIZE); - if (!strcasecmp("yes", tmp)) - gateway = 1; - free(tmp); - - /* - * Check for NTLM-to-basic settings - */ - tmp = new(MINIBUF_SIZE); - CFG_DEFAULT(cf, "NTLMToBasic", tmp, MINIBUF_SIZE); - if (!strcasecmp("yes", tmp)) - ntlmbasic = 1; - free(tmp); - - /* - * Setup the rest of tunnels. - */ - while ((tmp = config_pop(cf, "Tunnel"))) { - tunnel_add(&tunneld_list, tmp, gateway); - free(tmp); - } - - /* - * Bind the rest of proxy service ports. - */ - while ((tmp = config_pop(cf, "Listen"))) { - listen_add("Proxy", &proxyd_list, tmp, gateway); - free(tmp); - } - - /* - * Bind the rest of SOCKS5 service ports. - */ - while ((tmp = config_pop(cf, "SOCKS5Proxy"))) { - listen_add("SOCKS5 proxy", &socksd_list, tmp, gateway); - free(tmp); - } - - /* - * Accept only headers not specified on the command line. - * Command line has higher priority. - */ - while ((tmp = config_pop(cf, "Header"))) { - if (head_ok(tmp)) { - head = head_name(tmp); - if (!hlist_in(header_list, head)) - header_list = hlist_add(header_list, head_name(tmp), head_value(tmp), 0, 0); - free(head); - } else - syslog(LOG_ERR, "Invalid header format: %s\n", tmp); - - free(tmp); - } - - /* - * Add the rest of parent proxies. - */ - while ((tmp = config_pop(cf, "Proxy"))) { - parent_add(tmp, 0); - free(tmp); - } - - /* - * No ACLs on the command line? Use config file. - */ - if (rules == NULL) { - list = cf->options; - while (list) { - if (!(i=strcasecmp("Allow", list->key)) || !strcasecmp("Deny", list->key)) - if (!acl_add(&rules, list->value, i ? ACL_DENY : ACL_ALLOW)) - myexit(1); - list = list->next; - } - - while ((tmp = config_pop(cf, "Allow"))) - free(tmp); - while ((tmp = config_pop(cf, "Deny"))) - free(tmp); - } - - /* - * Single options. - */ - CFG_DEFAULT(cf, "Auth", cauth, MINIBUF_SIZE); - CFG_DEFAULT(cf, "Domain", cdomain, MINIBUF_SIZE); - CFG_DEFAULT(cf, "Password", cpassword, MINIBUF_SIZE); - CFG_DEFAULT(cf, "PassNTLMv2", cpassntlm2, MINIBUF_SIZE); - CFG_DEFAULT(cf, "PassNT", cpassnt, MINIBUF_SIZE); - CFG_DEFAULT(cf, "PassLM", cpasslm, MINIBUF_SIZE); - CFG_DEFAULT(cf, "Username", cuser, MINIBUF_SIZE); - CFG_DEFAULT(cf, "Workstation", cworkstation, MINIBUF_SIZE); - - tmp = new(MINIBUF_SIZE); - CFG_DEFAULT(cf, "Flags", tmp, MINIBUF_SIZE); - if (!cflags) - cflags = swap32(strtoul(tmp, NULL, 0)); - free(tmp); - - tmp = new(MINIBUF_SIZE); - CFG_DEFAULT(cf, "ISAScannerSize", tmp, MINIBUF_SIZE); - if (!scanner_plugin_maxsize && strlen(tmp)) { - scanner_plugin = 1; - scanner_plugin_maxsize = atoi(tmp); - } - free(tmp); - - while ((tmp = config_pop(cf, "SOCKS5Users"))) { - head = strchr(tmp, ':'); - if (!head) { - syslog(LOG_ERR, "Invalid username:password format for SOCKS5User: %s\n", tmp); - } else { - head[0] = 0; - users_list = hlist_add(users_list, tmp, head+1, 1, 1); - } - } - - - /* - * Add User-Agent matching patterns. - */ - while ((tmp = config_pop(cf, "ISAScannerAgent"))) { - scanner_plugin = 1; - if (!scanner_plugin_maxsize) - scanner_plugin_maxsize = 1; - - if ((i = strlen(tmp))) { - head = new(i + 3); - snprintf(head, i+3, "*%s*", tmp); - scanner_agent_list = plist_add(scanner_agent_list, 0, head); - } - free(tmp); - } - - /* - * Print out unused/unknown options. - */ - list = cf->options; - while (list) { - syslog(LOG_INFO, "Ignoring config file option: %s\n", list->key); - list = list->next; - } - - /* - CFG_DEFAULT(cf, "PidFile", pidfile, MINIBUF_SIZE); - CFG_DEFAULT(cf, "Uid", uid, MINIBUF_SIZE); - */ - } - - config_close(cf); - - if (!ntlmbasic && !strlen(cuser)) - carp("Parent proxy account username missing.\n", interactivehash || interactivepwd || magic_detect); - - if (!ntlmbasic && !strlen(cdomain)) - carp("Parent proxy account domain missing.\n", interactivehash || interactivepwd || magic_detect); - - if (!interactivehash && !parent_list) - carp("Parent proxy address missing.\n", interactivepwd || magic_detect); - - if (!interactivehash && !magic_detect && !proxyd_list) - carp("No proxy service ports were successfully opened.\n", interactivepwd); - - /* - * Set default value for the workstation. Hostname if possible. - */ - if (!strlen(cworkstation)) { -#if config_gethostname == 1 - gethostname(cworkstation, MINIBUF_SIZE); -#endif - if (!strlen(cworkstation)) - strlcpy(cworkstation, "cntlm", MINIBUF_SIZE); - - syslog(LOG_INFO, "Workstation name used: %s\n", cworkstation); - } - - /* - * Parse selected NTLM hash combination. - */ - if (strlen(cauth)) { - if (!strcasecmp("ntlm", cauth)) { - creds->hashnt = 1; - creds->hashlm = 1; - creds->hashntlm2 = 0; - } else if (!strcasecmp("nt", cauth)) { - creds->hashnt = 1; - creds->hashlm = 0; - creds->hashntlm2 = 0; - } else if (!strcasecmp("lm", cauth)) { - creds->hashnt = 0; - creds->hashlm = 1; - creds->hashntlm2 = 0; - } else if (!strcasecmp("ntlmv2", cauth)) { - creds->hashnt = 0; - creds->hashlm = 0; - creds->hashntlm2 = 1; - } else if (!strcasecmp("ntlm2sr", cauth)) { - creds->hashnt = 2; - creds->hashlm = 0; - creds->hashntlm2 = 0; - } else { - syslog(LOG_ERR, "Unknown NTLM auth combination.\n"); - myexit(1); - } - } - - if (socksd_list && !users_list) - syslog(LOG_WARNING, "SOCKS5 proxy will NOT require any authentication\n"); - - if (!magic_detect) - syslog(LOG_INFO, "Using following NTLM hashes: NTLMv2(%d) NT(%d) LM(%d)\n", creds->hashntlm2, creds->hashnt, creds->hashlm); - - if (cflags) { - syslog(LOG_INFO, "Using manual NTLM flags: 0x%X\n", swap32(cflags)); - creds->flags = cflags; - } - - /* - * Last chance to get password from the user - */ - if (interactivehash || (interactivepwd && !ntlmbasic)) { - printf("Password: "); - tcgetattr(0, &termold); - termnew = termold; - termnew.c_lflag &= ~(ISIG | ECHO); - tcsetattr(0, TCSADRAIN, &termnew); - fgets(cpassword, MINIBUF_SIZE, stdin); - tcsetattr(0, TCSADRAIN, &termold); - i = strlen(cpassword)-1; - trimr(cpassword); - printf("\n"); - } - - /* - * Convert optional PassNT, PassLM and PassNTLMv2 strings to hashes - * unless plaintext pass was used, which has higher priority. - * - * If plain password is present, calculate its NT and LM hashes - * and remove it from the memory. - */ - if (!strlen(cpassword)) { - if (strlen(cpassntlm2)) { - tmp = scanmem(cpassntlm2, 8); - if (!tmp) { - syslog(LOG_ERR, "Invalid PassNTLMv2 hash, terminating\n"); - exit(1); - } - auth_memcpy(creds, passntlm2, tmp, 16); - memset(creds->passntlm2+16, 0, 5); - free(tmp); - } - if (strlen(cpassnt)) { - tmp = scanmem(cpassnt, 8); - if (!tmp) { - syslog(LOG_ERR, "Invalid PassNT hash, terminating\n"); - exit(1); - } - auth_memcpy(creds, passnt, tmp, 16); - memset(creds->passnt+16, 0, 5); - free(tmp); - } - if (strlen(cpasslm)) { - tmp = scanmem(cpasslm, 8); - if (!tmp) { - syslog(LOG_ERR, "Invalid PassLM hash, terminating\n"); - exit(1); - } - auth_memcpy(creds, passlm, tmp, 16); - memset(creds->passlm+16, 0, 5); - free(tmp); - } - } else { - if (creds->hashnt || magic_detect || interactivehash) { - tmp = ntlm_hash_nt_password(cpassword); - auth_memcpy(creds, passnt, tmp, 21); - free(tmp); - } if (creds->hashlm || magic_detect || interactivehash) { - tmp = ntlm_hash_lm_password(cpassword); - auth_memcpy(creds, passlm, tmp, 21); - free(tmp); - } if (creds->hashntlm2 || magic_detect || interactivehash) { - tmp = ntlm2_hash_password(cuser, cdomain, cpassword); - auth_memcpy(creds, passntlm2, tmp, 16); - free(tmp); - } - memset(cpassword, 0, strlen(cpassword)); - } - - auth_strcpy(creds, user, cuser); - auth_strcpy(creds, domain, cdomain); - auth_strcpy(creds, workstation, cworkstation); - - free(cuser); - free(cdomain); - free(cworkstation); - free(cpassword); - free(cpassntlm2); - free(cpassnt); - free(cpasslm); - free(cauth); - - /* - * Try known NTLM auth combinations and print which ones work. - * User can pick the best (most secure) one as his config. - */ - if (magic_detect) { - magic_auth_detect(magic_detect); - goto bailout; - } - - if (interactivehash) { - if (creds->passlm) { - tmp = printmem(creds->passlm, 16, 8); - printf("PassLM %s\n", tmp); - free(tmp); - } - - if (creds->passnt) { - tmp = printmem(creds->passnt, 16, 8); - printf("PassNT %s\n", tmp); - free(tmp); - } - - if (creds->passntlm2) { - tmp = printmem(creds->passntlm2, 16, 8); - printf("PassNTLMv2 %s # Only for user '%s', domain '%s'\n", tmp, creds->user, creds->domain); - free(tmp); - } - goto bailout; - } - - /* - * If we're going to need a password, check we really have it. - */ - if (!ntlmbasic && ((creds->hashnt && !creds->passnt) || (creds->hashlm && !creds->passlm) || (creds->hashntlm2 && !creds->passntlm2))) { - syslog(LOG_ERR, "Parent proxy account password (or required hashes) missing.\n"); - myexit(1); - } - - /* - * Ok, we are ready to rock. If daemon mode was requested, - * fork and die. The child will not be group leader anymore - * and can thus create a new session for itself and detach - * from the controlling terminal. - */ - if (asdaemon) { - if (debug) - printf("Forking into background as requested.\n"); - - i = fork(); - if (i == -1) { - perror("Fork into background failed"); /* fork failed */ - myexit(1); - } else if (i) - myexit(0); /* parent */ - - setsid(); - umask(0); - chdir("/"); - i = open("/dev/null", O_RDWR); - if (i >= 0) { - dup2(i, 0); - dup2(i, 1); - dup2(i, 2); - if (i > 2) - close(i); - } - } - - /* - * Reinit syslog logging to include our PID, after forking - * it is going to be OK - */ - if (asdaemon) { - openlog("cntlm", LOG_CONS | LOG_PID, LOG_DAEMON); - syslog(LOG_INFO, "Daemon ready"); - } else { - openlog("cntlm", LOG_CONS | LOG_PID | LOG_PERROR, LOG_DAEMON); - syslog(LOG_INFO, "Cntlm ready, staying in the foreground"); - } - - /* - * Check and change UID. - */ - if (strlen(cuid)) { - if (getuid() && geteuid()) { - syslog(LOG_WARNING, "No root privileges; keeping identity %d:%d\n", getuid(), getgid()); - } else { - if (isdigit(cuid[0])) { - nuid = atoi(cuid); - ngid = nuid; - if (nuid <= 0) { - syslog(LOG_ERR, "Numerical uid parameter invalid\n"); - myexit(1); - } - } else { - pw = getpwnam(cuid); - if (!pw || !pw->pw_uid) { - syslog(LOG_ERR, "Username %s in -U is invalid\n", cuid); - myexit(1); - } - nuid = pw->pw_uid; - ngid = pw->pw_gid; - } - setgid(ngid); - i = setuid(nuid); - syslog(LOG_INFO, "Changing uid:gid to %d:%d - %s\n", nuid, ngid, strerror(errno)); - if (i) { - syslog(LOG_ERR, "Terminating\n"); - myexit(1); - } - } - } - - /* - * PID file requested? Try to create one (it must not exist). - * If we fail, exit with error. - */ - if (strlen(cpidfile)) { - umask(0); - cd = open(cpidfile, O_WRONLY | O_CREAT | O_TRUNC, 0644); - if (cd < 0) { - syslog(LOG_ERR, "Error creating a new PID file\n"); - myexit(1); - } - - tmp = new(50); - snprintf(tmp, 50, "%d\n", getpid()); - write(cd, tmp, strlen(tmp)); - free(tmp); - close(cd); - } - - /* - * Change the handler for signals recognized as clean shutdown. - * When the handler is called (termination request), it signals - * this news by adding 1 to the global quit variable. - */ - signal(SIGPIPE, SIG_IGN); - signal(SIGINT, &sighandler); - signal(SIGTERM, &sighandler); - signal(SIGHUP, &sighandler); - - /* - * Initialize the random number generator - */ - srandom(time(NULL)); - - if (precache) { - pthread_attr_init(&pattr); - pthread_attr_setdetachstate(&pattr, PTHREAD_CREATE_DETACHED); - pthread_attr_setstacksize(&pattr, STACK_SIZE); -#ifndef __CYGWIN__ - pthread_attr_setguardsize(&pattr, 0); -#endif - pthread_create(&pthr, &pattr, precache_thread, NULL); - pthread_attr_destroy(&pattr); - } - - /* - * This loop iterates over every connection request on any of - * the listening ports. We keep the number of created threads. - * - * We also check the "finished threads" list, threads_list, here and - * free the memory of all inactive threads. Then, we update the - * number of finished threads. - * - * The loop ends, when we were "killed" and all threads created - * are finished, OR if we were killed more than once. This way, - * we have a "clean" shutdown (wait for all connections to finish - * after the first kill) and a "forced" one (user insists and - * killed us twice). - */ - while (quit < 1 || tc != tj) { - struct thread_arg_s *data; - struct sockaddr_in caddr; - struct timeval tv; - socklen_t clen; - fd_set set; - plist_t t; - int tid = 0; - - FD_ZERO(&set); - - /* - * Watch for proxy ports. - */ - t = proxyd_list; - while (t) { - FD_SET(t->key, &set); - t = t->next; - } - - /* - * Watch for SOCKS5 ports. - */ - t = socksd_list; - while (t) { - FD_SET(t->key, &set); - t = t->next; - } - - /* - * Watch for tunneled ports. - */ - t = tunneld_list; - while (t) { - FD_SET(t->key, &set); - t = t->next; - } - - tv.tv_sec = 1; - tv.tv_usec = 0; - - /* - * Wait here for data (connection request) on any of the listening - * sockets. When ready, establish the connection. For the main - * port, a new proxy_thread() thread is spawned to service the HTTP - * request. For tunneled ports, tunnel_thread() thread is created. - * - */ - cd = select(FD_SETSIZE, &set, NULL, NULL, &tv); - if (cd > 0) { - for (i = 0; i < FD_SETSIZE; ++i) { - if (FD_ISSET(i, &set)) { - clen = sizeof(caddr); - cd = accept(i, (struct sockaddr *)&caddr, (socklen_t *)&clen); - - if (cd < 0) { - syslog(LOG_ERR, "Serious error during accept: %s\n", strerror(errno)); - continue; - } - - /* - * Check main access control list. - */ - if (acl_check(rules, caddr.sin_addr) != ACL_ALLOW) { - syslog(LOG_WARNING, "Connection denied for %s:%d\n", inet_ntoa(caddr.sin_addr), ntohs(caddr.sin_port)); - tmp = gen_denied_page(inet_ntoa(caddr.sin_addr)); - write(cd, tmp, strlen(tmp)); - free(tmp); - close(cd); - continue; - } - - /* - * Log peer IP if it's not localhost - */ - if (debug || (gateway && caddr.sin_addr.s_addr != htonl(INADDR_LOOPBACK))) - syslog(LOG_INFO, "Connection accepted from %s:%d\n", inet_ntoa(caddr.sin_addr), ntohs(caddr.sin_port)); - - pthread_attr_init(&pattr); - pthread_attr_setstacksize(&pattr, STACK_SIZE); -#ifndef __CYGWIN__ - pthread_attr_setguardsize(&pattr, 0); -#endif - - if (plist_in(proxyd_list, i)) { - if (!serialize) - tid = pthread_create(&pthr, &pattr, proxy_thread, (void *)cd); - else - proxy_thread((void *)cd); - } else if (plist_in(socksd_list, i)) { - tid = pthread_create(&pthr, &pattr, socks5_thread, (void *)cd); - } else { - data = (struct thread_arg_s *)new(sizeof(struct thread_arg_s)); - data->fd = cd; - data->target = plist_get(tunneld_list, i); - tid = pthread_create(&pthr, &pattr, tunnel_thread, (void *)data); - } - - pthread_attr_destroy(&pattr); - - if (tid) - syslog(LOG_ERR, "Serious error during pthread_create: %d\n", tid); - else - tc++; - } - } - } else if (cd < 0 && !quit) - syslog(LOG_ERR, "Serious error during select: %s\n", strerror(errno)); - - if (threads_list) { - pthread_mutex_lock(&threads_mtx); - t = threads_list; - while (t) { - plist_t tmp = t->next; - tid = pthread_join((pthread_t)t->key, (void *)&i); - - if (!tid) { - tj++; - if (debug) - printf("Joining thread %lu; rc: %d\n", t->key, i); - } else - syslog(LOG_ERR, "Serious error during pthread_join: %d\n", tid); - - free(t); - t = tmp; - } - threads_list = NULL; - pthread_mutex_unlock(&threads_mtx); - } - } - -bailout: - if (strlen(cpidfile)) - unlink(cpidfile); - - syslog(LOG_INFO, "Terminating with %d active threads\n", tc - tj); - pthread_mutex_lock(&connection_mtx); - plist_free(connection_list); - pthread_mutex_unlock(&connection_mtx); - - hlist_free(header_list); - plist_free(scanner_agent_list); - plist_free(tunneld_list); - plist_free(proxyd_list); - plist_free(socksd_list); - plist_free(rules); - - free(cuid); - free(cpidfile); - free(magic_detect); - free_auth(creds); - - parent_list = plist_free(parent_list); - - exit(0); -} - diff -Nru cntlm-0.35.1/README cntlm-0.91~rc6/README --- cntlm-0.35.1/README 2007-10-31 18:48:15.000000000 +0000 +++ cntlm-0.91~rc6/README 2010-04-21 16:40:47.000000000 +0000 @@ -1,67 +1,62 @@ Installation using packages ~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Most of the popular distros contain cntlm packages.n their repositories. +You can use the procedures described below to prepare a package of current cntlm +version if desired. + NOTE: generating packages traditionally requires root privileges (to be able to set proper ownership and permissions on package members). You can overcome that using -the magnificent "fakeroot" utility. BUT you can use it only for package creation, -NOT package installation. To install you packages, you have to be real root. +fakeroot. However, to install your packages you have to be root. + +*** SOURCE TARBALL *** + $ make tgz + or + $ make tbz2 *** DEBIAN PACKAGES *** -1) Files you need: +1) Quick way: - cntlm_0.XX-Y.diff.gz - cntlm_0.XX-Y.dsc - cntlm_0.XX.orig.tar.gz + $ make deb - Those can be acquired from the FTP and soon, from Debian pool directly. +2) From Debian/Ubuntu repository: + + Get these files (e.g. apt-get source cntlm): + + cntlm_0.XX-X.diff.gz + cntlm_0.XX-X.dsc + cntlm_0.XX.orig.tar.gz -2) Compilation steps: + Compile: $ dpkg-source -x cntlm_0.XX-Y.dsc $ cd cntlm-0.XX/ $ dpkg-buildpackage -b -rfakeroot - # dpkg -i ../cntlm_0.XX-Y_arch.deb Upon installation, the package takes care of creating a dedicated user for cntlm, init script integration, manages eventual configuration file updates with new upstream versions, things like restart of the daemon after future updates, etc. You can later revert all these changes with one command, should - you decide to remove cntlm from your system. Basic "apt-get remove" keeps your - configuration file intact, so you can easily reinstall cntlm when you've grown - older and wiser. :) Alternatively, you can get rid of everything using "apt-get - --purge remove". + you decide to remove cntlm from your system. *** RPM FROM SCRATCH *** -1) For the impatient: +1) Quick way: $ make rpm # you'll need root privs. or fakeroot utility - # rpm -i cntlm-X.XX-{arch}.rpm 2) Detailed howto (or if make rpm doesn't work for you) - As root: - - # redhat/build binary - - of as an unprivileged user: - - $ fakeroot redhat/build binary - - Either way, packages will then be in your current directory. - - --- OR --- - To build an RPM package from scratch, as root change to /usr/src/[redhat|rpm|whatever]/SOURCES - Copy there all files from cntlm's redhat/ directory plus appropriate version of - the source tar.gz and type: + Copy there all files from cntlm's rpm/ directory plus appropriate version of + the source tar.bz2 (see SOURCE TARBALL section above) and type: - # rpmbuild -ba cntlm.spec + $ rpmbuild -ba cntlm.spec Shortly after, you'll have source and binary RPMs ready in your ../SRPMS, resp. ../RPMS directories. @@ -70,11 +65,11 @@ broken RPM build environment. You should add this to your ~/.rpmmacros: %_sysconfdir /etc -*** RPM FROM *.SRC.RPM *** +*** RPM FROM *.src.rpm *** If you just want to create a binary package from src.rpm, as root type: - # rpmbuild --rebuild pkgname.src.rpm + $ rpmbuild --rebuild pkgname.src.rpm Resulting binary RPM will be at /usr/src/..../RPMS @@ -82,6 +77,25 @@ broken RPM build environment. You should add this to your ~/.rpmmacros: %_sysconfdir /etc +*** WINDOWS INSTALLER *** + + Traditional compilation steps: + + $ ./configure + $ make + + Prepare all binaries, manuals, config templates, Start Menu links and InnoSetup + project definition file: + + $ make win + + Then run InnoSetup compiler to pack it all into an automatic installer EXE: + + $ /your/path/to/ISCC.exe win/setup.iss + or + Open folder "win" in explorer, right click "setup.iss" and select "Compile". + + Both with generate an installer in the "win" folder. Traditional installation ~~~~~~~~~~~~~~~~~~~~~~~~ @@ -89,6 +103,7 @@ $ ./configure $ make +$ make install Cntlm does not require any dynamic libraries and there are no dependencies you have to satisfy before compilation, except for libpthreads. This library is @@ -96,27 +111,23 @@ system already, because it comes with libc. Next, install cntlm onto your system like so: -$ make install - -Default installation directories are /usr/local/bin, /usr/local/share/man and -/usr/local/etc. Should you want to install cntlm into a different location, -change the BINDIR, MANDIR and SYSCONFDIR in the Makefile or on the command line -(see below). Installation uses the "install" helper, which has many differences -across various platforms. Currently, we use GNU flavour by default and -autodetect AIX. You might need to change some parameters on other POSIX -systems. +Default installation directories are /usr/sbin, /usr/share/man and /etc. Should +you want to install cntlm into a different location, change the DESTDIR +installation prefix (from "/") to add a different installation prefix (e.g. +/usr/local). To change individual directories, use BINDIR, MANDIR and +SYSCONFDIR: -$ make install BINDIR=/usr/bin MANDIR=/usr/share/man +$ make SYSCONFDIR=/etc BINDIR=/usr/bin MANDIR=/usr/share/man +$ make install SYSCONFDIR=/etc BINDIR=/usr/bin MANDIR=/usr/share/man Cntlm is compiled with system-wide configuration file by default. That means -whenever you run cntlm, it looks into a hardcoded path (see SYSCONFDIR in the -Makefile) and tries to load cntml.conf. You cannot make it not to do so, unless -you use -c with an alternative file or /dev/null. This is standard behaviour -and probably what you want. On the other hand, some of you might not want to -use cntlm as a daemon started by init scripts and you would prefer setting up -everything on the command line. This is possible, just comment out SYSCONFDIR -variable definition in the Makefile before you compile cntlm and it will remove -this feature. +whenever you run cntlm, it looks into a hardcoded path (SYSCONFDIR) and tries +to load cntml.conf. You cannot make it not to do so, unless you use -c with an +alternative file or /dev/null. This is standard behaviour and probably what you +want. On the other hand, some of you might not want to use cntlm as a daemon +started by init scripts and you would prefer setting up everything on the +command line. This is possible, just comment out SYSCONFDIR variable definition +in the Makefile before you compile cntlm and it will remove this feature. Installation includes the main binary, the man page (see "man cntlm") and if the default config feature was not removed, it also installs a configuration @@ -127,22 +138,20 @@ Architectures ~~~~~~~~~~~~~ -The build system now has an independent autodetection of the build arch -endianness. The result is printed during the compilation and is also part of -cntlm's startup banner. +The build system now has an autodetection of the build arch endianness. Every +common CPU and OS out there is supported, including Windows, MacOS X, Linux, +*BSD, AIX. Compilers ~~~~~~~~~ -Cntlm was tested and successfully compiles with GCC and IBM XL C/C++. Other -compilers might work for you (then again, they might not). To compile with -XLC, use: - -$ make -f Makefile.xlc ... - -or rename Makefile.xlc to Makefile. The rest is the same. +Cntlm is tested against GCC and IBM XL C/C++, other C compilers will work +for you too. There are no compiler specific directives and options AFAIK. +compilers might work for you (then again, they might not). Specific +Makefiles for different compilers are supported by the ./configure script +(e.g. Makefile.xlc) Contact ~~~~~~~ -David Kubicek , +David Kubicek diff -Nru cntlm-0.35.1/redhat/cntlm.init cntlm-0.91~rc6/redhat/cntlm.init --- cntlm-0.35.1/redhat/cntlm.init 2007-10-31 18:48:15.000000000 +0000 +++ cntlm-0.91~rc6/redhat/cntlm.init 1970-01-01 00:00:00.000000000 +0000 @@ -1,175 +0,0 @@ -#!/bin/sh -# -# cntlmd: Start/stop the cntlm proxy. -# -# chkconfig: 2345 26 89 -# Description: Cntlm is meant to be given your proxy address and becomming -# the primary proxy then, listening on a selected local port. -# You point all your proxy-aware programs to it and don't ever -# have to deal with proxy authentication again. -# -### BEGIN INIT INFO -# Provides: cntlm -# Required-Start: $syslog $network $time -# Required-Stop: $syslog $network $time -# Default-Start: 2 3 4 5 -# Default-Stop: 0 1 6 -# Short-Description: Authenticating HTTP accelerator for NTLM secured proxies -# Description: Cntlm is meant to be given your proxy address and becomming -# the primary proxy then, listening on a selected local port. -# You point all your proxy-aware programs to it and don't ever -# have to deal with proxy authentication again. -### END INIT INFO - -# Determining Linux RedHat/SuSE -# -# /etc/redhat-release -# /etc/SuSE-release - -SuSE=false -RedHat=false - -if [ -f /etc/SuSE-release ]; then - SuSE=true -elif [ -f /etc/redhat-release ]; then - RedHat=true -else - echo "Error: your platform is not supported by $0" > /dev/stderr - exit 1 -fi - -# Source function library SuSE/RedHat. - -if $SuSE; then - if [ -f /lib/lsb/init-functions ]; then - . /lib/lsb/init-functions - else - echo "Error: your platform is not supported by $0" > /dev/stderr - exit 1 - fi -else - if [ -f /etc/init.d/functions ]; then - . /etc/init.d/functions - else - echo "Error: your platform is not supported by $0" > /dev/stderr - exit 1 - fi -fi - -[ -r /etc/sysconfig/cntlmd ] && . /etc/sysconfig/cntlmd - -# First reset status of this service SuSE/RedHat - -if $SuSE; then - rc_reset -else - RETVAL=0 -fi - -# Return values acc. to LSB for all commands but status: -# 0 - success -# 1 - generic or unspecified error -# 2 - invalid or excess argument(s) -# 3 - unimplemented feature (e.g. "reload") -# 4 - insufficient privilege -# 5 - program is not installed -# 6 - program is not configured -# 7 - program is not running -# -# Note that starting an already running service, stopping -# or restarting a not-running service as well as the restart -# with force-reload (in case signalling is not supported) are -# considered a success. - -# Shell functions sourced from /etc/rc.status only on SuSE Linux: -# rc_check check and set local and overall rc status -# rc_status check and set local and overall rc status -# rc_status -v ditto but be verbose in local rc status -# rc_status -v -r ditto and clear the local rc status -# rc_failed set local and overall rc status to failed -# rc_failed set local and overall rc status to -# rc_reset clear local rc status (overall remains) -# rc_exit exit appropriate to overall rc status - -test -f $DAEMON || exit 5 - -start() { - # Start daemons. - echo -n "Starting $DESC: " - - if $SuSE; then - startproc -p $PIDFILE $DAEMON $OPTARGS 2>/dev/null - rc_status -v - else - daemon cntlm $OPTARGS 2>/dev/null - RETVAL=$? - echo - [ $RETVAL -eq 0 ] && touch $LOCKFILE - return $RETVAL - fi -} - -stop() { - echo -n "Shutting down $DESC: " - - if $SuSE; then - ## Stop daemon with killproc(8) and if this fails - ## set echo the echo return value. - - killproc -p $PIDFILE -TERM $DAEMON - - # Remember status and be verbose - rc_status -v - - else - killproc cntlm - RETVAL=$? - echo - [ $RETVAL -eq 0 ] && rm -f $LOCKFILE - return $RETVAL - fi -} - - -# See how we were called. -case "$1" in - start) - start - ;; - stop) - stop - ;; - status) - echo -n "Checking for $DESC: " - - if $SuSE; then - ## Check status with checkproc(8), if process is running - ## checkproc will return with exit status 0. - - # Status has a slightly different for the status command: - # 0 - service running - # 1 - service dead, but /var/run/ pid file exists - # 2 - service dead, but /var/lock/ lock file exists - # 3 - service not running - - # NOTE: checkproc returns LSB compliant status values. - checkproc -p $PIDFILE $DAEMON - rc_status -v - else - status cntlm - fi - ;; - restart|reload) - stop - start - ;; - *) - echo $"Usage: $0 {start|stop|restart|status}" - exit 1 -esac - -if $SuSE; then - rc_exit -else - exit $RETVAL -fi diff -Nru cntlm-0.35.1/redhat/cntlm.spec cntlm-0.91~rc6/redhat/cntlm.spec --- cntlm-0.35.1/redhat/cntlm.spec 2007-11-23 20:13:38.000000000 +0000 +++ cntlm-0.91~rc6/redhat/cntlm.spec 1970-01-01 00:00:00.000000000 +0000 @@ -1,104 +0,0 @@ -Summary: Fast NTLM authentication proxy with tunneling -Name: cntlm -Version: 0.35.1 -Release: 1%{?dist} -License: GNU GPL V2 -Group: System Environment/Daemons -URL: http://cntlm.sourceforge.net/ -Source0: %{name}-%{version}.tar.gz -Source1: cntlm.init -Source2: cntlm.sysconfig -BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n) - -%description -Cntlm is a fast and efficient NTLM proxy, with support for TCP/IP tunneling, -authenticated connection caching, ACLs, proper daemon logging and behaviour -and much more. It has up to ten times faster responses than similar NTLM -proxies, while using by orders or magnitude less RAM and CPU. Manual page -contains detailed information. - -%prep -%setup -q -n %{name}-%{version} - -%build -./configure -make SYSCONFDIR=%{_sysconfdir} \ - BINDIR=%{_sbindir} \ - MANDIR=%{_mandir} - -%install -rm -rf $RPM_BUILD_ROOT - -%makeinstall SYSCONFDIR=$RPM_BUILD_ROOT/%{_sysconfdir} \ - BINDIR=$RPM_BUILD_ROOT/%{_sbindir} \ - MANDIR=$RPM_BUILD_ROOT/%{_mandir} - -install -D -m 755 %{SOURCE1} $RPM_BUILD_ROOT/%{_initrddir}/cntlmd -install -D -m 644 %{SOURCE2} $RPM_BUILD_ROOT/%{_sysconfdir}/sysconfig/cntlmd - -%clean -rm -rf $RPM_BUILD_ROOT - -%files -%defattr(-,root,root,-) -%doc LICENSE README COPYRIGHT -%{_sbindir}/cntlm -%{_mandir}/man1/cntlm.1* -%config(noreplace) %{_sysconfdir}/cntlm.conf -%config(noreplace) %{_sysconfdir}/sysconfig/cntlmd -%config(noreplace) %{_initrddir}/cntlmd - -%pre -if [ "$1" -eq 1 ]; then - /usr/sbin/useradd -s /sbin/nologin -m -r -d /var/run/cntlm cntlm 2>/dev/null -fi -: - -%post -if [ "$1" -eq 1 ]; then - if [ -x /usr/lib/lsb/install_initd ]; then - /usr/lib/lsb/install_initd /etc/init.d/cntlmd - elif [ -x /sbin/chkconfig ]; then - /sbin/chkconfig --add cntlmd - else - for i in 2 3 4 5; do - ln -sf /etc/init.d/cntlmd /etc/rc.d/rc${i}.d/S26cntlmd - done - for i in 1 6; do - ln -sf /etc/init.d/cntlmd /etc/rc.d/rc${i}.d/K89cntlmd - done - fi -fi -: - -%preun -if [ "$1" -eq 0 ]; then - /etc/init.d/cntlmd stop > /dev/null 2>&1 - if [ -x /usr/lib/lsb/remove_initd ]; then - /usr/lib/lsb/install_initd /etc/init.d/cntlmd - elif [ -x /sbin/chkconfig ]; then - /sbin/chkconfig --del cntlmd - else - rm -f /etc/rc.d/rc?.d/???cntlmd - fi -fi -: - -%postun -if [ "$1" -eq 0 ]; then - /usr/sbin/userdel -r cntlm 2>/dev/null -fi -: - -%changelog -* Fri Jul 27 2007 Radislav Vrnata -- added support for SuSE Linux - -* Wed Jul 26 2007 Radislav Vrnata -- fixed %pre, %post, %preun, %postun macros bugs affecting upgrade process - -* Mon May 30 2007 Since 0.28 maintained by - -* Mon May 28 2007 Radislav Vrnata -- Version 0.27 -- First release diff -Nru cntlm-0.35.1/redhat/cntlm.sysconfig cntlm-0.91~rc6/redhat/cntlm.sysconfig --- cntlm-0.35.1/redhat/cntlm.sysconfig 2007-10-31 18:48:15.000000000 +0000 +++ cntlm-0.91~rc6/redhat/cntlm.sysconfig 1970-01-01 00:00:00.000000000 +0000 @@ -1,15 +0,0 @@ -# -# DAEMON Location of the binary -# PIDFILE Make sure that you or, if used, -U uid can create/write it -# TIMEOUT How long to wait before forcing cntlm to stop with a second -# signal when active connections are still not finished -# RUNAS Name or number of the non-privileged account to run as -# - -DAEMON=/usr/sbin/cntlm -PIDFILE=/var/run/cntlm/cntlmd.pid -LOCKFILE=/var/lock/subsys/cntlmd -TIMEOUT=5 -RUNAS=cntlm -DESC="CNTLM Authentication Proxy" -OPTARGS="-U $RUNAS -P $PIDFILE" diff -Nru cntlm-0.35.1/redhat/rules cntlm-0.91~rc6/redhat/rules --- cntlm-0.35.1/redhat/rules 2007-10-31 18:48:15.000000000 +0000 +++ cntlm-0.91~rc6/redhat/rules 1970-01-01 00:00:00.000000000 +0000 @@ -1,53 +0,0 @@ -#!/bin/sh -# -# Usage: rules [binary|clean] -# - -if [ ! -f VERSION -o ! -f Makefile ]; then - echo "This command must be run from the main source directory!" >&2 - exit 1 -fi - -RC=~/.rpmmacros -RPMS="BUILD RPMS SOURCES SPECS SRPMS tmp" -DIR=`pwd`/tmp -NAME=cntlm-`cat VERSION` - -function restorerc { - if [ -f $RC.tmp$$ ]; then - rm -f $RC # In case it didn't exist before - mv $RC.tmp$$ $RC 2>/dev/null # mv wouldn't overwrite it, there'd be no source - fi -} - -trap restorerc INT # Make sure we don't destroy original rc file - -if [ "$1" = "binary" ]; then - make clean - rm -f cntlm*.rpm 2>/dev/null - cp $RC $RC.tmp$$ 2>/dev/null # Save originam rpm configuration - cat >> $RC << _EOF_ -%_builddir %{_topdir}/BUILD -%_rpmdir %{_topdir}/RPMS -%_sourcedir %{_topdir}/SOURCES -%_specdir %{_topdir}/SPECS -%_srcrpmdir %{_topdir}/SRPMS -%_topdir $DIR -_EOF_ - - for i in $RPMS; do mkdir -p $DIR/$i; done # Create new rpm build structure - TMP=`pwd` - rm -f $DIR/tmp/$NAME 2>/dev/null - ln -s $TMP $DIR/tmp/$NAME # Generate source .tar.gz - sed "s/^\./$NAME/" doc/files.txt | tar zchf $DIR/SOURCES/$NAME.tar.gz --no-recursion -C $DIR/tmp -T - - cp redhat/cntlm.* $DIR/SOURCES/ # Prepare build environment - - rpmbuild -ba $DIR/SOURCES/cntlm.spec # Build RPM and copy to current dir - cp $DIR/SRPMS/*rpm . 2>/dev/null - cp $DIR/RPMS/*/cntlm*rpm . 2>/dev/null - - restorerc -elif [ "$1" = "clean" ]; then - for i in $RPMS; do rm -rf $DIR/$i; done # Clean the whole mess, keep packages - rmdir $DIR 2>/dev/null || true -fi diff -Nru cntlm-0.35.1/rpm/cntlm.init cntlm-0.91~rc6/rpm/cntlm.init --- cntlm-0.35.1/rpm/cntlm.init 1970-01-01 00:00:00.000000000 +0000 +++ cntlm-0.91~rc6/rpm/cntlm.init 2010-03-22 09:51:25.000000000 +0000 @@ -0,0 +1,272 @@ +#!/bin/sh +# +# cntlmd: Start/stop the cntlm proxy. +# +# chkconfig: 2345 26 89 +# Description: Cntlm is meant to be given your proxy address and becomming +# the primary proxy then, listening on a selected local port. +# You point all your proxy-aware programs to it and don't ever +# have to deal with proxy authentication again. +# +# Authors: Radislav Vrnata +# Michal Strnad +# Christian Wittmer +# +### BEGIN INIT INFO +# Provides: cntlm +# Required-Start: $syslog $network $time +# Should-Start: $remote_fs +# Required-Stop: $syslog $network $time +# Should-Stop: $remote_fs +# Default-Start: 2 3 5 +# Default-Stop: 0 1 6 +# Short-Description: start/stop the cntlm proxy +# Description: ntlm is meant to be given your proxy address and becomming +# the primary proxy then, listening on a selected local port. +# You point all your proxy-aware programs to it and don't ever +# have to deal with proxy authentication again. +### END INIT INFO +# +# Note on runlevels: +# 0 - halt/poweroff 6 - reboot +# 1 - single user +# 2 - multiuser without network exported +# 3 - multiuser with network (text mode) +# 4 - Not used/User-definable +# 5 - multiuser with network and X11 (xdm) +# 6 - reboot +# + +# Determining Linux RedHat/SuSE +# +# /etc/redhat-release +# /etc/SuSE-release + +SuSE=false +RedHat=false + +if [ -f /etc/SuSE-release ]; then + SuSE=true +elif [ -f /etc/redhat-release ]; then + RedHat=true +else + echo "Error: your platform is not supported by $0" > /dev/stderr + exit 1 +fi + + +# Source function library SuSE/RedHat. +if $SuSE; then + # Source LSB init functions + # providing start_daemon, killproc, pidofproc, + # log_success_msg, log_failure_msg and log_warning_msg. + # This is currently not used by UnitedLinux based distributions and + # not needed for init scripts for UnitedLinux only. If it is used, + # the functions from rc.status should not be sourced or used. + #. /lib/lsb/init-functions + + # Shell functions sourced from /etc/rc.status: + # rc_check check and set local and overall rc status + # rc_status check and set local and overall rc status + # rc_status -v be verbose in local rc status and clear it afterwards + # rc_status -v -r ditto and clear both the local and overall rc status + # rc_status -s display "skipped" and exit with status 3 + # rc_status -u display "unused" and exit with status 3 + # rc_failed set local and overall rc status to failed + # rc_failed set local and overall rc status to + # rc_reset clear both the local and overall rc status + # rc_exit exit appropriate to overall rc status + # rc_active checks whether a service is activated by symlinks + + # Return values acc. to LSB for all commands but status: + # 0 - success + # 1 - generic or unspecified error + # 2 - invalid or excess argument(s) + # 3 - unimplemented feature (e.g. "reload") + # 4 - user had insufficient privileges + # 5 - program is not installed + # 6 - program is not configured + # 7 - program is not running + # 8--199 - reserved (8--99 LSB, 100--149 distrib, 150--199 appl) + # + # Note that starting an already running service, stopping + # or restarting a not-running service as well as the restart + # with force-reload (in case signaling is not supported) are + # considered a success. + test -f /etc/rc.status && . /etc/rc.status || { + echo "Error: your platform is not supported by $0" > /dev/stderr; + exit 1 + } + rc_reset +else + test -f /etc/init.d/functions && . /etc/init.d/functions || { + echo "Error: your platform is not supported by $0" > /dev/stderr; + exit 1 + } + RETVAL=0 +fi + +# Check for existence of needed config file and read it +CNTLM_CONFIG="/etc/cntlm.conf" +test -r $CNTLM_CONFIG || { echo "$CNTLM_CONFIG not existing"; + if [ "$1" = "stop" ]; then exit 0; + else exit 6; fi; } + +# Check for existence of needed sysconfig file and read it +if $SuSE ; then + CNTLM_SYSCONFIG="/etc/sysconfig/cntlm" +else + CNTLM_SYSCONFIG="/etc/sysconfig/cntlmd" +fi +test -r $CNTLM_SYSCONFIG && . $CNTLM_SYSCONFIG || { + echo "$CNTLM_SYSCONFIG not existing"; + if [ "$1" = "stop" ]; then exit 0; + else exit 6; fi; } + +# some defaults +[ -z "${DAEMON}" ] && DAEMON=/usr/sbin/cntlm +[ -z "${DESC}" ] && DESC="CNTLM Authentication Proxy" +[ -z "${PIDFILE}" ] && PIDFILE="/var/run/cntlm/cntlmd.pid" +if $SuSE ; then + [ -z "${LOCKFILE}" ] && LOCKFILE="/var/lock/subsys/cntlm" +else + [ -z "${LOCKFILE}" ] && LOCKFILE="/var/lock/subsys/cntlmd" +fi +[ -z "${RUNAS}" ] && RUNAS="cntlm" + +# if no "Proxy" is set in cntlm.conf try '127.0.0.1:3128' as a default +if [ `/bin/cat $CNTLM_CONFIG | grep -e "^Listen" >/dev/null; echo $?` -eq 0 ]; then + CNTLM_LISTEN= +else + CNTLM_LISTEN="-l 127.0.0.1:3128" +fi + +# Check for missing binaries (stale symlinks should not happen) +# Note: Special treatment of stop for LSB conformance +test -x $DAEMON || { echo "$DAEMON not installed"; + if [ "$1" = "stop" ]; then exit 0; + else exit 5; fi; } + +case "$1" in + start) + echo -n "Starting ${DESC}: " + if $SuSE; then + ## Start daemon with startproc(8). If this fails + ## the return value is set appropriately by startproc. + /sbin/startproc -p $PIDFILE $DAEMON -P $PIDFILE $CNTLM_LISTEN -U $RUNAS $OPTARGS &>/dev/null + + # Remember status and be verbose + rc_status -v + else + daemon cntlm -P $PIDFILE $CNTLM_LISTEN -U $RUNAS $OPTARGS 2>/dev/null + RETVAL=$? + echo + [ $RETVAL -eq 0 ] && touch $LOCKFILE + exit $RETVAL + fi + ;; + stop) + echo -n "Shutting down ${DESC}: " + if $SuSE; then + ## Stop daemon with killproc(8) and if this fails + ## killproc sets the return value according to LSB. + /sbin/killproc -p $PIDFILE -TERM $DAEMON &>/dev/null + + # Remember status and be verbose + rc_status -v + else + killproc cntlm + RETVAL=$? + echo + [ $RETVAL -eq 0 ] && rm -f $LOCKFILE + exit $RETVAL + fi + ;; + try-restart|condrestart) + ## Do a restart only if the service was active before. + ## Note: try-restart is now part of LSB (as of 1.9). + ## RH has a similar command named condrestart. + if test "$1" = "condrestart"; then + echo "${attn} Use try-restart ${done}(LSB)${attn} rather than condrestart ${warn}(RH)${norm}" + fi + $0 status + if test $? = 0; then + $0 restart + else + if $SuSE; then + rc_reset # Not running is not a failure. + # Remember status and be quiet + rc_status + else + exit 0 + fi + fi + ;; + restart) + ## Stop the service and regardless of whether it was + ## running or not, start it again. + $0 stop + $0 start + + if $SuSE; then + # Remember status and be quiet + rc_status + fi + ;; + force-reload|reload) + ## Signal the daemon to reload its config. Most daemons + ## do this on signal 1 (SIGHUP). + ## If it does not support it, restart the service if it + ## is running. + + # cntlm does not support SIGHUP, so restart + echo -n "Reload ${DESC}: " + ## if it supports it: + #/sbin/killproc -p $PIDFILE -HUP $DAEMON + + # Remember status and be verbose + #rc_status -v + + ## Otherwise: + $0 try-restart + + if $SuSE; then + # Remember status and be quiet + rc_status + fi + ;; + status) + echo -n "Checking for ${DESC}: " + if $SuSE; then + ## Check status with checkproc(8), if process is running + ## checkproc will return with exit status 0. + + # Return value is slightly different for the status command: + # 0 - service up and running + # 1 - service dead, but /var/run/ pid file exists + # 2 - service dead, but /var/lock/ lock file exists + # 3 - service not running (unused) + # 4 - service status unknown :-( + # 5--199 reserved (5--99 LSB, 100--149 distro, 150--199 appl.) + + # NOTE: checkproc returns LSB compliant status values. + /sbin/checkproc -p $PIDFILE $DAEMON + # NOTE: rc_status knows that we called this init script with + # "status" option and adapts its messages accordingly. + + # Remember status and be verbose + rc_status -v + else + status cntlm + fi + ;; + *) + echo "Usage: $0 {start|stop|status|try-restart|restart|force-reload|reload}" + exit 1 + ;; +esac +if $SuSE; then + rc_exit +else + exit $RETVAL +fi diff -Nru cntlm-0.35.1/rpm/cntlm.spec cntlm-0.91~rc6/rpm/cntlm.spec --- cntlm-0.35.1/rpm/cntlm.spec 1970-01-01 00:00:00.000000000 +0000 +++ cntlm-0.91~rc6/rpm/cntlm.spec 2010-04-30 21:02:02.000000000 +0000 @@ -0,0 +1,161 @@ +Summary: Fast NTLM authentication proxy with tunneling +Name: cntlm +Version: 0.91rc6 +Release: 1%{?dist} +License: GNU GPL V2 +%if 0%{?suse_version} +Group: Productivity/Networking/Web/Proxy +%else +Group: System/Daemons +%endif +URL: http://cntlm.sourceforge.net/ +Source0: %{name}-%{version}.tar.bz2 +Source1: %{name}.init +Source2: %{name}.sysconfig + + +%if 0%{?suse_version} +Prereq: util-linux %{?insserv_prereq} %{?fillup_prereq} +%else +Prereq: which /sbin/chkconfig +%endif +Prereq: /usr/sbin/useradd /usr/bin/getent + +Provides: cntlm = %{version} + +BuildRoot: %{_tmppath}/%{name}-%{version}-root + +%description +Cntlm is a fast and efficient NTLM proxy, with support for TCP/IP tunneling, +authenticated connection caching, ACLs, proper daemon logging and behaviour +and much more. It has up to ten times faster responses than similar NTLM +proxies, while using by orders or magnitude less RAM and CPU. Manual page +contains detailed information. + +%prep +%setup -q -n %{name}-%{version} + +%build +./configure +make SYSCONFDIR=%{_sysconfdir} \ + BINDIR=%{_sbindir} \ + MANDIR=%{_mandir} + +%install +# Clean up in case there is trash left from a previous build +rm -rf $RPM_BUILD_ROOT +mkdir $RPM_BUILD_ROOT + +# Create the target build directory hierarchy +%if 0%{?suse_version} + mkdir -p ${RPM_BUILD_ROOT}/var/adm/fillup-templates +%else + mkdir -p $RPM_BUILD_ROOT%{_sysconfdir}/sysconfig +%endif + +mkdir -p $RPM_BUILD_ROOT/sbin + +%makeinstall SYSCONFDIR=$RPM_BUILD_ROOT/%{_sysconfdir} \ + BINDIR=$RPM_BUILD_ROOT/%{_sbindir} \ + MANDIR=$RPM_BUILD_ROOT/%{_mandir} +%if 0%{?suse_version} + install -D -m 755 %{SOURCE1} $RPM_BUILD_ROOT/%{_initrddir}/cntlm + install -D -m 644 %{SOURCE2} $RPM_BUILD_ROOT/var/adm/fillup-templates/sysconfig.cntlm + ln -sf %{_initrddir}/cntlm $RPM_BUILD_ROOT/sbin/rccntlm +%else + install -D -m 755 %{SOURCE1} $RPM_BUILD_ROOT/%{_initrddir}/cntlmd + install -D -m 644 %{SOURCE2} $RPM_BUILD_ROOT/%{_sysconfdir}/sysconfig/cntlmd + ln -sf %{_initrddir}/cntlmd $RPM_BUILD_ROOT/sbin/rccntlmd +%endif + +%clean +rm -rf $RPM_BUILD_ROOT + +%pre +if [ "$1" -eq 1 ]; then + [ -z "`%{_bindir}/getent passwd "cntlm"`" ] && { + useradd -s /sbin/nologin -m -r -d /var/run/cntlm cntlm 2>/dev/null + } +fi +: + +%post +%if 0%{?suse_version} +%{fillup_and_insserv cntlm} +%else + if [ "$1" -eq 1 ]; then + if [ -x /usr/lib/lsb/install_initd ]; then + /usr/lib/lsb/install_initd /etc/init.d/cntlmd + elif [ -x /sbin/chkconfig ]; then + /sbin/chkconfig --add cntlmd + else + for i in 2 3 4 5; do + ln -sf /etc/init.d/cntlmd /etc/rc.d/rc${i}.d/S26cntlmd + done + for i in 1 6; do + ln -sf /etc/init.d/cntlmd /etc/rc.d/rc${i}.d/K89cntlmd + done + fi + fi + : +%endif + +%preun +%if 0%{?suse_version} +%{stop_on_removal cntlm} +%else + if [ "$1" -eq 0 ]; then + /etc/init.d/cntlmd stop > /dev/null 2>&1 + if [ -x /usr/lib/lsb/remove_initd ]; then + /usr/lib/lsb/install_initd /etc/init.d/cntlmd + elif [ -x /sbin/chkconfig ]; then + /sbin/chkconfig --del cntlmd + else + rm -f /etc/rc.d/rc?.d/???cntlmd + fi + fi + : +%endif + +%postun +if [ "$1" -eq 0 ]; then + /usr/sbin/userdel -r cntlm 2>/dev/null +fi +: +%if 0%{?suse_version} +%{insserv_cleanup} +%else + if [ -x /usr/lib/lsb/remove_initd ]; then + /usr/lib/lsb/install_initd /etc/init.d/cntlmd + elif [ -x /sbin/chkconfig ]; then + /sbin/chkconfig --del cntlmd + else + rm -f /etc/rc.d/rc?.d/???cntlmd + fi + : +%endif + +%files +%defattr(-,root,root,-) +%doc LICENSE README COPYRIGHT +%{_sbindir}/cntlm +%{_mandir}/man1/cntlm.1* +%config(noreplace) %{_sysconfdir}/cntlm.conf +%if 0%{?suse_version} + %config(noreplace) /var/adm/fillup-templates/sysconfig.cntlm + %{_initrddir}/cntlm + /sbin/rccntlm +%else + %config(noreplace) %{_sysconfdir}/sysconfig/cntlmd + %{_initrddir}/cntlmd + /sbin/rccntlmd +%endif + +%changelog +* Thu Mar 18 2010 : Version 0.90 +- Major rewrite of proxy code +- NoProxy option added to bypass proxy for certain addresses +- Ability to work as a standalone proxy added +- few changes in spec file to package successfully for SuSE + and RedHat distros using openSuSE BuildService by + Michal Strnad diff -Nru cntlm-0.35.1/rpm/cntlm.sysconfig cntlm-0.91~rc6/rpm/cntlm.sysconfig --- cntlm-0.35.1/rpm/cntlm.sysconfig 1970-01-01 00:00:00.000000000 +0000 +++ cntlm-0.91~rc6/rpm/cntlm.sysconfig 2010-03-22 23:27:47.000000000 +0000 @@ -0,0 +1,36 @@ +## Path: Network/Proxy/Cntlm + +## Type: string +## Default: /usr/sbin/cntlm +# CNTLM binary location +DAEMON="/usr/sbin/cntlm" + +## Type: string +## Default: /usr/sbin/cntlm +# Location of CNTLM's PID file. +# Make sure that you or, if used, -U uid can create/write it +PIDFILE="/var/run/cntlm/cntlmd.pid" + +## Description: Timeout before forced shutdown +## Type: integer +## Default: 1 +# How long to wait before forcing cntlm to stop with a second +# signal when active connections are still not finished +TIMEOUT=1 + +## Type: string +## Default: cntlm +# Name or number of the non-privileged account to run as +RUNAS=cntlm + +## Type: string +## Default: "CNTLM Authentication Proxy" +# CNTLM custom service description +DESC="CNTLM Authentication Proxy" + +## Type: string +## Default: "" +# List o optional arguments one would specify on the command line. +# See the cntlm man page for list of available arguments +# with their description. +OPTARGS="-U $RUNAS -P $PIDFILE" diff -Nru cntlm-0.35.1/rpm/rules cntlm-0.91~rc6/rpm/rules --- cntlm-0.35.1/rpm/rules 1970-01-01 00:00:00.000000000 +0000 +++ cntlm-0.91~rc6/rpm/rules 2010-03-20 22:00:51.000000000 +0000 @@ -0,0 +1,38 @@ +#!/bin/sh +# +# Usage: rules [binary|clean] +# + +if [ ! -f VERSION -o ! -f Makefile ]; then + echo "This command must be run from the main source directory!" >&2 + exit 1 +fi + +RPMS="BUILD RPMS SOURCES SPECS SRPMS tmp" +DIR=`pwd`/tmp +NAME=cntlm-`cat VERSION` + +if [ "$1" = "binary" ]; then + rm -f cntlm*.rpm 2>/dev/null + for i in $RPMS; do mkdir -p $DIR/$i; done # Create new rpm build structure + + make tbz2 + mv $NAME.tar.bz2 $DIR/SOURCES + cp rpm/cntlm.* $DIR/SOURCES # Prepare build environment + + rpmbuild \ + --define "_topdir $DIR" \ + --define "_sourcedir %_topdir/SOURCES" \ + --define "_builddir %_topdir/BUILD" \ + --define "_buildrootdir %_topdir/BUILD" \ + --define "_rpmdir %_topdir/RPMS" \ + --define "_specdir %_topdir/SPECS" \ + --define "_srcrpmdir %_topdir/SRPMS" \ + -ba $DIR/SOURCES/cntlm.spec + + cp $DIR/SRPMS/*rpm . 2>/dev/null + cp $DIR/RPMS/*/cntlm*rpm . 2>/dev/null +elif [ "$1" = "clean" ]; then + for i in $RPMS; do rm -rf $DIR/$i; done # Clean the whole mess, keep packages + rmdir $DIR 2>/dev/null || true +fi diff -Nru cntlm-0.35.1/scanner.c cntlm-0.91~rc6/scanner.c --- cntlm-0.35.1/scanner.c 1970-01-01 00:00:00.000000000 +0000 +++ cntlm-0.91~rc6/scanner.c 2010-04-30 08:29:20.000000000 +0000 @@ -0,0 +1,286 @@ +/* + * CNTLM is free software; you can redistribute it and/or modify it under the + * terms of the GNU General Public License as published by the Free Software + * Foundation; either version 2 of the License, or (at your option) any later + * version. + * + * CNTLM is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., 51 Franklin + * St, Fifth Floor, Boston, MA 02110-1301, USA. + * + * Copyright (c) 2007 David Kubicek + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "utils.h" +#include "socket.h" +#include "http.h" +#include "globals.h" +#include "forward.h" +#include "scanner.h" + +/* + * This code is a piece of shit, but it works. Cannot rewrite it now, because + * I don't have ISA AV filter anymore - wouldn't be able to test it. + */ +int scanner_hook(rr_data_t request, rr_data_t response, struct auth_s *credentials, int cd, int *sd, long maxKBs) { + char *buf, *line, *pos, *tmp, *pat, *post, *isaid, *uurl; + int bsize, lsize, size, len, i, w, nc; + rr_data_t newreq, newres; + plist_t list; + + int ok = 1; + int done = 0; + int headers_initiated = 0; + long c, progress = 0, filesize = 0; + + /* + * Let's limit the responses we examine to an absolute minimum + */ + if (!request->req || response->code != 200 + || http_has_body(request, response) != -1 + || hlist_subcmp(response->headers, "Transfer-Encoding", "chunked") + || !hlist_subcmp(response->headers, "Proxy-Connection", "close")) + return PLUG_SENDHEAD | PLUG_SENDDATA; + + tmp = hlist_get(request->headers, "User-Agent"); + if (tmp) { + tmp = lowercase(strdup(tmp)); + list = scanner_agent_list; + while (list) { + pat = lowercase(strdup(list->aux)); + if (debug) + printf("scanner_hook: matching U-A header (%s) to %s\n", tmp, pat); + if (!fnmatch(pat, tmp, 0)) { + if (debug) + printf("scanner_hook: positive match!\n"); + maxKBs = 0; + free(pat); + break; + } + free(pat); + list = list->next; + } + free(tmp); + } + + bsize = SAMPLE; + buf = new(bsize); + + len = 0; + do { + size = read(*sd, buf + len, SAMPLE - len - 1); + if (debug) + printf("scanner_hook: read %d of %d\n", size, SAMPLE - len); + if (size > 0) + len += size; + } while (size > 0 && len < SAMPLE - 1); + + if (strstr(buf, "Downloading status") && (pos=strstr(buf, "ISAServerUniqueID=")) && (pos = strchr(pos, '"'))) { + pos++; + c = strlen(pos); + for (i = 0; i < c && pos[i] != '"'; ++i); + + if (pos[i] == '"') { + isaid = substr(pos, 0, i); + if (debug) + printf("scanner_hook: ISA id = %s\n", isaid); + + lsize = BUFSIZE; + line = new(lsize); + do { + i = so_recvln(*sd, &line, &lsize); + + c = strlen(line); + if (len + c >= bsize) { + bsize *= 2; + tmp = realloc(buf, bsize); + if (tmp == NULL) + break; + else + buf = tmp; + } + + strcat(buf, line); + len += c; + + if (i >= 0 && ( + ((pos = strstr(line, "UpdatePage(")) + && isdigit(pos[11])) + || + ((pos = strstr(line, "DownloadFinished(")) + && isdigit(pos[17]) + && (done = 1)) )) { + if (debug) + printf("scanner_hook: %s", line); + + if ((pos = strstr(line, "To be downloaded"))) { + filesize = atol(pos+16); + if (debug) + printf("scanner_hook: file size detected: %ld KiBs (max: %ld)\n", filesize/1024, maxKBs); + + if (maxKBs && (maxKBs == 1 || filesize/1024 > maxKBs)) + break; + + /* + * We have to send HTTP protocol ID so we can send the notification + * headers during downloading. Once we've done that, it cannot appear + * again, which it would if we returned PLUG_SENDHEAD, so we must + * remember to not include it. + */ + headers_initiated = 1; + tmp = new(MINIBUF_SIZE); + snprintf(tmp, MINIBUF_SIZE, "HTTP/1.%s 200 OK\r\n", request->http); + w = write(cd, tmp, strlen(tmp)); + free(tmp); + } + + if (!headers_initiated) { + if (debug) + printf("scanner_hook: Giving up, \"To be downloaded\" line not found!\n"); + break; + } + + /* + * Send a notification header to the client, just so it doesn't timeout + */ + if (!done) { + tmp = new(MINIBUF_SIZE); + progress = atol(line+12); + snprintf(tmp, MINIBUF_SIZE, "ISA-Scanner: %ld of %ld\r\n", progress, filesize); + w = write(cd, tmp, strlen(tmp)); + free(tmp); + } + + /* + * If download size is unknown beforehand, stop when downloaded amount is over ISAScannerSize + */ + if (!filesize && maxKBs && maxKBs != 1 && progress/1024 > maxKBs) + break; + } + } while (i > 0 && !done); + + if (i >= 0 && done && (pos = strstr(line, "\",\"")+3) && (c = strchr(pos, '"') - pos) > 0) { + tmp = substr(pos, 0, c); + pos = urlencode(tmp); + free(tmp); + + uurl = urlencode(request->url); + + post = new(BUFSIZE); + snprintf(post, BUFSIZE-1, "%surl=%s&%sSaveToDisk=YES&%sOrig=%s", isaid, pos, isaid, isaid, uurl); + + if (debug) + printf("scanner_hook: Getting file with URL data = %s\n", request->url); + + tmp = new(MINIBUF_SIZE); + snprintf(tmp, MINIBUF_SIZE, "%d", (int)strlen(post)); + + newres = new_rr_data(); + newreq = dup_rr_data(request); + + free(newreq->method); + newreq->method = strdup("POST"); + hlist_mod(newreq->headers, "Referer", request->url, 1); + hlist_mod(newreq->headers, "Content-Type", "application/x-www-form-urlencoded", 1); + hlist_mod(newreq->headers, "Content-Length", tmp, 1); + free(tmp); + + nc = proxy_connect(credentials); + c = proxy_authenticate(&nc, newreq, newres, credentials); + if (c && newres->code == 407) { + if (debug) + printf("scanner_hook: Authentication OK, getting the file...\n"); + } else { + if (debug) + printf("scanner_hook: Authentication failed or refused!\n"); + close(nc); + nc = 0; + } + + /* + * The POST request for the real file + */ + reset_rr_data(newres); + if (nc && headers_send(nc, newreq) && write(nc, post, strlen(post)) && headers_recv(nc, newres)) { + if (debug) + hlist_dump(newres->headers); + + /* + * We always know the filesize here. Send it to the client, because ISA doesn't!!! + * The clients progress bar doesn't work without it and it stinks! + */ + if (filesize || progress) { + tmp = new(20); + snprintf(tmp, 20, "%ld", filesize ? filesize : progress); + newres->headers = hlist_mod(newres->headers, "Content-Length", tmp, 1); + } + + /* + * Here we remember if previous code already sent some headers + * to the client. In such case, do not include the HTTP/1.x ID. + */ + newres->skip_http = headers_initiated; + copy_rr_data(response, newres); + close(*sd); + *sd = nc; + + len = 0; + ok = PLUG_SENDHEAD | PLUG_SENDDATA; + } else if (debug) + printf("scanner_hook: New request failed\n"); + + free(newreq); + free(newres); + free(post); + free(uurl); + } + + free(line); + free(isaid); + } else if (debug) + printf("scanner_hook: ISA id not found\n"); + } + + if (len) { + if (debug) { + printf("scanner_hook: flushing %d original bytes\n", len); + hlist_dump(response->headers); + } + + if (!headers_send(cd, response)) { + if (debug) + printf("scanner_hook: failed to send headers\n"); + free(buf); + return PLUG_ERROR; + } + + size = write(cd, buf, len); + if (size > 0) + ok = PLUG_SENDDATA; + else + ok = PLUG_ERROR; + } + + if (debug) + printf("scanner_hook: ending with %d\n", ok); + + free(buf); + return ok; +} + diff -Nru cntlm-0.35.1/scanner.h cntlm-0.91~rc6/scanner.h --- cntlm-0.35.1/scanner.h 1970-01-01 00:00:00.000000000 +0000 +++ cntlm-0.91~rc6/scanner.h 2010-03-25 01:24:22.000000000 +0000 @@ -0,0 +1,41 @@ +/* + * CNTLM is free software; you can redistribute it and/or modify it under the + * terms of the GNU General Public License as published by the Free Software + * Foundation; either version 2 of the License, or (at your option) any later + * version. + * + * CNTLM is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., 51 Franklin + * St, Fifth Floor, Boston, MA 02110-1301, USA. + * + * Copyright (c) 2007 David Kubicek + * + */ + +#ifndef _SCANNER_H +#define _SCANNER_H + +#include "utils.h" + +/* + * ISA plugin flags + */ +#define PLUG_NONE 0x0000 +#define PLUG_SENDHEAD 0x0001 +#define PLUG_SENDDATA 0x0002 +#define PLUG_ERROR 0x8000 +#define PLUG_ALL 0x7FFF + +/* + * Plugin download sample size + */ +#define SAMPLE 4096 + +extern int scanner_hook(rr_data_t request, rr_data_t response, struct auth_s *credentials, int cd, int *sd, long maxKBs); + +#endif /* _SCANNER_H */ diff -Nru cntlm-0.35.1/socket.c cntlm-0.91~rc6/socket.c --- cntlm-0.35.1/socket.c 2007-11-20 22:24:16.000000000 +0000 +++ cntlm-0.91~rc6/socket.c 2010-03-25 01:24:22.000000000 +0000 @@ -19,7 +19,9 @@ * */ +#include #include +#include #include #include #include @@ -55,13 +57,15 @@ * Returns: socket descriptor */ int so_connect(struct in_addr host, int port) { - int fd; + int flags, fd, rc; struct sockaddr_in saddr; + // struct timeval tv; + // fd_set fds; - fd = socket(PF_INET, SOCK_STREAM, 0); - if (fd < 0) { - perror("cannot create socket()"); - exit(1); + if ((fd = socket(PF_INET, SOCK_STREAM, 0)) < 0) { + if (debug) + printf("so_connect: create: %s\n", strerror(errno)); + return -1; } memset(&saddr, 0, sizeof(saddr)); @@ -69,8 +73,50 @@ saddr.sin_port = htons(port); saddr.sin_addr = host; - if (connect(fd, (struct sockaddr *)&saddr, sizeof(saddr)) < 0) + if ((flags = fcntl(fd, F_GETFL, 0)) < 0) { + if (debug) + printf("so_connect: get flags: %s\n", strerror(errno)); + close(fd); + return -1; + } + + /* NON-BLOCKING connect with timeout + if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) < 0) { + if (debug) + printf("so_connect: set non-blocking: %s\n", strerror(errno)); + close(fd); + return -1; + } + */ + + rc = connect(fd, (struct sockaddr *)&saddr, sizeof(saddr)); + + /* + printf("connect = %d\n", rc); + if (rc < 0 && (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINPROGRESS)) { + FD_ZERO(&fds); + FD_SET(fd, &fds); + tv.tv_sec = 10; + tv.tv_usec = 0; + printf("select!\n"); + rc = select(fd+1, NULL, &fds, NULL, &tv) - 1; + printf("select = %d\n", rc); + } + */ + + if (rc < 0) { + if (debug) + printf("so_connect: %s\n", strerror(errno)); + close(fd); + return -1; + } + + if (fcntl(fd, F_SETFL, flags & ~O_NONBLOCK) < 0) { + if (debug) + printf("so_connect: set blocking: %s\n", strerror(errno)); + close(fd); return -1; + } return fd; } @@ -86,8 +132,10 @@ fd = socket(PF_INET, SOCK_STREAM, 0); if (fd < 0) { - perror("Cannot create socket"); - exit(1); + if (debug) + printf("so_listen: new socket: %s\n", strerror(errno)); + close(fd); + return -1; } clen = 1; @@ -99,11 +147,14 @@ if (bind(fd, (struct sockaddr *)&saddr, sizeof(saddr))) { syslog(LOG_ERR, "Cannot bind port %d: %s!\n", port, strerror(errno)); + close(fd); return -1; } - if (listen(fd, 5)) + if (listen(fd, 5)) { + close(fd); return -1; + } return fd; } @@ -160,10 +211,7 @@ * the performance was very similar. Given the fact that it keeps us * from creating a whole buffering scheme around the socket (HTTP * connection is both line and block oriented, switching back and forth), - * it is actually quite cool. ;) - * - * Streams using fdopen() didn't yield such comfort (memory allocation, - * split r/w FILE *args in functions, etc) and simplicity. + * it is actually OK. */ int so_recvln(int fd, char **buf, int *size) { int len = 0; @@ -179,7 +227,7 @@ (*buf)[len++] = c; /* - * end of buffer, still no EOL? + * End of buffer, still no EOL? Resize the buffer */ if (len == *size-1 && c != '\n') { if (debug) @@ -192,7 +240,7 @@ *buf = tmp; } } - VAL(*buf, char, len) = 0; + (*buf)[len] = 0; return r; } diff -Nru cntlm-0.35.1/socket.h cntlm-0.91~rc6/socket.h --- cntlm-0.35.1/socket.h 2007-11-20 22:43:58.000000000 +0000 +++ cntlm-0.91~rc6/socket.h 2010-03-28 23:58:51.000000000 +0000 @@ -25,6 +25,8 @@ #include #include +#include "config/config.h" + #if config_socklen_t != 1 #define socklen_t uint32_t #endif diff -Nru cntlm-0.35.1/swap.h cntlm-0.91~rc6/swap.h --- cntlm-0.35.1/swap.h 2007-10-31 18:48:15.000000000 +0000 +++ cntlm-0.91~rc6/swap.h 2010-03-28 23:58:51.000000000 +0000 @@ -23,6 +23,7 @@ #define _SWAP_H #include + #include "config/config.h" #define swap16(x) \ diff -Nru cntlm-0.35.1/utils.c cntlm-0.91~rc6/utils.c --- cntlm-0.35.1/utils.c 2007-10-31 18:48:15.000000000 +0000 +++ cntlm-0.91~rc6/utils.c 2010-04-13 00:02:53.000000000 +0000 @@ -28,6 +28,7 @@ #include #include #include +#include #include "config/config.h" #include "swap.h" @@ -42,16 +43,32 @@ -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,10,11,12,13,14,15,-1,-1,-1,-1,-1, -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1}; +void myexit(int rc) { + if (rc) + fprintf(stderr, "Exitting with error. Check daemon logs or run with -v.\n"); + + exit(rc); +} + +void croak(const char *msg, int console) { + if (console) + printf("%s", msg); + else + syslog(LOG_ERR, "%s", msg); + + myexit(1); +} + /* * Add a new item to a list. Every plist_t variable must be * initialized to NULL (or pass NULL for "list" when adding - * the first item). This is required to strip down the - * complexity to minimum and not to need any plist_new func. + * the first item). This is for simplicity's sake (we don't + * need any plist_new). * * This list type allows to store an arbitrary pointer * associating it with the key. */ -plist_t plist_add(plist_t list, unsigned long key, char *aux) { +plist_t plist_add(plist_t list, unsigned long key, void *aux) { plist_t tmp, t = list; tmp = malloc(sizeof(struct plist_s)); @@ -87,6 +104,8 @@ if (t) { plist_t tmp = t->next; + if (t->aux) + free(t->aux); free(t); if (ot == NULL) return tmp; @@ -121,7 +140,7 @@ t = list; while (t) { - printf("List data: %lu => %s\n", (unsigned long int)t->key, t->aux); + printf("List data: %lu => 0x%08x\n", (unsigned long int)t->key, (unsigned int)t->aux); t = t->next; } } @@ -146,15 +165,21 @@ * discarding all closed ones on the way. Return the first * match. * + * Use this method only for lists of descriptors! + * * In conjunction with plist_add, the list behaves as a FIFO. * This feature is used for rotating cached connections in the * list, so that none is left too long unused (proxy timeout). - * Returns only key, not aux. + * + * Returns key value (descriptor) and if aux != NULL, *aux gets + * aux pointer value (which caller must free if != NULL). */ -int plist_pop(plist_t *list) { + +int plist_pop(plist_t *list, void **aux) { plist_t tmp, t; int id = 0; int ok = 0; + void *a = NULL; if (list == NULL || *list == NULL) return 0; @@ -162,11 +187,14 @@ t = *list; while (!ok && t) { id = t->key; + a = t->aux; tmp = t->next; - if (so_closed(id)) + if (so_closed(id)) { close(id); - else + if (t->aux) + free(t->aux); + } else ok = 1; free(t); @@ -175,7 +203,13 @@ *list = t; - return (ok ? id : 0); + if (ok) { + if (aux != NULL) + *aux = a; + return id; + } + + return 0; } /* @@ -183,14 +217,14 @@ */ int plist_count(plist_t list) { plist_t t = list; - int ret = 0; + int rc = 0; while (t) { - ret++; + rc++; t = t->next; } - return ret; + return rc; } /* @@ -212,27 +246,27 @@ /* * The same as plist_add. Here we have two other arguments. - * They are treated as booleans - true means to duplicate a - * key/value, false means to store the pointer directly. + * They are boolean flags - HLIST_ALLOC means to duplicate a + * key/value, HLIST_NOALLOC means to store the pointer directly. * * Caller decides this on a by-call basis. Part of the manipulation * routines is a "free". That method always deallocates both the * key and the value. So for static or temporary keys/values, - * the caller instructs us to duplicate the necessary amount + * the caller can instruct us to duplicate the necessary amount * of heap. This mechanism is used to minimize memory-related - * bugs throughout the code and tens of free's in the main - * module. + * bugs throughout the code and tons of free's. */ -hlist_t hlist_add(hlist_t list, char *key, char *value, int allockey, int allocvalue) { +hlist_t hlist_add(hlist_t list, char *key, char *value, hlist_add_t allockey, hlist_add_t allocvalue) { hlist_t tmp, t = list; if (key == NULL || value == NULL) return list; tmp = malloc(sizeof(struct hlist_s)); - tmp->key = (allockey ? strdup(key) : key); - tmp->value = (allocvalue ? strdup(value) : value); + tmp->key = (allockey == HLIST_ALLOC ? strdup(key) : key); + tmp->value = (allocvalue == HLIST_ALLOC ? strdup(value) : value); tmp->next = NULL; + tmp->islist = 0; if (list == NULL) return tmp; @@ -252,7 +286,7 @@ hlist_t tmp = NULL, t = list; while (t) { - tmp = hlist_add(tmp, t->key, t->value, 1, 1); + tmp = hlist_add(tmp, t->key, t->value, HLIST_ALLOC, HLIST_ALLOC); t = t->next; } @@ -309,7 +343,7 @@ free(t->value); t->value = strdup(value); } else if (add) { - list = hlist_add(list, key, value, 1, 1); + list = hlist_add(list, key, value, HLIST_ALLOC, HLIST_ALLOC); } return list; @@ -335,14 +369,14 @@ */ int hlist_count(hlist_t list) { hlist_t t = list; - int ret = 0; + int rc = 0; while (t) { - ret++; + rc++; t = t->next; } - return ret; + return rc; } /* @@ -368,17 +402,42 @@ int found = 0; char *tmp, *low; + lowercase(low = strdup(substr)); tmp = hlist_get(list, key); if (tmp) { lowercase(tmp = strdup(tmp)); - lowercase(low = strdup(substr)); - if (strstr(tmp, substr)) + if (strstr(tmp, low)) found = 1; - free(low); free(tmp); } + free(low); + return found; +} + +/* + * Test if substr is part of the header's value. + * Both case-insensitive, checks all headers, not just first one. + */ +int hlist_subcmp_all(hlist_t list, const char *key, const char *substr) { + hlist_t t = list; + int found = 0; + char *tmp, *low; + + lowercase(low = strdup(substr)); + while (t) { + if (!strcasecmp(t->key, key)) { + lowercase(tmp = strdup(t->value)); + if (strstr(tmp, low)) + found = 1; + + free(tmp); + } + t = t->next; + } + + free(low); return found; } @@ -428,7 +487,7 @@ l = MIN(len, strlen(src)-pos); if (l <= 0) - return NULL; + return new(1); tmp = new(l+1); strlcpy(tmp, src+pos, l+1); @@ -437,44 +496,6 @@ } /* - * Ture if src is a header. This is just a basic check - * for the colon delimiter. Might eventually become more - * sophisticated. :) - */ -int head_ok(const char *src) { - return strcspn(src, ":") != strlen(src); -} - -/* - * Extract the header name from the source. - */ -char *head_name(const char *src) { - int i; - - i = strcspn(src, ":"); - if (i != strlen(src)) - return substr(src, 0, i); - else - return NULL; -} - -/* - * Extract the header value from the source. - */ -char *head_value(const char *src) { - char *sub; - - if ((sub = strchr(src, ':'))) { - sub++; - while (*sub == ' ') - sub++; - - return strdup(sub); - } else - return NULL; -} - -/* * Allocate memory and initialize a new rr_data_t structure. */ rr_data_t new_rr_data(void) { @@ -484,16 +505,60 @@ data->req = 0; data->code = 0; data->skip_http = 0; + data->body_len = 0; + data->empty = 1; + data->port = 0; data->headers = NULL; data->method = NULL; data->url = NULL; + data->rel_url = NULL; + data->hostname = NULL; data->http = NULL; data->msg = NULL; + data->body = NULL; + data->errmsg = NULL; /* for static strings - we don't free, dup, nor copy */ return data; } /* + * Copy the req/res data. + */ +rr_data_t copy_rr_data(rr_data_t dst, rr_data_t src) { + if (src == NULL || dst == NULL) + return NULL; + + reset_rr_data(dst); + dst->req = src->req; + dst->code = src->code; + dst->skip_http = src->skip_http; + dst->body_len = src->body_len; + dst->empty = src->empty; + dst->port = src->port; + + if (src->headers) + dst->headers = hlist_dup(src->headers); + if (src->method) + dst->method = strdup(src->method); + if (src->url) + dst->url = strdup(src->url); + if (src->rel_url) + dst->rel_url = strdup(src->rel_url); + if (src->hostname) + dst->hostname = strdup(src->hostname); + if (src->http) + dst->http = strdup(src->http); + if (src->msg) + dst->msg = strdup(src->msg); + if (src->body && src->body_len > 0) { + dst->body = new(src->body_len); + memcpy(dst->body, src->body, src->body_len); + } + + return dst; +} + +/* * Duplicate the req/res data. */ rr_data_t dup_rr_data(rr_data_t data) { @@ -503,21 +568,43 @@ return NULL; tmp = new_rr_data(); - tmp->req = data->req; - tmp->code = data->code; - tmp->skip_http = data->skip_http; - if (data->headers) - tmp->headers = hlist_dup(data->headers); - if (data->method) - tmp->method = strdup(data->method); - if (data->url) - tmp->url = strdup(data->url); - if (data->http) - tmp->http = strdup(data->http); - if (data->msg) - tmp->msg = strdup(data->msg); - - return tmp; + return copy_rr_data(tmp, data); +} + +/* + * Reset, freeing if neccessary + */ +rr_data_t reset_rr_data(rr_data_t data) { + if (data == NULL) + return NULL; + + data->req = 0; + data->code = 0; + data->skip_http = 0; + data->body_len = 0; + data->empty = 1; + data->port = 0; + + if (data->headers) hlist_free(data->headers); + if (data->method) free(data->method); + if (data->url) free(data->url); + if (data->rel_url) free(data->rel_url); + if (data->hostname) free(data->hostname); + if (data->http) free(data->http); + if (data->msg) free(data->msg); + if (data->body) free(data->body); + + data->headers = NULL; + data->method = NULL; + data->url = NULL; + data->rel_url = NULL; + data->hostname = NULL; + data->http = NULL; + data->msg = NULL; + data->body = NULL; + data->errmsg = NULL; + + return data; } /* @@ -531,8 +618,11 @@ if (data->headers) hlist_free(data->headers); if (data->method) free(data->method); if (data->url) free(data->url); + if (data->rel_url) free(data->rel_url); + if (data->hostname) free(data->hostname); if (data->http) free(data->http); if (data->msg) free(data->msg); + if (data->body) free(data->body); free(data); } @@ -542,9 +632,7 @@ char *trimr(char *buf) { int i; - i = strlen(buf)-1; - while (i >= 0 && (buf[i] == '\r' || buf[i] == '\n' || buf[i] == '\t' || buf[i] == ' ')) - i--; + for (i = strlen(buf)-1; i >= 0 && isspace(buf[i]); --i); buf[i+1] = 0; return buf; @@ -558,10 +646,13 @@ size_t len; char *tmp; + if (!src) + return NULL; + len = strlen(src)+1; - tmp = malloc(len); - memcpy(tmp, src, len); - + tmp = calloc(1, len); + memcpy(tmp, src, len-1); + return tmp; } #endif @@ -662,7 +753,7 @@ } int unicode(char **dst, char *src) { - char *ret; + char *tmp; int l, i; if (!src) { @@ -671,11 +762,11 @@ } l = MIN(64, strlen(src)); - ret = new(2*l); + tmp = new(2*l); for (i = 0; i < l; ++i) - ret[2*i] = src[i]; + tmp[2*i] = src[i]; - *dst = ret; + *dst = tmp; return 2*l; } diff -Nru cntlm-0.35.1/utils.h cntlm-0.91~rc6/utils.h --- cntlm-0.35.1/utils.h 2007-11-20 22:24:36.000000000 +0000 +++ cntlm-0.91~rc6/utils.h 2010-04-30 08:29:20.000000000 +0000 @@ -22,14 +22,34 @@ #ifndef _UTILS_H #define _UTILS_H +#if defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__) +# include +#endif #include +#include + #include "config/config.h" -#define BUFSIZE 1024 +#define BUFSIZE 4096 #define MINIBUF_SIZE 50 #define VAL(var, type, offset) *((type *)(var+offset)) #define MEM(var, type, offset) (type *)(var+offset) -#define MIN(a, b) ((a) < (b) ? (a) : (b)) + +#if !defined(__FreeBSD__) && !defined(__NetBSD__) && !defined(__OpenBSD__) +# define MIN(a, b) ((a) < (b) ? (a) : (b)) +#endif + +/* +#define isalnum(c) (isalpha(c) || isdigit(c)) +#define isspace(c) ((c) == ' ' || (c) == '\f' || (c) == '\t' || (c) == '\r' || (c) == '\n') +*/ + +/* + * Solaris doesn't have LOG_PERROR + */ +#ifndef LOG_PERROR +# define LOG_PERROR LOG_CONS +#endif /* * Two single-linked list types. First is for storing headers, @@ -40,16 +60,22 @@ struct hlist_s { char *key; char *value; + int islist; struct hlist_s *next; }; typedef struct plist_s *plist_t; struct plist_s { unsigned long key; - char *aux; + void *aux; struct plist_s *next; }; +typedef enum { + HLIST_NOALLOC = 0, + HLIST_ALLOC +} hlist_add_t; + /* * Request/response data structure. Complete and parsed req/res is * kept in this. See below for (de)allocation routines. @@ -60,10 +86,17 @@ hlist_t headers; int code; int skip_http; + int body_len; + int empty; + int port; char *method; char *url; + char *rel_url; + char *hostname; char *http; char *msg; + char *body; + char *errmsg; }; /* @@ -72,18 +105,22 @@ struct thread_arg_s { int fd; char *target; + struct sockaddr_in addr; }; -extern plist_t plist_add(plist_t list, unsigned long key, char *aux); +extern void myexit(int rc); +extern void croak(const char *msg, int console); + +extern plist_t plist_add(plist_t list, unsigned long key, void *aux); extern plist_t plist_del(plist_t list, unsigned long key); extern int plist_in(plist_t list, unsigned long key); extern void plist_dump(plist_t list); extern char *plist_get(plist_t list, int key); -extern int plist_pop(plist_t *list); +extern int plist_pop(plist_t *list, void **aux); extern int plist_count(plist_t list); extern plist_t plist_free(plist_t list); -extern hlist_t hlist_add(hlist_t list, char *key, char *value, int allockey, int allocvalue); +extern hlist_t hlist_add(hlist_t list, char *key, char *value, hlist_add_t allockey, hlist_add_t allocvalue); extern hlist_t hlist_dup(hlist_t list); extern hlist_t hlist_del(hlist_t list, const char *key); extern hlist_t hlist_mod(hlist_t list, char *key, char *value, int add); @@ -91,6 +128,7 @@ extern int hlist_count(hlist_t list); extern char *hlist_get(hlist_t list, const char *key); extern int hlist_subcmp(hlist_t list, const char *key, const char *substr); +extern int hlist_subcmp_all(hlist_t list, const char *key, const char *substr); extern hlist_t hlist_free(hlist_t list); extern void hlist_dump(hlist_t list); @@ -100,15 +138,14 @@ extern char *trimr(char *buf); extern char *lowercase(char *str); extern char *uppercase(char *str); -extern int head_ok(const char *src); -extern char *head_name(const char *src); -extern char *head_value(const char *src); extern int unicode(char **dst, char *src); extern char *new(size_t size); extern char *urlencode(const char *str); extern rr_data_t new_rr_data(void); +extern rr_data_t copy_rr_data(rr_data_t dst, rr_data_t src); extern rr_data_t dup_rr_data(rr_data_t data); +extern rr_data_t reset_rr_data(rr_data_t data); extern void free_rr_data(rr_data_t data); extern char *printmem(char *src, size_t len, int bitwidth); diff -Nru cntlm-0.35.1/VERSION cntlm-0.91~rc6/VERSION --- cntlm-0.35.1/VERSION 2007-11-23 20:13:00.000000000 +0000 +++ cntlm-0.91~rc6/VERSION 2010-04-30 21:02:02.000000000 +0000 @@ -1 +1 @@ -0.35.1 +0.91rc6 diff -Nru cntlm-0.35.1/win/Cntlm Homepage.url cntlm-0.91~rc6/win/Cntlm Homepage.url --- cntlm-0.35.1/win/Cntlm Homepage.url 1970-01-01 00:00:00.000000000 +0000 +++ cntlm-0.91~rc6/win/Cntlm Homepage.url 2010-03-20 22:00:51.000000000 +0000 @@ -0,0 +1,2 @@ +[InternetShortcut] +URL=http://cntlm.sf.net/ Binary files /tmp/aw4dKrNvh4/cntlm-0.35.1/win/cntlm.ico and /tmp/Hg8UQxTWOs/cntlm-0.91~rc6/win/cntlm.ico differ diff -Nru cntlm-0.35.1/win/README.txt cntlm-0.91~rc6/win/README.txt --- cntlm-0.35.1/win/README.txt 1970-01-01 00:00:00.000000000 +0000 +++ cntlm-0.91~rc6/win/README.txt 2010-03-20 22:00:51.000000000 +0000 @@ -0,0 +1,27 @@ +Cntlm Installation Manual for Windows +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +- Run setup.exe installer +- Edit cntlm.ini +- Start Cntlm + +Visit http://cntlm.sf.net for HOWTO's and configuration tips. + +Starting and stopping +~~~~~~~~~~~~~~~~~~~~~ + +You can use Cntlm Start Menu shortcuts to start, stop and configure +the application. Cntlm is installed as an auto-start service. + +OR: +Start -> Settings -> Control Panel -> Administrative Tools -> Services + +OR (command line): +net start cntlm +net stop cntlm + + +Uninstalling +~~~~~~~~~~~~ +Stop Cntlm service, run uninstaller from your Start Menu, or use +native Windows "Add/Remove Programs" Control Panel. diff -Nru cntlm-0.35.1/win/setup.iss.in cntlm-0.91~rc6/win/setup.iss.in --- cntlm-0.35.1/win/setup.iss.in 1970-01-01 00:00:00.000000000 +0000 +++ cntlm-0.91~rc6/win/setup.iss.in 2010-03-30 20:11:45.000000000 +0000 @@ -0,0 +1,46 @@ +[Setup] +AppId={{4D753458-961F-45DA-B5E3-7B44D4E368B4} +AppName=Cntlm +AppVerName=Cntlm v$VERSION +AppCopyright=Copyright (C) 2007-2010 David Kubicek +AppPublisher=David Kubicek +AppPublisherURL=http://cntlm.sf.net/ +LicenseFile=license.txt + +DefaultDirName={pf}\Cntlm +DefaultGroupName=Cntlm +SetupIconFile=cntlm.ico +UninstallDisplayIcon={app}\cntlm.ico +Uninstallable=yes +OutputBaseFileName=cntlm-$VERSION-setup +OutputDir=. + +[Files] +Source: "cntlm.exe"; DestDir: "{app}" +Source: "cygrunsrv.exe"; DestDir: "{app}" +Source: "cygwin1.dll"; DestDir: "{app}" +Source: "cntlm.ini"; DestDir: "{app}"; Flags: uninsneveruninstall confirmoverwrite +Source: "cntlm_manual.pdf"; DestDir: "{app}" +Source: "README.txt"; DestDir: "{app}"; Flags: isreadme +Source: "Cntlm Homepage.url"; DestDir: "{app}" +Source: "Software Updates.url"; DestDir: "{app}" +Source: "Support Website.url"; DestDir: "{app}" + +[Run] +Filename: "{app}\cygrunsrv.exe"; StatusMsg: "Stopping Cntlm service..."; Parameters: " --stop cntlm" +Filename: "{app}\cygrunsrv.exe"; StatusMsg: "Removing Cntlm service..."; Parameters: " --remove cntlm" +Filename: "{app}\cygrunsrv.exe"; StatusMsg: "Installing Cntlm service..."; Parameters: "--install cntlm -s KILL -t auto -p ""{app}\cntlm.exe"" -d ""Cntlm Authentication Proxy"" -f ""HTTP Accelerator"" -a -f" + +[Icons] +Name: "{group}\cntlm.ini"; Filename: "{app}\cntlm.ini" +Name: "{group}\Start Cntlm Authentication Proxy"; Filename: "{sys}\net.exe"; Parameters: "start cntlm"; WorkingDir: {app} +Name: "{group}\Stop Cntlm Authentication Proxy"; Filename: "{sys}\net.exe"; Parameters: "stop cntlm"; WorkingDir: {app} +Name: "{group}\Tools\Uninstall Cntlm"; Filename: "{uninstallexe}" +Name: "{group}\Tools\Cntlm Homepage"; Filename: "{app}\Cntlm Homepage.url" +Name: "{group}\Tools\Software Updates"; Filename: "{app}\Software Updates.url" +Name: "{group}\Tools\Support Website"; Filename: "{app}\Support Website.url" +Name: "{group}\Tools\PDF configuration manual"; Filename: "{app}\cntlm_manual.pdf" + +[UninstallRun] +Filename: "{app}\cygrunsrv.exe"; StatusMsg: "Stopping Cntlm service..."; Parameters: " --stop cntlm" +Filename: "{app}\cygrunsrv.exe"; StatusMsg: "Removing Cntlm service..."; Parameters: " --remove cntlm" diff -Nru cntlm-0.35.1/win/Software Updates.url cntlm-0.91~rc6/win/Software Updates.url --- cntlm-0.35.1/win/Software Updates.url 1970-01-01 00:00:00.000000000 +0000 +++ cntlm-0.91~rc6/win/Software Updates.url 2010-03-20 22:00:51.000000000 +0000 @@ -0,0 +1,2 @@ +[InternetShortcut] +URL=http://sourceforge.net/projects/cntlm/files/ diff -Nru cntlm-0.35.1/win/Support Website.url cntlm-0.91~rc6/win/Support Website.url --- cntlm-0.35.1/win/Support Website.url 1970-01-01 00:00:00.000000000 +0000 +++ cntlm-0.91~rc6/win/Support Website.url 2010-03-20 22:00:51.000000000 +0000 @@ -0,0 +1,2 @@ +[InternetShortcut] +URL=http://sourceforge.net/tracker/?group_id=197861