mirror of
https://github.com/3proxy/3proxy.git
synced 2026-10-28 22:05:48 +08:00
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>
560 lines
13 KiB
C
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;
|
|
|
|
}
|