home *** CD-ROM | disk | FTP | other *** search
Wrap
/* Copyright (c) 1995-1999 NEC USA, Inc. All rights reserved. */ /* */ /* The redistribution, use and modification in source or binary forms of */ /* this software is subject to the conditions set forth in the copyright */ /* document ("Copyright") included with this distribution. */ /* * $Id: proxy.c,v 1.94.2.2.2.9 1999/02/26 00:04:59 wlu Exp $ */ #include "socks5p.h" #include "daemon.h" #include "validate.h" #include "protocol.h" #include "msgids.h" #include "null.h" #include "info.h" #include "log.h" #include "msg.h" #include "upwd.h" #include "gss.h" #include "tcp.h" #include "udp.h" #include "tracer.h" #include "packet.h" #ifndef ESTABLISH_TIMEOUT #define ESTABLISH_TIMEOUT 10 #endif #ifndef START_TIMEOUT #define START_TIMEOUT 60 #endif #define PROXY_IOFLAGS S5_IOFLAGS_TIMED|S5_IOFLAGS_NBYTES|S5_IOFLAGS_RESTART static int HandleS4Connection(S5LinkInfo *pri, S5IOInfo *iio, list *auths, double *timerm) { #ifdef FORK_SOCKD dup2(pri->in, 0); dup2(pri->in, 1); dup2(pri->in, 2); close(pri->in); execlp("sockd", "sockd", NULL); return EXIT_ERR; #else char buf[256+256+8], *tmp, resp[] = { SOCKS4_VERSION, SOCKS_FAIL, (char)0xff, (char)0xff, (char)0xff, (char)0xff, (char)0xff, (char)0xff }; S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "Proxy: Acting as a Socks4 server"); if (auths) { for ( ;auths; auths = auths->next) { if (auths->dataint == AUTH_NONE || auths->dataint == AUTH_PASSWD) break; } if (!auths) { S5LogUpdate(S5LogDefaultHandle, S5_LOG_WARNING, MSGID_SERVER_AUTH_NONE, "Socks%d: No acceptable authentication method found", (int)pri->peerVersion); if (S5IOSend(iio->fd, NULL, resp, sizeof(resp), 0, PROXY_IOFLAGS, timerm) != sizeof(resp)) return EXIT_ERR; return EXIT_AUTH; } } if (S5IORecv(iio->fd, NULL, buf, 8, 0, PROXY_IOFLAGS, timerm) != 8) { S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(0), 0, "Socks4: Read failed: %m"); return EXIT_ERR; } pri->peerAuth = AUTH_NONE; pri->peerVersion = buf[SP_VERSION]; pri->peerCommand = buf[SP_COMMAND]; if (lsGetProtoAddr(SOCKS4_VERSION, buf, &pri->dstAddr) < 0) { S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(0), 0, "Socks4: Invalid address passed from client"); return EXIT_ERR; } for (tmp = buf, *tmp = '\0'; tmp < buf+sizeof(buf)-1; *++tmp = '\0') { if (S5IORecv(iio->fd, iio, tmp, 1, 0, PROXY_IOFLAGS, timerm) != 1) { S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(0), 0, "Socks4: Read failed: %m"); return EXIT_ERR; } if (*tmp == '\0') break; } S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "Socks4: Read user: %s", buf); strcpy(pri->srcUser, buf); if (lsNullSrvAuth(iio->fd, &iio->auth, pri->srcUser) != AUTH_OK) { S5LogUpdate(S5LogDefaultHandle, S5_LOG_WARNING, MSGID_SERVER_AUTH_FAILED, "Socks%d: Authentication method %d failed", (int)pri->peerVersion, 0); resp[2] = SOCKS_BAD_ID; if (S5IOSend(iio->fd, NULL, resp, sizeof(resp), 0, PROXY_IOFLAGS, timerm) != sizeof(resp)) return EXIT_ERR; return EXIT_AUTH; } /* If we haven't gotten authentication specific read and write functions */ /* we should set the client functions to be "normal", rather than timed */ /* Since in all likelyhood, it will take a long time to read in a whole */ /* buffers worth of data (in fact it may never happen)... */ if (pri->peerCommand != SOCKS_CONNECT && pri->peerCommand != SOCKS_BIND) { S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(0), MSGID_SERVER_BAD_COMMAND, "Socks%d: Invalid command: %d", (int)pri->peerVersion, (int)pri->peerCommand); S5IOSend(iio->fd, NULL, resp, sizeof(resp), 0, PROXY_IOFLAGS, timerm); return EXIT_ERR; } S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "Socks4: Done initialization"); return 0; #endif } static int AuthOK(u_char auth) { switch (auth) { #ifdef HAVE_LIBGSSAPI_KRB5 case AUTH_GSSAPI: #endif case AUTH_PASSWD: case AUTH_NONE: return 1; default: return 0; } } static int HandleS5Connection(S5LinkInfo *pri, S5IOInfo *iio, list *auths, double *timerm) { char fail[] = { 0x05, (char)0xff }, buf[256+2]; int ret, i; list *tl; if (S5IORecv(iio->fd, iio, buf, 2, 0, PROXY_IOFLAGS, timerm) != 2) { S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(0), 0, "Socks5: Read failed: %m"); return EXIT_ERR; } if (S5IORecv(iio->fd, iio, buf+2, (u_char)buf[1], 0, PROXY_IOFLAGS, timerm) != (u_char)buf[1]) { S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(0), 0, "Socks5: Read failed: %m"); return EXIT_ERR; } pri->peerAuth = 0xff; if (!auths) { /* Anything is ok, pick the first one the client wanted to do... */ for (i = 0; i < ((int)(u_char)buf[1]); i++) if (AuthOK((u_char)buf[i+2])) break; if (i != ((int)(u_char)buf[1])) pri->peerAuth = (u_char)(buf[i+2]); } else for (tl = auths; tl; tl = tl->next) { /* For each method (in order) in the server's config file, See if */ /* the client wanted to do this method...And, of course, make sure */ /* we can do it... */ for (i = 0; i < ((int)(u_char)buf[1]); i++) if (tl->dataint == (int)(u_char)buf[i+2]) break; if (i == (int)(u_char)buf[1] || !AuthOK((u_char)buf[i+2])) continue; pri->peerAuth = (u_char)buf[i+2]; break; } buf[1] = (u_char)pri->peerAuth; if (S5IOSend(iio->fd, iio, buf, 2, 0, PROXY_IOFLAGS, timerm) != 2) { S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(0), 0, "Socks5: Write failed: %m"); return EXIT_ERR; } if (pri->peerAuth == 0xff) { S5LogUpdate(S5LogDefaultHandle, S5_LOG_WARNING, MSGID_SERVER_AUTH_NONE, "Socks%d: No acceptable authentication method found", (int)pri->peerVersion); return EXIT_AUTH; } S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "Socks5: Told client to do authentication method #%d", (int)pri->peerAuth); switch (pri->peerAuth) { case AUTH_PASSWD: ret = lsPasswdSrvAuth(iio->fd, &iio->auth, pri->srcUser); break; case AUTH_GSSAPI: ret = lsGssapiSrvAuth(iio->fd, &iio->auth, pri->srcUser); break; case AUTH_NONE: ret = lsNullSrvAuth (iio->fd, &iio->auth, pri->srcUser); break; default: S5LogUpdate(S5LogDefaultHandle, S5_LOG_WARNING, MSGID_SERVER_AUTH_BAD, "Socks%d: Bad Authentication method number: %d", (int)pri->peerVersion, (int)pri->peerAuth); S5IOSend(iio->fd, iio, fail, sizeof(fail), 0, PROXY_IOFLAGS, timerm); ret = AUTH_FAIL; } if (ret == AUTH_FAIL) { S5LogUpdate(S5LogDefaultHandle, S5_LOG_WARNING, MSGID_SERVER_AUTH_FAILED, "Socks%d: Authentication method %d failed", (int)pri->peerVersion, (int)pri->peerAuth); return EXIT_AUTH; } if (lsReadRequest(iio->fd, iio, &pri->dstAddr, &pri->peerVersion, &pri->peerCommand, &pri->peerReserved) < 0) { S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(0), 0, "Socks5: Read request failed: %m"); return EXIT_ERR; } return EXIT_OK; } /* HandlePxyConnection will be the routine for Socks5, which handles the */ /* initial part of the connection. Specifically, we want to do several */ /* things here. We want to check the version, if its not version 5, we can */ /* either exec the old sockd, or we can call a separate routine to handle */ /* the connection establishment. (That routine should read the name, and */ /* possible make an ident query). If the version is version 5, we have to */ /* decide what kind of authentication we are going to do. This would mean */ /* reading the number of methods, and then the method numbers themselves, */ /* after that we pick the one we'd like to do, and send back a reply with */ /* my version (5), and the method number we picked, or 0xff if we couldn't */ /* find one that was acceptable given the information we already have (the */ /* source host.) We probably want to drop the connection immediately if we */ /* know there is no acceptable auth from a host, so we don't waste fd's. */ /* after all this is done, we read the command, then finally do it. Well, */ /* if we haven't already quit, that is. */ int HandlePxyConnection(S5IOHandle fd) { char buf[] = { SOCKS5_VERSION, SOCKS5_FAIL, 0, SOCKS5_IPV4ADDR, 0, 0, 0, 0, 0, 0 }; int action, dir, rval = EXIT_ERR, len = sizeof(S5NetAddr), cleaned = 0; double timerm = (double)START_TIMEOUT; list *auths; S5Packet inPacket, outPacket; S5CommandInfo ci; S5FilterInfo fi; S5NetAddr route; S5LinkInfo li; S5IOInfo iio; memset(&li, 0, sizeof(S5LinkInfo)); memset(&fi, 0, sizeof(S5FilterInfo)); memset(&ci, 0, sizeof(S5CommandInfo)); S5BufSetupContext(&iio); iio.fd = fd; if (getpeername(iio.fd, (ss *)&li.srcAddr, &len) < 0) { S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(0), 0, "Proxy: getpeername failed: %m"); goto cleanup; } len = sizeof(S5NetAddr); if (getsockname(iio.fd, (ss *)&li.bndAddr, &len) < 0) { S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(0), 0, "Proxy: getsockname failed: %m"); goto cleanup; } GetName(li.srcName, &li.srcAddr); if (!GetRoute(&li.srcAddr, li.srcName, "tcp", &route)) { /* If somehow we got a connection from somewhere on an interface which */ /* we wouldn't use to get to that same place, this is bad (IP SPOOF?), */ /* so we'll quit. */ if (route.sin.sin_addr.s_addr != INADDR_ANY && route.sin.sin_addr.s_addr != li.bndAddr.sin.sin_addr.s_addr) { S5LogUpdate(S5LogDefaultHandle, S5_LOG_WARNING, MSGID_SERVER_WRONG_ROUTE, "Proxy: Received connection via wrong route"); S5LogUpdate(S5LogDefaultHandle, S5_LOG_INFO, MSGID_SERVER_AUTH_FAILED, "Auth Failed: (%s:%d)", li.srcName, (int)ntohs(lsAddr2Port(&li.srcAddr))); goto cleanup; } } /* If this host was "banned", quit... */ if ((GetAuths(&li, &auths)) < 0) { S5LogUpdate(S5LogDefaultHandle, S5_LOG_WARNING, MSGID_SERVER_BANNED_HOST, "Proxy: Received connection from banned host"); S5LogUpdate(S5LogDefaultHandle, S5_LOG_INFO, MSGID_SERVER_AUTH_FAILED, "Auth Failed: (%s:%d)", li.srcName, (int)ntohs(lsAddr2Port(&li.srcAddr))); goto cleanup; } if ((S5IORecv(iio.fd, NULL, (char *)&li.peerVersion, 1, MSG_PEEK, PROXY_IOFLAGS, &timerm)) != 1) { S5LogUpdate(S5LogDefaultHandle, S5_LOG_WARNING, MSGID_SERVER_RECV_VERSION, "Proxy: Unable to determine version number: %m"); goto cleanup; } timerm = (double)ESTABLISH_TIMEOUT; switch (li.peerVersion) { case SOCKS5_VERSION: rval = HandleS5Connection(&li, &iio, auths, &timerm); break; case SOCKS4_VERSION: if (getenv("SOCKS5_V4SUPPORT")) { rval = HandleS4Connection(&li, &iio, auths, &timerm); break; } /* fall through... */ default: S5LogUpdate(S5LogDefaultHandle, S5_LOG_WARNING, MSGID_SERVER_BAD_VERSION, "Proxy: Received request with incompatible version number: %d", (int)li.peerVersion); S5IOSend(iio.fd, NULL, buf, sizeof(buf), 0, PROXY_IOFLAGS, &timerm); rval = EXIT_ERR; } if (rval < 0) { S5LogUpdate(S5LogDefaultHandle, S5_LOG_INFO, MSGID_SERVER_AUTH_FAILED, "Auth Failed: (%s:%d)", li.srcName, (int)ntohs(lsAddr2Port(&li.srcAddr))); goto cleanup; } S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "Proxy: vers:%d cmnd:%d addr:%s port:%d user:%s", (int)li.peerVersion, (int)li.peerCommand, ADDRANDPORT(&li.dstAddr), li.srcUser); /* If we haven't gotten authentication specific read and write functions */ /* we should set the client functions to be "normal", rather than timed */ /* Since in all likelyhood, it will take a long time to read in a whole */ /* buffers worth of data (in fact it may never happen)... */ switch (li.peerCommand) { case SOCKS_PING: case SOCKS_TRACER: rval = PTSetup (&iio, &li, &ci); break; case SOCKS_CONNECT: case SOCKS_BIND: rval = TcpSetup(&iio, &li, &ci); break; case SOCKS_UDP: rval = UdpSetup(&iio, &li, &ci); break; default: S5LogUpdate(S5LogDefaultHandle, S5_LOG_WARNING, MSGID_SERVER_BAD_COMMAND, "Proxy: Bad command number %d", (int)li.peerCommand); lsSendResponse(iio.fd, &iio, &li.dstAddr, li.peerVersion, (li.peerVersion == SOCKS5_VERSION)?SOCKS5_BADCMND:SOCKS_BAD_ID, 0, NULL); S5BufCleanContext(&iio); rval = -1; } if (rval < 0) { S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "Proxy: Command setup failed"); cleaned = 1; goto cleanup; } PacketPrintSetup(&li, &fi); inPacket.data = NULL; outPacket.data = NULL; inPacket.oob = 0; outPacket.oob = 0; for (action = S5_ACTION_READ, dir = S5_DIRECTION_ANY;; ) { switch (action) { case S5_ACTION_READ: inPacket.off = 0; case S5_ACTION_MORE_READ: /* Need more info... */ if ((rval = ci.recvpkt(&inPacket, &li, ci.option, &dir)) <= 0) { /* but we had an error... */ action = S5_ACTION_CLOSE; continue; } else { /* Update the byte count on the read not the write? */ if (dir == S5_DIRECTION_IN) { li.inbc += rval; } else { li.outbc += rval; } break; } case S5_ACTION_WRITE: /* Just write... */ if (ci.sendpkt(&outPacket, &li, ci.option, &dir) <= 0) { action = S5_ACTION_CLOSE; continue; } else { /* Go back to reading... */ dir = S5_DIRECTION_ANY; action = S5_ACTION_READ; continue; } case S5_ACTION_MORE_WRITE: if (ci.sendpkt(&outPacket, &li, ci.option, &dir) <= 0) { action = S5_ACTION_CLOSE; continue; } else { break; } case S5_ACTION_CLOSE: /* Any error messages that need to get printed must be done */ /* within ci.clean */ goto cleanup; } /* Only valid way of getting here is via S5_ACTION_READ */ if (fi.filter && !fi.filter(&inPacket, &outPacket, &li, fi.option, &dir, &action)) continue; action = S5_ACTION_WRITE; outPacket = inPacket; } cleanup: if (ci.option && ci.clean) { S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "Proxy: cleaning command context"); ci.clean(&li, ci.option); cleaned = 1; } if (!cleaned) S5BufCleanContext(&iio); if (fi.clean) { S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "Proxy: cleaning filter context"); fi.clean(fi.option); } S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "Proxy: done cleaning up"); return rval; }