proxy.c
上传用户:sddyfurun
上传日期:2007-01-04
资源大小:525k
文件大小:17k
源码类别:

代理服务器

开发平台:

Unix_Linux

  1. /* Copyright (c) 1995,1996,1997 NEC Corporation.  All rights reserved.       */
  2. /*                                                                           */
  3. /* The redistribution, use and modification in source or binary forms of     */
  4. /* this software is subject to the conditions set forth in the copyright     */
  5. /* document ("Copyright") included with this distribution.                   */
  6. /*
  7.  * $Id: proxy.c,v 1.94.2.2.2.5 1998/11/04 00:03:37 steve Exp $
  8.  */
  9. #include "socks5p.h"
  10. #include "daemon.h"
  11. #include "validate.h"
  12. #include "protocol.h"
  13. #include "msgids.h"
  14. #include "null.h"
  15. #include "info.h"
  16. #include "log.h"
  17. #include "msg.h"
  18. #include "upwd.h"
  19. #include "gss.h"
  20. #include "tcp.h"
  21. #include "udp.h"
  22. #include "tracer.h"
  23. #include "packet.h"
  24. #ifndef ESTABLISH_TIMEOUT
  25. #define ESTABLISH_TIMEOUT 10
  26. #endif
  27.     
  28. #ifndef START_TIMEOUT
  29. #define START_TIMEOUT 60
  30. #endif
  31. #ifdef MONITOR
  32. #include "monSocks.h"
  33. #define NAMEORNULL(x) ((*(x) == '')?NULL:(x))
  34. #define SINADDR(x)   (&(x)->sin)
  35. typedef S5MonHandle MONH_TYPE;
  36. #define MON_OPEN(x)            S5SocksMonOpen("Socks5 Monitor", S5_MON_MEM, (x))
  37. #define MON_CLOSE(x)           S5SocksMonClose((x))
  38. #define MON_START(x, y)        S5SocksMonPut((x), S5_LINK_START,  (y), 0, NULL, 0,   NULL)
  39. #define MON_UPDATE(x, y, i, o) S5SocksMonPut((x), S5_LINK_UPDATE, (y), 0, NULL, 0,   NULL)
  40. #define MON_ERROR(x, y, v, m)  S5SocksMonPut((x), S5_LINK_ERROR,  (y), 0, NULL, (v), (m))
  41. #define MON_END(x, y)          S5SocksMonPut((x), S5_LINK_END,    (y), 0, NULL, 0,   NULL)
  42. #endif
  43. #define PROXY_IOFLAGS S5_IOFLAGS_TIMED|S5_IOFLAGS_NBYTES|S5_IOFLAGS_RESTART
  44. static int HandleS4Connection(S5LinkInfo *pri, S5IOInfo *iio, list *auths, double *timerm) {
  45. #ifdef FORK_SOCKD
  46.     dup2(pri->in, 0);
  47.     dup2(pri->in, 1);
  48.     dup2(pri->in, 2);
  49.     close(pri->in);
  50.     
  51.     execlp("sockd", "sockd", NULL);
  52.     return EXIT_ERR;
  53. #else
  54.     char buf[256+256+8], *tmp, resp[] = { SOCKS4_VERSION, SOCKS_FAIL, (char)0xff, (char)0xff, (char)0xff, (char)0xff, (char)0xff, (char)0xff }; 
  55.     S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "Proxy: Acting as a Socks4 server");
  56.     if (auths) {
  57. for ( ;auths; auths = auths->next) {
  58.     if (auths->dataint == AUTH_NONE || auths->dataint == AUTH_PASSWD) break;
  59. }
  60. if (!auths) {
  61.     S5LogUpdate(S5LogDefaultHandle, S5_LOG_WARNING, MSGID_SERVER_AUTH_NONE, "Socks%d: No acceptable authentication method found", (int)pri->peerVersion);
  62.     if (S5IOSend(iio->fd, NULL, resp, sizeof(resp), 0, PROXY_IOFLAGS, timerm) != sizeof(resp)) return EXIT_ERR; 
  63.     return EXIT_AUTH;
  64. }
  65.     }
  66.     
  67.     if (S5IORecv(iio->fd, NULL, buf, 8, 0, PROXY_IOFLAGS, timerm) != 8) {
  68. S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(0), 0, "Socks4: Read failed: %m");
  69. return EXIT_ERR;
  70.     }
  71.     pri->peerAuth     = AUTH_NONE;
  72.     pri->peerVersion  = buf[SP_VERSION];
  73.     pri->peerCommand  = buf[SP_COMMAND];
  74.     if (lsGetProtoAddr(SOCKS4_VERSION, buf, &pri->dstAddr) < 0) {
  75. S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(0), 0, "Socks4: Invalid address passed from client");
  76. return EXIT_ERR;
  77.     }
  78.     for (tmp = buf, *tmp = ''; tmp < buf+sizeof(buf)-1; *++tmp = '') { 
  79. if (S5IORecv(iio->fd, iio, tmp, 1, 0, PROXY_IOFLAGS, timerm) != 1) {
  80.     S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(0), 0, "Socks4: Read failed: %m");
  81.     return EXIT_ERR;
  82. }
  83. if (*tmp == '') break; 
  84.     }
  85.     S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "Socks4: Read user: %s", buf);
  86.     strcpy(pri->srcUser, buf);
  87.     if (lsNullSrvAuth(iio->fd, &iio->auth, pri->srcUser) != AUTH_OK) {
  88. S5LogUpdate(S5LogDefaultHandle, S5_LOG_WARNING, MSGID_SERVER_AUTH_FAILED, "Socks%d: Authentication method %d failed", (int)pri->peerVersion, 0);
  89. resp[2] = SOCKS_BAD_ID;
  90. if (S5IOSend(iio->fd, NULL, resp, sizeof(resp), 0, PROXY_IOFLAGS, timerm) != sizeof(resp)) return EXIT_ERR; 
  91. return EXIT_AUTH;
  92.     }
  93.     /* If we haven't gotten authentication specific read and write functions */
  94.     /* we should set the client functions to be "normal", rather than timed  */
  95.     /* Since in all likelyhood, it will take a long time to read in a whole  */
  96.     /* buffers worth of data (in fact it may never happen)...                */
  97.     if (pri->peerCommand != SOCKS_CONNECT && pri->peerCommand != SOCKS_BIND) {
  98. S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(0), MSGID_SERVER_BAD_COMMAND, "Socks%d: Invalid command: %d", (int)pri->peerVersion, (int)pri->peerCommand);
  99. S5IOSend(iio->fd, NULL, resp, sizeof(resp), 0, PROXY_IOFLAGS, timerm); 
  100. return EXIT_ERR;
  101.     }
  102.     S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "Socks4: Done initialization");
  103.     return 0;
  104. #endif
  105. }
  106. static int AuthOK(u_char auth) {
  107.     switch (auth) {
  108. #ifdef HAVE_LIBGSSAPI_KRB5
  109. case AUTH_GSSAPI: 
  110. #endif
  111. case AUTH_PASSWD:   
  112. case AUTH_NONE:   return 1;
  113. default:          return 0;
  114.     }
  115. }
  116. static int HandleS5Connection(S5LinkInfo *pri, S5IOInfo *iio, list *auths, double *timerm) {
  117.     char fail[] = { 0x05, (char)0xff }, buf[256+2];
  118.     int ret, i;
  119.     list *tl;
  120.   
  121.     if (S5IORecv(iio->fd, iio, buf, 2, 0, PROXY_IOFLAGS, timerm) != 2) {
  122. S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(0), 0, "Socks5: Read failed: %m");
  123. return EXIT_ERR;
  124.     }
  125.     if (S5IORecv(iio->fd, iio, buf+2, (u_char)buf[1], 0, PROXY_IOFLAGS, timerm) != (u_char)buf[1]) {
  126. S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(0), 0, "Socks5: Read failed: %m");
  127. return EXIT_ERR;
  128.     }
  129.     pri->peerAuth = 0xff;
  130.     
  131.     if (!auths) {
  132. /* Anything is ok, pick the first one the client wanted to do...    */
  133. for (i = 0; i < ((int)(u_char)buf[1]); i++) if (AuthOK((u_char)buf[i+2])) break;
  134. if (i != ((int)(u_char)buf[1])) pri->peerAuth = (u_char)(buf[i+2]);
  135.     } else for (tl = auths; tl; tl = tl->next) {
  136. /* For each method (in order) in the server's config file, See if    */
  137. /* the client wanted to do this method...And, of course, make sure   */
  138. /* we can do it...                                                   */
  139. for (i = 0; i < ((int)(u_char)buf[1]); i++) if (tl->dataint == (int)(u_char)buf[i+2]) break;
  140. if (i == (int)(u_char)buf[1] || !AuthOK((u_char)buf[i+2])) continue;
  141. pri->peerAuth = (u_char)buf[i+2];
  142. break;
  143.     }
  144.     buf[1] = (u_char)pri->peerAuth;
  145.     if (S5IOSend(iio->fd, iio, buf, 2, 0, PROXY_IOFLAGS, timerm) != 2) {
  146. S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(0), 0, "Socks5: Write failed: %m");
  147. return EXIT_ERR;
  148.     }
  149.     if (pri->peerAuth == 0xff) {
  150. S5LogUpdate(S5LogDefaultHandle, S5_LOG_WARNING, MSGID_SERVER_AUTH_NONE, "Socks%d: No acceptable authentication method found", (int)pri->peerVersion);
  151. return EXIT_AUTH;
  152.     }
  153.     
  154.     S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "Socks5: Told client to do authentication method #%d", (int)pri->peerAuth);
  155.     switch (pri->peerAuth) {
  156. case AUTH_PASSWD: ret = lsPasswdSrvAuth(iio->fd, &iio->auth, pri->srcUser); break;
  157. case AUTH_GSSAPI: ret = lsGssapiSrvAuth(iio->fd, &iio->auth, pri->srcUser); break;
  158. case AUTH_NONE:   ret = lsNullSrvAuth  (iio->fd, &iio->auth, pri->srcUser); break;
  159. default:
  160.     S5LogUpdate(S5LogDefaultHandle, S5_LOG_WARNING, MSGID_SERVER_AUTH_BAD, "Socks%d: Bad Authentication method number: %d", (int)pri->peerVersion, (int)pri->peerAuth);
  161.     S5IOSend(iio->fd, iio, fail, sizeof(fail), 0, PROXY_IOFLAGS, timerm);
  162.     ret = AUTH_FAIL;
  163.     }
  164.     if (ret == AUTH_FAIL) {
  165. S5LogUpdate(S5LogDefaultHandle, S5_LOG_WARNING, MSGID_SERVER_AUTH_FAILED, "Socks%d: Authentication method %d failed", (int)pri->peerVersion, (int)pri->peerAuth);
  166. return EXIT_AUTH;
  167.     } 
  168.     if (lsReadRequest(iio->fd, iio, &pri->dstAddr, &pri->peerVersion, &pri->peerCommand, &pri->peerReserved) < 0) {
  169. S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(0), 0, "Socks5: Read request failed: %m");
  170. return EXIT_ERR;
  171.     }
  172.     return EXIT_OK;
  173. }
  174. /* HandlePxyConnection will be the routine for Socks5, which handles the     */
  175. /* initial part of the connection.  Specifically, we want to do several      */
  176. /* things here.  We want to check the version, if its not version 5, we can  */
  177. /* either exec the old sockd, or we can call a separate routine to handle    */
  178. /* the connection establishment.  (That routine should read the name, and    */
  179. /* possible make an ident query).  If the version is version 5, we have to   */
  180. /* decide what kind of authentication we are going to do.  This would mean   */
  181. /* reading the number of methods, and then the method numbers themselves,    */
  182. /* after that we pick the one we'd like to do, and send back a reply with    */
  183. /* my version (5), and the method number we picked, or 0xff if we couldn't   */
  184. /* find one that was acceptable given the information we already have (the   */
  185. /* source host.)  We probably want to drop the connection immediately if we  */
  186. /* know there is no acceptable auth from a host, so we don't waste fd's.     */
  187. /* after all this is done, we read the command, then finally do it.  Well,   */
  188. /* if we haven't already quit, that is.                                      */
  189. int HandlePxyConnection(S5IOHandle fd) {
  190.     char buf[] = { SOCKS5_VERSION, SOCKS5_FAIL, 0, SOCKS5_IPV4ADDR, 0, 0, 0, 0, 0, 0 };
  191.     int action, dir, rval = EXIT_ERR, len = sizeof(S5NetAddr), cleaned = 0;
  192.     double timerm = (double)START_TIMEOUT;
  193.     list *auths;
  194.     S5Packet inPacket, outPacket;
  195.     S5CommandInfo ci;
  196.     S5FilterInfo fi;
  197.     S5NetAddr route;
  198.     S5LinkInfo li;
  199.     S5IOInfo iio;
  200. #ifdef MONITOR
  201.     static MONH_TYPE smonHandle = NULL;
  202.     MONH_TYPE monHandle = NULL;
  203.     MONH_TYPE *mhp = NULL;
  204.     int monStarted = 0;
  205. #endif
  206.     memset(&li, 0, sizeof(S5LinkInfo));
  207.     memset(&fi, 0, sizeof(S5FilterInfo));
  208.     memset(&ci, 0, sizeof(S5CommandInfo));
  209.     S5BufSetupContext(&iio);
  210.     iio.fd = fd;
  211.     if (getpeername(iio.fd, (ss *)&li.srcAddr, &len) < 0) {
  212. S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(0), 0, "Proxy: getpeername failed: %m");
  213. goto cleanup;
  214.     }
  215.     len = sizeof(S5NetAddr);
  216.     if (getsockname(iio.fd, (ss *)&li.bndAddr, &len) < 0) {
  217. S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(0), 0, "Proxy: getsockname failed: %m");
  218. goto cleanup;
  219.     }
  220.     GetName(li.srcName, &li.srcAddr);
  221.     if (!GetRoute(&li.srcAddr, li.srcName, "tcp", &route)) {
  222.      /* If somehow we got a connection from somewhere on an interface which   */
  223.      /* we wouldn't use to get to that same place, this is bad (IP SPOOF?),   */
  224.      /* so we'll quit.                                                        */
  225.      if (route.sin.sin_addr.s_addr != INADDR_ANY &&
  226.     route.sin.sin_addr.s_addr != li.bndAddr.sin.sin_addr.s_addr) {
  227.     S5LogUpdate(S5LogDefaultHandle, S5_LOG_WARNING, MSGID_SERVER_WRONG_ROUTE, "Proxy: Received connection via wrong route");
  228.     S5LogUpdate(S5LogDefaultHandle, S5_LOG_INFO, MSGID_SERVER_AUTH_FAILED, "Auth Failed: (%s:%d)", li.srcName, (int)ntohs(lsAddr2Port(&li.srcAddr)));
  229.     goto cleanup;
  230.      }
  231.     }
  232.     /* If this host was "banned", quit...                                    */
  233.     if ((GetAuths(&li, &auths)) < 0) {
  234. S5LogUpdate(S5LogDefaultHandle, S5_LOG_WARNING, MSGID_SERVER_BANNED_HOST, "Proxy: Received connection from banned host");
  235. S5LogUpdate(S5LogDefaultHandle, S5_LOG_INFO, MSGID_SERVER_AUTH_FAILED, "Auth Failed: (%s:%d)", li.srcName, (int)ntohs(lsAddr2Port(&li.srcAddr)));
  236. goto cleanup;
  237.     }
  238.     
  239.     if ((S5IORecv(iio.fd, NULL, (char *)&li.peerVersion, 1, MSG_PEEK, PROXY_IOFLAGS, &timerm)) != 1) {
  240. S5LogUpdate(S5LogDefaultHandle, S5_LOG_WARNING, MSGID_SERVER_RECV_VERSION, "Proxy: Unable to determine version number: %m");
  241. goto cleanup;
  242.     }
  243.     timerm = (double)ESTABLISH_TIMEOUT;
  244.     switch (li.peerVersion) {
  245. case SOCKS4_VERSION:
  246.     rval = HandleS4Connection(&li, &iio, auths, &timerm);
  247.     break;
  248. case SOCKS5_VERSION:
  249.     rval = HandleS5Connection(&li, &iio, auths, &timerm);
  250.     break;
  251. default:
  252.     S5LogUpdate(S5LogDefaultHandle, S5_LOG_WARNING, MSGID_SERVER_BAD_VERSION, "Proxy: Received request with incompatible version number: %d", (int)li.peerVersion);
  253.     S5IOSend(iio.fd, NULL, buf, sizeof(buf), 0, PROXY_IOFLAGS, &timerm);
  254.     rval = EXIT_ERR;
  255.     }
  256. #ifdef MONITOR
  257.     if (servermode == PREFORKING) mhp = &smonHandle;
  258.     else                        mhp = &monHandle;
  259.     if (!*mhp) MON_OPEN(mhp);
  260.     if (*mhp) {
  261.         S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "Proxy: Starting monitor");
  262.         if (!MON_START(*mhp, &li)) {
  263.             monStarted = 1;
  264.             S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "Proxy: Monitor started");
  265.         }
  266.         else
  267.             S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "Proxy: Monitor not started
  268. ");
  269.     }
  270. #endif
  271.     if (rval < 0) {
  272. S5LogUpdate(S5LogDefaultHandle, S5_LOG_INFO, MSGID_SERVER_AUTH_FAILED, "Auth Failed: (%s:%d)", li.srcName, (int)ntohs(lsAddr2Port(&li.srcAddr)));
  273. #ifdef MONITOR
  274. if (!monStarted) goto cleanup;
  275. if (rval == EXIT_AUTH) monStarted += MON_ERROR(*mhp, &li, 1, "Authentication failed");
  276. else                   monStarted += MON_ERROR(*mhp, &li, 0, "Protocol Initialization failed");
  277. #endif
  278. goto cleanup;
  279.     }
  280.     S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "Proxy: vers:%d cmnd:%d addr:%s port:%d user:%s",
  281.     (int)li.peerVersion, (int)li.peerCommand, ADDRANDPORT(&li.dstAddr), li.srcUser);
  282.     /* If we haven't gotten authentication specific read and write functions */
  283.     /* we should set the client functions to be "normal", rather than timed  */
  284.     /* Since in all likelyhood, it will take a long time to read in a whole  */
  285.     /* buffers worth of data (in fact it may never happen)...                */
  286.     switch (li.peerCommand) {
  287.   case SOCKS_PING:    
  288.   case SOCKS_TRACER:  rval = PTSetup (&iio, &li, &ci);  break;
  289. case SOCKS_CONNECT: 
  290. case SOCKS_BIND:    rval = TcpSetup(&iio, &li, &ci); break;
  291. case SOCKS_UDP:     rval = UdpSetup(&iio, &li, &ci); break;
  292. default:
  293.     S5LogUpdate(S5LogDefaultHandle, S5_LOG_WARNING, MSGID_SERVER_BAD_COMMAND, "Proxy: Bad command number %d", (int)li.peerCommand);
  294.       lsSendResponse(iio.fd, &iio, &li.dstAddr, li.peerVersion, (li.peerVersion == SOCKS5_VERSION)?SOCKS5_BADCMND:SOCKS_BAD_ID, 0, NULL);
  295.          S5BufCleanContext(&iio);
  296.     rval = -1;
  297.     }
  298.     if (rval < 0) {
  299. S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "Proxy: Command setup failed");
  300. cleaned = 1;
  301. #ifdef MONITOR
  302. if (!monStarted) goto cleanup;
  303. if (rval == EXIT_AUTH) monStarted += MON_ERROR(*mhp, &li, 1, "Authorization failed");
  304. else                   monStarted += MON_ERROR(*mhp, &li, 0, "Command Setup failed");
  305. #endif
  306. goto cleanup;
  307.     }
  308.     PacketPrintSetup(&li, &fi);
  309.     inPacket.data  = NULL;
  310.     outPacket.data = NULL;
  311.     inPacket.oob  = 0;
  312.     outPacket.oob = 0;
  313.     for (action = S5_ACTION_READ, dir = S5_DIRECTION_ANY;; ) {
  314. switch (action) {
  315.     case S5_ACTION_READ:
  316. inPacket.off = 0;
  317.     case S5_ACTION_MORE_READ:
  318. /* Need more info...                                         */
  319. if ((rval = ci.recvpkt(&inPacket, &li, ci.option, &dir)) <= 0) {
  320.     /* but we had an error...                                */
  321.     action = S5_ACTION_CLOSE;
  322.     continue;
  323. } else {
  324.     /* Update the byte count on the read not the write?      */
  325.     if (dir == S5_DIRECTION_IN) {
  326. li.inbc += rval;
  327. #ifdef MONITOR
  328. if (monStarted) monStarted += MON_UPDATE(*mhp, &li, 0, rval);
  329. #endif
  330.     } else {
  331. li.outbc += rval;
  332. #ifdef MONITOR
  333. if (monStarted) monStarted += MON_UPDATE(*mhp, &li, rval, 0);
  334. #endif
  335.     }
  336.     break;
  337. }
  338.     case S5_ACTION_WRITE:
  339. /* Just write...                                             */
  340. if (ci.sendpkt(&outPacket, &li, ci.option, &dir) <= 0) {
  341.     action = S5_ACTION_CLOSE;
  342.     continue;
  343. } else {
  344.     /* Go back to reading...                                 */
  345.     dir    = S5_DIRECTION_ANY;
  346.     action = S5_ACTION_READ;
  347.     continue;
  348. }
  349.     case S5_ACTION_MORE_WRITE:
  350. if (ci.sendpkt(&outPacket, &li, ci.option, &dir) <= 0) {
  351.     action = S5_ACTION_CLOSE;
  352.     continue;
  353. } else {
  354.     break;
  355. }
  356.     case S5_ACTION_CLOSE:
  357. /* Any error messages that need to get printed must be done  */
  358. /* within ci.clean                                           */
  359. goto cleanup;
  360. }
  361. /* Only valid way of getting here is via S5_ACTION_READ              */
  362. if (fi.filter && !fi.filter(&inPacket, &outPacket, &li, fi.option, &dir, &action)) continue;
  363. action    = S5_ACTION_WRITE;
  364. outPacket = inPacket;
  365.     }
  366.   cleanup:
  367. #ifdef MONITOR
  368.     if (monStarted) {
  369.         S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "Proxy: sending monitor end message");
  370.         monStarted += MON_END(*mhp, &li);
  371.     }
  372.     if (*mhp && ((mhp != &smonHandle) || !monStarted)) {
  373.         S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "Proxy: closing monitor handle");
  374.         MON_CLOSE(*mhp);
  375.     }
  376. #endif
  377.     if (ci.option && ci.clean) {
  378. S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "Proxy: cleaning command context");
  379. ci.clean(&li, ci.option);
  380. cleaned = 1;
  381.     }
  382.     if (!cleaned) S5BufCleanContext(&iio);
  383.     if (fi.clean) {
  384. S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "Proxy: cleaning filter context");
  385. fi.clean(fi.option);
  386.     }
  387.     S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "Proxy: done cleaning up");
  388.     return rval;
  389. }