3proxy/src/webadmin.c
Vladimir Dubrovin 21ba6e9d0c Reload the configuration atomically
A reload reads the file into a new configuration, allocated from the
defaults, and prepares its services without starting them. Only if the
whole file is read without an error does it come into force: the old
configuration's services stop accepting, the new ones start. On an
error everything prepared is dropped and the old configuration, which
never stopped, stays in force.

Each service holds its configuration until it is freed, after its last
client; a configuration no longer in force is released when the last
holder lets go (confhold/confput). freeconf() now only releases what a
configuration holds, touching nothing global.

What runs without a service of its own reads the configuration in
force, runconf, rather than conf, which a reload points at the one
being read: the resolver, which holds it while it waits (a client
resolves with its own service's), the log, the thread pool, the admin
pages, which walk the services under config_mutex, and the signal
handlers.

The users table, havelog and the resolver choice are per configuration
now, the resolver applied when the configuration comes into force, so
a refused file leaves the users as they were. The log file, the
configuration file name, the string table, daemon mode and the filter
list belong to the process and are handed from one configuration to
the next. Settings left out of a new file go back to their defaults,
where before they kept the old values.

The gap between the old and the new listening sockets remains: a
connection queued on an old service's socket when it closes is reset.
Handing the socket over is the next phase.

A test case reloads a broken file (nothing changes, alice still gets
in, bob from the refused file does not) and a good one (bob only, the
service on its new port, the old one closed, a download through the
old service finishing whole). 40 reloads, a third refused, under load
show no memory errors under AddressSanitizer.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 20:15:14 +03:00

535 lines
14 KiB
C

/*
3APA3A simplest proxy server
(c) 2002-2026 by Vladimir Dubrovin <vlad@3proxy.org>
please read License Agreement
*/
#include "proxy.h"
#ifdef WITH_HTTPSRV
#define RETURN(xxx) { param->res = xxx; goto CLEANRET; }
#define LINESIZE 65536
FILE * confopen();
extern void decodeurl(unsigned char *s, int filter);
struct printparam {
char buf[1024];
int inbuf;
struct clientparam *cp;
};
static void stdpr(struct printparam* pp, char *buf, int inbuf){
if((pp->inbuf + inbuf > 1024) || !buf) {
socksend(pp->cp, pp->cp->clisock, (unsigned char *)pp->buf, pp->inbuf, pp->cp->srv->timeouts[STRING_S]);
pp->inbuf = 0;
if(!buf) return;
}
if(inbuf >= 1000){
socksend(pp->cp, pp->cp->clisock, (unsigned char *)buf, inbuf, pp->cp->srv->timeouts[STRING_S]);
}
else {
memcpy(pp->buf + pp->inbuf, buf, inbuf);
pp->inbuf += inbuf;
}
}
static void stdcbf(void *cb, char *buf, int inbuf){
int delay = 0;
int i;
for(i = 0; i < inbuf; i++){
switch(buf[i]){
case '&':
if(delay){
stdpr((struct printparam*)cb, buf+i-delay, delay);
delay = 0;
}
stdpr((struct printparam*)cb, "&amp;", 5);
break;
case '<':
if(delay){
stdpr((struct printparam*)cb, buf+i-delay, delay);
delay = 0;
}
stdpr((struct printparam*)cb, "&lt;", 4);
break;
case '>':
if(delay){
stdpr((struct printparam*)cb, buf+i-delay, delay);
delay = 0;
}
stdpr((struct printparam*)cb, "&gt;", 4);
break;
default:
delay++;
break;
}
}
if(delay){
stdpr((struct printparam*)cb, buf+i-delay, delay);
}
}
/*
static char * templateprint(struct printparam* pp, int *level, struct dictionary *dict, char * template){
char *s, *s2;
for(; template && *template; ){
if(!( s = strchr(template, '<'))){
stdpr(pp, template, (int)strlen(template));
return template + strlen(template);
}
if(s[1] != '%' || s[2] == '%'){
stdpr(pp, template, (int)(s - template) + 1);
template = s + 1;
continue;
}
if(s[2] == '/' && (s2 = strchr(s + 2, '>')) && *(s2 - 1) == '%'){
if(--*level < 0) return NULL;
return s2 + 1;
}
}
return template;
}
*/
static void printstr(struct printparam* pp, char* str){
stdpr(pp, str, str?(int)strlen(str):0);
}
static void printval(void *value, int type, int level, struct printparam* pp){
struct node pn, cn;
struct property *p;
int i, pi;
pn.iteration = NULL;
pn.parent = NULL;
pn.type = type;
pn.value = value;
printstr(pp, "<item>");
for(pi = 0; pi < (int)datatypes[type].properties_count; ) {
p = datatypes[type].properties + pi;
cn.iteration = NULL;
cn.parent = &pn;
cn.type = p->type;
cn.value = (*p->e_f)(&pn);
if(cn.value){
for(i = 0; i < level; i++) printstr(pp, "\t");
if(strcmp(p->name, "next")){
printstr(pp, "<parameter>");
printstr(pp, "<name>");
printstr(pp, p->name);
printstr(pp, "</name>");
printstr(pp, "<type>");
printstr(pp, datatypes[p->type].type);
printstr(pp, "</type>");
printstr(pp, "<description>");
printstr(pp, p->description);
printstr(pp, "</description>");
}
if(datatypes[p->type].p_f){
printstr(pp, "<value><![CDATA[");
(*datatypes[p->type].p_f)(&cn, stdcbf, pp);
printstr(pp, "]]></value>\n");
printstr(pp, "</parameter>");
}
else {
if(!strcmp(p->name, "next")){
/* printstr(pp, "<!-- -------------------- -->\n"); */
printstr(pp, "</item>\n<item>");
pi = 0;
pn.value = value = cn.value;
continue;
}
else {
printstr(pp, "\n");
printval(cn.value, cn.type, level+1, pp);
printstr(pp, "</parameter>");
}
}
}
pi++;
}
printstr(pp, "</item>");
}
char * admin_stringtable[]={
"HTTP/1.0 401 Authentication Required\r\n"
"WWW-Authenticate: Basic realm=\"proxy\"\r\n"
"Connection: close\r\n"
"Content-type: text/html; charset=us-ascii\r\n"
"\r\n"
"<html><head><title>401 Authentication Required</title></head>\r\n"
"<body><h2>401 Authentication Required</h2><h3>Access to requested resource disallowed by administrator or you need valid username/password to use this resource</h3></body></html>\r\n",
"HTTP/1.0 200 OK\r\n"
"Connection: close\r\n"
"Expires: Thu, 01 Dec 1994 16:00:00 GMT\r\n"
"Cache-Control: no-cache\r\n"
"Content-type: text/html\r\n"
"\r\n"
"<http><head><title>%s configuration page</title></head>\r\n"
"<table width=\'100%%\' border=\'0\'>\r\n"
"<tr><td width=\'150\' valign=\'top\'>\r\n"
"<h2>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;"
"&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</h2>\r\n"
"<A HREF=\'/C'>Counters</A><br>\r\n"
"<A HREF=\'/R'>Reload</A><br>\r\n"
"<A HREF=\'/S'>Running Services</A>\r\n"
"</td><td>"
"<h2>%s %s configuration</h2>",
"HTTP/1.0 200 OK\r\n"
"Connection: close\r\n"
"Cache-Control: no-cache\r\n"
"Content-type: text/xml; charset=utf-8\r\n"
"Content-disposition: inline; filename=running.xml\r\n"
"\r\n"
"<?xml version=\"1.0\"?>\r\n"
"<?xml-stylesheet href=\"/SX\" type=\"text/css\"?>\r\n"
"<services>\r\n"
"<description>Services currently running and connected clients</description>\r\n",
"</services>\r\n",
"HTTP/1.0 200 OK\r\n"
"Connection: close\r\n"
"Cache-Control: no-cache\r\n"
"Content-type: text/css\r\n"
"\r\n"
"services {\r\n"
" display: block;\r\n"
" margin: 10px auto 10px auto;\r\n"
" width: 80%;\r\n"
" background: black;\r\n"
" font-family: sans-serif;\r\n"
" font-size: small;\r\n"
" color: silver;\r\n"
" }\r\n"
"item {\r\n"
" display: block;\r\n"
" margin-bottom: 10px;\r\n"
" border: 2px solid #CCC;\r\n"
" padding: 10px;\r\n"
" spacing: 2px;\r\n"
" }\r\n"
"parameter {\r\n"
" display: block;\r\n"
" padding: 2px;\r\n"
" margin-top: 10px;\r\n"
" border: 1px solid grey;\r\n"
" background: #EEE;\r\n"
" color: black;\r\n"
" }\r\n"
"name {\r\n"
" display: inline;\r\n"
" float: left;\r\n"
" margin-right: 5px;\r\n"
" font-weight: bold;\r\n"
" }\r\n"
"type {\r\n"
" display: inline;\r\n"
" font-size: x-small;\r\n"
" margin-right: 5px;\r\n"
" color: #666;\r\n"
" white-space: nowrap;\r\n"
" font-style: italic;\r\n"
" }\r\n"
"description {\r\n"
" display: inline;\r\n"
" margin-right: 5px;\r\n"
" white-space: nowrap;\r\n"
" }\r\n"
"value {\r\n"
" display: block;\r\n"
" margin-right: 5px;\r\n"
" }\r\n",
"<br /><br /><br /><br /><br /><br /><br /><br /><br /><br /><br />\r\n"
"<pre><font size=\'-2\'><b>"
COPYRIGHT
"</b></font>\r\n"
"</td></tr></table></body></html>",
"<h3>Counters</h3>\r\n"
"<table border = \'1\'>\r\n"
"<tr align=\'center\'><td>Action</td><td>#/Desc</td><td>Active</td>"
"<td>Users</td><td>Source Address</td><td>Destination Address</td>"
"<td>Port</td>"
"<td>Limit</td><td>Units</td><td>Value</td>"
"<td>Reset</td><td>Updated</td><td>Position</td></tr>\r\n",
"</table>\r\n",
NULL
};
#define authreq admin_stringtable[0]
#define ok admin_stringtable[1]
#define xml admin_stringtable[2]
#define postxml admin_stringtable[3]
#define style admin_stringtable[4]
#define tail admin_stringtable[5]
#define counters admin_stringtable[6]
#define counterstail admin_stringtable[7]
static int printportlist(char *buf, int bufsize, struct portlist* pl, char * delim){
int printed = 0;
for(; pl; pl = pl->next){
if(printed > (bufsize - 64)) break;
if(pl->startport != pl->endport)
printed += sprintf(buf+printed, "%hu-%hu%s", pl->startport, pl->endport, pl->next?delim:"");
else {
/*
struct servent *se=NULL;
if(pl->startport)se = getservbyport((int)ntohs(pl->startport), NULL);
printed += sprintf(buf+printed, "%hu(%s)%s", pl->startport, se?se->s_name:"unknown", pl->next?delim:"");
*/
printed += sprintf(buf+printed, "%hu%s", pl->startport, pl->next?delim:"");
}
if(printed > (bufsize - 64)) {
printed += sprintf(buf+printed, "...");
break;
}
}
return printed;
}
static int printuserlist(char *buf, int bufsize, struct userlist* ul, char * delim){
int printed = 0;
for(; ul; ul = ul->next){
if(printed > (bufsize - 64)) break;
printed += sprintf(buf+printed, "%s%s", ul->user, ul->next?delim:"");
if(printed > (bufsize - 64)) {
printed += sprintf(buf+printed, "...");
break;
}
}
return printed;
}
int printiple(char *buf, struct iplist* ipl);
static int printiplist(char *buf, int bufsize, struct iplist* ipl, char * delim){
int printed = 0;
for(; ipl; ipl = ipl->next){
if(printed > (bufsize - 128)) break;
printed += printiple(buf+printed, ipl);
if(printed > (bufsize - 128)) {
printed += sprintf(buf+printed, "...");
break;
}
}
return printed;
}
/* The admin pages are http operations: the service, request parsing and
authorization belong to httpsrv, and what is left here is the page itself.
A star in the url carries the selector the pages used to read out of the
path, so /C with a star gives D2 or S2 to disable or enable a counter. */
static char * admin_open(struct printparam *pp, struct clientparam *param)
{
char *buf;
pp->inbuf = 0;
pp->cp = param;
buf = malloc(LINESIZE);
if(!buf) return NULL;
sprintf(buf, ok, runconf->stringtable?(char *)runconf->stringtable[2]:"3proxy",
runconf->stringtable?(char *)runconf->stringtable[2]:"3[APA3A] tiny proxy",
runconf->stringtable?(char *)runconf->stringtable[3]:"");
printstr(pp, buf);
return buf;
}
static void admin_close(struct printparam *pp, char *buf)
{
printstr(pp, tail);
printstr(pp, NULL);
if(buf) free(buf);
}
int op_admin(struct httpreq *r, const unsigned char *params)
{
struct printparam pp;
char *buf;
buf = admin_open(&pp, r->param);
if(!buf) return 1;
printstr(&pp, (char *)runconf->stringtable[WEBBANNERS]);
admin_close(&pp, buf);
return 0;
}
int op_admin_counters(struct httpreq *r, const unsigned char *params)
{
struct clientparam *param = r->param;
struct printparam pp;
char *buf;
const char *sel;
int limited;
limited = param->srv->s_option;
/* In limited mode a counter may be looked at but not switched. */
sel = limited? "" : r->path + r->globstart;
buf = admin_open(&pp, param);
if(!buf) return 1;
printstr(&pp, counters);
{
struct trafcount *cp;
int num = 0;
for(cp = runconf->trafcounter; cp; cp = cp->next, num++){
int inbuf = 0;
if(cp->ace && (limited || param->redirected)){
if(!ACLmatches(cp->ace, param))continue;
}
if(sel[0] == 'S' && atoi(sel+1) == num) cp->disabled=0;
if(sel[0] == 'D' && atoi(sel+1) == num) cp->disabled=1;
inbuf += sprintf(buf, "<tr><td>%s</td><td>", cp->ace?aceaction(cp->ace->action):"-");
if(cp->number || cp->comment)
inbuf += sprintf(buf+inbuf, "%d/%s</td>" , cp->number,
(cp->comment)?cp->comment:"&nbsp;");
else
inbuf += sprintf(buf+inbuf, " - </td>");
inbuf += sprintf(buf+inbuf, "<td><A HREF=\'/C%c%d\'>%s</A></td><td>",
(cp->disabled)?'S':'D',
num,
(cp->disabled)?"NO":"YES"
);
if(!cp->ace || !cp->ace->users){
inbuf += sprintf(buf+inbuf, "<center>ANY</center>");
}
else {
inbuf += printuserlist(buf+inbuf, LINESIZE-800, cp->ace->users, ",<br />\r\n");
}
inbuf += sprintf(buf+inbuf, "</td><td>");
if(!cp->ace || !cp->ace->src){
inbuf += sprintf(buf+inbuf, "<center>ANY</center>");
}
else {
inbuf += printiplist(buf+inbuf, LINESIZE-512, cp->ace->src, ",<br />\r\n");
}
inbuf += sprintf(buf+inbuf, "</td><td>");
if(!cp->ace || !cp->ace->dst){
inbuf += sprintf(buf+inbuf, "<center>ANY</center>");
}
else {
inbuf += printiplist(buf+inbuf, LINESIZE-512, cp->ace->dst, ",<br />\r\n");
}
inbuf += sprintf(buf+inbuf, "</td><td>");
if(!cp->ace || !cp->ace->ports){
inbuf += sprintf(buf+inbuf, "<center>ANY</center>");
}
else {
inbuf += printportlist(buf+inbuf, LINESIZE-128, cp->ace->ports, ",<br />\r\n");
}
if(cp->ace && (cp->ace->action == NOCOUNTIN || cp->ace->action == NOCOUNTOUT || cp->ace->action == NOCOUNTALL)) {
inbuf += sprintf(buf+inbuf,
"</td><td colspan=\'6\' align=\'center\'>exclude from limitation</td></tr>\r\n"
);
}
else {
inbuf += sprintf(buf+inbuf,
"</td><td>%"PRIu64"</td>"
"<td>MB%s</td>"
"<td>%"PRIu64".%"PRIu64"</td>"
"<td>%s</td>",
cp->traflim64 / (1024 * 1024),
rotations[cp->type],
cp->v->traf64 / (1024 * 1024),
(((cp->v->traf64/16) *10) / (64*1024))%10,
cp->v->cleared?ctime(&cp->v->cleared):"never"
);
inbuf += sprintf(buf + inbuf,
"<td>%s</td>"
"<td>%i</td>"
"</tr>\r\n",
cp->v->updated?ctime(&cp->v->updated):"never",
cp->number
);
}
printstr(&pp, buf);
}
}
printstr(&pp, counterstail);
admin_close(&pp, buf);
return 0;
}
int op_admin_reload(struct httpreq *r, const unsigned char *params)
{
struct printparam pp;
char *buf;
buf = admin_open(&pp, r->param);
if(!buf) return 1;
if(r->param->srv->s_option) printstr(&pp, (char *)runconf->stringtable[WEBBANNERS]);
else {
needreload = 1;
printstr(&pp, "<h3>Reload scheduled</h3>");
}
admin_close(&pp, buf);
return 0;
}
int op_admin_services(struct httpreq *r, const unsigned char *params)
{
struct clientparam *param = r->param;
struct printparam pp;
const char *sel;
if(param->srv->s_option) return op_admin(r, params);
sel = r->path + r->globstart;
/* This page is xml, so it carries its own headers instead of the html
wrapper the other pages share. */
pp.inbuf = 0;
pp.cp = param;
if(sel[0] == 'X') printstr(&pp, style);
else {
printstr(&pp, xml);
printstr(&pp, "<threadpool>");
printval(&tpool, TYPE_THREADPOOL, 0, &pp);
printstr(&pp, "</threadpool>\r\n");
/* services unlink themselves under config_mutex */
_3proxy_mutex_lock(&config_mutex);
printval(runconf->services, TYPE_SERVER, 0, &pp);
_3proxy_mutex_unlock(&config_mutex);
printstr(&pp, postxml);
}
printstr(&pp, NULL);
return 0;
}
#endif