links/af_unix.c
2021-08-28 18:37:32 +03:00

312 lines
7 KiB
C

/* af_unix.c
* (c) 2002 Mikulas Patocka
* This file is a part of the Links program, released under GPL
*/
#include "links.h"
#ifndef USE_AF_UNIX
int s_unix_fd = -1;
int bind_to_af_unix(unsigned char *name)
{
return -1;
}
void af_unix_close(void)
{
}
#else
#ifdef OS2
#ifndef HAVE_SYS_UN_H
#define HAVE_SYS_UN_H
#endif
struct os2_sockaddr_un {
u_short sun_family;
char sun_path[108];
};
#define sockaddr_un os2_sockaddr_un
#ifndef AF_UNIX
#define AF_UNIX 1
#endif
#ifndef PF_UNIX
#define PF_UNIX 1
#endif
#elif defined(HAVE_SYS_UN_H)
#include <sys/un.h>
#endif
#if defined(__GNU__)
#define SOCKET_TIMEOUT_HACK
#endif
static void af_unix_connection(void *);
#define ADDR_SIZE 4096
union address {
struct sockaddr s;
#ifdef HAVE_SYS_UN_H
struct sockaddr_un suni;
#endif
struct sockaddr_in sin;
unsigned char buffer[ADDR_SIZE];
};
static union address s_unix;
static union address s_unix_acc;
static socklen_t s_unix_l;
int s_unix_fd = -1;
static int s_unix_master = 0;
#define S2C1_HANDSHAKE_LENGTH 6
#define C2S2_HANDSHAKE_LENGTH sizeof(struct links_handshake)
#define S2C3_HANDSHAKE_LENGTH sizeof(struct links_handshake)
static struct links_handshake {
unsigned char version[30];
unsigned char system_name[32];
unsigned char system_id;
unsigned char sizeof_long;
} links_handshake;
#define HANDSHAKE_WRITE(hndl, sz) \
if ((r = hard_write(hndl, (unsigned char *)&links_handshake, sz)) != (sz))
#define HANDSHAKE_READ(hndl, sz) \
if ((r = hard_read(hndl, (unsigned char *)&received_handshake, sz)) != (sz) || memcmp(&received_handshake, &links_handshake, sz))
#ifdef OS2
static inline int have_pf_unix(void)
{
s_unix_fd = c_socket(PF_UNIX, SOCK_STREAM, 0);
return s_unix_fd >= 0;
}
#endif
#if !defined(HAVE_SYS_UN_H) || defined(OS2)
static inline int get_address_localhost(unsigned char *name)
{
unsigned short port;
if (!name) {
port = LINKS_PORT;
} else if (!strcmp(cast_const_char name, "pmshell")) {
port = LINKS_PORT + 2;
} else {
port = LINKS_PORT;
while (*name) {
port = 257 * port + (*name * 2);
name++;
}
port %= LINKS_G_PORT_LEN;
port += LINKS_G_PORT_START;
port &= ~1;
}
if (anonymous) {
port++;
}
memset(&s_unix, 0, sizeof s_unix);
s_unix.sin.sin_family = AF_INET;
s_unix.sin.sin_port = htons(port);
s_unix.sin.sin_addr.s_addr = htonl(0x7f000001);
s_unix_l = sizeof(struct sockaddr_in);
/*debug("get address %d", port);*/
return PF_INET;
}
#endif
static int get_address(unsigned char *name)
{
#ifdef HAVE_SYS_UN_H
unsigned char *path;
#ifdef OS2
if (!have_pf_unix()) {
return get_address_localhost(name);
}
path = stracpy(cast_uchar "\\socket\\links\\");
#else
if (!links_home) return -1;
path = stracpy(links_home);
#endif
add_to_strn(&path, cast_uchar LINKS_SOCK_NAME);
if (anonymous) {
add_to_strn(&path, cast_uchar LINKS_ANONYMOUS_SOCK_SUFFIX);
}
if (name) {
unsigned char *n = stracpy(name);
check_filename(&n);
add_to_strn(&path, cast_uchar "-");
add_to_strn(&path, n);
mem_free(n);
}
#ifdef OS2
s_unix_l = sizeof(struct sockaddr_un);
#else
s_unix_l = (socklen_t)((unsigned char *)&s_unix.suni.sun_path - (unsigned char *)&s_unix.suni + strlen(cast_const_char path) + 1);
#endif
if (strlen(cast_const_char path) > sizeof(union address) || (size_t)s_unix_l > sizeof(union address)) {
mem_free(path);
return -1;
}
memset(&s_unix, 0, sizeof s_unix);
s_unix.suni.sun_family = AF_UNIX;
strcpy(cast_char s_unix.suni.sun_path, cast_const_char path);
mem_free(path);
return PF_UNIX;
#else
return get_address_localhost(name);
#endif
}
static void unlink_unix(void)
{
#if defined(HAVE_SYS_UN_H) && !defined(OS2)
if (s_unix.s.sa_family == AF_UNIX) {
int rs;
/*debug("unlink: %s", s_unix.suni.sun_path);*/
EINTRLOOP(rs, unlink(s_unix.suni.sun_path));
if (rs) {
/*perror("unlink");*/
}
}
#endif
}
int bind_to_af_unix(unsigned char *name)
{
int u = 0;
int a1 = 1;
int cnt = 0;
int af;
int r;
int rs;
struct links_handshake received_handshake;
memset(&links_handshake, 0, sizeof links_handshake);
safe_strncpy(links_handshake.version, cast_uchar("Links " VERSION_STRING), sizeof links_handshake.version);
safe_strncpy(links_handshake.system_name, system_name, sizeof links_handshake.system_name);
links_handshake.system_id = SYSTEM_ID;
links_handshake.sizeof_long = sizeof(long);
if ((af = get_address(name)) == -1) {
close_socket(&s_unix_fd);
return -1;
}
again:
if (s_unix_fd == -1) {
s_unix_fd = c_socket(af, SOCK_STREAM, 0);
if (s_unix_fd == -1) return -1;
}
#if defined(SOL_SOCKET) && defined(SO_REUSEADDR)
EINTRLOOP(rs, setsockopt(s_unix_fd, SOL_SOCKET, SO_REUSEADDR, (void *)&a1, sizeof a1));
#endif
EINTRLOOP(rs, bind(s_unix_fd, &s_unix.s, s_unix_l));
if (rs) {
/*debug("bind: %d, %s", errno, strerror(errno));*/
if (af == PF_INET && errno == EADDRNOTAVAIL) {
/* do not try to connect if the user has not configured loopback interface */
EINTRLOOP(rs, close(s_unix_fd));
return -1;
}
EINTRLOOP(rs, close(s_unix_fd));
s_unix_fd = c_socket(af, SOCK_STREAM, 0);
if (s_unix_fd == -1) return -1;
#if defined(SOL_SOCKET) && defined(SO_REUSEADDR)
EINTRLOOP(rs, setsockopt(s_unix_fd, SOL_SOCKET, SO_REUSEADDR, (void *)&a1, sizeof a1));
#endif
EINTRLOOP(rs, connect(s_unix_fd, &s_unix.s, s_unix_l));
if (rs) {
retry:
/*debug("connect: %d, %s", errno, strerror(errno));*/
if (++cnt < MAX_BIND_TRIES) {
portable_sleep(100);
EINTRLOOP(rs, close(s_unix_fd));
s_unix_fd = -1;
goto again;
}
#ifdef SOCKET_TIMEOUT_HACK
retry_unlink:
#endif
EINTRLOOP(rs, close(s_unix_fd));
s_unix_fd = -1;
if (!u) {
unlink_unix();
u = 1;
goto again;
}
return -1;
}
#ifdef SOCKET_TIMEOUT_HACK
if (!can_read_timeout(s_unix_fd, AF_UNIX_SOCKET_TIMEOUT))
goto retry_unlink;
#endif
HANDSHAKE_READ(s_unix_fd, S2C1_HANDSHAKE_LENGTH) {
if (r != S2C1_HANDSHAKE_LENGTH) goto retry;
goto close_and_fail;
}
HANDSHAKE_WRITE(s_unix_fd, C2S2_HANDSHAKE_LENGTH)
goto close_and_fail;
HANDSHAKE_READ(s_unix_fd, S2C3_HANDSHAKE_LENGTH)
goto close_and_fail;
return s_unix_fd;
}
EINTRLOOP(rs, listen(s_unix_fd, 100));
if (rs) {
error("ERROR: listen failed: %d", errno);
close_and_fail:
EINTRLOOP(rs, close(s_unix_fd));
s_unix_fd = -1;
return -1;
}
s_unix_master = 1;
set_handlers(s_unix_fd, af_unix_connection, NULL, NULL);
return -1;
}
static void af_unix_connection(void *xxx)
{
socklen_t l = s_unix_l;
int ns;
int r;
int rs;
struct links_handshake received_handshake;
memset(&s_unix_acc, 0, sizeof s_unix_acc);
ns = c_accept(s_unix_fd, &s_unix_acc.s, &l);
if (ns == -1) return;
HANDSHAKE_WRITE(ns, S2C1_HANDSHAKE_LENGTH) {
EINTRLOOP(rs, close(ns));
return;
}
HANDSHAKE_READ(ns, C2S2_HANDSHAKE_LENGTH) {
portable_sleep(100); /* workaround for a race in previous Links version */
EINTRLOOP(rs, close(ns));
return;
}
HANDSHAKE_WRITE(ns, S2C3_HANDSHAKE_LENGTH) {
EINTRLOOP(rs, close(ns));
return;
}
if (!F) {
init_term(ns, ns, win_func);
set_highpri();
}
#ifdef G
else {
gfx_connection(ns);
}
#endif
}
void af_unix_close(void)
{
close_socket(&s_unix_fd);
if (s_unix_master) {
unlink_unix();
s_unix_master = 0;
}
}
#endif