bitchx/source/network.c

869 lines
19 KiB
C

/*
* network.c -- handles stuff dealing with connecting and name resolving
*
* Written by Jeremy Nelson in 1995
* See the COPYRIGHT file or do /help ircii copyright
*/
#define SET_SOURCE_SOCKET
#include "irc.h"
static char cvsrevision[] = "$Id$";
CVS_REVISION(network_c)
#include "struct.h"
#include "ircaux.h"
#include "output.h"
#include "vars.h"
#include "struct.h"
#define MAIN_SOURCE
#include "modval.h"
#ifdef HAVE_SYS_UN_H
#include <sys/un.h>
#endif
#ifdef HAVE_SYS_FILIO_H
#include <sys/filio.h>
#endif
#ifdef PARANOID
/* NaiL^d0d: no hijack please, we need random bytes, in stdlib.h */
#include <stdlib.h>
#endif
extern char hostname[NAME_LEN+1];
#ifndef WTERM_C
extern int use_socks;
#else
#define use_socks 0
#endif
char *socks_user = NULL;
#if !defined(WTERM_C) && !defined(STERM_C)
/*
* Stuff pertaining to bouncing through socks proxies.
*
* written by Joshua J. Drake
* on Nov. 4, 1998
* last modified, nov. 14
*/
#define SOCKS_PORT 1080
#define SOCKS4_VERSION 4
#define SOCKS5_VERSION 5
/* auth types */
#define AUTH_NONE 0x00
#define AUTH_GSSAPI 0x01
#define AUTH_PASSWD 0x02
#define AUTH_CHAP 0x03
/* auth errors */
#define AUTH_OK 0
#define AUTH_FAIL -1
/* commands */
#define SOCKS_CONNECT 1
#define SOCKS_BIND 2
#define SOCKS_UDP 3
#define SOCKS_PING 0x80
#define SOCKS_TRACER 0x81
#define SOCKS_ANY 0xff
/* errors */
#define SOCKS5_NOERR 0x00
#define SOCKS5_RESULT 0x00
#define SOCKS5_FAIL 0x01
#define SOCKS5_AUTHORIZE 0x02
#define SOCKS5_NETUNREACH 0x03
#define SOCKS5_HOSTUNREACH 0x04
#define SOCKS5_CONNREF 0x05
#define SOCKS5_TTLEXP 0x06
#define SOCKS5_BADCMND 0x07
#define SOCKS5_BADADDR 0x08
/* flags */
#define SOCKS5_FLAG_NONAME 0x01
#define SOCKS5_FLAG_VERBOSE 0x02
#define SOCKS5_IPV4ADDR 0x01
#define SOCKS5_HOSTNAME 0x03
#define SOCKS5_IPV6ADDR 0x04
char *socks4_error(char cd)
{
switch (cd)
{
case 91:
return "rejected or failed";
break;
case 92:
return "no identd";
break;
case 93:
return "identd response != username";
break;
default:
return "Unknown error";
}
}
char *socks5_error(char cd)
{
switch (cd)
{
case SOCKS5_FAIL:
return "Rejected or failed";
break;
case SOCKS5_AUTHORIZE:
return "Connection not allowed by ruleset";
break;
case SOCKS5_NETUNREACH:
return "Network unreachable";
break;
case SOCKS5_HOSTUNREACH:
return "Host unreachable";
break;
case SOCKS5_CONNREF:
return "Connection refused";
break;
case SOCKS5_TTLEXP:
return "Time to live expired";
break;
case SOCKS5_BADCMND:
return "Bad command";
break;
case SOCKS5_BADADDR:
return "Bad address";
break;
default:
return "Unknown error";
}
}
/*
* try to negotiate a SOCKS4 connection.
*
*/
int socks4_connect(int s, int portnum, struct sockaddr_in *server)
{
struct _sock_connect {
char version;
char type;
unsigned short port;
unsigned long address;
char username[NAME_LEN+1];
} sock4_connect;
char socksreq[10];
char *p;
int red;
memset(&sock4_connect, 0, sizeof(sock4_connect));
sock4_connect.version = SOCKS4_VERSION;
sock4_connect.type = SOCKS_CONNECT;
sock4_connect.port = server->sin_port;
strncpy(sock4_connect.username, socks_user? socks_user: getenv("USER") ? getenv("USER") : username, NAME_LEN);
p = inet_ntoa(server->sin_addr);
sock4_connect.address = inet_addr(p);
if ((red = write(s, &sock4_connect, 8 + strlen(sock4_connect.username) + 1)) == -1)
{
bitchsay("Cannot write to socks proxy: %s", strerror(errno));
return 0;
}
alarm(get_int_var(CONNECT_TIMEOUT_VAR));
if ((red = read(s, socksreq, 8)) == -1)
{
alarm(0);
bitchsay("Cannot read from socks proxy: %s", strerror(errno));
return 0;
}
alarm(0);
if (socksreq[1] != 90)
{
bitchsay("Cannot connect to SOCKS4 proxy: %s", socks4_error(socksreq[1]));
return 0;
}
return 1;
}
/*
* try to negotiate a SOCKS5 connection. (with the socket/username, to the server)
*/
int socks5_connect(int s, int portnum, struct sockaddr_in *server)
{
struct _sock_connect {
char version;
char type;
char authtype;
char addrtype;
unsigned long address;
unsigned short port;
char username[NAME_LEN+1];
} sock5_connect;
char tmpbuf[25], *p;
struct in_addr tmpAddr;
unsigned short tmpI;
int red;
/* propose any authentication */
memset(&sock5_connect, 0, sizeof(sock5_connect));
sock5_connect.version = SOCKS5_VERSION;
sock5_connect.type = SOCKS_CONNECT;
sock5_connect.authtype = AUTH_NONE; /* AUTH_PASSWD, AUTH_GSSAPI, AUTH_CHAP */
if ((red = write(s, &sock5_connect, 4)) == -1)
{
bitchsay("Cannot write to proxy: %s", strerror(errno));
return 0;
}
memset(tmpbuf, 0, sizeof(tmpbuf));
alarm(get_int_var(CONNECT_TIMEOUT_VAR));
if ((red = read(s, tmpbuf, sizeof(tmpbuf)-1)) == -1)
{
alarm(0);
bitchsay("Cannot use SOCKS5 proxy, read failed during auth: %s", strerror(errno));
return 0;
}
alarm(0);
/* report server desired authentication (if not none) */
if (tmpbuf[1] != AUTH_NONE)
{
bitchsay("Cannot use SOCKS5 proxy, server wants type %x authentication.", tmpbuf[1]);
return 0;
}
/* try to bounce to target */
memset(&sock5_connect, 0, sizeof(sock5_connect));
sock5_connect.version = SOCKS5_VERSION;
sock5_connect.type = SOCKS_CONNECT;
sock5_connect.addrtype = SOCKS5_IPV4ADDR;
p = inet_ntoa(server->sin_addr);
sock5_connect.address = inet_addr(p);
sock5_connect.port = server->sin_port;
if ((red = write(s, &sock5_connect, 10)) == -1)
{
bitchsay("Cannot write to the proxy: %s", strerror(errno));
return 0;
}
memset(tmpbuf, 0, sizeof(tmpbuf)-1);
alarm(get_int_var(CONNECT_TIMEOUT_VAR));
if ((red = read(s, tmpbuf, sizeof(tmpbuf)-1)) == -1)
{
alarm(0);
bitchsay("Cannot use SOCKS5 proxy, read failed during bounce: %s. Attempting SOCKS4", strerror(errno));
return 0;
}
alarm(0);
if (tmpbuf[0] != SOCKS5_VERSION)
{
bitchsay("This is not a SOCKS5 proxy.");
return 0;
}
if (tmpbuf[1] != SOCKS5_NOERR)
{
bitchsay("Cannot use SOCKS5 proxy, server failed: %s", socks5_error(tmpbuf[1]));
return 0;
}
/*
* read the rest of the response (depending on what type of address they use)
*/
alarm(get_int_var(CONNECT_TIMEOUT_VAR));
switch (tmpbuf[3])
{
case 1:
read(s, tmpbuf, 4);
memcpy(&tmpAddr.s_addr, tmpbuf, 4);
read(s, tmpbuf, 2);
tmpbuf[3] = '\0';
tmpI = atoi(tmpbuf);
bitchsay("SOCKS5 bounce successful, your address will be: %s:%d", inet_ntoa(tmpAddr), ntohs(tmpI));
break;
case 3:
{
char buffer[256];
read(s, tmpbuf, 1);
tmpbuf[1] = '\0';
tmpI = atoi(tmpbuf);
read(s, tmpbuf, tmpI);
tmpbuf[tmpI] = '\0';
strncpy(buffer, tmpbuf, sizeof(buffer));
read(s, tmpbuf, 2);
tmpbuf[3] = '\0';
tmpI = atoi(tmpbuf);
bitchsay("SOCKS5 bounce successful, your address will be: %s:%d", buffer, ntohs(tmpI));
break;
}
case 4:
read(s, tmpbuf, 18);
/* don't report address of ipv6 addresses. */
bitchsay("SOCKS5 bounce successful. [ipv6]");
break;
default:
bitchsay("error tmpbuf[3]: %x", tmpbuf[3]);
alarm(0);
return 0;
}
alarm(0);
return 1;
}
int handle_socks(int fd, struct sockaddr_in addr, char *host, int portnum)
{
struct sockaddr_in proxy;
struct hostent *hp;
memset(&proxy, 0, sizeof(proxy));
if (!(hp = gethostbyname(host)))
{
bitchsay("Unable to resolve SOCKS proxy host address: %s", host);
return -1;
}
memcpy(&proxy.sin_addr, hp->h_addr, hp->h_length);
proxy.sin_family = AF_INET;
proxy.sin_port = htons(portnum);
alarm(get_int_var(CONNECT_TIMEOUT_VAR));
if (connect(fd, (struct sockaddr *)&proxy, sizeof(proxy)) < 0)
{
alarm(0);
bitchsay("Unable to connect to SOCKS5 proxy: %s", strerror(errno));
close(fd);
return -1;
}
alarm(0);
if (!socks5_connect(fd, portnum, &addr))
{
close(fd);
if ((fd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
{
bitchsay("Unable to get socket: %s", strerror(errno));
return -1;
}
alarm(get_int_var(CONNECT_TIMEOUT_VAR));
if (connect(fd, (struct sockaddr *)&proxy, sizeof(proxy)) < 0)
{
alarm(0);
bitchsay("Unable to connect to SOCKS4 proxy: %s", strerror(errno));
close(fd);
return -1;
}
alarm(0);
if (!socks4_connect(fd, portnum, &addr))
{
close(fd);
return -1;
}
}
return fd;
}
#endif
/*
* connect_by_number: Wheeeee. Yet another monster function i get to fix
* for the sake of it being inadequate for extension.
*
* we now take four arguments:
*
* - hostname - name of the host (pathname) to connect to (if applicable)
* - portnum - port number to connect to or listen on (0 if you don't care)
* - service - 0 - set up a listening socket
* 1 - set up a connecting socket
* - protocol - 0 - use the TCP protocol
* 1 - use the UDP protocol
*
*
* Returns:
* Non-negative number -- new file descriptor ready for use
* -1 -- could not open a new file descriptor or
* an illegal value for the protocol was specified
* -2 -- call to bind() failed
* -3 -- call to listen() failed.
* -4 -- call to connect() failed
* -5 -- call to getsockname() failed
* -6 -- the name of the host could not be resolved
* -7 -- illegal or unsupported request
* -8 -- no socks access
*
*
* Credit: I couldnt have put this together without the help of BSD4.4-lite
* User SupplimenTary Document #20 (Inter-process Communications tutorial)
*/
int BX_connect_by_number(char *hostn, unsigned short *portnum, int service, int protocol, int nonblocking)
{
int fd = -1;
int is_unix = (hostn && *hostn == '/');
int sock_type, proto_type;
#ifndef __OPENNT
sock_type = (is_unix) ? AF_UNIX : AF_INET;
#else
sock_type = AF_INET;
#endif
proto_type = (protocol == PROTOCOL_TCP) ? SOCK_STREAM : SOCK_DGRAM;
if ((fd = socket(sock_type, proto_type, 0)) < 0)
return -1;
set_socket_options (fd);
/* Unix domain server */
#ifdef HAVE_SYS_UN_H
if (is_unix)
{
struct sockaddr_un name;
memset(&name, 0, sizeof(struct sockaddr_un));
name.sun_family = AF_UNIX;
strcpy(name.sun_path, hostn);
#ifdef HAVE_SUN_LEN
# ifdef SUN_LEN
name.sun_len = SUN_LEN(&name);
# else
name.sun_len = strlen(hostn) + 1;
# endif
#endif
if (is_unix && (service == SERVICE_SERVER))
{
if (bind(fd, (struct sockaddr *)&name, strlen(name.sun_path) + sizeof(name.sun_family)))
return close(fd), -2;
if (protocol == PROTOCOL_TCP)
if (listen(fd, 4) < 0)
return close(fd), -3;
}
/* Unix domain client */
else if (service == SERVICE_CLIENT)
{
alarm(get_int_var(CONNECT_TIMEOUT_VAR));
if (connect (fd, (struct sockaddr *)&name, strlen(name.sun_path) + 2) < 0)
{
alarm(0);
return close(fd), -4;
}
alarm(0);
}
}
else
#endif
/* Inet domain server */
if (!is_unix && (service == SERVICE_SERVER))
{
socklen_t length;
#ifdef IP_PORTRANGE
int ports;
#endif
/* Even on an IPv6 client this opens up a IPv4 socket... for now.
* (Some OSes need two sockets to be able to accept both IPv4 and
* IPv6 connections). */
struct sockaddr_in name;
memset(&name, 0, sizeof name);
name.sin_family = AF_INET;
if (hostn)
inet_aton(hostn, &name.sin_addr);
else
name.sin_addr.s_addr = htonl(INADDR_ANY);
name.sin_port = htons(*portnum);
#ifdef PARANOID
name.sin_port += (unsigned short)(rand() & 255);
#endif
#ifdef IP_PORTRANGE
if (getenv("EPIC_USE_HIGHPORTS"))
{
ports = IP_PORTRANGE_HIGH;
setsockopt(fd, IPPROTO_IP, IP_PORTRANGE,
(char *)&ports, sizeof(ports));
}
#endif
if (bind(fd, (struct sockaddr *)&name, sizeof(name)))
return close(fd), -2;
length = sizeof (name);
if (getsockname(fd, (struct sockaddr *)&name, &length))
return close(fd), -5;
*portnum = ntohs(name.sin_port);
if (protocol == PROTOCOL_TCP)
if (listen(fd, 4) < 0)
return close(fd), -3;
#ifdef NON_BLOCKING_CONNECTS
if (nonblocking && set_non_blocking(fd) < 0)
return close(fd), -4;
#endif
}
/* Inet domain client */
else if (!is_unix && (service == SERVICE_CLIENT))
{
struct sockaddr_foobar server;
socklen_t server_len;
#ifdef WINNT
char buf[BIG_BUFFER_SIZE+1];
#endif
#ifdef IPV6
struct addrinfo hints = { 0 };
struct addrinfo *res_list, *res;
#else
struct hostent *hp;
struct sockaddr_in localaddr;
if (LocalHostName)
{
memset(&localaddr, 0, sizeof(struct sockaddr_in));
localaddr.sin_family = AF_INET;
localaddr.sin_addr = LocalHostAddr.sf_addr;
localaddr.sin_port = 0;
if (bind(fd, (struct sockaddr *)&localaddr, sizeof(localaddr)))
return close(fd), -2;
}
#endif
memset(&server, 0, sizeof server);
#ifndef WINNT
#ifdef IPV6
hints.ai_family = AF_UNSPEC;
hints.ai_flags = AI_ADDRCONFIG;
hints.ai_socktype = proto_type;
if (getaddrinfo(hostn, NULL, &hints, &res_list) != 0)
return close(fd), -6;
close(fd);
fd = -1;
for (res = res_list; res; res = res->ai_next)
{
fd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
if (fd >= 0)
break;
}
if (fd < 0)
{
freeaddrinfo(res_list);
return -1;
}
set_socket_options(fd);
memcpy(&server, res->ai_addr, res->ai_addrlen);
server_len = res->ai_addrlen;
server.sf_port = htons(*portnum);
memset(&hints, 0, sizeof hints);
hints.ai_family = res->ai_family;
freeaddrinfo(res_list);
if (LocalHostName && !getaddrinfo(LocalHostName, NULL, &hints, &res) && res)
{
int retval = bind(fd, (struct sockaddr *) res->ai_addr, res->ai_addrlen);
freeaddrinfo(res);
if (retval)
return close(fd), -2;
}
#else
if (isdigit((unsigned char)hostn[strlen(hostn)-1]))
inet_aton(hostn, (struct in_addr *)&server.sf_addr);
else
{
if (!(hp = gethostbyname(hostn)))
return close(fd), -6;
memcpy(&server.sf_addr, hp->h_addr, hp->h_length);
}
server.sf_family = AF_INET;
server.sf_port = htons(*portnum);
server_len = sizeof server.sins.sin;
#endif /* IPV6 */
#else
/* for some odd reason resolv() fails on NT... */
/* server = (*(struct sockaddr_in *) hostn);*/
if (!hostn)
{
gethostname(buf, sizeof(buf));
hostn = buf;
}
if ((server.sf_addr.s_addr = inet_addr(hostn)) == -1)
{
if ((hp = gethostbyname(hostn)) != NULL)
{
memset(&server, 0, sizeof(server));
memcpy(&server.sf_addr, hp->h_addr, hp->h_length);
server.sf_family = hp->h_addrtype;
}
else
return (-2);
}
else
server.sf_family = AF_INET;
server.sf_port = (unsigned short) htons(*portnum);
server_len = sizeof server.sins.sin;
#endif /* WINNT */
#ifdef NON_BLOCKING_CONNECTS
if (!use_socks && nonblocking && set_non_blocking(fd) < 0)
return close(fd), -4;
#endif
#if !defined(WTERM_C) && !defined(STERM_C) && !defined(IPV6)
if (use_socks && get_string_var(SOCKS_HOST_VAR))
{
fd = handle_socks(fd, *((struct sockaddr_in*) &server), get_string_var(SOCKS_HOST_VAR), get_int_var(SOCKS_PORT_VAR));
if (fd == -1)
return -4;
else
return fd;
}
#endif
alarm(get_int_var(CONNECT_TIMEOUT_VAR));
if (connect(fd, (struct sockaddr *)&server, server_len) < 0 && errno != EINPROGRESS)
{
alarm(0);
return close(fd), -4;
}
alarm(0);
}
/* error */
else
return close(fd), -7;
return fd;
}
int lame_resolv (const char *hostname, struct sockaddr_foobar *buffer)
{
#ifdef IPV6
struct addrinfo *res;
if (getaddrinfo(hostname, NULL, NULL, &res) || !res ||
res->ai_addrlen > sizeof *buffer)
return -1;
memmove(buffer, res->ai_addr, res->ai_addrlen);
freeaddrinfo(res);
return 0;
#else
struct hostent *hp;
if (!(hp = gethostbyname(hostname)))
return -1;
buffer->sf_family = AF_INET;
memmove(&buffer->sf_addr, hp->h_addr, hp->h_length);
return 0;
#endif
}
#ifdef IPV6
extern struct sockaddr_foobar *BX_lookup_host (const char *host)
{
static struct sockaddr_foobar sf;
struct addrinfo *res;
if (!getaddrinfo(host, NULL, NULL, &res) && res)
{
memcpy(&sf, res->ai_addr, sizeof(struct sockaddr_foobar));
freeaddrinfo(res);
return &sf;
}
return NULL;
}
extern char *BX_host_to_ip (const char *host)
{
struct addrinfo *res;
struct sockaddr_foobar *sf;
static char ip[128];
if (!getaddrinfo(host, NULL, NULL, &res) && res)
{
sf = (struct sockaddr_foobar*) res->ai_addr;
inet_ntop(sf->sf_family, (sf->sf_family == AF_INET) ?
(void*) &sf->sf_addr : (void*) &sf->sf_addr6, ip, 128);
freeaddrinfo(res);
return ip;
}
else
return empty_string;
}
extern char *BX_ip_to_host (const char *ip)
{
static char host[128];
struct addrinfo hints = { 0 };
struct addrinfo *res;
hints.ai_family = AF_UNSPEC;
hints.ai_flags = AI_NUMERICHOST;
if (getaddrinfo(ip, NULL, &hints, &res) == 0)
{
if (!res || getnameinfo(res->ai_addr, res->ai_addrlen, host, 128, NULL, 0, 0))
strlcpy(host, ip, sizeof host);
freeaddrinfo(res);
}
else
strlcpy(host, ip, sizeof host);
return host;
}
extern char *BX_one_to_another (const char *what)
{
if (isdigit(what[strlen(what)-1]) || strchr(what, ':'))
return ip_to_host (what);
else
return host_to_ip (what);
}
#else
extern struct sockaddr_foobar *BX_lookup_host (const char *host)
{
struct hostent *he;
static struct sockaddr_foobar sf;
alarm(1);
he = gethostbyname(host);
alarm(0);
if (he)
{
sf.sf_family = AF_INET;
memcpy(&sf.sf_addr, he->h_addr, sizeof(struct in_addr));
return &sf;
}
else
return NULL;
}
extern char *BX_host_to_ip (const char *host)
{
struct hostent *hep = gethostbyname(host);
static char ip[30];
return (hep ? snprintf(ip, sizeof ip, "%u.%u.%u.%u",
hep->h_addr[0] & 0xff,
hep->h_addr[1] & 0xff,
hep->h_addr[2] & 0xff,
hep->h_addr[3] & 0xff),
ip : empty_string);
}
extern char *BX_ip_to_host (const char *ip)
{
struct in_addr ia;
struct hostent *he;
static char host[101];
ia.s_addr = inet_addr(ip);
he = gethostbyaddr((char*) &ia, sizeof(struct in_addr), AF_INET);
return (he ? strncpy(host, he->h_name, 100): empty_string);
}
extern char *BX_one_to_another (const char *what)
{
if (!isdigit((unsigned char)what[strlen(what)-1]))
return host_to_ip (what);
else
return ip_to_host (what);
}
#endif
/*
* It is possible for a race condition to exist; such that select()
* indicates that a listen()ing socket is able to receive a new connection
* and that a later accept() call will still block because the connection
* has been closed in the interim. This wrapper for accept() attempts to
* defeat this by making the accept() call nonblocking.
*/
int my_accept (int s, struct sockaddr *addr, socklen_t *addrlen)
{
int retval;
set_non_blocking(s);
retval = accept(s, addr, addrlen);
set_blocking(s);
return retval;
}
int BX_set_non_blocking(int fd)
{
#ifdef NON_BLOCKING_CONNECTS
int res;
#if defined(NBLOCK_POSIX)
int nonb = 0;
nonb |= O_NONBLOCK;
#elif defined(NBLOCK_BSD)
int nonb = 0;
nonb |= O_NDELAY;
#elif defined(NBLOCK_SYSV)
res = 1;
if (ioctl (fd, FIONBIO, &res) < 0)
return -1;
#else
#error no idea how to set an fd to non-blocking
#endif
#if (defined(NBLOCK_POSIX) || defined(NBLOCK_BSD)) && !defined(NBLOCK_SYSV)
if ((res = fcntl(fd, F_GETFL, 0)) == -1)
return -1;
else if (fcntl(fd, F_SETFL, res | nonb) == -1)
return -1;
#endif
#endif
return 0;
}
int BX_set_blocking(int fd)
{
#ifdef NON_BLOCKING_CONNECTS
int res;
#if defined(NBLOCK_POSIX)
int nonb = 0;
nonb |= O_NONBLOCK;
#elif defined(NBLOCK_BSD)
int nonb = 0;
nonb |= O_NDELAY;
#elif defined(NBLOCK_SYSV)
res = 0;
if (ioctl (fd, FIONBIO, &res) < 0)
return -1;
#else
#error no idea how to return an fd blocking
#endif
#if (defined(NBLOCK_POSIX) || defined(NBLOCK_BSD)) && !defined(NBLOCK_SYSV)
if ((res = fcntl(fd, F_GETFL, 0)) == -1)
return -1;
else if (fcntl(fd, F_SETFL, res &~ nonb) == -1)
return -1;
#endif
#endif
return 0;
}