mirror of
https://github.com/3proxy/3proxy.git
synced 2026-11-05 09:25:49 +08:00
Names are resolved with getaddrinfo(), thread-safe everywhere: what was left of gethostbyname() (my_gethostbyname() over gethostbyname_r(), in its glibc and Solaris forms, GETHOSTBYNAME_R, the mutex for the plain one) served only the IPv4-only resolver already gone. Makefile.Solaris (Sun's compiler, _SOLARIS) and Makefile.unix go: CMake builds for the other systems; the documents say so, and their examples use Makefile.FreeBSD. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1088 lines
33 KiB
C
1088 lines
33 KiB
C
/*
|
|
3APA3A simplest proxy server
|
|
(c) 2002-2026 by Vladimir Dubrovin <vlad@3proxy.org>
|
|
|
|
please read License Agreement
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include "proxy.h"
|
|
/* returns 0 on success, 1 if atyp is not supported for family, 2 if the
|
|
address is null. The address is set in both the 0 and the 2 case. */
|
|
int socks5_setaddr(int family, int atyp, const unsigned char *addr, PROXYSOCKADDRTYPE *sa){
|
|
int i, len = 4;
|
|
|
|
memset(sa, 0, sizeof(*sa));
|
|
if(atyp == 4){
|
|
if(family == 4) return 1;
|
|
len = 16;
|
|
*SAFAMILY(sa) = AF_INET6;
|
|
memcpy(SAADDR(sa), addr, 16);
|
|
}
|
|
else if(family == 6){
|
|
unsigned char prefix[12] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255};
|
|
|
|
*SAFAMILY(sa) = AF_INET6;
|
|
memcpy(SAADDR(sa), prefix, 12);
|
|
memcpy(12 + (unsigned char *)SAADDR(sa), addr, 4);
|
|
}
|
|
else {
|
|
*SAFAMILY(sa) = AF_INET;
|
|
memcpy(SAADDR(sa), addr, 4);
|
|
}
|
|
for(i = 0; i < len && !addr[i]; i++);
|
|
return (i == len)? 2 : 0;
|
|
}
|
|
|
|
#ifdef __linux__
|
|
#include <sched.h>
|
|
|
|
/* Threads stay in the network namespace the process started in. A socket
|
|
belongs to the namespace it is made in, whatever thread uses it later,
|
|
so a service's other namespaces are entered for socket() alone. What a
|
|
thread does without making a socket of its own, a name looked up through
|
|
the system, a log line, RADIUS, happens in the main namespace. */
|
|
int mainnsfd = -1;
|
|
/* set in a thread left outside the main namespace, which is not reused */
|
|
static __thread int nsfailed;
|
|
|
|
int nsbroken(void){
|
|
return nsfailed;
|
|
}
|
|
|
|
/* Opens the main namespace, once and while /proc can still be reached: a
|
|
chroot may take it away. */
|
|
int nsopen(void){
|
|
if(mainnsfd < 0) mainnsfd = open("/proc/self/ns/net", O_RDONLY | O_CLOEXEC);
|
|
return mainnsfd < 0;
|
|
}
|
|
|
|
SOCKET nssocket(struct srvparam *srv, struct sockfuncs *sf, void *state, int side, int domain, int type, int protocol){
|
|
int nsfd = (side == NS_OUT)? srv->o_nsfd : srv->i_nsfd;
|
|
SOCKET s;
|
|
int err;
|
|
|
|
if(nsfd < 0) return sf->_socket(state, domain, type, protocol);
|
|
if(setns(nsfd, CLONE_NEWNET)) return INVALID_SOCKET;
|
|
s = sf->_socket(state, domain, type, protocol);
|
|
err = errno;
|
|
if(setns(mainnsfd, CLONE_NEWNET)){
|
|
/* Left in the other namespace: no socket from here, and the
|
|
connection fails. The thread ends with it. */
|
|
if(s != INVALID_SOCKET) sf->_closesocket(state, s);
|
|
nsfailed = 1;
|
|
return INVALID_SOCKET;
|
|
}
|
|
errno = err;
|
|
return s;
|
|
}
|
|
#else
|
|
/* no network namespaces: a socket is made where it always is */
|
|
SOCKET nssocket(struct srvparam *srv, struct sockfuncs *sf, void *state, int side, int domain, int type, int protocol){
|
|
return sf->_socket(state, domain, type, protocol);
|
|
}
|
|
#endif
|
|
|
|
|
|
char * copyright = COPYRIGHT;
|
|
|
|
#if defined(__APPLE__) || defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__) || defined(__DragonFly__)
|
|
# define MYRAND_ARC4RANDOM 1
|
|
#elif defined(__GLIBC__) && ((__GLIBC__ > 2) || ((__GLIBC__ == 2) && (__GLIBC_MINOR__ >= 36)))
|
|
# define MYRAND_ARC4RANDOM 1
|
|
#elif defined(__GLIBC__) && ((__GLIBC__ < 2) || ((__GLIBC__ == 2) && (__GLIBC_MINOR__ < 25)))
|
|
# define MYRAND_FALLBACK 1
|
|
#elif defined(__linux__)
|
|
# define MYRAND_GETRANDOM 1
|
|
# include <sys/random.h>
|
|
#elif defined(_WIN32)
|
|
# define MYRAND_BCRYPTGENRANDOM 1
|
|
# include <bcrypt.h>
|
|
# pragma comment(lib, "bcrypt.lib")
|
|
#else
|
|
# define MYRAND_FALLBACK 1
|
|
#endif
|
|
|
|
|
|
|
|
#ifndef _WIN32
|
|
pthread_attr_t pa;
|
|
|
|
|
|
void daemonize(void){
|
|
pid_t pid = fork();
|
|
if(pid > 0) {
|
|
usleep(SLEEPTIME);
|
|
_exit(0);
|
|
}
|
|
if(pid < 0) {
|
|
perror("fork()");
|
|
return;
|
|
}
|
|
setsid();
|
|
}
|
|
|
|
#endif
|
|
|
|
unsigned char **stringtable = NULL;
|
|
|
|
#ifdef WITH_UN
|
|
void make_un(const unsigned char *path, struct sockaddr_un * sun){
|
|
memset(sun, 0, sizeof(*sun));
|
|
sun->sun_family = AF_UNIX;
|
|
strncpy(sun->sun_path, (char *)path, sizeof(sun->sun_path) - 1);
|
|
if(*path == '@')*sun->sun_path = 0;
|
|
}
|
|
#endif
|
|
|
|
|
|
int myinet_ntop(int af, void *src, char *dst, socklen_t size){
|
|
#ifdef WITH_UN
|
|
if(af == AF_UNIX){
|
|
struct sockaddr_un *sun = (struct sockaddr_un *)src;
|
|
int ephemeral = 0;
|
|
char *path = sun->sun_path;
|
|
char *basename;
|
|
if(!path[0] && path[1]){
|
|
ephemeral = 1;
|
|
*dst++ = '@';
|
|
path++;
|
|
}
|
|
basename = strrchr(path, '/');
|
|
if(basename) basename++;
|
|
else basename = path;
|
|
if(size > 0){
|
|
strncpy(dst, basename, (size > 40) ? 40 : size - (ephemeral + 1));
|
|
dst[((size > 40) ? 40 : size - (ephemeral + 1))] = 0;
|
|
}
|
|
return (int)strlen(dst);
|
|
}
|
|
#endif
|
|
if(af != AF_INET6){
|
|
unsigned u = ntohl(((struct in_addr *)src)->s_addr);
|
|
return sprintf(dst, "%u.%u.%u.%u",
|
|
((u&0xFF000000)>>24),
|
|
((u&0x00FF0000)>>16),
|
|
((u&0x0000FF00)>>8),
|
|
((u&0x000000FF)));
|
|
}
|
|
*dst = 0;
|
|
inet_ntop(af, src, dst, size);
|
|
return (int)strlen(dst);
|
|
}
|
|
|
|
char *rotations[] = {
|
|
"",
|
|
"/min",
|
|
"/hour",
|
|
"/day",
|
|
"/week",
|
|
"/month",
|
|
"/year",
|
|
"",
|
|
};
|
|
|
|
|
|
/* State of the process, not configuration. */
|
|
time_t curtime;
|
|
int timetoexit = 0, needreload = 0;
|
|
|
|
|
|
#ifndef _WIN32
|
|
/* PTHREAD_STACK_MIN is 128K on glibc/aarch64 and glibc/powerpc and may be
|
|
a sysconf() call with _GNU_SOURCE. pthread_attr_setstacksize() fails with
|
|
EINVAL below it and the thread silently gets the 8M system default stack.
|
|
*/
|
|
size_t threadstacksize(int extra){
|
|
long size = BASESTACKSIZE + TLSSTACKSIZE + extra;
|
|
|
|
if(size < (long)PTHREAD_STACK_MIN) size = (long)PTHREAD_STACK_MIN;
|
|
return (size_t)size;
|
|
}
|
|
#endif
|
|
|
|
struct threadpool tpool;
|
|
|
|
/* A configuration starts as confdefaults; conf is the current one, at first
|
|
confboot, which exists before main() runs. */
|
|
const struct extparam confdefaults = {
|
|
#include "confinit.h"
|
|
};
|
|
struct extparam confboot = {
|
|
#include "confinit.h"
|
|
};
|
|
/* conf is the configuration commands act on: the one being read during a
|
|
reload, the one in force otherwise. runconf is the one in force, for
|
|
whatever runs meanwhile without a service of its own. */
|
|
struct extparam *conf = &confboot;
|
|
struct extparam *runconf = &confboot;
|
|
int (*reattachfunc)(struct clientparam *param, int auth) = NULL;
|
|
void (*listenertrafficfunc)(struct clientparam *param) = NULL;
|
|
|
|
/* Authorizes a request on a connection which may outlive a reload, an HTTP
|
|
one kept alive: as a client of the service whose rules apply now, the
|
|
newest one on its listener after a reload (unless noforce). */
|
|
int clientauth(struct clientparam *param){
|
|
struct srvparam *orig;
|
|
int res;
|
|
|
|
if(param->version < runconf->version){
|
|
if(param->srv->noforce || !reattachfunc) param->version = runconf->version;
|
|
else if((res = (*reattachfunc)(param, 0))) return res;
|
|
}
|
|
if(!param->newsrv) return (*param->srv->authfunc)(param);
|
|
orig = param->srv;
|
|
param->srv = param->newsrv;
|
|
res = (*param->srv->authfunc)(param);
|
|
param->srv = orig;
|
|
return res;
|
|
}
|
|
_3proxy_mutex_t confref_mutex;
|
|
|
|
|
|
char* NULLADDR="\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0";
|
|
|
|
uint32_t myrand(void){
|
|
#if defined(MYRAND_ARC4RANDOM)
|
|
return arc4random();
|
|
#elif defined(MYRAND_GETRANDOM)
|
|
uint32_t r = 0;
|
|
ssize_t n;
|
|
do {
|
|
n = getrandom(&r, sizeof(r), 0);
|
|
} while(n < 0 && errno == EINTR);
|
|
return r;
|
|
#elif defined(MYRAND_BCRYPTGENRANDOM)
|
|
uint32_t r = 0;
|
|
BCryptGenRandom(NULL, (PUCHAR)&r, sizeof(r), BCRYPT_USE_SYSTEM_PREFERRED_RNG);
|
|
return r;
|
|
#elif defined(MYRAND_CRYPTGENRANDOM)
|
|
uint32_t r = 0;
|
|
HCRYPTPROV prov;
|
|
if(CryptAcquireContext(&prov, NULL, NULL, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT|CRYPT_SILENT)){
|
|
CryptGenRandom(prov, sizeof(r), (BYTE *)&r);
|
|
CryptReleaseContext(prov, 0);
|
|
}
|
|
return r;
|
|
#else
|
|
return ((uint32_t)rand() << 16) ^ (uint32_t)rand();
|
|
#endif
|
|
}
|
|
|
|
|
|
|
|
/* Set while clients are served by fibers on event loops, which keep their
|
|
sockets registered and remember what each was found ready for: an
|
|
operation finding a socket not ready, and a socket about to be closed,
|
|
are told to its loop. errno is left as it was. */
|
|
void (*fiberblockfunc)(SOCKET s, short events) = NULL;
|
|
void (*fiberclosefunc)(SOCKET s) = NULL;
|
|
/* set as well: whether this thread is a loop's, which is not to wait */
|
|
int (*onloopfunc)(void) = NULL;
|
|
/* and what runs func(arg) on the thread's own stack, not a fiber's */
|
|
void (*onthreadstackfunc)(void (*func)(void *arg), void *arg) = NULL;
|
|
#define BLOCKED(s, ev) do { if(fiberblockfunc && (errno == EAGAIN || errno == EWOULDBLOCK)) \
|
|
fiberblockfunc((s), (ev)); } while(0)
|
|
|
|
#ifdef _WIN32
|
|
SOCKET WINAPI def_socket(void* state, int domain, int type, int protocol){
|
|
return socket(domain, type, protocol);
|
|
}
|
|
SOCKET WINAPI def_accept(void* state, SOCKET s, struct sockaddr * addr, int * addrlen){
|
|
SOCKET r = accept(s, addr, addrlen);
|
|
|
|
if(r == INVALID_SOCKET) BLOCKED(s, POLLIN);
|
|
return r;
|
|
}
|
|
int WINAPI def_bind(void* state, SOCKET s, const struct sockaddr *addr, int addrlen){
|
|
return bind(s, addr, addrlen);
|
|
}
|
|
int WINAPI def_listen(void* state, SOCKET s, int backlog){
|
|
return listen(s, backlog);
|
|
}
|
|
int WINAPI def_connect(void* state, SOCKET s, const struct sockaddr *name, int namelen){
|
|
int r = connect(s, name, namelen);
|
|
|
|
if(r) BLOCKED(s, POLLOUT);
|
|
return r;
|
|
}
|
|
int WINAPI def_getpeername(void* state, SOCKET s, struct sockaddr * name, int * namelen){
|
|
return getpeername(s, name, namelen);
|
|
}
|
|
int WINAPI def_getsockname(void* state, SOCKET s, struct sockaddr * name, int * namelen){
|
|
return getsockname(s, name, namelen);
|
|
}
|
|
int WINAPI def_getsockopt(void* state, SOCKET s, int level, int optname, char * optval, int * optlen){
|
|
return getsockopt(s, level, optname, optval, optlen);
|
|
}
|
|
int WINAPI def_setsockopt(void* state, SOCKET s, int level, int optname, const char *optval, int optlen){
|
|
return setsockopt(s, level, optname, optval, optlen);
|
|
}
|
|
int WINAPI def_prepoll(void* state, struct pollfd *fds, unsigned int nfds){
|
|
return 0;
|
|
}
|
|
int WINAPI def_send(void* state, SOCKET s, const char *msg, int len, int flags){
|
|
int r = send(s, msg, len, flags);
|
|
|
|
if(r < 0) BLOCKED(s, POLLOUT);
|
|
return r;
|
|
}
|
|
int WINAPI def_sendto(void* state, SOCKET s, const char *msg, int len, int flags, const struct sockaddr *to, int tolen){
|
|
int r = sendto(s, msg, len, flags, to, tolen);
|
|
|
|
if(r < 0) BLOCKED(s, POLLOUT);
|
|
return r;
|
|
}
|
|
|
|
int WINAPI def_recv(void* state, SOCKET s, char *buf, int len, int flags){
|
|
int r = recv(s, buf, len, flags);
|
|
|
|
if(r < 0) BLOCKED(s, POLLIN);
|
|
return r;
|
|
}
|
|
int WINAPI def_recvfrom(void* state, SOCKET s, char * buf, int len, int flags, struct sockaddr * from, int * fromlen){
|
|
int r = recvfrom(s, buf, len, flags, from, fromlen);
|
|
|
|
if(r < 0) BLOCKED(s, POLLIN);
|
|
return r;
|
|
}
|
|
int WINAPI def_shutdown(void* state, SOCKET s, int how){
|
|
return shutdown(s, how);
|
|
}
|
|
int WINAPI def_closesocket(void* state, SOCKET s){
|
|
if(fiberclosefunc) fiberclosefunc(s);
|
|
return closesocket(s);
|
|
}
|
|
#else
|
|
SOCKET def_socket(void* state, int domain, int type, int protocol){
|
|
return socket(domain, type, protocol);
|
|
}
|
|
SOCKET def_accept(void* state, SOCKET s, struct sockaddr * addr, socklen_t* addrlen){
|
|
SOCKET r = accept(s, addr, addrlen);
|
|
|
|
if(r == INVALID_SOCKET) BLOCKED(s, POLLIN);
|
|
return r;
|
|
}
|
|
int def_bind(void* state, SOCKET s, const struct sockaddr *addr, socklen_t addrlen){
|
|
return bind(s, addr, addrlen);
|
|
}
|
|
int def_getpeername(void* state, SOCKET s, struct sockaddr * name, socklen_t* namelen){
|
|
return getpeername(s, name, namelen);
|
|
}
|
|
int def_getsockname(void* state, SOCKET s, struct sockaddr * name, socklen_t* namelen){
|
|
return getsockname(s, name, namelen);
|
|
}
|
|
int def_listen(void* state, SOCKET s, int backlog){
|
|
return listen(s, backlog);
|
|
}
|
|
int def_connect(void* state, SOCKET s, const struct sockaddr *name, socklen_t namelen){
|
|
int r = connect(s, name, namelen);
|
|
|
|
if(r && errno == EINPROGRESS && fiberblockfunc) fiberblockfunc(s, POLLOUT);
|
|
return r;
|
|
}
|
|
int def_getsockopt(void* state, SOCKET s, int level, int optname, void * optval, socklen_t * optlen){
|
|
return getsockopt(s, level, optname, optval, optlen);
|
|
}
|
|
int def_setsockopt(void* state, int s, int level, int optname, const void *optval, socklen_t optlen){
|
|
return setsockopt(s, level, optname, optval, optlen);
|
|
}
|
|
|
|
int def_prepoll(void* state, struct pollfd *fds, nfds_t nfds){
|
|
return 0;
|
|
}
|
|
|
|
|
|
/* a peer gone is an error of the send, not SIGPIPE, where the system can
|
|
say so for a call: SIGPIPE is ignored too, for TLS and splice() */
|
|
#ifdef MSG_NOSIGNAL
|
|
#define SENDFLAGS MSG_NOSIGNAL
|
|
#else
|
|
#define SENDFLAGS 0
|
|
#endif
|
|
ssize_t def_send(void* state, SOCKET s, const void *msg, size_t len, int flags){
|
|
ssize_t r = send(s, msg, len, flags | SENDFLAGS);
|
|
|
|
if(r < 0) BLOCKED(s, POLLOUT);
|
|
return r;
|
|
}
|
|
ssize_t def_sendto(void* state, SOCKET s, const void *msg, size_t len, int flags, const struct sockaddr *to, socklen_t tolen){
|
|
ssize_t r = sendto(s, msg, len, flags | SENDFLAGS, to, tolen);
|
|
|
|
if(r < 0) BLOCKED(s, POLLOUT);
|
|
return r;
|
|
}
|
|
ssize_t def_recv(void* state, SOCKET s, void *buf, size_t len, int flags){
|
|
ssize_t r = recv(s, buf, len, flags);
|
|
|
|
if(r < 0) BLOCKED(s, POLLIN);
|
|
return r;
|
|
}
|
|
ssize_t def_recvfrom(void* state, SOCKET s, void * buf, size_t len, int flags, struct sockaddr * from, socklen_t* fromlen){
|
|
ssize_t r = recvfrom(s, buf, len, flags, from, fromlen);
|
|
|
|
if(r < 0) BLOCKED(s, POLLIN);
|
|
return r;
|
|
}
|
|
int def_shutdown(void* state, SOCKET s, int how){
|
|
return shutdown(s, how);
|
|
}
|
|
int def_closesocket(void* state, SOCKET s){
|
|
if(fiberclosefunc) fiberclosefunc(s);
|
|
return close(s);
|
|
}
|
|
#endif
|
|
|
|
struct sockfuncs so = {
|
|
NULL,
|
|
NULL,
|
|
def_socket,
|
|
def_accept,
|
|
def_bind,
|
|
def_listen,
|
|
def_connect,
|
|
def_getpeername,
|
|
def_getsockname,
|
|
def_getsockopt,
|
|
def_setsockopt,
|
|
def_prepoll,
|
|
def_send,
|
|
def_sendto,
|
|
def_recv,
|
|
def_recvfrom,
|
|
def_shutdown,
|
|
def_closesocket
|
|
};
|
|
|
|
#ifndef _WIN32
|
|
/* close() of a descriptor a fiber may have waited on, a pipe of splice():
|
|
its loop forgets it first, or a socket given the same number later would
|
|
be taken for it, and never registered. */
|
|
int fdclose(int fd){
|
|
if(fiberclosefunc) fiberclosefunc(fd);
|
|
return close(fd);
|
|
}
|
|
#endif
|
|
|
|
/* Whether s, found readable, is: data or its end there. A fiber's wait
|
|
may answer with what a socket was reported ready for, until an
|
|
operation finds it not to be: whatever takes readability for an end,
|
|
rather than reading, asks first. A peek below any layer over the socket,
|
|
which a TLS read would consume. */
|
|
int sockreadable(SOCKET s){
|
|
char c;
|
|
|
|
if(!fiberblockfunc) return 1;
|
|
if(recv(s, &c, 1, MSG_PEEK) < 0 && (errno == EAGAIN || errno == EWOULDBLOCK)){
|
|
fiberblockfunc(s, POLLIN);
|
|
return 0;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
/* Waits as poll() does for fds of sockets made with sf, once their layers
|
|
know of nothing ready already. A layer may have a socket waited for
|
|
other than as asked, a TLS read for it to be writable: whatever it gets
|
|
is then the caller's chance to try again, revents what the caller asked
|
|
for, and events are given back as they were. With no fds it only
|
|
sleeps. */
|
|
#define POLLFAIL (POLLERR|POLLHUP|POLLNVAL)
|
|
/* set once clients are served by fibers on event loops: the wait of a
|
|
fiber, -2 for anything else */
|
|
int (*fiberpollfunc)(struct pollfd *fds, unsigned nfds, int timeout) = NULL;
|
|
|
|
int sockpoll(struct sockfuncs *sf, void *state, struct pollfd *fds, unsigned nfds, int timeout){
|
|
short own[64], *events = own;
|
|
unsigned i;
|
|
int res;
|
|
|
|
if(!nfds){
|
|
if(timeout > 0 && (!fiberpollfunc || fiberpollfunc(NULL, 0, timeout) == -2))
|
|
usleep(timeout * SLEEPTIME);
|
|
return 0;
|
|
}
|
|
if(nfds > sizeof(own)/sizeof(own[0]) && !(events = malloc(nfds * sizeof(*events)))) return -1;
|
|
for(i = 0; i < nfds; i++) events[i] = fds[i].events;
|
|
if(!(res = sf->_prepoll(state, fds, nfds))){
|
|
/* a fiber waits in its loop */
|
|
if(!fiberpollfunc || (res = fiberpollfunc(fds, nfds, timeout)) == -2){
|
|
#ifdef _WIN32
|
|
res = WSAPoll(fds, nfds, timeout);
|
|
#else
|
|
res = poll(fds, (nfds_t)nfds, timeout);
|
|
#endif
|
|
}
|
|
for(i = 0; res > 0 && i < nfds; i++)
|
|
if(fds[i].events != events[i] && (fds[i].revents & ~POLLFAIL))
|
|
fds[i].revents = events[i] | (fds[i].revents & POLLFAIL);
|
|
}
|
|
for(i = 0; i < nfds; i++) fds[i].events = events[i];
|
|
if(events != own) free(events);
|
|
return res;
|
|
}
|
|
|
|
|
|
int parsehost(int family, unsigned char *host, struct sockaddr *sa){
|
|
char *sp=NULL,*se=NULL;
|
|
uint16_t port=0;
|
|
int ret = 0;
|
|
|
|
if(!host) return 2;
|
|
if(*host == '[') se=strchr((char *)host, ']');
|
|
if ( (sp = strchr(se?se:(char *)host, ':')) && !strchr(sp+1, ':')) *sp = 0;
|
|
if(se){
|
|
*se = 0;
|
|
}
|
|
if(sp){
|
|
port = atoi(sp+1);
|
|
}
|
|
ret = !getip46(family, host + (se!=0), (struct sockaddr *)sa);
|
|
if(se) *se = ']';
|
|
if(sp) *sp = ':';
|
|
if(port)*SAPORT(sa) = htons(port);
|
|
return ret;
|
|
}
|
|
|
|
int parsehostname(char *hostname, struct clientparam *param, uint16_t port){
|
|
char *sp=NULL,*se=NULL;
|
|
int ret = 0;
|
|
|
|
if(!hostname || !*hostname)return 2;
|
|
if(*hostname == '[') se=strchr(hostname, ']');
|
|
if ((sp = strchr(se?se:hostname, ':'))) {
|
|
if(strchr(sp+1, ':'))sp = NULL;
|
|
else *sp = 0;
|
|
}
|
|
if(se){
|
|
*se = 0;
|
|
}
|
|
if(!*hostname){
|
|
if(sp){
|
|
port = atoi(sp+1);
|
|
*sp = ':';
|
|
}
|
|
*SAPORT(¶m->req) = htons(port);
|
|
return 0;
|
|
}
|
|
if(strlen(hostname + (se!=0)) > 253){
|
|
if(se) *se = ']';
|
|
if(sp) *sp = ':';
|
|
return 1;
|
|
}
|
|
if(PARAMSTR(param, hostname, hostname + (se!=0))){
|
|
if(se) *se = ']';
|
|
if(sp) *sp = ':';
|
|
return 1;
|
|
}
|
|
if(sp){
|
|
port = atoi(sp+1);
|
|
}
|
|
ret = !getip46m(param->srv->resolvmode, param->srv->family, param->hostname, (struct sockaddr *)¶m->req);
|
|
if(se) *se = ']';
|
|
if(sp) *sp = ':';
|
|
*SAPORT(¶m->req) = htons(port);
|
|
memset(¶m->sinsr, 0, sizeof(param->sinsr));
|
|
return ret;
|
|
}
|
|
|
|
/* Copies a string into one of a connection's fixed fields. One too long for
|
|
it is refused rather than cut short, which could name another host or
|
|
another user; NULL empties it. The string may be part of the field. */
|
|
int paramstr(unsigned char *field, size_t size, const unsigned char *s){
|
|
size_t len;
|
|
|
|
if(!s){
|
|
*field = 0;
|
|
return 0;
|
|
}
|
|
len = strlen((const char *)s);
|
|
if(len >= size) return 1;
|
|
memmove(field, s, len + 1);
|
|
return 0;
|
|
}
|
|
|
|
int parseusername(char *username, struct clientparam *param, int extpasswd){
|
|
char *sb = NULL, *se = NULL, *sp = NULL;
|
|
|
|
if(!username || !*username) return 1;
|
|
if(param->srv->needuser && (sb = strchr(username, ':')) && (se = strchr(sb + 1, ':')) && (!extpasswd || (sp = strchr(se + 1, ':')))){
|
|
*sb = 0;
|
|
*se = 0;
|
|
if(sp) *sp = 0;
|
|
if((*(sb+1) && PARAMSTR(param, password, sb+1)) ||
|
|
(*username && PARAMSTR(param, username, username))){
|
|
*sb = ':';
|
|
*se = ':';
|
|
if(sp) *sp = ':';
|
|
return 1;
|
|
}
|
|
username = se+1;
|
|
}
|
|
if(extpasswd){
|
|
if(!sp) sp = strchr(username, ':');
|
|
if(sp){
|
|
*sp = 0;
|
|
if(PARAMSTR(param, extpassword, sp+1)) extpasswd = -1;
|
|
}
|
|
}
|
|
if(extpasswd < 0 || PARAMSTR(param, extusername, username)) extpasswd = -1;
|
|
if(sb) *sb = ':';
|
|
if(se) *se = ':';
|
|
if(sp) *sp = ':';
|
|
return extpasswd < 0;
|
|
}
|
|
|
|
int parseconnusername(char *username, struct clientparam *param, int extpasswd, uint16_t port){
|
|
char *sb, *se;
|
|
if(!username || !*username) return 1;
|
|
if ((sb=strchr(username, param->srv->delimchar)) == NULL){
|
|
if(!*param->hostname && param->remsock == INVALID_SOCKET) return 2;
|
|
if(*param->hostname)parsehostname((char *)param->hostname, param, *SAPORT(¶m->req)? ntohs(*SAPORT(¶m->req)) : port);
|
|
return parseusername(username, param, extpasswd);
|
|
}
|
|
while ((se=strchr(sb+1, param->srv->delimchar)))sb=se;
|
|
*(sb) = 0;
|
|
if(parseusername(username, param, extpasswd)) return 3;
|
|
*(sb) = param->srv->delimchar;
|
|
if(parsehostname(sb+1, param, *SAPORT(¶m->req)? ntohs(*SAPORT(¶m->req)) : port)) return 4;
|
|
return 0;
|
|
}
|
|
|
|
|
|
/* What waits for a socket waits in sockpoll(): a call on it never does. */
|
|
void nonblock(SOCKET sock){
|
|
#ifdef _WIN32
|
|
unsigned long ul = 1;
|
|
ioctlsocket(sock, FIONBIO, &ul);
|
|
#else
|
|
fcntl(sock, F_SETFL, O_NONBLOCK | fcntl(sock, F_GETFL));
|
|
#endif
|
|
}
|
|
|
|
/* accept() with nothing to accept: another took it, or it was gone */
|
|
int acceptagain(void){
|
|
#ifdef _WIN32
|
|
int err = WSAGetLastError();
|
|
return err == WSAEWOULDBLOCK || err == WSAECONNRESET || err == WSAEINTR;
|
|
#else
|
|
return errno == EAGAIN || errno == EWOULDBLOCK || errno == ECONNABORTED || errno == EINTR;
|
|
#endif
|
|
}
|
|
|
|
int connectwithpoll(struct clientparam *param, SOCKET sock, struct sockaddr *sa, SASIZETYPE size, int to){
|
|
struct pollfd fds[1];
|
|
#ifdef _WIN32
|
|
unsigned long ul = 1;
|
|
ioctlsocket(sock, FIONBIO, &ul);
|
|
#else
|
|
fcntl(sock,F_SETFL, O_NONBLOCK | fcntl(sock,F_GETFL));
|
|
#endif
|
|
if(param?param->srv->so._connect(param->sostate, sock,sa,size) : so._connect(so.state, sock,sa,size)) {
|
|
if(errno != EAGAIN && errno != EINPROGRESS) return 13;
|
|
}
|
|
if(!errno) return 0;
|
|
memset(fds, 0, sizeof(fds));
|
|
fds[0].fd = sock;
|
|
fds[0].events = POLLOUT;
|
|
if((param?sockpoll(¶m->srv->so, param->sostate, fds, 1, to*1000):sockpoll(&so, so.state, fds, 1, to*1000)) <= 0 || !(fds[0].revents & POLLOUT) || (fds[0].revents & (POLLERR|POLLHUP))) {
|
|
return 13;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
/* Number of ports tried before giving up when the range has to be searched by
|
|
* hand. The kernel option picks a free port itself and needs no retries. */
|
|
#define RANGETRIES 10
|
|
|
|
static int bindrange(struct clientparam *param, SOCKET sock, PROXYSOCKADDRTYPE *sa, uint32_t range, int *tries);
|
|
|
|
/* -Di / -De: ties a socket to the interface asked for, for TCP and UDP alike.
|
|
Where the option is set per protocol, family is the one of the address the
|
|
socket will be bound to. */
|
|
int bindtodevice(struct sockfuncs *sf, void *state, SOCKET sock, const char *device, int family){
|
|
if(!device) return 0;
|
|
#if defined SO_BINDTODEVICE
|
|
if(sf->_setsockopt(state, sock, SOL_SOCKET, SO_BINDTODEVICE, device, strlen(device) + 1))
|
|
return 1;
|
|
#elif defined IP_BOUND_IF
|
|
{
|
|
int idx;
|
|
idx = if_nametoindex(device);
|
|
if(!idx || (family == AF_INET && sf->_setsockopt(state, sock, IPPROTO_IP, IP_BOUND_IF, &idx, sizeof(idx))))
|
|
return 1;
|
|
#ifndef IPV6_BOUND_IF
|
|
#define IPV6_BOUND_IF 125
|
|
#endif
|
|
if(family == AF_INET6 && sf->_setsockopt(state, sock, IPPROTO_IPV6, IPV6_BOUND_IF, &idx, sizeof(idx))) return 1;
|
|
}
|
|
#endif
|
|
return 0;
|
|
}
|
|
|
|
/* the external side, a socket to be bound to sinsl */
|
|
int extbindtodevice(struct clientparam *param, SOCKET sock){
|
|
#if defined SO_BINDTODEVICE || defined IP_BOUND_IF
|
|
return bindtodevice(¶m->srv->so, param->sostate, sock, param->srv->obindtodevice, *SAFAMILY(¶m->sinsl));
|
|
#else
|
|
return 0;
|
|
#endif
|
|
}
|
|
|
|
/* the internal side, a socket to be bound to sincl */
|
|
int intbindtodevice(struct clientparam *param, SOCKET sock){
|
|
#if defined SO_BINDTODEVICE || defined IP_BOUND_IF
|
|
return bindtodevice(¶m->srv->so, param->sostate, sock, param->srv->ibindtodevice, *SAFAMILY(¶m->sincl));
|
|
#else
|
|
return 0;
|
|
#endif
|
|
}
|
|
|
|
int doconnect(struct clientparam * param){
|
|
SASIZETYPE size;
|
|
|
|
|
|
if (*SAFAMILY(¶m->sincl) == *SAFAMILY(¶m->req) && !memcmp(SAADDR(¶m->sincl), SAADDR(¶m->req), SAADDRLEN(¶m->req)) &&
|
|
*SAPORT(¶m->sincl) == *SAPORT(¶m->req)) return 519;
|
|
|
|
if (param->operation == ADMIN || (( param->operation == DNSRESOLVE || param->operation == BIND || param->operation == UDPASSOC) && !param->redirected))
|
|
return 0;
|
|
if (param->operation == UDPASSOC && param->ctrlsocksrv != INVALID_SOCKET) return 0;
|
|
if (param->remsock != INVALID_SOCKET){
|
|
if(param->operation == UDPASSOC) return 0;
|
|
size = sizeof(param->sinsr);
|
|
if(param->srv->so._getpeername(param->sostate, param->remsock, (struct sockaddr *)¶m->sinsr, &size)==-1) {return (14);}
|
|
}
|
|
else {
|
|
int tries = RANGETRIES;
|
|
|
|
if(SAISNULL(¶m->sinsr)){
|
|
if(SAISNULL(¶m->req)) {
|
|
return 100;
|
|
}
|
|
*SAFAMILY(¶m->sinsr) = *SAFAMILY(¶m->req);
|
|
memcpy(SAADDR(¶m->sinsr), SAADDR(¶m->req), SAADDRLEN(¶m->req));
|
|
}
|
|
if(!*SAPORT(¶m->sinsr))*SAPORT(¶m->sinsr) = *SAPORT(¶m->req);
|
|
for(;;){
|
|
if ((param->remsock=nssocket(param->srv, ¶m->srv->so, param->sostate, NS_OUT, SASOCK(¶m->sinsr), SOCK_STREAM,
|
|
#ifdef WITH_UN
|
|
*SAFAMILY(¶m->sinsr) == AF_UNIX? 0 :
|
|
#endif
|
|
IPPROTO_TCP
|
|
)) == INVALID_SOCKET) {return (11);}
|
|
if(SAISNULL(¶m->sinsl)){
|
|
#ifdef WITH_UN
|
|
if(*SAFAMILY(¶m->sinsr) == AF_UNIX) param->sinsl = param->sinsr;
|
|
else
|
|
#endif
|
|
if(*SAFAMILY(¶m->sinsr) == AF_INET6) param->sinsl = param->srv->extsa6;
|
|
else
|
|
param->sinsl = param->srv->extsa;
|
|
}
|
|
*SAPORT(¶m->sinsl) = 0;
|
|
setopts(param->remsock, ¶m->srv->srvsockopts, param->srv->maxseg);
|
|
|
|
if(extbindtodevice(param, param->remsock)) return 12;
|
|
#ifdef IP_BIND_ADDRESS_NO_PORT
|
|
/* A port the kernel chooses is chosen by connect(), for the pair with
|
|
the destination: one in use towards another destination, or closing
|
|
from an earlier connection, does as well. bind() would take one of
|
|
its own, and with many connections find none left, or search long
|
|
for it. */
|
|
if((param->operation >= 256 || (param->operation & CONNECT) || param->redirected)
|
|
# ifdef WITH_UN
|
|
&& *SAFAMILY(¶m->sinsl) != AF_UNIX
|
|
# endif
|
|
){
|
|
int one = 1;
|
|
|
|
param->srv->so._setsockopt(param->sostate, param->remsock, IPPROTO_IP, IP_BIND_ADDRESS_NO_PORT, (char *)&one, sizeof(one));
|
|
}
|
|
#endif
|
|
#ifdef WITH_UN
|
|
if(*SAFAMILY(¶m->sinsl) != AF_UNIX)
|
|
#endif
|
|
if(bindrange(param, param->remsock, ¶m->sinsl, param->extport, &tries)==-1) {
|
|
return 12;
|
|
}
|
|
|
|
if(!(param->operation >= 256 || (param->operation & CONNECT) || param->redirected)) break;
|
|
if(!connectwithpoll(param, param->remsock,(struct sockaddr *)¶m->sinsr,SASIZE(¶m->sinsr),param->srv->timeouts[CONNECT_TO])) break;
|
|
/* The port was free to bind, but the pair it makes with the destination
|
|
is still closing from an earlier connection through the same range,
|
|
which Windows refuses. Another port out of the range may do. Anything
|
|
else is the destination's own answer, and is not worth a second port.
|
|
The ports come out of the allowance the bind is already spending, so
|
|
the range is not searched any harder than one bind would search it and
|
|
the attempts parentretries makes are not multiplied. Counted here as
|
|
well, since the kernel option hands out a port without spending any of
|
|
the allowance. */
|
|
if(!param->extport || errno != EADDRINUSE || --tries <= 0) return 13;
|
|
param->srv->so._closesocket(param->sostate, param->remsock);
|
|
param->remsock = INVALID_SOCKET;
|
|
}
|
|
size = sizeof(param->sinsl);
|
|
if(param->srv->so._getsockname(param->sostate, param->remsock, (struct sockaddr *)¶m->sinsl, &size)==-1) {return (15);}
|
|
}
|
|
if (param->nconnectfilters){
|
|
FILTER_ACTION action;
|
|
|
|
action = handleconnectflt(param);
|
|
if(action != PASS) return 19;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* Bind sock to sa, taking the local port from the range if one is set. The
|
|
* range is packed as first | last << 16.
|
|
*
|
|
* IP_LOCAL_PORT_RANGE leaves the choice to the kernel, which knows which ports
|
|
* are free. Where the option does not exist, or the kernel refuses it, or the
|
|
* address family is not one it covers, pick a port at random instead and retry
|
|
* on failure, since the one picked may already be taken.
|
|
*
|
|
* *tries is how many ports may still be taken out of the range. A caller which
|
|
* has to come back for another port, because the one it was given turned out
|
|
* to be unusable for the connection it wanted, carries the same count along
|
|
* and so cannot spend more attempts than one call would.
|
|
*/
|
|
static int bindrange(struct clientparam *param, SOCKET sock, PROXYSOCKADDRTYPE *sa, uint32_t range, int *tries)
|
|
{
|
|
uint16_t first, last;
|
|
|
|
if(!range) return param->srv->so._bind(param->sostate, sock, (struct sockaddr *)sa, SASIZE(sa));
|
|
|
|
#ifdef IP_LOCAL_PORT_RANGE
|
|
if(*SAFAMILY(sa) == AF_INET &&
|
|
!param->srv->so._setsockopt(param->sostate, sock, IPPROTO_IP, IP_LOCAL_PORT_RANGE,
|
|
(char *)&range, sizeof(range))){
|
|
*SAPORT(sa) = 0;
|
|
return param->srv->so._bind(param->sostate, sock, (struct sockaddr *)sa, SASIZE(sa));
|
|
}
|
|
#endif
|
|
|
|
first = (uint16_t)(range & 0xffff);
|
|
last = (uint16_t)(range >> 16);
|
|
|
|
while(*tries > 0){
|
|
(*tries)--;
|
|
*SAPORT(sa) = htons((uint16_t)(first + (myrand() % (unsigned)(last - first + 1))));
|
|
if(!param->srv->so._bind(param->sostate, sock, (struct sockaddr *)sa, SASIZE(sa))) return 0;
|
|
}
|
|
|
|
/* Every port tried was taken. Fall back to an ephemeral one, which is
|
|
what the kernel option above does when it cannot honour the range, so
|
|
an exhausted range behaves the same way on every platform. */
|
|
*SAPORT(sa) = 0;
|
|
return param->srv->so._bind(param->sostate, sock, (struct sockaddr *)sa, SASIZE(sa));
|
|
}
|
|
|
|
int bindwithrange(struct clientparam *param, SOCKET sock, PROXYSOCKADDRTYPE *sa, uint32_t range)
|
|
{
|
|
int tries = RANGETRIES;
|
|
|
|
return bindrange(param, sock, sa, range, &tries);
|
|
}
|
|
|
|
int scanaddr(const unsigned char *s, uint32_t * ip, uint32_t * mask) {
|
|
unsigned d1, d2, d3, d4, m;
|
|
int res;
|
|
if ((res = sscanf((char *)s, "%u.%u.%u.%u/%u", &d1, &d2, &d3, &d4, &m)) < 4) return 0;
|
|
if(mask && res == 4) *mask = 0xFFFFFFFF;
|
|
else if (mask) *mask = htonl(0xFFFFFFFF << (32 - m));
|
|
*ip = htonl ((d1<<24) ^ (d2<<16) ^ (d3<<8) ^ d4);
|
|
return res;
|
|
}
|
|
|
|
/* The default resolver: name servers, configured or the system's, or NULL
|
|
for the system routines. resolvmode is the configuration's, for names
|
|
resolved outside a service; each service has its own. */
|
|
/* Milliseconds on a clock which only goes forward, at the same pace as the
|
|
time of day but not moved with it: a deadline does not come at once, or
|
|
never, when the clock of the system is set. */
|
|
uint64_t monotime(void){
|
|
#ifdef _WIN32
|
|
LARGE_INTEGER c, f;
|
|
|
|
QueryPerformanceFrequency(&f);
|
|
QueryPerformanceCounter(&c);
|
|
return (uint64_t)(c.QuadPart / f.QuadPart) * 1000 +
|
|
(uint64_t)(c.QuadPart % f.QuadPart) * 1000 / (uint64_t)f.QuadPart;
|
|
#elif defined(CLOCK_MONOTONIC)
|
|
struct timespec ts;
|
|
|
|
clock_gettime(CLOCK_MONOTONIC, &ts);
|
|
return (uint64_t)ts.tv_sec * 1000 + (uint64_t)ts.tv_nsec / 1000000;
|
|
#else
|
|
struct timeval tv;
|
|
|
|
gettimeofday(&tv, NULL);
|
|
return (uint64_t)tv.tv_sec * 1000 + (uint64_t)tv.tv_usec / 1000;
|
|
#endif
|
|
}
|
|
|
|
RESOLVFUNC resolvfunc = NULL;
|
|
int resolvmode = RESOLVE_DEFAULT;
|
|
|
|
uint32_t fakeresolver (int af, unsigned char *name, unsigned char * value){
|
|
if(af == AF_INET6){
|
|
memset(value, 0, 16);
|
|
value[15] = 2;
|
|
}
|
|
else {
|
|
value[0] = 127;
|
|
value[1] = 0;
|
|
value[2] = 0;
|
|
value[3] = 2;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
/* The resolver of a mode, NULL for the system routines */
|
|
static RESOLVFUNC moderesolver(int mode){
|
|
switch(mode){
|
|
case RESOLVE_FAKE:
|
|
return fakeresolver;
|
|
case RESOLVE_SYSTEM:
|
|
return NULL;
|
|
}
|
|
return resolvfunc;
|
|
}
|
|
|
|
|
|
int afdetect(unsigned char *name){
|
|
int ndots=0, ncols=0, nhex=0;
|
|
int i;
|
|
|
|
for(i=0; name[i]; i++){
|
|
if(name[i] == '.'){
|
|
if(++ndots > 3) {
|
|
return -1;
|
|
}
|
|
}
|
|
else if(name[i] == ':'){
|
|
if(++ncols > 7) {
|
|
return -1;
|
|
}
|
|
}
|
|
else if(name[i] == '%' || (name[i] >= 'a' && name[i] <= 'f') || (name[i] >= 'A' && name[i] <= 'F')){
|
|
nhex++;
|
|
}
|
|
else if(name[i] <'0' || name[i] >'9') {
|
|
return -1;
|
|
}
|
|
}
|
|
if(ndots == 3 && ncols == 0 && nhex == 0){
|
|
return AF_INET;
|
|
}
|
|
if(ncols >= 2) {
|
|
return AF_INET6;
|
|
}
|
|
return -1;
|
|
|
|
}
|
|
|
|
/* A name resolved the way a mode says: a service's names with its own */
|
|
#ifndef NOSTDRESOLVE
|
|
/* What the C library's resolver is asked, and its answer */
|
|
struct stdresolve {
|
|
unsigned char *name;
|
|
int family;
|
|
struct sockaddr *sa;
|
|
uint32_t res;
|
|
};
|
|
|
|
/* getaddrinfo(): run on a thread's own stack, which glibc may take much of,
|
|
for IPv6 most (alloca() sized by the thread's stack, not a fiber's) */
|
|
static void stdresolve46(void *arg){
|
|
struct stdresolve *r = (struct stdresolve *)arg;
|
|
struct addrinfo *ai, hint;
|
|
|
|
r->res = 0;
|
|
memset(&hint, 0, sizeof(hint));
|
|
hint.ai_family = (r->family == 6 || r->family == 64)?AF_INET6:AF_INET;
|
|
if (getaddrinfo((char *)r->name, NULL, &hint, &ai)) {
|
|
if(r->family == 64 || r->family == 46){
|
|
hint.ai_family = (r->family == 64)?AF_INET:AF_INET6;
|
|
if (getaddrinfo((char *)r->name, NULL, &hint, &ai)) return;
|
|
}
|
|
else return;
|
|
}
|
|
if(ai){
|
|
if(ai->ai_addr->sa_family == AF_INET || ai->ai_addr->sa_family == AF_INET6){
|
|
*SAFAMILY(r->sa)=ai->ai_addr->sa_family;
|
|
memcpy(SAADDR(r->sa), SAADDR(ai->ai_addr), SAADDRLEN(ai->ai_addr));
|
|
r->res = *SAFAMILY(r->sa);
|
|
}
|
|
freeaddrinfo(ai);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
uint32_t getip46m(int mode, int family, unsigned char *name, struct sockaddr *sa){
|
|
int detect;
|
|
RESOLVFUNC tmpresolv;
|
|
|
|
if(!sa) return 0;
|
|
if(!family) {
|
|
family = 4;
|
|
}
|
|
|
|
detect = afdetect(name);
|
|
if(detect != -1){
|
|
if(family == 4 && detect != AF_INET) return 0;
|
|
*SAFAMILY(sa) = (family == 6)? AF_INET6 : detect;
|
|
return inet_pton(*SAFAMILY(sa), (char *)name, SAADDR(sa))>0? *SAFAMILY(sa) : 0;
|
|
}
|
|
|
|
if((tmpresolv = moderesolver(mode))){
|
|
int f = (family == 6 || family == 64)?AF_INET6:AF_INET;
|
|
*SAFAMILY(sa) = f;
|
|
if(tmpresolv(f, name, SAADDR(sa))) return f;
|
|
if(family == 4 || family == 6) return 0;
|
|
f = (family == 46)? AF_INET6 : AF_INET;
|
|
*SAFAMILY(sa) = f;
|
|
if(tmpresolv(f, name, SAADDR(sa))) return f;
|
|
return 0;
|
|
}
|
|
#ifndef NOSTDRESOLVE
|
|
{
|
|
struct stdresolve r;
|
|
|
|
r.name = name;
|
|
r.family = family;
|
|
r.sa = sa;
|
|
/* a fiber's stack is not one to give it */
|
|
if(onthreadstackfunc) (*onthreadstackfunc)(stdresolve46, &r);
|
|
else stdresolve46(&r);
|
|
return r.res;
|
|
}
|
|
#endif
|
|
return 0;
|
|
}
|
|
|
|
uint32_t getip46(int family, unsigned char *name, struct sockaddr *sa){
|
|
return getip46m(resolvmode, family, name, sa);
|
|
}
|
|
|
|
int hascap(const unsigned char *line, const char *cap){
|
|
int len = (int)strlen(cap);
|
|
for(; *line; line++) if(!strncasecmp((char *)line, cap, len)) return 1;
|
|
return 0;
|
|
}
|
|
|
|
int getmultiline(struct clientparam *param, DIRECTION which, unsigned char *buf, int bufsize, const char *cap, int *found){
|
|
int i;
|
|
do {
|
|
i = sockgetlinebuf(param, which, buf, bufsize, '\n', param->srv->timeouts[STRING_L]);
|
|
if(i > 0 && cap && found && !*found){
|
|
buf[i] = 0;
|
|
if(hascap(buf, cap)) *found = 1;
|
|
}
|
|
} while (i > 3 && buf[3] == '-');
|
|
return i;
|
|
}
|