mirror of
https://github.com/3proxy/3proxy.git
synced 2026-10-28 22:05:48 +08:00
The string up to the first comma names an ODBC log; the user and the password are kept apart as its parameters, never shown, and do not tell logs apart. Another log or -l naming the same data source with other parameters gives them to the logger, which reconnects with them. The password is whatever follows the comma after the user. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
988 lines
27 KiB
C
988 lines
27 KiB
C
/*
|
|
3APA3A simplest proxy server
|
|
(c) 2002-2026 by Vladimir Dubrovin <vlad@3proxy.org>
|
|
|
|
please read License Agreement
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
#include "proxy.h"
|
|
_3proxy_mutex_t log_mutex; /* the list of loggers and what they are held by */
|
|
|
|
|
|
/* the connection lines with no connection of their own are formatted for */
|
|
struct clientparam logparam;
|
|
struct srvparam logsrv;
|
|
|
|
/* writes failed in a row before a log is closed and opened again, and how
|
|
long one that could not be opened again waits before the next attempt */
|
|
#define LOGERRORS 3
|
|
#define LOGRETRY 60
|
|
|
|
|
|
void dolog(struct clientparam * param, const unsigned char *s){
|
|
if(param && param->srv)param->srv->logfunc(param, s);
|
|
else logerror(s);
|
|
}
|
|
|
|
|
|
void clearstat(struct clientparam * param) {
|
|
/* the traffic logged, or dropped, counted for the listener first */
|
|
if(param->srv && listenertrafficfunc && (param->statssrv64 || param->statscli64))
|
|
(*listenertrafficfunc)(param);
|
|
|
|
#ifdef _WIN32
|
|
struct timeb tb;
|
|
|
|
ftime(&tb);
|
|
param->time_start = (time_t)tb.time;
|
|
param->msec_start = (unsigned)tb.millitm;
|
|
|
|
#else
|
|
struct timeval tv;
|
|
struct timezone tz;
|
|
gettimeofday(&tv, &tz);
|
|
|
|
param->time_start = (time_t)tv.tv_sec;
|
|
param->msec_start = (tv.tv_usec / 1000);
|
|
#endif
|
|
param->statscli64 = param->statssrv64 = param->nreads = param->nwrites =
|
|
param->nconnects = 0;
|
|
}
|
|
|
|
|
|
char months[12][4] = {
|
|
"Jan", "Feb", "Mar", "Apr", "May", "Jun",
|
|
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
|
|
};
|
|
|
|
|
|
int dobuf2(struct clientparam * param, unsigned char * buf, const unsigned char *s, const unsigned char * doublec, struct tm* tm, char * format){
|
|
int i, j;
|
|
int len;
|
|
time_t sec;
|
|
unsigned msec;
|
|
|
|
long timezone;
|
|
unsigned delay;
|
|
|
|
#ifdef _WIN32
|
|
struct timeb tb;
|
|
#else
|
|
struct timeval tv;
|
|
struct timezone tz;
|
|
#endif
|
|
|
|
if(!param) param = &logparam;
|
|
|
|
|
|
|
|
#ifdef _WIN32
|
|
ftime(&tb);
|
|
sec = (time_t)tb.time;
|
|
msec = (unsigned)tb.millitm;
|
|
timezone = tm->tm_isdst*60 - tb.timezone;
|
|
|
|
#else
|
|
gettimeofday(&tv, &tz);
|
|
|
|
sec = (time_t)tv.tv_sec;
|
|
msec = tv.tv_usec / 1000;
|
|
#ifdef _SOLARIS
|
|
timezone = -altzone / 60;
|
|
#else
|
|
timezone = tm->tm_gmtoff / 60;
|
|
#endif
|
|
#endif
|
|
|
|
delay = param->time_start?((unsigned) ((sec - param->time_start))*1000 + msec) - param->msec_start : 0;
|
|
*buf = 0;
|
|
for(i=0, j=0; format[j] && i < 4040; j++){
|
|
if(format[j] == '%' && format[j+1]){
|
|
j++;
|
|
switch(format[j]){
|
|
case '%':
|
|
buf[i++] = '%';
|
|
break;
|
|
case 'y':
|
|
sprintf((char *)buf+i, "%.2d", tm->tm_year%100);
|
|
i+=2;
|
|
break;
|
|
case 'Y':
|
|
sprintf((char *)buf+i, "%.4d", tm->tm_year+1900);
|
|
i+=4;
|
|
break;
|
|
case 'm':
|
|
sprintf((char *)buf+i, "%.2d", tm->tm_mon+1);
|
|
i+=2;
|
|
break;
|
|
case 'o':
|
|
sprintf((char *)buf+i, "%s", months[tm->tm_mon]);
|
|
i+=3;
|
|
break;
|
|
case 'd':
|
|
sprintf((char *)buf+i, "%.2d", tm->tm_mday);
|
|
i+=2;
|
|
break;
|
|
case 'H':
|
|
sprintf((char *)buf+i, "%.2d", tm->tm_hour);
|
|
i+=2;
|
|
break;
|
|
case 'M':
|
|
sprintf((char *)buf+i, "%.2d", tm->tm_min);
|
|
i+=2;
|
|
break;
|
|
case 'S':
|
|
sprintf((char *)buf+i, "%.2d", tm->tm_sec);
|
|
i+=2;
|
|
break;
|
|
case 't':
|
|
sprintf((char *)buf+i, "%.10u", (unsigned)sec);
|
|
i+=10;
|
|
break;
|
|
case 'b':
|
|
i+=sprintf((char *)buf+i, "%u", delay?(unsigned)(param->statscli64 * 1000./delay):0);
|
|
break;
|
|
case 'B':
|
|
i+=sprintf((char *)buf+i, "%u", delay?(unsigned)(param->statssrv64 * 1000./delay):0);
|
|
break;
|
|
case 'D':
|
|
i+=sprintf((char *)buf+i, "%u", delay);
|
|
break;
|
|
case '.':
|
|
sprintf((char *)buf+i, "%.3u", msec);
|
|
i+=3;
|
|
break;
|
|
case 'z':
|
|
sprintf((char *)buf+i, "%+.2ld%.2u", timezone / 60, (unsigned)(timezone%60));
|
|
i+=5;
|
|
break;
|
|
case 'U':
|
|
if(*param->username){
|
|
for(len = 0; i< 4000 && param->username[len]; len++){
|
|
buf[i] = param->username[len];
|
|
if(param->srv->nonprintable && (buf[i] < 0x20 || strchr((char *)param->srv->nonprintable, buf[i]))) buf[i] = param->srv->replace;
|
|
if(doublec && strchr((char *)doublec, buf[i])) {
|
|
buf[i+1] = buf[i];
|
|
i++;
|
|
}
|
|
i++;
|
|
}
|
|
}
|
|
else {
|
|
buf[i++] = '-';
|
|
}
|
|
break;
|
|
case 'n':
|
|
len = (int)strlen((char *)param->hostname);
|
|
if (len > 0 && !strchr((char *)param->hostname, ':')) for(len = 0; param->hostname[len] && i < 4000; len++, i++){
|
|
buf[i] = param->hostname[len];
|
|
if(param->srv->nonprintable && (buf[i] < 0x20 || strchr((char *)param->srv->nonprintable, buf[i]))) buf[i] = param->srv->replace;
|
|
if(doublec && strchr((char *)doublec, buf[i])) {
|
|
buf[i+1] = buf[i];
|
|
i++;
|
|
}
|
|
}
|
|
else {
|
|
buf[i++] = '[';
|
|
i += myinet_ntop(*SAFAMILY(¶m->req), SAADDR(¶m->req), (char *)buf + i, 64);
|
|
buf[i++] = ']';
|
|
}
|
|
break;
|
|
|
|
case 'N':
|
|
if(param->service <= MAX_SERVICE) {
|
|
len = (runconf->stringtable)? (int)strlen((char *)runconf->stringtable[SERVICES + param->service]) : 0;
|
|
if(len > 20) len = 20;
|
|
memcpy(buf+i, (len)?runconf->stringtable[SERVICES + param->service]:(unsigned char*)"-", (len)?len:1);
|
|
i += (len)?len:1;
|
|
}
|
|
break;
|
|
case 'E':
|
|
sprintf((char *)buf+i, "%.05d", param->res);
|
|
i += 5;
|
|
break;
|
|
case 'T':
|
|
if(s){
|
|
for(len = 0; i<4000 && s[len]; len++){
|
|
buf[i] = s[len];
|
|
if(param->srv->nonprintable && (buf[i] < 0x20 || strchr((char *)param->srv->nonprintable, buf[i]))) buf[i] = param->srv->replace;
|
|
if(doublec && strchr((char *)doublec, buf[i])) {
|
|
buf[i+1] = buf[i];
|
|
i++;
|
|
}
|
|
i++;
|
|
}
|
|
}
|
|
break;
|
|
case 'e':
|
|
i += myinet_ntop(*SAFAMILY(¶m->sinsl), SAADDR(¶m->sinsl), (char *)buf + i, 64);
|
|
break;
|
|
case 'i':
|
|
i += myinet_ntop(*SAFAMILY(¶m->sincl), SAADDR(¶m->sincl), (char *)buf + i, 64);
|
|
break;
|
|
case 'C':
|
|
i += myinet_ntop(*SAFAMILY(¶m->sincr), SAADDR(¶m->sincr), (char *)buf + i, 64);
|
|
break;
|
|
case 'R':
|
|
i += myinet_ntop(*SAFAMILY(¶m->sinsr), SAADDR(¶m->sinsr), (char *)buf + i, 64);
|
|
break;
|
|
case 'Q':
|
|
i += myinet_ntop(*SAFAMILY(¶m->req), SAADDR(¶m->req), (char *)buf + i, 64);
|
|
break;
|
|
case 'p':
|
|
sprintf((char *)buf+i, "%hu", ntohs(*SAPORT(¶m->srv->intsa)));
|
|
i += (int)strlen((char *)buf+i);
|
|
break;
|
|
case 'c':
|
|
sprintf((char *)buf+i, "%hu", ntohs(*SAPORT(¶m->sincr)));
|
|
i += (int)strlen((char *)buf+i);
|
|
break;
|
|
case 'r':
|
|
sprintf((char *)buf+i, "%hu", ntohs(*SAPORT(¶m->sinsr)));
|
|
i += (int)strlen((char *)buf+i);
|
|
break;
|
|
case 'q':
|
|
sprintf((char *)buf+i, "%hu", ntohs(*SAPORT(¶m->req)));
|
|
i += (int)strlen((char *)buf+i);
|
|
break;
|
|
case 'L':
|
|
sprintf((char *)buf+i, "%"PRIu64"", param->cycles);
|
|
i += (int)strlen((char *)buf+i);
|
|
break;
|
|
case 'I':
|
|
sprintf((char *)buf+i, "%"PRIu64"", param->statssrv64);
|
|
i += (int)strlen((char *)buf+i);
|
|
break;
|
|
case 'O':
|
|
sprintf((char *)buf+i, "%"PRIu64"", param->statscli64);
|
|
i += (int)strlen((char *)buf+i);
|
|
break;
|
|
case 'h':
|
|
sprintf((char *)buf+i, "%d", param->redirected);
|
|
i += (int)strlen((char *)buf+i);
|
|
break;
|
|
case '1':
|
|
case '2':
|
|
case '3':
|
|
case '4':
|
|
case '5':
|
|
case '6':
|
|
case '7':
|
|
case '8':
|
|
case '9':
|
|
{
|
|
int k, pmin=0, pmax=0;
|
|
for (k = j; isnumber(format[k]); k++);
|
|
if(format[k] == '-' && isnumber(format[k+1])){
|
|
pmin = atoi(format + j) - 1;
|
|
k++;
|
|
pmax = atoi(format + k) -1;
|
|
for (; isnumber(format[k]); k++);
|
|
j = k;
|
|
}
|
|
if(!s || format[k]!='T') break;
|
|
for(k = 0, len = 0; s[len] && i < 4000; len++){
|
|
if(isspace(s[len])){
|
|
k++;
|
|
while(isspace(s[len+1]))len++;
|
|
if(k == pmin) continue;
|
|
}
|
|
if(k>=pmin && k<=pmax) {
|
|
buf[i] = s[len];
|
|
if(param->srv->nonprintable && (buf[i] < 0x20 || strchr((char *)param->srv->nonprintable, buf[i]))) buf[i] = param->srv->replace;
|
|
if(doublec && strchr((char *)doublec, buf[i])) {
|
|
buf[i+1] = buf[i];
|
|
i++;
|
|
}
|
|
i++;
|
|
}
|
|
}
|
|
break;
|
|
|
|
}
|
|
case 0:
|
|
j--;
|
|
default:
|
|
buf[i++] = format[j];
|
|
}
|
|
}
|
|
else buf[i++] = format[j];
|
|
}
|
|
buf[i] = 0;
|
|
return i;
|
|
}
|
|
|
|
int timechanged (time_t oldtime, time_t newtime, ROTATION lt){
|
|
struct tm tmold;
|
|
struct tm *tm;
|
|
tm = localtime(&oldtime);
|
|
if(!tm) return 0;
|
|
tmold = *tm;
|
|
tm = localtime(&newtime);
|
|
if(!tm) return 0;
|
|
switch(lt){
|
|
case MINUTELY:
|
|
if(tm->tm_min != tmold.tm_min)return 1;
|
|
break;
|
|
case HOURLY:
|
|
if(tm->tm_hour != tmold.tm_hour)return 1;
|
|
break;
|
|
case DAILY:
|
|
if(tm->tm_yday != tmold.tm_yday)return 1;
|
|
break;
|
|
case MONTHLY:
|
|
if(tm->tm_mon != tmold.tm_mon)return 1;
|
|
break;
|
|
case ANNUALLY:
|
|
if(tm->tm_year != tmold.tm_year)return 1;
|
|
break;
|
|
case WEEKLY:
|
|
if(((newtime - oldtime) > (60*60*24*7))
|
|
|| tm->tm_wday < tmold.tm_wday
|
|
|| (tm->tm_wday == tmold.tm_wday && (newtime - oldtime) > (60*60*24*6))
|
|
)return 1;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
unsigned char * dologname (unsigned char *buf, unsigned char *name, const unsigned char *ext, ROTATION lt, time_t t) {
|
|
struct tm *ts;
|
|
static const char * const rot_fmt[] = {
|
|
[NONE] = "%s",
|
|
[ANNUALLY] = "%s.%04d",
|
|
[MONTHLY] = "%s.%04d.%02d",
|
|
[DAILY] = "%s.%04d.%02d.%02d",
|
|
[HOURLY] = "%s.%04d.%02d.%02d-%02d",
|
|
[MINUTELY] = "%s.%04d.%02d.%02d-%02d.%02d",
|
|
};
|
|
|
|
ts = localtime(&t);
|
|
if(strlen((char *)name) >= 4096){
|
|
*buf = 0;
|
|
return buf;
|
|
}
|
|
if(strchr((char *)name, '%')){
|
|
dobuf2(NULL, buf, NULL, NULL, ts, (char *)name);
|
|
}
|
|
else if(lt == WEEKLY){
|
|
t = t - (ts->tm_wday * (60*60*24));
|
|
ts = localtime(&t);
|
|
sprintf((char *)buf, "%s.%04d.%02d.%02d", name, ts->tm_year+1900, ts->tm_mon+1, ts->tm_mday);
|
|
}
|
|
else if((unsigned)lt < sizeof(rot_fmt)/sizeof(rot_fmt[0]) && rot_fmt[lt]){
|
|
sprintf((char *)buf, rot_fmt[lt], name, ts->tm_year+1900, ts->tm_mon+1, ts->tm_mday, ts->tm_hour, ts->tm_min);
|
|
}
|
|
if(ext){
|
|
strcat((char *)buf, ".");
|
|
strcat((char *)buf, (char *)ext);
|
|
}
|
|
return buf;
|
|
}
|
|
|
|
/* The line for a connection, its length: with nothing else done, as a log
|
|
formats it. */
|
|
int logbuf(struct clientparam * param, unsigned char * buf, const unsigned char *s, const unsigned char * doublec){
|
|
struct tm* tm;
|
|
char * format;
|
|
time_t t;
|
|
|
|
time(&t);
|
|
format = (param && param->srv && param->srv->logformat)?(char *)param->srv->logformat : DEFLOGFORMAT;
|
|
tm = (*format == 'G' || *format == 'g')?
|
|
gmtime(&t) : localtime(&t);
|
|
return dobuf2(param, buf, s, doublec, tm, format + 1);
|
|
}
|
|
|
|
/* The line, with the traffic counted and the statistics cleared, as logging
|
|
it does; for plugins. */
|
|
int dobuf(struct clientparam * param, unsigned char * buf, const unsigned char *s, const unsigned char * doublec){
|
|
int i;
|
|
int has_srv;
|
|
|
|
has_srv = param && param->srv;
|
|
if(has_srv && param->trafcountfunc)(*param->trafcountfunc)(param);
|
|
i = logbuf(param, buf, s, doublec);
|
|
if(has_srv) clearstat(param);
|
|
return i;
|
|
}
|
|
|
|
|
|
static int logstdoff = 0; /* a daemon or a service: no standard output and error */
|
|
static int logticking = 0; /* logtick() runs, and flushes the files */
|
|
static struct logger *loggers = NULL;
|
|
/* the last log configured, where errors go with no standard error */
|
|
static struct logger *logerrlast = NULL;
|
|
static const struct logtype *logtypes[8];
|
|
static int nlogtypes = 0;
|
|
|
|
static int logtext(struct logger *lg, struct clientparam *param, const unsigned char *s, unsigned char *buf){
|
|
return logbuf(param, buf, s, NULL);
|
|
}
|
|
|
|
static int matchnull(const unsigned char *selector){
|
|
return !strcmp((char *)selector, "/dev/null");
|
|
}
|
|
|
|
static const struct logtype logtype_none = {
|
|
"none", matchnull, NULL, NULL, NULL, NULL, NULL, 0, 1, 0
|
|
};
|
|
|
|
static int matchstdout(const unsigned char *selector){
|
|
return !*selector;
|
|
}
|
|
|
|
static int stdwrite(FILE *fp, const unsigned char *buf){
|
|
if(logstdoff) return 0;
|
|
if(fprintf(fp, "%s\n", buf) < 0) return 1;
|
|
fflush(fp);
|
|
return 0;
|
|
}
|
|
|
|
static int stdoutwrite(struct logger *lg, struct clientparam *param, const unsigned char *buf, int len){
|
|
return stdwrite(stdout, buf);
|
|
}
|
|
|
|
static int stderrwrite(struct logger *lg, struct clientparam *param, const unsigned char *buf, int len){
|
|
return stdwrite(stderr, buf);
|
|
}
|
|
|
|
static const struct logtype logtype_stdout = {
|
|
"stdout", matchstdout, NULL, logtext, stdoutwrite, NULL, NULL, 0, 1, 1
|
|
};
|
|
|
|
/* errors and warnings, and the lines of a log which fails */
|
|
static const struct logtype logtype_stderr = {
|
|
"stderr", NULL, NULL, logtext, stderrwrite, NULL, NULL, 0, 1, 1
|
|
};
|
|
|
|
struct logger lognull = {NULL, &logtype_none, (unsigned char *)"/dev/null"};
|
|
static struct logger logstdout = {NULL, &logtype_stdout, (unsigned char *)""};
|
|
static struct logger logstderr = {NULL, &logtype_stderr, (unsigned char *)"stderr"};
|
|
|
|
#ifndef _WIN32
|
|
/* openlog() is one for the process: each syslog logger sets its own ident
|
|
before it writes */
|
|
static _3proxy_mutex_t syslog_mutex;
|
|
static const char *syslogident = NULL;
|
|
|
|
static int matchsyslog(const unsigned char *selector){
|
|
return *selector == '@';
|
|
}
|
|
|
|
static int sysloginit(struct logger *lg){
|
|
_3proxy_mutex_lock(&syslog_mutex);
|
|
/* connected now: after a chroot there is no /dev/log to find */
|
|
syslogident = (char *)lg->selector + 1;
|
|
openlog(syslogident, LOG_PID | LOG_NDELAY, LOG_DAEMON);
|
|
_3proxy_mutex_unlock(&syslog_mutex);
|
|
return 0;
|
|
}
|
|
|
|
static int syslogwrite(struct logger *lg, struct clientparam *param, const unsigned char *buf, int len){
|
|
_3proxy_mutex_lock(&syslog_mutex);
|
|
if(syslogident != (char *)lg->selector + 1){
|
|
syslogident = (char *)lg->selector + 1;
|
|
openlog(syslogident, LOG_PID | LOG_NDELAY, LOG_DAEMON);
|
|
}
|
|
syslog(LOG_INFO, "%s", buf);
|
|
_3proxy_mutex_unlock(&syslog_mutex);
|
|
return 0;
|
|
}
|
|
|
|
static const struct logtype logtype_syslog = {
|
|
"syslog", matchsyslog, sysloginit, logtext, syslogwrite, NULL, NULL, 0, 1, 1
|
|
};
|
|
#endif
|
|
|
|
/* a file, named after the time it was opened if rotated */
|
|
#define LOGNAMESIZE 8192
|
|
|
|
static int matchfile(const unsigned char *selector){
|
|
return 1;
|
|
}
|
|
|
|
static int fileinit(struct logger *lg){
|
|
unsigned char *name;
|
|
FILE *fp;
|
|
int err;
|
|
|
|
if(!(name = malloc(LOGNAMESIZE))) return 21;
|
|
dologname(name, lg->selector, NULL, lg->rotation, lg->rotated);
|
|
errno = ENAMETOOLONG;
|
|
fp = *name? fopen((char *)name, "a") : NULL;
|
|
err = errno;
|
|
free(name);
|
|
errno = err;
|
|
if(!fp) return 1;
|
|
lg->data = fp;
|
|
return 0;
|
|
}
|
|
|
|
static int filewrite(struct logger *lg, struct clientparam *param, const unsigned char *buf, int len){
|
|
FILE *fp = (FILE *)lg->data;
|
|
|
|
if(!fp) return 1;
|
|
if(fprintf(fp, "%s\n", buf) < 0) return 1;
|
|
/* with nobody to flush it later, a standalone service */
|
|
if(!logticking && fflush(fp)) return 1;
|
|
return 0;
|
|
}
|
|
|
|
static void fileclose(struct logger *lg){
|
|
if(lg->data) fclose((FILE *)lg->data);
|
|
lg->data = NULL;
|
|
}
|
|
|
|
/* the seconds in a period of rotation */
|
|
static time_t rotperiod(ROTATION rt){
|
|
time_t t = 1;
|
|
|
|
switch(rt){
|
|
case ANNUALLY:
|
|
t = t * 12;
|
|
case MONTHLY:
|
|
t = t * 4;
|
|
case WEEKLY:
|
|
t = t * 7;
|
|
case DAILY:
|
|
t = t * 24;
|
|
case HOURLY:
|
|
t = t * 60;
|
|
case MINUTELY:
|
|
t = t * 60;
|
|
default:
|
|
break;
|
|
}
|
|
return t;
|
|
}
|
|
|
|
/* Once the period changes, the file is reopened under its new name, the
|
|
one rotate periods old removed, and the one just closed passed to the
|
|
archiver. */
|
|
static void filetick(struct logger *lg, time_t t){
|
|
unsigned char *name, *cmd;
|
|
const unsigned char *ext;
|
|
FILE *fp;
|
|
time_t period;
|
|
size_t len, add;
|
|
int i;
|
|
|
|
if(lg->data) fflush((FILE *)lg->data);
|
|
if(!lg->reopen && !timechanged(lg->rotated, t, lg->rotation)) return;
|
|
if(!(name = malloc(LOGNAMESIZE))) return;
|
|
dologname(name, lg->selector, NULL, lg->rotation, t);
|
|
/* the old one kept if the new one cannot be opened */
|
|
if(*name && (fp = fopen((char *)name, "a"))){
|
|
if(lg->data) fclose((FILE *)lg->data);
|
|
lg->data = fp;
|
|
lg->failed = lg->errors = 0;
|
|
}
|
|
lg->rotated = t;
|
|
lg->reopen = 0;
|
|
ext = (lg->archiver && lg->archiverc > 1)? lg->archiver[1] : NULL;
|
|
/* a name is at most 4096 bytes, its rotation suffix and ext to come */
|
|
if(lg->rotation == NONE || !lg->rotate || (ext && strlen((char *)ext) > 255)){
|
|
free(name);
|
|
return;
|
|
}
|
|
period = rotperiod(lg->rotation);
|
|
dologname(name, lg->selector, ext, lg->rotation, (lg->rotated - period * lg->rotate));
|
|
remove((char *)name);
|
|
if(lg->archiver && (cmd = malloc(4 * LOGNAMESIZE))){
|
|
*cmd = 0;
|
|
len = 0;
|
|
for(i = 2; i < lg->archiverc && len < 512; i++){
|
|
cmd[len++] = ' ';
|
|
cmd[len] = 0;
|
|
if(!strcmp((char *)lg->archiver[i], "%A") || !strcmp((char *)lg->archiver[i], "%F")){
|
|
dologname(name, lg->selector, (lg->archiver[i][1] == 'A')? ext : NULL, lg->rotation, (lg->rotated - period));
|
|
add = strlen((char *)name);
|
|
if(len + add + 3 >= 4 * LOGNAMESIZE) break;
|
|
cmd[len++] = '\"';
|
|
memcpy(cmd + len, name, add);
|
|
len += add;
|
|
cmd[len++] = '\"';
|
|
}
|
|
else {
|
|
add = strlen((char *)lg->archiver[i]);
|
|
if(len + add + 1 >= 4 * LOGNAMESIZE) break;
|
|
memcpy(cmd + len, lg->archiver[i], add);
|
|
len += add;
|
|
}
|
|
cmd[len] = 0;
|
|
}
|
|
if(len > 1 && system((char *)cmd + 1)){}
|
|
free(cmd);
|
|
}
|
|
free(name);
|
|
}
|
|
|
|
static const struct logtype logtype_file = {
|
|
"file", matchfile, fileinit, logtext, filewrite, fileclose, filetick, 0, 0, 1
|
|
};
|
|
|
|
|
|
/* The types a selector is matched against, the file last: ODBC and RADIUS,
|
|
which only 3proxy itself has, are added by it. */
|
|
void logtypeadd(const struct logtype *type){
|
|
if(nlogtypes < (int)(sizeof(logtypes)/sizeof(logtypes[0]))) logtypes[nlogtypes++] = type;
|
|
}
|
|
|
|
static const struct logtype *logmatch(const unsigned char *selector){
|
|
static const struct logtype * const builtin[] = {
|
|
&logtype_none, &logtype_stdout,
|
|
#ifndef _WIN32
|
|
&logtype_syslog,
|
|
#endif
|
|
};
|
|
int i;
|
|
|
|
for(i = 0; i < (int)(sizeof(builtin)/sizeof(builtin[0])); i++)
|
|
if(builtin[i]->match(selector)) return builtin[i];
|
|
for(i = 0; i < nlogtypes; i++)
|
|
if(logtypes[i]->match && logtypes[i]->match(selector)) return logtypes[i];
|
|
return &logtype_file;
|
|
}
|
|
|
|
/* The standard loggers, before anything is logged. */
|
|
void loginit(void){
|
|
static int done = 0;
|
|
|
|
if(done) return;
|
|
done = 1;
|
|
_3proxy_mutex_init(&log_mutex);
|
|
_3proxy_mutex_init(&lognull.mutex);
|
|
_3proxy_mutex_init(&logstdout.mutex);
|
|
_3proxy_mutex_init(&logstderr.mutex);
|
|
lognull.next = &logstdout;
|
|
loggers = &lognull;
|
|
srvinit(&logsrv, &logparam);
|
|
/* not a service: it holds no configuration and no log, and the first
|
|
configuration is never freed, only what it holds */
|
|
logsrv.config = &confboot;
|
|
logsrv.logger = NULL;
|
|
#ifndef _WIN32
|
|
_3proxy_mutex_init(&syslog_mutex);
|
|
/* kept for the life of the process, as every syslog logger is */
|
|
(void)logget((const unsigned char *)"@3proxy", NONE);
|
|
#endif
|
|
}
|
|
|
|
/* The length of the name in a log string: for a type with parameters up to
|
|
the comma they follow. */
|
|
static size_t lognamelen(const struct logtype *type, const unsigned char *selector){
|
|
return type->params? strcspn((char *)selector, ",") : strlen((char *)selector);
|
|
}
|
|
|
|
/* Parameters which differ from those a logger has: given to it, and the
|
|
logger opened again with them. Called holding a reference. */
|
|
static void logsetparams(struct logger *lg, const unsigned char *params){
|
|
unsigned char *p = NULL;
|
|
|
|
if(params && !(p = (unsigned char *)strdup((char *)params))) return;
|
|
_3proxy_mutex_lock(&lg->mutex);
|
|
if(lg->params) free(lg->params);
|
|
lg->params = p;
|
|
if(lg->type->close) lg->type->close(lg);
|
|
lg->errors = 0;
|
|
lg->failed = 0;
|
|
if(lg->type->init && lg->type->init(lg)){
|
|
lg->failed = 1;
|
|
lg->failtime = time(NULL);
|
|
}
|
|
_3proxy_mutex_unlock(&lg->mutex);
|
|
}
|
|
|
|
/* The logger named so, opened if none is: held until logput(). NULL, with
|
|
errno set, if it cannot be opened. A logger found with other parameters
|
|
takes the new ones. */
|
|
struct logger *logget(const unsigned char *selector, ROTATION rotation){
|
|
struct logger *lg;
|
|
const struct logtype *type;
|
|
const unsigned char *params;
|
|
size_t len;
|
|
int err = 0, changed;
|
|
|
|
_3proxy_mutex_lock(&log_mutex);
|
|
type = logmatch(selector);
|
|
len = lognamelen(type, selector);
|
|
params = selector[len]? selector + len + 1 : NULL;
|
|
for(lg = loggers; lg; lg = lg->next){
|
|
if(lg->type == type && !strncmp((char *)lg->selector, (char *)selector, len) && !lg->selector[len]){
|
|
lg->refs++;
|
|
changed = (params && lg->params)? strcmp((char *)params, (char *)lg->params) : (params != NULL || lg->params != NULL);
|
|
_3proxy_mutex_unlock(&log_mutex);
|
|
if(changed) logsetparams(lg, params);
|
|
return lg;
|
|
}
|
|
}
|
|
if(!(lg = calloc(1, sizeof(*lg))) || !(lg->selector = malloc(len + 1)) ||
|
|
(params && !(lg->params = (unsigned char *)strdup((char *)params)))){
|
|
if(lg){
|
|
if(lg->selector) free(lg->selector);
|
|
free(lg);
|
|
}
|
|
_3proxy_mutex_unlock(&log_mutex);
|
|
errno = ENOMEM;
|
|
return NULL;
|
|
}
|
|
memcpy(lg->selector, selector, len);
|
|
lg->selector[len] = 0;
|
|
lg->type = type;
|
|
lg->rotation = rotation;
|
|
lg->rotated = time(NULL);
|
|
_3proxy_mutex_init(&lg->mutex);
|
|
/* < 0: not to be reached now, and tried again later */
|
|
if(lg->type->init && (err = lg->type->init(lg)) < 0){
|
|
lg->failed = 1;
|
|
lg->failtime = lg->rotated;
|
|
}
|
|
else if(err){
|
|
err = errno;
|
|
_3proxy_mutex_unlock(&log_mutex);
|
|
_3proxy_mutex_destroy(&lg->mutex);
|
|
if(lg->params) free(lg->params);
|
|
free(lg->selector);
|
|
free(lg);
|
|
errno = err;
|
|
return NULL;
|
|
}
|
|
lg->refs = 1;
|
|
lg->next = loggers;
|
|
loggers = lg;
|
|
_3proxy_mutex_unlock(&log_mutex);
|
|
return lg;
|
|
}
|
|
|
|
void logref(struct logger *lg){
|
|
if(!lg) return;
|
|
_3proxy_mutex_lock(&log_mutex);
|
|
lg->refs++;
|
|
_3proxy_mutex_unlock(&log_mutex);
|
|
}
|
|
|
|
static void logfreearchiver(struct logger *lg){
|
|
int i;
|
|
|
|
if(!lg->archiver) return;
|
|
for(i = 0; i < lg->archiverc; i++) if(lg->archiver[i]) free(lg->archiver[i]);
|
|
free(lg->archiver);
|
|
lg->archiver = NULL;
|
|
lg->archiverc = 0;
|
|
}
|
|
|
|
/* The last to let go of a logger closes it, unless it is a standard one. */
|
|
void logput(struct logger *lg){
|
|
struct logger **lp;
|
|
int gone = 0;
|
|
|
|
if(!lg) return;
|
|
_3proxy_mutex_lock(&log_mutex);
|
|
if(--lg->refs <= 0 && !lg->type->standard){
|
|
for(lp = &loggers; *lp; lp = &(*lp)->next){
|
|
if(*lp == lg){
|
|
*lp = lg->next;
|
|
gone = 1;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
_3proxy_mutex_unlock(&log_mutex);
|
|
if(!gone) return;
|
|
if(lg->type->close) lg->type->close(lg);
|
|
_3proxy_mutex_destroy(&lg->mutex);
|
|
logfreearchiver(lg);
|
|
if(lg->params) free(lg->params);
|
|
free(lg->selector);
|
|
free(lg);
|
|
}
|
|
|
|
/* Where errors go: standard error, or for a daemon the last log
|
|
configured, which a text one has to be. Held until logput(). */
|
|
static struct logger *logerrget(void){
|
|
struct logger *lg;
|
|
|
|
_3proxy_mutex_lock(&log_mutex);
|
|
lg = logstdoff? logerrlast : &logstderr;
|
|
if(lg) lg->refs++;
|
|
_3proxy_mutex_unlock(&log_mutex);
|
|
return lg;
|
|
}
|
|
|
|
void logerrset(struct logger *lg){
|
|
struct logger *old;
|
|
|
|
if(lg && !lg->type->text) lg = NULL;
|
|
_3proxy_mutex_lock(&log_mutex);
|
|
if(lg) lg->refs++;
|
|
old = logerrlast;
|
|
logerrlast = lg;
|
|
_3proxy_mutex_unlock(&log_mutex);
|
|
logput(old);
|
|
}
|
|
|
|
/* For a daemon or a service: standard output is no log, and errors go to
|
|
the last log configured. */
|
|
void logstdclose(void){
|
|
if(logstdoff) return;
|
|
fflush(stdout);
|
|
fflush(stderr);
|
|
logstdoff = 1;
|
|
}
|
|
|
|
static void logwrite(struct logger *lg, struct clientparam *param, const unsigned char *buf, int len);
|
|
|
|
/* The line of a log which failed, to standard error, or to where errors go.
|
|
Not to another log of the same: once there it would come back. */
|
|
static void logfallback(struct logger *lg, struct clientparam *param, const unsigned char *buf, int len){
|
|
struct logger *to;
|
|
|
|
if(len < 0) return;
|
|
if(!(to = logerrget())) return;
|
|
if(to != lg && !to->failed) logwrite(to, param, buf, len);
|
|
logput(to);
|
|
}
|
|
|
|
/* The part of a log string fit to be shown: its name, without parameters
|
|
(an ODBC user and password), and at most 200 bytes. */
|
|
size_t logselen(const unsigned char *selector){
|
|
size_t len = lognamelen(logmatch(selector), selector);
|
|
|
|
return len > 200? 200 : len;
|
|
}
|
|
|
|
static void logwrite(struct logger *lg, struct clientparam *param, const unsigned char *buf, int len){
|
|
int res = 0, failed, report = 0;
|
|
time_t t;
|
|
|
|
_3proxy_mutex_lock(&lg->mutex);
|
|
if(lg->failed && (t = time(NULL)) - lg->failtime >= LOGRETRY){
|
|
if(lg->type->init && lg->type->init(lg)) lg->failtime = t;
|
|
else lg->failed = lg->errors = 0;
|
|
}
|
|
if(!(failed = lg->failed) && lg->type->write){
|
|
if((res = lg->type->write(lg, param, buf, len))){
|
|
/* open again what keeps failing, and if it cannot be
|
|
opened, write elsewhere for a while */
|
|
if(++lg->errors >= LOGERRORS){
|
|
lg->errors = 0;
|
|
if(lg->type->close) lg->type->close(lg);
|
|
if(lg->type->init && lg->type->init(lg)){
|
|
lg->failed = 1;
|
|
lg->failtime = time(NULL);
|
|
report = 1;
|
|
}
|
|
}
|
|
}
|
|
else lg->errors = 0;
|
|
}
|
|
_3proxy_mutex_unlock(&lg->mutex);
|
|
if(failed || res) logfallback(lg, param, buf, len);
|
|
if(report){
|
|
unsigned char msg[300];
|
|
|
|
sprintf((char *)msg, "Log %.200s failed, its lines go to the error log", lg->selector);
|
|
logerror(msg);
|
|
}
|
|
}
|
|
|
|
/* The log function of the services, unless a plugin puts its own before
|
|
it: the traffic counted, the line written to the service's log, the
|
|
statistics cleared. */
|
|
void logdo(struct clientparam * param, const unsigned char *s){
|
|
struct logger *lg = param->srv? param->srv->logger : NULL;
|
|
int len = -1;
|
|
|
|
if(param->trafcountfunc)(*param->trafcountfunc)(param);
|
|
if(lg && !param->nolog && lg->type->write){
|
|
if(lg->type->format) len = lg->type->format(lg, param, s, param->tmpbuf);
|
|
if(len >= 0 || !lg->type->format) logwrite(lg, param, param->tmpbuf, len);
|
|
}
|
|
clearstat(param);
|
|
}
|
|
|
|
/* An error or a warning, with no connection: where errors go. */
|
|
void logerror(const unsigned char *s){
|
|
struct logger *lg;
|
|
unsigned char *buf;
|
|
int len;
|
|
|
|
if(!(lg = logerrget())) return;
|
|
if(lg->type->format && lg->type->write && (buf = malloc(CLIENTTMPBUFSIZE))){
|
|
len = lg->type->format(lg, &logparam, s, buf);
|
|
if(len >= 0) logwrite(lg, &logparam, buf, len);
|
|
free(buf);
|
|
}
|
|
logput(lg);
|
|
}
|
|
|
|
/* What a configuration says of its last log, once it is in force: the
|
|
rotation, and that errors go there for a daemon. */
|
|
void logcommit(struct extparam *c){
|
|
struct logger *lg = c->logger;
|
|
unsigned char **archiver = NULL;
|
|
int i, archiverc = 0;
|
|
|
|
logerrset(lg);
|
|
if(!lg || lg->type != &logtype_file) return;
|
|
if(c->archiver && c->archiverc > 0 && (archiver = calloc(c->archiverc, sizeof(*archiver)))){
|
|
for(archiverc = 0; archiverc < c->archiverc; archiverc++){
|
|
if(!(archiver[archiverc] = (unsigned char *)strdup((char *)c->archiver[archiverc]))) break;
|
|
}
|
|
if(archiverc < c->archiverc){
|
|
for(i = 0; i < archiverc; i++) free(archiver[i]);
|
|
free(archiver);
|
|
archiver = NULL;
|
|
archiverc = 0;
|
|
}
|
|
}
|
|
_3proxy_mutex_lock(&log_mutex);
|
|
_3proxy_mutex_lock(&lg->mutex);
|
|
if(lg->rotation != c->logtype) lg->reopen = 1;
|
|
lg->rotation = c->logtype;
|
|
lg->rotate = c->rotate;
|
|
logfreearchiver(lg);
|
|
lg->archiver = archiver;
|
|
lg->archiverc = archiverc;
|
|
_3proxy_mutex_unlock(&lg->mutex);
|
|
_3proxy_mutex_unlock(&log_mutex);
|
|
}
|
|
|
|
/* Rotation and flushing, for every logger: from the main loop. */
|
|
void logtick(time_t t){
|
|
struct logger *lg;
|
|
|
|
logticking = 1;
|
|
_3proxy_mutex_lock(&log_mutex);
|
|
for(lg = loggers; lg; lg = lg->next){
|
|
if(!lg->type->tick) continue;
|
|
_3proxy_mutex_lock(&lg->mutex);
|
|
lg->type->tick(lg, t);
|
|
_3proxy_mutex_unlock(&lg->mutex);
|
|
}
|
|
_3proxy_mutex_unlock(&log_mutex);
|
|
}
|
|
|
|
/* Closes every log as the process stops, its services done. */
|
|
void logfinish(void){
|
|
struct logger *lg;
|
|
|
|
_3proxy_mutex_lock(&log_mutex);
|
|
for(lg = loggers; lg; lg = lg->next){
|
|
if(!lg->type->close) continue;
|
|
_3proxy_mutex_lock(&lg->mutex);
|
|
lg->type->close(lg);
|
|
lg->failed = 1;
|
|
lg->failtime = time(NULL);
|
|
_3proxy_mutex_unlock(&lg->mutex);
|
|
}
|
|
_3proxy_mutex_unlock(&log_mutex);
|
|
}
|