3proxy/src/3proxy.c
Vladimir Dubrovin d613750b94 Logs: a logger for each log string, shared and closed once unused
A logger (struct logger) is identified by the string log or -l gives it:
"" stdout, "/dev/null" none, "@IDENT" syslog, "&DSN" ODBC, "radius",
anything else a file. Each type (struct logtype) has init, formatting of
a connection into a buffer, writing, close, and a periodic tick for
rotation and flushing; ODBC and RADIUS register their types from 3proxy,
which alone has them. Services and configurations hold the logger they
use; the last to let go closes it, except the standard ones (none,
stdout, stderr, syslog), which are opened at startup and kept.

-l takes every log type now, and a file named by several services or by
both log and -l is opened once. rotate and archiver are bound to the
logger of the last log command when its configuration comes into force,
and every file logger is rotated by the main loop.

Errors with no connection go to a new stderr logger; after daemon, or as
a Windows service, stdout logging writes nothing and errors go to the
last log configured. A log whose writes keep failing is closed and
reopened; while it cannot be, its lines go to stderr and it is retried
every minute. ODBC's own reconnection throttle gives way to this.

Formatting (logbuf) is separate from counting traffic and clearing the
statistics, done once by logdo(), the services' log function, so a later
step can format or copy a connection's line and write it from a thread
of its own. dologname() and timechanged() move to log.c, which the
standalone services link as well.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 11:44:55 +03:00

560 lines
13 KiB
C

