3proxy/src/sockgetchar.c
Vladimir Dubrovin 9612bbdd22
Some checks are pending
C/C++ CI Linux / ${{ matrix.target }} (ubuntu-24.04-arm) (push) Waiting to run
C/C++ CI Linux / ${{ matrix.target }} (ubuntu-latest) (push) Waiting to run
C/C++ CI MacOS / ${{ matrix.target }} (macos-15) (push) Waiting to run
C/C++ CI Windows / ${{ matrix.target }} (windows-2022) (push) Waiting to run
C/C++ CI cmake / ${{ matrix.target }} (macos-15) (push) Waiting to run
C/C++ CI cmake / ${{ matrix.target }} (ubuntu-24.04-arm) (push) Waiting to run
C/C++ CI cmake / ${{ matrix.target }} (ubuntu-latest) (push) Waiting to run
C/C++ CI cmake / ${{ matrix.target }} (windows-2022) (push) Waiting to run
C/C++ CI cmake / ubuntu-latest (wolfSSL) (push) Waiting to run
Unix sockets: no addresses on streams, abstract names, tcppm port 0
On Linux a proxy listening on a Unix socket relayed only the first
bytes of an answer (result 95): sockmap() read with recvfrom() into the
client's address, which for a Unix stream socket Linux fills with an
empty AF_UNIX one, and then sent with sendto() to that address, which a
connected Unix stream socket refuses. A TCP connection has no use for
addresses: sockmap() now sends and receives with NULL ones, and
connect-back sends its command with socksend() and reads the answer
with sockrecvfrom() without an address, which a NULL sin now allows.

An abstract socket name (-iunix:@NAME) was bound with the whole size of
struct sockaddr_un, padded with zeros, so a client naming it by its
bytes, as most do, reached another name. SASIZE() now gives an abstract
address its exact size; getsockname() calls that took SASIZE() of the
old contents as the buffer size take the buffer's size instead.

tcppm refused port 0 for a unix: target, though the manual gives
"tcppm 8080 unix:/var/run/app.sock 0" as the example: the port of a
Unix socket target is ignored, so 0 is allowed.

A test case covers a proxy on a Unix socket and on an abstract one on
Linux, tcppm to a Unix socket, a parent reached through one, and a
listening Unix socket across reloads: taken over each time, its file
kept, and removed once no service listens on it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 12:05:27 +03:00

233 lines
8.1 KiB
C

