summaryrefslogtreecommitdiff
path: root/proxy.c
diff options
context:
space:
mode:
Diffstat (limited to 'proxy.c')
-rw-r--r--proxy.c793
1 files changed, 793 insertions, 0 deletions
diff --git a/proxy.c b/proxy.c
new file mode 100644
index 000000000..e9c88238f
--- /dev/null
+++ b/proxy.c
@@ -0,0 +1,793 @@
+/* Proxy detector.
+ *
+ * (C) 2003 Anope Team
+ * Contact us at info@anope.org
+ *
+ * Please read COPYING and README for furhter details.
+ *
+ * Based on the original code of Epona by Lara.
+ * Based on the original code of Services by Andy Church.
+ *
+ * $Id: proxy.c,v 1.11 2004/02/14 21:22:55 dane Exp $
+ *
+ */
+
+#include "services.h"
+#include "pseudo.h"
+#include <fcntl.h>
+
+/* Hashed list of HostCache; threads must not use it! */
+HostCache *hcache[1024];
+
+/*************************************************************************/
+
+/* Equivalent to inet_ntoa */
+
+void ntoa(struct in_addr addr, char *ipaddr, int len)
+{
+ unsigned char *bytes = (unsigned char *) &addr.s_addr;
+ snprintf(ipaddr, len, "%u.%u.%u.%u", bytes[0], bytes[1], bytes[2],
+ bytes[3]);
+}
+
+/*************************************************************************/
+
+#ifdef USE_THREADS
+
+/*************************************************************************/
+
+#define HASH(host) ((tolower((host)[0])&31)<<5 | (tolower((host)[1])&31))
+
+/* Proxy queue; access controlled by queuemut */
+SList pxqueue;
+
+pthread_mutex_t queuemut = PTHREAD_MUTEX_INITIALIZER;
+pthread_cond_t queuecond = PTHREAD_COND_INITIALIZER;
+
+#if !defined(HAS_NICKIP) && !defined(HAVE_GETHOSTBYNAME_R6) && !defined(HAVE_GETHOSTBYNAME_R5) && !defined(HAVE_GETHOSTBYNAME_R3)
+pthread_mutex_t resmut = PTHREAD_MUTEX_INITIALIZER;
+#endif
+
+static uint32 aton(char *ipaddr);
+static void proxy_akill(char *host);
+static HostCache *proxy_cache_add(char *host);
+static void proxy_cache_del(HostCache * hc);
+static HostCache *proxy_cache_find(char *host);
+static int proxy_connect(unsigned long ip, unsigned short port);
+static void proxy_queue_cleanup_unlock(void *arg);
+static void proxy_queue_lock(void);
+static void proxy_queue_signal(void);
+static void proxy_queue_unlock(void);
+static void proxy_queue_wait(void);
+static int proxy_read(int s, char *buf, size_t buflen);
+#ifndef HAS_NICKIP
+static uint32 proxy_resolve(char *host);
+#endif
+static int proxy_scan(uint32 ip);
+static void *proxy_thread_main(void *arg);
+
+/*************************************************************************/
+
+/* Equivalent to inet_addr */
+
+static uint32 aton(char *ipaddr)
+{
+ int i;
+ long lv;
+ char *endptr;
+ uint32 res;
+ unsigned char *bytes = (unsigned char *) &res;
+
+ for (i = 0; i < 4; i++) {
+ if (!*ipaddr)
+ return INADDR_NONE;
+
+ lv = strtol(ipaddr, &endptr, 10);
+ if (lv < 0 || lv > 255 || (*endptr != 0 && *endptr != '.'))
+ return INADDR_NONE;
+
+ bytes[i] = (unsigned char) lv;
+ ipaddr = (!*endptr ? endptr : ++endptr);
+ }
+
+ if (*endptr)
+ return INADDR_NONE;
+
+ return res;
+}
+
+/*************************************************************************/
+
+void get_proxy_stats(long *nrec, long *memuse)
+{
+ int i;
+ long mem = 0, count = 0;
+ HostCache *hc;
+
+ for (i = 0; i < 1024; i++) {
+ for (hc = hcache[i]; hc; hc = hc->next) {
+ count += 1;
+ mem += sizeof(HostCache);
+ mem += strlen(hc->host) + 1;
+ }
+ }
+
+ *nrec = count;
+ *memuse = mem;
+}
+
+/*************************************************************************/
+
+/* Akills the given host, and issues a GLOBOPS if configured so */
+
+static void proxy_akill(char *host)
+{
+ s_akill("*", host, s_OperServ, time(NULL),
+ time(NULL) + (ProxyExpire ? ProxyExpire : 86400 * 2),
+ ProxyAkillReason);
+ if (WallProxy)
+ wallops(s_OperServ, "Insecure proxy \2%s\2 has been AKILLed.",
+ host);
+}
+
+/*************************************************************************/
+
+/* Adds a cache entry after having it allocated */
+
+static HostCache *proxy_cache_add(char *host)
+{
+ HostCache *hc;
+ int index = HASH(host);
+
+ hc = scalloc(1, sizeof(HostCache));
+ hc->host = sstrdup(host);
+ hc->used = time(NULL);
+
+ hc->prev = NULL;
+ hc->next = hcache[index];
+ if (hc->next)
+ hc->next->prev = hc;
+ hcache[index] = hc;
+
+ if (debug)
+ alog("debug: Added %s to host cache", host);
+
+ return hc;
+}
+
+/*************************************************************************/
+
+/* Deletes and frees a proxy cache entry */
+
+static void proxy_cache_del(HostCache * hc)
+{
+ /* Just to be sure */
+ if (hc->status < 0)
+ return;
+
+ if (debug)
+ alog("debug: Deleting %s from host cache", hc->host);
+
+ if (hc->status > HC_NORMAL)
+ s_rakill("*", hc->host);
+
+ if (hc->next)
+ hc->next->prev = hc->prev;
+ if (hc->prev)
+ hc->prev->next = hc->next;
+ else
+ hcache[HASH(hc->host)] = hc->next;
+
+ if (hc->host)
+ free(hc->host);
+
+ free(hc);
+}
+
+/*************************************************************************/
+
+/* Finds a proxy cache entry */
+
+static HostCache *proxy_cache_find(char *host)
+{
+ HostCache *hc;
+
+ for (hc = hcache[HASH(host)]; hc; hc = hc->next) {
+ if (stricmp(hc->host, host) == 0)
+ return hc;
+ }
+
+ return NULL;
+}
+
+/*************************************************************************/
+
+/* Checks whether the specified host is in the cache.
+ * If so:
+ * * if it's a proxy, take the appropriate actions, including killing nick
+ * * if it's not a proxy, do nothing
+ * If not:
+ * * add the host to the cache
+ * * add the host to the queue
+ * * send a signal to a waiting thread (if any)
+ *
+ * Returns 0 if nick is to be added to internal list, 1 else
+ */
+
+int proxy_check(char *nick, char *host, uint32 ip)
+{
+ int i;
+ char **message;
+ HostCache *hc;
+
+ if ((hc = proxy_cache_find(host))) {
+ hc->used = time(NULL);
+
+ if (hc->status <= HC_NORMAL)
+ return 0;
+
+ proxy_akill(host);
+ return 0;
+ }
+
+ for (message = ProxyMessage, i = 0; i < 8 && *message && **message;
+ message++, i++)
+ notice(s_GlobalNoticer, nick, *message);
+
+ hc = proxy_cache_add(host);
+#ifdef HAS_NICKIP
+ hc->ip = htonl(ip);
+#endif
+ hc->status = HC_QUEUED;
+
+ proxy_queue_lock();
+ slist_add(&pxqueue, hc);
+ if (debug)
+ alog("debug: Added %s to proxy queue", hc->host);
+ proxy_queue_signal();
+ proxy_queue_unlock();
+
+ return 0;
+}
+
+/*************************************************************************/
+
+/* Initiates a non-blocking connection */
+
+static int proxy_connect(unsigned long ip, unsigned short port)
+{
+ struct sockaddr_in sin;
+ int s;
+
+ fd_set fds;
+ struct timeval tv;
+ int error, errlen;
+
+ if ((s = socket(PF_INET, SOCK_STREAM, 0)) == -1)
+ return -1;
+
+ if (fcntl(s, F_SETFL, O_NONBLOCK) == -1) {
+ close(s);
+ return -1;
+ }
+
+ memset(&sin, 0, sizeof(struct sockaddr_in));
+
+ sin.sin_family = AF_INET;
+ sin.sin_addr.s_addr = ip;
+ sin.sin_port = htons(port);
+
+ if (connect(s, (struct sockaddr *) &sin, sizeof(struct sockaddr_in)) ==
+ -1 && errno != EINPROGRESS) {
+ close(s);
+ return -1;
+ }
+
+ FD_ZERO(&fds);
+ FD_SET(s, &fds);
+
+ tv.tv_sec = ProxyTimeout;
+ tv.tv_usec = 0;
+
+ if (select(s + 1, NULL, &fds, NULL, &tv) <= 0) {
+ close(s);
+ return -1;
+ }
+
+ errlen = sizeof(int);
+ if (getsockopt(s, SOL_SOCKET, SO_ERROR, &error, &errlen) == -1
+ || error != 0) {
+ close(s);
+ return -1;
+ }
+
+ return s;
+}
+
+/*************************************************************************/
+
+/* Deletes expired cache entries */
+
+void proxy_expire()
+{
+ int i;
+ HostCache *hc, *next;
+ time_t t = time(NULL);
+
+ for (i = 0; i < 1024; i++) {
+ for (hc = hcache[i]; hc; hc = next) {
+ next = hc->next;
+
+ /* Don't expire not scanned yet entries */
+ if (hc->status < HC_NORMAL)
+ continue;
+
+ if (hc->status == HC_NORMAL
+ && t - hc->used >= ProxyCacheExpire) {
+ proxy_cache_del(hc);
+ continue;
+ }
+
+ if (ProxyExpire && hc->status > HC_NORMAL
+ && t - hc->used >= ProxyExpire) {
+ alog("proxy: Expiring proxy %s", hc->host);
+ proxy_cache_del(hc);
+ }
+ }
+ }
+}
+
+/*************************************************************************/
+
+/* Initializes the proxy detector. Returns 1 on success, 0 on error. */
+
+int proxy_init(void)
+{
+ int i;
+ pthread_t th;
+
+ slist_init(&pxqueue);
+
+ for (i = 1; i <= ProxyThreads; i++) {
+ if (pthread_create(&th, NULL, proxy_thread_main, NULL))
+ return 0;
+ if (pthread_detach(th))
+ return 0;
+ if (debug)
+ alog("debug: Creating proxy thread %ld (%d of %d)", (long) th,
+ i, ProxyThreads);
+ }
+
+ alog("Proxy detector initialized");
+
+ return 1;
+}
+
+/*************************************************************************/
+
+static void proxy_queue_cleanup_unlock(void *arg)
+{
+ proxy_queue_unlock();
+}
+
+/*************************************************************************/
+
+static void proxy_queue_lock(void)
+{
+ if (debug)
+ alog("debug: Thread %ld: Locking proxy queue mutex",
+ (long) pthread_self());
+ pthread_mutex_lock(&queuemut);
+}
+
+/*************************************************************************/
+
+static void proxy_queue_signal(void)
+{
+ if (debug)
+ alog("debug: Thread %ld: Signaling proxy queue condition",
+ (long) pthread_self());
+ pthread_cond_signal(&queuecond);
+}
+
+/*************************************************************************/
+
+static void proxy_queue_unlock(void)
+{
+ if (debug)
+ alog("debug: Thread %ld: Unlocking proxy queue mutex",
+ (long) pthread_self());
+ pthread_mutex_unlock(&queuemut);
+}
+
+/*************************************************************************/
+
+static void proxy_queue_wait(void)
+{
+ if (debug)
+ alog("debug: Thread %ld: waiting proxy queue condition",
+ (long) pthread_self());
+ pthread_cond_wait(&queuecond, &queuemut);
+}
+
+/*************************************************************************/
+
+/* Reads from the socket, in a non-blocking manner */
+
+static int proxy_read(int s, char *buf, size_t buflen)
+{
+ fd_set fds;
+ struct timeval tv;
+
+ FD_ZERO(&fds);
+ FD_SET(s, &fds);
+
+ tv.tv_sec = ProxyTimeout;
+ tv.tv_usec = 0;
+
+ if (select(s + 1, &fds, NULL, NULL, &tv) <= 0)
+ return -1;
+
+ return recv(s, buf, buflen, 0);
+}
+
+/*************************************************************************/
+
+/* Resolves hostnames in a thread safe manner */
+
+#ifndef HAS_NICKIP
+
+static uint32 proxy_resolve(char *host)
+{
+ struct hostent *hentp = NULL;
+ uint32 ip = INADDR_NONE;
+#if defined(HAVE_GETHOSTBYNAME_R6)
+ struct hostent hent;
+ char hbuf[8192];
+ int herrno;
+
+ if (gethostbyname_r(host, &hent, hbuf, sizeof(hbuf), &hentp, &herrno) <
+ 0)
+ hentp = NULL;
+#elif defined(HAVE_GETHOSTBYNAME_R5)
+ struct hostent hent char hbuf[8192];
+ int herrno;
+ hentp = gethostbyname_r(host, &hent, hbuf, sizeof(hbuf), &herrno);
+#elif defined(HAVE_GETHOSTBYNAME_R3)
+ struct hostent hent;
+ struct hostent_data data;
+ hentp = gethostbyname_r(host, &hent, &data);
+#else
+ /* Make it safe that way */
+ pthread_mutex_lock(&resmut);
+ hentp = gethostbyname(host);
+#endif
+
+ if (hentp) {
+ memcpy(&ip, hentp->h_addr, sizeof(hentp->h_length));
+ if (debug) {
+ char ipbuf[16];
+ struct in_addr addr;
+ addr.s_addr = ip;
+ ntoa(addr, ipbuf, sizeof(ipbuf));
+ alog("debug: Thread %ld: resolved %s to %s",
+ (long) pthread_self(), host, ipbuf);
+ }
+ }
+#if !defined(HAVE_GETHOSTBYNAME_R6) && !defined(HAVE_GETHOSTBYNAME_R5) && !defined(HAVE_GETHOSTBYNAME_R3)
+ pthread_mutex_unlock(&resmut);
+#endif
+
+ return ip;
+}
+
+#endif
+
+/*************************************************************************/
+
+/* Scans the given host for proxy */
+
+static int proxy_scan(uint32 ip)
+{
+ int s; /* Socket */
+ int i;
+
+ if (ip == INADDR_NONE)
+ return HC_NORMAL;
+
+ /* Scan for SOCKS (4/5) */
+
+ for (i = 0; i < 2; i++) {
+ if ((s = proxy_connect(ip, 1080)) == -1)
+ break;
+
+ if (ProxyCheckSocks4 && i == 0) {
+ /* SOCKS4 */
+
+ char buf[9];
+ uint32 sip;
+
+ sip = aton(ProxyTestServer);
+ sip = htonl(sip);
+
+ buf[0] = 4;
+ buf[1] = 1;
+ buf[2] = (((unsigned short) ProxyTestPort) >> 8) & 0xFF;
+ buf[3] = ((unsigned short) ProxyTestPort) & 0xFF;
+ buf[4] = (sip >> 24) & 0xFF;
+ buf[5] = (sip >> 16) & 0xFF;
+ buf[6] = (sip >> 8) & 0xFF;
+ buf[7] = sip & 0xFF;
+ buf[8] = 0;
+
+ if (send(s, buf, 9, 0) != 9) {
+ close(s);
+ return HC_NORMAL;
+ }
+
+ if (proxy_read(s, buf, 2) != 2) {
+ close(s);
+ continue;
+ }
+
+ if (buf[1] == 90) {
+ close(s);
+ return HC_SOCKS4;
+ }
+
+ } else if (ProxyCheckSocks5 && i == 1) {
+ /* SOCKS5 */
+
+ char buf[10];
+ uint32 sip;
+
+ if (send(s, "\5\1\0", 3, 0) != 3) {
+ close(s);
+ continue;
+ }
+
+ memset(buf, 0, sizeof(buf));
+
+ if (proxy_read(s, buf, 2) != 2) {
+ close(s);
+ continue;
+ }
+
+ if (buf[0] != 5 || buf[1] != 0) {
+ close(s);
+ continue;
+ }
+
+ sip = aton(ProxyTestServer);
+ sip = htonl(sip);
+
+ buf[0] = 5;
+ buf[1] = 1;
+ buf[2] = 0;
+ buf[3] = 1;
+ buf[4] = (sip >> 24) & 0xFF;
+ buf[5] = (sip >> 16) & 0xFF;
+ buf[6] = (sip >> 8) & 0xFF;
+ buf[7] = sip & 0xFF;
+ buf[8] = (((unsigned short) ProxyTestPort) >> 8) & 0xFF;
+ buf[9] = ((unsigned short) ProxyTestPort) & 0xFF;
+
+ if (send(s, buf, 10, 0) != 10) {
+ close(s);
+ continue;
+ }
+
+ memset(buf, 0, sizeof(buf));
+
+ if (proxy_read(s, buf, 2) != 2) {
+ close(s);
+ continue;
+ }
+
+ if (buf[0] == 5 && buf[1] == 0) {
+ close(s);
+ return HC_SOCKS5;
+ }
+ }
+
+ close(s);
+ }
+
+ /* Scan for HTTP proxy */
+ for (i = 0; i < 3; i++) {
+ if ((i ==
+ 0 ? ProxyCheckHTTP2 : (i ==
+ 1 ? ProxyCheckHTTP1 : ProxyCheckHTTP3))
+ && (s =
+ proxy_connect(ip,
+ (i == 0 ? 8080 : (i == 1 ? 3128 : 80)))) !=
+ -1) {
+ int bread;
+ char buf[64];
+
+ snprintf(buf, sizeof(buf), "CONNECT %s:%d HTTP/1.0\n\n",
+ ProxyTestServer, ProxyTestPort);
+ if (send(s, buf, strlen(buf), 0) == strlen(buf)) {
+ if ((bread = proxy_read(s, buf, 15)) >= 12) {
+ buf[bread] = 0;
+
+ if (!strnicmp(buf, "HTTP/1.0 200", 12) || !stricmp(buf, "HTTP/1.1 200 Co")) { /* Apache may return 200 OK
+ even if it's not processing
+ the CONNECT request. :/ */
+ close(s);
+ return HC_HTTP;
+ }
+ }
+ }
+ close(s);
+ }
+ }
+
+ /* Scan for Wingate */
+ if (ProxyCheckWingate && (s = proxy_connect(ip, 23)) != -1) {
+ char buf[9];
+
+ if (proxy_read(s, buf, 8) == 8) {
+ buf[8] = '\0';
+ if (!stricmp(buf, "Wingate>") || !stricmp(buf, "Too many")) {
+ close(s);
+ return HC_WINGATE;
+ }
+ }
+ close(s);
+ }
+
+ return HC_NORMAL;
+}
+
+/*************************************************************************/
+
+/* Proxy detector threads entry point */
+
+static void *proxy_thread_main(void *arg)
+{
+ while (1) {
+ pthread_cleanup_push(&proxy_queue_cleanup_unlock, NULL);
+ proxy_queue_lock();
+ proxy_queue_wait();
+ pthread_cleanup_pop(1);
+
+ /* We loop until there is no more host to check in the list */
+ while (1) {
+ HostCache *hc = NULL;
+ int status;
+
+ pthread_cleanup_push(&proxy_queue_cleanup_unlock, NULL);
+ proxy_queue_lock();
+ if (pxqueue.count > 0) {
+ hc = pxqueue.list[0];
+ hc->status = HC_PROGRESS;
+ slist_delete(&pxqueue, 0);
+ }
+ pthread_cleanup_pop(1);
+
+ if (!hc)
+ break;
+
+ if (debug) {
+ if (hc->ip) {
+ char ipbuf[16];
+ struct in_addr in;
+ in.s_addr = hc->ip;
+ ntoa(in, ipbuf, sizeof(ipbuf));
+ alog("debug: Scanning host %s [%s] for proxy",
+ hc->host, ipbuf);
+ } else {
+ alog("debug: Scanning host %s for proxy", hc->host);
+ }
+ }
+#ifndef HAS_NICKIP
+ /* Test if it's an IP, and if not try to resolve the hostname */
+ if ((hc->ip = aton(hc->host)) == INADDR_NONE)
+ hc->ip = proxy_resolve(hc->host);
+#endif
+ status = proxy_scan(hc->ip);
+
+ if (debug) {
+ char ipbuf[16];
+ struct in_addr in;
+ in.s_addr = hc->ip;
+ ntoa(in, ipbuf, sizeof(ipbuf));
+ alog("debug: Scan for %s [%s] complete, result: %d",
+ hc->host, ipbuf, status);
+ }
+
+ if (status > HC_NORMAL)
+ proxy_akill(hc->host);
+
+ hc->status = status;
+ }
+ }
+
+ return NULL;
+}
+
+/*************************************************************************/
+
+#endif
+
+/*************************************************************************/
+
+/* OperServ CACHE */
+
+int do_cache(User * u)
+{
+#ifdef USE_THREADS
+ char *cmd = strtok(NULL, " ");
+ char *pattern = strtok(NULL, " ");
+
+ if (!ProxyDetect) {
+ notice_lang(s_OperServ, u, OPER_CACHE_DISABLED);
+ return MOD_CONT;
+ }
+
+ if (!cmd || !pattern) {
+ syntax_error(s_OperServ, u, "CACHE", OPER_CACHE_SYNTAX);
+ } else if (!stricmp(cmd, "DEL")) {
+ HostCache *hc;
+
+ if (!(hc = proxy_cache_find(pattern))) {
+ notice_lang(s_OperServ, u, OPER_CACHE_NOT_FOUND, pattern);
+ return MOD_CONT;
+ }
+
+ proxy_cache_del(hc);
+ notice_lang(s_OperServ, u, OPER_CACHE_REMOVED, pattern);
+
+ if (readonly)
+ notice_lang(s_OperServ, u, READ_ONLY_MODE);
+ } else if (!stricmp(cmd, "LIST")) {
+ char *option = strtok(NULL, " ");
+ int i, restrict = 0, count = 0, total = 0;
+ HostCache *hc;
+
+ static int statusdesc[7] = {
+ OPER_CACHE_QUEUED,
+ OPER_CACHE_PROGRESS,
+ OPER_CACHE_NORMAL,
+ OPER_CACHE_WINGATE,
+ OPER_CACHE_SOCKS4,
+ OPER_CACHE_SOCKS5,
+ OPER_CACHE_HTTP
+ };
+
+ if (option && !stricmp(option, "QUEUED"))
+ restrict = 1;
+ else if (option && !stricmp(option, "ALL"))
+ restrict = 2;
+
+ notice_lang(s_OperServ, u, OPER_CACHE_HEADER);
+
+ for (i = 0; i < 1024; i++) {
+ for (hc = hcache[i]; hc; hc = hc->next) {
+ if (!match_wild_nocase(pattern, hc->host))
+ continue;
+ if ((restrict == 0 && hc->status <= HC_NORMAL)
+ || (restrict == 1 && hc->status >= HC_NORMAL))
+ continue;
+ total++;
+ if (count >= ProxyMax)
+ continue;
+ notice_lang(s_OperServ, u, OPER_CACHE_LIST, hc->host,
+ getstring(u->na, statusdesc[hc->status + 2]));
+ count++;
+ }
+ }
+
+ notice_lang(s_OperServ, u, OPER_CACHE_FOOTER, count, total);
+
+ } else {
+ syntax_error(s_OperServ, u, "CACHE", OPER_CACHE_SYNTAX);
+ }
+#else
+ notice_lang(s_OperServ, u, OPER_CACHE_DISABLED);
+#endif
+ return MOD_CONT;
+}
+
+/*************************************************************************/