/*
3APA3A simplest proxy server
(c) 2002-2026 by Vladimir Dubrovin <vlad@3proxy.org>
please read License Agreement
*/
#include "proxy.h"
#ifdef WITH_SSL
void ssl_install(void);
#endif
#ifdef WITH_PCRE
void pcre_install(void);
#endif
#ifdef WITH_TRANSPARENT
void transparent_install(void);
#endif
#ifdef WITH_HTTPSRV
void httpsrv_init(void);
#endif
#ifndef _WIN32
#include <sys/resource.h>
#ifndef NOPLUGINS
#include <dlfcn.h>
#endif
#else
#endif
#ifndef DEFAULTCONFIG
#define DEFAULTCONFIG conf->stringtable[25]
#endif
FILE * confopen();
extern unsigned char *strings[];
extern struct counter_header cheader;
extern struct counter_record crecord;
time_t basetime = 0;
void doschedule(void);
#ifdef _WIN32
OSVERSIONINFO osv;
int service = 0;
void cyclestep(void);
#ifndef _WINCE
SERVICE_STATUS_HANDLE hSrv;
DWORD dwCurrState;
int SetStatus( DWORD dwState, DWORD dwExitCode, DWORD dwProgress )
{
SERVICE_STATUS srvStatus;
srvStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
srvStatus.dwCurrentState = dwCurrState = dwState;
srvStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_PAUSE_CONTINUE | SERVICE_ACCEPT_SHUTDOWN;
srvStatus.dwWin32ExitCode = dwExitCode;
srvStatus.dwServiceSpecificExitCode = 0;
srvStatus.dwCheckPoint = dwProgress;
srvStatus.dwWaitHint = 3000;
return SetServiceStatus( hSrv, &srvStatus );
}
void __stdcall CommandHandler( DWORD dwCommand )
{
switch( dwCommand )
{
case SERVICE_CONTROL_STOP:
case SERVICE_CONTROL_SHUTDOWN:
SetStatus( SERVICE_STOP_PENDING, 0, 1 );
timetoexit = 1;
runconf->paused++;
/* ServiceMain() reports the service stopped once cyclestep() has
seen this and the services are done */
break;
case SERVICE_CONTROL_PAUSE:
SetStatus( SERVICE_PAUSE_PENDING, 0, 1 );
runconf->paused++;
SetStatus( SERVICE_PAUSED, 0, 0 );
break;
case SERVICE_CONTROL_CONTINUE:
SetStatus( SERVICE_CONTINUE_PENDING, 0, 1 );
needreload = 1;
SetStatus( SERVICE_RUNNING, 0, 0 );
break;
default: ;
}
}
void __stdcall ServiceMain(int argc, unsigned char* argv[] )
{
hSrv = RegisterServiceCtrlHandler((LPCSTR)conf->stringtable[1], (LPHANDLER_FUNCTION)CommandHandler);
if( hSrv == 0 ) return;
SetStatus( SERVICE_START_PENDING, 0, 1 );
SetStatus( SERVICE_RUNNING, 0, 0 );
cyclestep();
logfinish();
/* the last thing: the process may be ended once this is reported */
SetStatus( SERVICE_STOPPED, 0, 0 );
}
#endif
#else
void mysigusr1 (int sig){
needreload = 1;
}
int even = 0;
/* Signals may come while a reload reads the file: they act on the
configuration in force. */
void mysigpause (int sig){
runconf->paused++;
even = !even;
if(!even){
needreload = 1;
}
}
void mysigterm (int sig){
runconf->paused++;
timetoexit = 1;
}
#endif
void dumpmem(void);
struct schedule *schedule;
int wday = 0;
void doschedule(void){
struct schedule *sched, *prevsched = NULL, *nextsched;
int res;
curtime = time(0);
for(sched=schedule; sched; sched=sched->next){
if(needreload || timetoexit || (curtime > sched->start_time && timechanged(sched->start_time, curtime, sched->type))){
sched->start_time = curtime;
nextsched = sched->next;
res = (*sched->function)(sched->data);
switch(res){
case 1:
if(prevsched) prevsched->next = nextsched;
else schedule = nextsched;
break;
}
}
prevsched = sched;
}
}
void dumpcounters(struct trafcount *tlin, int counterd){
unsigned char tmpbuf[8192];
struct trafcount *tl;
if(counterd >= 0 && tlin) {
curtime = time(0);
if(cheader.updated && conf->countertype && timechanged(cheader.updated, curtime, conf->countertype)){
FILE * cfp;
cfp = fopen((char *)dologname(tmpbuf, (unsigned char *)conf->counterfile, NULL, conf->countertype, cheader.updated), "w");
if(cfp){
for(tl = tlin; cfp && tl; tl = tl->next){
if(tl->type >= conf->countertype)
fprintf(cfp, "%05d %020"PRIu64"%s%s\n", tl->number, tl->v->traf64, tl->comment?" #" : "", tl->comment? tl->comment : "");
}
fclose(cfp);
}
}
cheader.updated = curtime;
if(lseek(counterd, 0, SEEK_SET) >= 0 && write(counterd, &cheader, sizeof(struct counter_header))){}
for(tl=tlin; tl; tl = tl->next){
if(tl->number){
if(lseek(counterd,
sizeof(struct counter_header) + (tl->number - 1) * sizeof(struct counter_record),
SEEK_SET) >= 0){
crecord.traf64 = tl->v->traf64;
crecord.cleared = tl->v->cleared;
crecord.updated = tl->v->updated;
if(write(counterd, &crecord, sizeof(struct counter_record))){}
}
}
if(tl->type!=NEVER && timechanged(tl->v->cleared, curtime, tl->type)){
tl->v->cleared = curtime;
tl->v->traf64 = 0;
}
}
}
}
/* Lets the services finish before the process exits, for EXITWAIT ms at
most: each one waits for its connections, and a connection may hang on
its peer. Posts left over from reloads are consumed on the way, the
count decides. */
#define EXITWAIT 3000
static unsigned long nowms(void){
#ifdef _WIN32
return (unsigned long)GetTickCount();
#else
struct timeval tv;
gettimeofday(&tv, NULL);
return (unsigned long)tv.tv_sec * 1000 + tv.tv_usec / 1000;
#endif
}
static void waitservices(void){
unsigned long start = nowms(), passed;
for(;;){
int n;
_3proxy_mutex_lock(&runningservices_mutex);
n = runningservices;
_3proxy_mutex_unlock(&runningservices_mutex);
if(!n) return;
/* a clock set back makes this huge, which ends the wait as well */
passed = nowms() - start;
if(passed >= EXITWAIT) return;
if(_3proxy_sem_timedlock(serviceexited, (unsigned)(EXITWAIT - passed))) return;
}
}
void cyclestep(void){
struct tm *tm;
time_t minutecounter;
minutecounter = time(0);
for(;;){
usleep(SLEEPTIME*999);
curtime = time(0);
if(needreload) {
doschedule();
reload();
needreload = 0;
}
doschedule();
sysdnscheck();
if(timechanged(minutecounter, curtime, MINUTELY)) {
struct filemon *fm;
struct stat sb;
for(fm=conf->fmon; fm; fm=fm->next){
if(!stat(fm->path, &sb)){
if(fm->sb.st_mtime != sb.st_mtime || fm->sb.st_size != sb.st_size){
stat(fm->path, &fm->sb);
needreload = 1;
}
}
}
}
if(timechanged(basetime, curtime, DAILY)) {
tm = localtime(&curtime);
if(tm){
wday = (1 << tm->tm_wday);
tm->tm_hour = tm->tm_min = tm->tm_sec = 0;
basetime = mktime(tm);
}
}
logtick(curtime);
if(conf->counterd >= 0 && conf->trafcounter) {
if(timechanged(cheader.updated, curtime, MINUTELY)){
dumpcounters(conf->trafcounter, conf->counterd);
}
}
if(timetoexit){
conf->paused++;
doschedule();
waitservices();
pooldrain();
return;
}
}
}
#define RETURN(x) {res = x; goto CLEARRETURN;}
#ifndef _WINCE
int main(int argc, char * argv[]) {
#else
int WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR lpCmdLine, int nCmdShow){
int argc;
char ** argv;
WNDCLASS wc;
HWND hwnd = 0;
#endif
int res = 0;
FILE * fp = NULL;
#ifdef _WIN32
unsigned char * arg;
WSADATA wd;
unsigned char tmpbuf[8192];
WSAStartup(MAKEWORD( 1, 1 ), &wd);
osv.dwOSVersionInfoSize = sizeof(osv);
GetVersionEx(&osv);
#endif
#ifdef _WINCE
argc = ceparseargs((char *)lpCmdLine);
argv = ceargv;
if(FindWindow(L"3proxy", L"3proxy")) return 0;
ZeroMemory(&wc,sizeof(wc));
wc.hbrBackground=(HBRUSH)GetStockObject(BLACK_BRUSH);
wc.hInstance=hInstance;
wc.hCursor=LoadCursor(NULL,IDC_ARROW);
wc.lpfnWndProc=DefWindowProc;
wc.style=CS_HREDRAW|CS_VREDRAW;
wc.lpszClassName=L"3proxy";
RegisterClass(&wc);
hwnd = CreateWindowEx(0,L"3proxy",L"3proxy",WS_VISIBLE|WS_POPUP,0,0,0,0,0,0,hInstance,0);
#endif
conf->stringtable = strings;
#ifdef _WIN32
#ifndef _WINCE
if((argc == 2 || argc == 3)&& !strcmp((char *)argv[1], "--install")) {
sprintf((char *)tmpbuf, "%s will be installed and started.\n"
"By clicking Yes you confirm you read and accepted License Agreement.\n"
"You can use Administration/Services to control %s service.",
conf->stringtable[1], conf->stringtable[2]);
if(MessageBox(NULL, (LPCSTR)tmpbuf, (LPCSTR)conf->stringtable[2], MB_YESNO|MB_ICONASTERISK) != IDYES) return 1;
*tmpbuf = '\"';
if (!(res = SearchPath(NULL, argv[0], ".exe", 256, (char *)tmpbuf+1, (LPTSTR*)&arg))) {
perror("Failed to find executable filename");
RETURN(102);
}
strcat((char *)tmpbuf, "\" \"");
if(!(res = GetFullPathName ((argc == 3)?argv[2]:(char*)DEFAULTCONFIG, 256, (char *)tmpbuf+res+4, (char **)&arg))){
perror("Failed to find config filename");
RETURN(103);
}
strcat((char *)tmpbuf, "\" --service");
if(osv.dwPlatformId == VER_PLATFORM_WIN32_NT){
SC_HANDLE sch;
if(!(sch = OpenSCManager(NULL, NULL, GENERIC_WRITE|SERVICE_START ))){
perror("Failed to open Service Manager");
RETURN(101);
}
if (!(sch = CreateService(sch, (LPCSTR)conf->stringtable[1], (LPCSTR)conf->stringtable[2], GENERIC_EXECUTE, SERVICE_WIN32_OWN_PROCESS, SERVICE_AUTO_START, SERVICE_ERROR_IGNORE, (char *)tmpbuf, NULL, NULL, NULL, NULL, NULL))){
perror("Failed to create service");
RETURN(103);
}
if (!StartService(sch, 0, NULL)) {
perror("Failed to start service");
RETURN(103);
}
}
else {
HKEY runsrv;
if(RegOpenKeyEx( HKEY_LOCAL_MACHINE,
"Software\\Microsoft\\Windows\\CurrentVersion\\RunServices",
0,
KEY_ALL_ACCESS,
&runsrv) != ERROR_SUCCESS){
perror("Failed to open registry");
RETURN(104);
}
if(RegSetValueEx( runsrv,
(LPCSTR)conf->stringtable[1],
0,
REG_EXPAND_SZ,
(BYTE *)tmpbuf,
(int)strlen((char *)tmpbuf)+1)!=ERROR_SUCCESS){
perror("Failed to set registry value");
RETURN(105);
}
}
return 0;
}
if((argc == 2 || argc == 3)&& !strcmp((char *)argv[1], "--remove")) {
if(osv.dwPlatformId == VER_PLATFORM_WIN32_NT){
SC_HANDLE sch;
if(!(sch = OpenSCManager(NULL, NULL, GENERIC_WRITE))){
perror("Failed to open Service Manager\n");
RETURN(106);
}
if (!(sch = OpenService(sch, (LPCSTR)conf->stringtable[1], DELETE))){
perror("Failed to open service");
RETURN(107);
}
if (!DeleteService(sch)){
perror("Failed to delete service");
RETURN(108);
}
}
else {
HKEY runsrv;
if(RegOpenKeyEx( HKEY_LOCAL_MACHINE,
"Software\\Microsoft\\Windows\\CurrentVersion\\RunServices",
0,
KEY_ALL_ACCESS,
&runsrv) != ERROR_SUCCESS){
perror("Failed to open registry");
RETURN(109);
}
if(RegDeleteValue(runsrv, (LPCSTR)conf->stringtable[1]) != ERROR_SUCCESS){
perror("Failed to clear registry");
RETURN(110);
}
}
RETURN(0);
}
if(argc==3 && !strcmp(argv[2], "--service")){
service = 1;
argc = 2;
}
#endif
#endif
conf->conffile = strdup((argc==2)?argv[1]:(char*)DEFAULTCONFIG);
if(conf->conffile && *conf->conffile != '-') {
fp = confopen();
#ifndef _WIN32
if(!fp) fp = stdin;
#endif
}
if(argc > 2 || !(fp)) {
fprintf(stderr, "Usage: %s [conffile]\n", argv[0]);
#ifdef _WIN32
fprintf(stderr, "\n\t%s --install [conffile]\n\tto install as service\n"
"\n\t%s --remove\n\tto remove service\n", argv[0], argv[0]);
#else
fprintf(stderr, "\n if conffile is missing, configuration is expected from stdin\n");
#endif
fprintf(stderr, "available socket options:\n\t%s\n", printopts("\n\t"));
fprintf(stderr, "\n%s %s\n%s\n", conf->stringtable[2], conf->stringtable[3], copyright);
return 1;
}
#ifdef __linux__
nsopen(); /* before a chroot in the configuration takes /proc away */
#endif
loginit(); /* standard output and error, syslog, before a chroot too */
#if defined _WIN32 && !defined _WINCE
if(service) logstdclose();
#endif
sysdnsinit(); /* the system's name servers and hosts, before a chroot too */
#ifdef WITH_ODBC
logtypeadd(&logtype_odbc);
#endif
#ifndef NORADIUS
logtypeadd(&logtype_radius);
#endif
_3proxy_mutex_init(&config_mutex);
_3proxy_mutex_init(&confref_mutex);
_3proxy_mutex_init(&listener_mutex);
reattachfunc = reattach;
listenertrafficfunc = listenertraffic;
_3proxy_mutex_init(&runningservices_mutex);
_3proxy_mutex_init(&bandlim_mutex);
_3proxy_mutex_init(&connlim_mutex);
_3proxy_mutex_init(&tc_mutex);
_3proxy_mutex_init(&tpool.mutex);
#ifndef NORADIUS
_3proxy_mutex_init(&rad_mutex);
#endif
if(_3proxy_sem_init_timed(serviceexited, 0, 0x7fffffff)){
fprintf(stderr, "semaphore init failed\n");
return 1;
}
#ifdef WITH_SSL
ssl_install();
#endif
#ifdef WITH_PCRE
pcre_install();
#endif
#ifdef WITH_TRANSPARENT
transparent_install();
#endif
#ifdef WITH_HTTPSRV
httpsrv_init();
#endif
confbegin();
initcommands();
/* numbered before it is read, as a reload numbers the new one */
conf->version++;
res = readconfig(fp);
if(!res) {
confdnscommit();
logcommit(conf);
}
resolvmode = conf->resolvmode;
/* readconfig() names the failing line, which an exit status would cut
to 8 bits: a failed configuration exits with 1 */
if(res) RETURN(1);
fclose(fp); fp = NULL;
#ifdef _WIN32
#ifndef _WINCE
if(service){
SERVICE_TABLE_ENTRY ste[] =
{
{ (LPSTR)conf->stringtable[1], (LPSERVICE_MAIN_FUNCTION)ServiceMain},
{ NULL, NULL }
};
if(!StartServiceCtrlDispatcher( ste ))cyclestep();
}
else
#endif
{
cyclestep();
}
#else
signal(SIGCONT, mysigpause);
signal(SIGTERM, mysigterm);
signal(SIGUSR1, mysigusr1);
signal(SIGPIPE, SIG_IGN);
cyclestep();
#endif
CLEARRETURN:
if(fp && fp != stdin) {fclose(fp); fp = NULL;}
return res;
}