/*
3APA3A simplest proxy server
(c) 2002-2026 by Vladimir Dubrovin <vlad@3proxy.org>
please read License Agreement
*/
#include "proxy.h"
int socksend(struct clientparam *param, SOCKET sock, unsigned char * buf, int bufsize, int to){
int sent = 0;
int res;
struct pollfd fds;
fds.fd = sock;
fds.events = POLLOUT;
do {
if(timetoexit) return 0;
/* try to send first; poll only when the socket would block */
res = param?param->srv->so._send(param->sostate, sock, (char *)buf + sent, bufsize - sent, 0) : so._send(so.state, sock, (char *)buf + sent, bufsize - sent, 0);
if(res > 0) {
sent += res;
continue;
}
if(res < 0 && errno != EAGAIN && errno != EINTR) break;
res = param?param->srv->so._poll(param->sostate, &fds, 1, to*1000):so._poll(so.state, &fds, 1, to*1000);
if(res < 0 && (errno == EAGAIN || errno == EINTR)) continue;
if(res < 1) break;
} while (sent < bufsize);
return sent;
}
int socksendto(struct clientparam *param, SOCKET sock, struct sockaddr * sin, unsigned char * buf, int bufsize, int to){
int sent = 0;
int res;
struct pollfd fds;
fds.fd = sock;
do {
if(timetoexit) return 0;
fds.events = POLLOUT;
res = param?param->srv->so._poll(param->sostate, &fds, 1, to):so._poll(so.state, &fds, 1, to);
if(res < 0 && (errno == EAGAIN || errno == EINTR)) continue;
if(res < 1) break;
res = param?param->srv->so._sendto(param->sostate, sock, (char *)buf + sent, bufsize - sent, 0, sin, SASIZE(sin)):so._sendto(so.state, sock, (char *)buf + sent, bufsize - sent, 0, sin, SASIZE(sin));
if(res < 0) {
if(errno != EAGAIN && errno != EINTR) break;
continue;
}
sent += res;
} while (sent < bufsize);
return sent;
}
int sockrecvfrom(struct clientparam *param, SOCKET sock, struct sockaddr * sin, unsigned char * buf, int bufsize, int to){
struct pollfd fds;
SASIZETYPE sasize;
int res;
fds.fd = sock;
fds.events = POLLIN;
if(timetoexit) return EOF;
res = param?param->srv->so._poll(param->sostate, &fds, 1, to):so._poll(so.state, &fds, 1, to);
if (res<1) return 0;
sasize = sizeof(PROXYSOCKADDRTYPE);
/* sin NULL for a stream socket: nothing to learn the address into */
do {
res = param?param->srv->so._recvfrom(param->sostate, sock, (char *)buf, bufsize, 0, (struct sockaddr *)sin, sin? &sasize : NULL):so._recvfrom(so.state, sock, (char *)buf, bufsize, 0, (struct sockaddr *)sin, sin? &sasize : NULL);
} while (res < 0 && (errno == EAGAIN || errno == EINTR));
return res;
}
/* Starts an empty buffer over from its beginning, and goes back to the
connection's own buffer if a filter needed a bigger one. Does nothing
while anything is left unread. */
void bufrelease(struct clientparam * param, DIRECTION which){
if(which){
if(param->srvinbuf != param->srvoffset) return;
param->srvinbuf = param->srvoffset = 0;
if(param->srvbuf == param->srvbufdata) return;
free(param->srvbuf);
param->srvbuf = param->srvbufdata;
param->srvbufsize = SRVBUFSIZE(param->srv);
}
else {
if(param->cliinbuf != param->clioffset) return;
param->cliinbuf = param->clioffset = 0;
if(param->clibuf == param->clibufdata) return;
free(param->clibuf);
param->clibuf = param->clibufdata;
param->clibufsize = SRVBUFSIZE(param->srv);
}
}
/* Makes room for size bytes from the start of what is unread, which is
moved there: a body to be filtered whole is read into one buffer, which
may have to be bigger than the connection's own. Returns 1 when that
much cannot be had. */
int bufroom(struct clientparam * param, DIRECTION which, unsigned long size){
unsigned char **buf = which? &param->srvbuf : &param->clibuf;
unsigned char *own = which? param->srvbufdata : param->clibufdata;
unsigned *inbuf = which? &param->srvinbuf : &param->cliinbuf;
unsigned *offset = which? &param->srvoffset : &param->clioffset;
unsigned *bufsize = which? &param->srvbufsize : &param->clibufsize;
unsigned left = *inbuf - *offset;
unsigned char *nb;
if(*offset){
memmove(*buf, *buf + *offset, left);
*inbuf = left;
*offset = 0;
}
if(size <= *bufsize) return 0;
if((unsigned long)(unsigned)size != size || !(nb = malloc(size))) return 1;
memcpy(nb, *buf, left);
if(*buf != own) free(*buf);
*buf = nb;
*bufsize = (unsigned)size;
return 0;
}
int sockgetcharcli(struct clientparam * param, int timeosec, int timeousec){
int len;
unsigned keep = 0;
if(param->cliinbuf && param->clioffset < param->cliinbuf){
return (int)param->clibuf[param->clioffset++];
}
if(param->climark == -2) return EOF; /* nothing more until it is cleared */
if(param->climark >= 0){
/* what was read since the mark stays, moved to the start */
keep = param->cliinbuf - (unsigned)param->climark;
if(param->climark) memmove(param->clibuf, param->clibuf + param->climark, keep);
param->climark = 0;
param->clioffset = param->cliinbuf = keep;
if(keep >= param->clibufsize){
param->climark = -2;
return EOF;
}
}
else bufrelease(param, CLIENT);
if ((len = sockrecvfrom(param, param->clisock, (struct sockaddr *)&param->sincr, param->clibuf + keep, param->clibufsize - keep, timeosec*1000 + timeousec))<=0) return EOF;
param->cliinbuf = keep + len;
param->clioffset = keep + 1;
return (int)param->clibuf[keep];
}
/* Waits until size bytes are in the buffer, from its start, without taking
them: for a look at what comes before it is read the usual way, or for
something which has to be had whole. Stops short at the end of the
buffer, at the end of the stream, or when nothing comes in time; returns
how much is there, at most size. */
unsigned long sockfillbuf(struct clientparam * param, DIRECTION which, unsigned long size, int timeosec){
unsigned char *buf = which? param->srvbuf : param->clibuf;
unsigned *inbuf = which? &param->srvinbuf : &param->cliinbuf;
unsigned *offset = which? &param->srvoffset : &param->clioffset;
unsigned bufsize = which? param->srvbufsize : param->clibufsize;
unsigned want;
int len;
if(*offset){
memmove(buf, buf + *offset, *inbuf - *offset);
*inbuf -= *offset;
*offset = 0;
}
while(*inbuf < size && *inbuf < bufsize){
want = (size - *inbuf < bufsize - *inbuf)? (unsigned)(size - *inbuf) : bufsize - *inbuf;
len = which?
sockrecvfrom(param, param->remsock, (struct sockaddr *)&param->sinsr, buf + *inbuf, (int)want, timeosec*1000)
: sockrecvfrom(param, param->clisock, (struct sockaddr *)&param->sincr, buf + *inbuf, (int)want, timeosec*1000);
if(len <= 0) break;
*inbuf += len;
if(which){
param->nreads++;
param->statssrv64 += len;
}
}
return *inbuf < size? *inbuf : size;
}
int sockgetcharsrv(struct clientparam * param, int timeosec, int timeousec){
int len;
if(param->srvinbuf && param->srvoffset < param->srvinbuf){
return (int)param->srvbuf[param->srvoffset++];
}
bufrelease(param, SERVER);
if ((len = sockrecvfrom(param, param->remsock, (struct sockaddr *)&param->sinsr, param->srvbuf, param->srvbufsize, timeosec*1000 + timeousec))<=0) return EOF;
param->srvinbuf = len;
param->srvoffset = 1;
param->nreads++;
param->statssrv64 += len;
return (int)*param->srvbuf;
}
int sockgetlinebuf(struct clientparam * param, DIRECTION which, unsigned char * buf, int bufsize, int delim, int to){
int c;
int i=0;
for(;;){
unsigned char *base = which? param->srvbuf : param->clibuf;
unsigned *offp = which? &param->srvoffset : &param->clioffset;
unsigned *inp = which? &param->srvinbuf : &param->cliinbuf;
/* drain already-buffered bytes in bulk instead of one call per byte */
if(base && *offp < *inp && i < bufsize){
int avail = (int)(*inp - *offp);
int n = (bufsize - i < avail)? bufsize - i : avail;
if(delim != EOF){
unsigned char *d = (unsigned char *)memchr(base + *offp, delim, n);
if(d) n = (int)(d - (base + *offp)) + 1;
/* caller may use param->srvbuf / param->clibuf as buf */
memmove(buf + i, base + *offp, n);
i += n; *offp += n;
if(d || i >= bufsize) return i;
}
else {
memmove(buf + i, base + *offp, n);
i += n; *offp += n;
if(i >= bufsize) return i;
}
}
if(i >= bufsize) return i;
/* buffer empty: one call forces a refill (with poll + accounting), then bulk-drain the rest */
if((c = which? sockgetcharsrv(param, to, 0) : sockgetcharcli(param, to, 0)) == EOF) return i;
buf[i++] = c;
if((delim != EOF && c == delim) || i >= bufsize) return i;
}
}