/* 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 863 2005-08-29 14:19:39Z geniusdex $
*
*/
#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;
socklen_t 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;
}
/*************************************************************************/
syntax highlighted by Code2HTML, v. 0.9.1