|
|
|
|
|
|
|
|
constant cvs_version="$Id$"; |
|
|
|
|
|
|
ArgCache argcache; |
|
|
#define IN_ROXEN |
#include <roxen.h> |
#include <config.h> |
#include <module.h> |
#include <stat.h> |
#include <timers.h> |
|
|
inherit "global_variables"; |
#ifdef SNMP_AGENT |
inherit "snmpagent"; |
#endif |
#ifdef SMTP_RELAY |
inherit "smtprelay"; |
#endif |
inherit "hosts"; |
inherit "disk_cache"; |
inherit "fsgc"; |
|
inherit "supports"; |
inherit "module_support"; |
inherit "config_userdb"; |
|
|
Thread.Thread backend_thread; |
|
|
|
|
|
|
#define LOC_S(X,Y) _STR_LOCALE("roxen_start",X,Y) |
#define LOC_M(X,Y) _STR_LOCALE("roxen_message",X,Y) |
#define LOC_C(X,Y) _STR_LOCALE("roxen_config",X,Y) |
#define CALL_M(X,Y) _LOCALE_FUN("roxen_message",X,Y) |
|
|
|
#ifdef SSL3_DEBUG |
# define SSL3_WERR(X) report_debug("TLS port %s: %s\n", get_url(), X) |
#else |
# define SSL3_WERR(X) |
#endif |
|
#ifdef THREAD_DEBUG |
# define THREAD_WERR(X) report_debug("Thread: "+X+"\n") |
#else |
# define THREAD_WERR(X) |
#endif |
|
#ifdef LOG_GC_VERBOSE |
#define LOG_GC_HISTOGRAM |
#endif |
|
#ifdef LOG_GC_HISTOGRAM |
#define LOG_GC_TIMESTAMPS |
#endif |
|
|
#if constant(System.dumpable) |
#define enable_coredumps(X) System.dumpable(X) |
#else |
#define enable_coredumps(X) |
#endif |
|
#define DDUMP(X) sol( combine_path( __FILE__, "../../" + X ), dump ) |
protected function sol = master()->set_on_load; |
|
|
constant pike_cycle_depth = 0; |
|
#ifdef TEST_EUID_CHANGE |
int test_euid_change; |
#endif |
|
string md5( string what ) |
{ |
return Gmp.mpz(Crypto.MD5.hash( what ),256)->digits(32); |
} |
|
string query_configuration_dir() |
{ |
return configuration_dir; |
} |
|
array(string|int) filename_2 (program|object o) |
{ |
if( objectp( o ) ) |
o = object_program( o ); |
|
string fname = Program.defined (o); |
int line; |
if (fname) { |
array(string) p = fname / ":"; |
if (sizeof (p) > 1 && p[-1] != "" && sscanf (p[-1], "%d%*c", int l) == 1) { |
fname = p[..<1] * ":"; |
line = l; |
} |
} |
|
else if( !fname ) { |
fname = master()->program_name( o ); |
if (!fname) |
return ({0, 0}); |
} |
|
string cwd = getcwd() + "/"; |
if (has_prefix (fname, cwd)) |
fname = fname[sizeof (cwd)..]; |
else if (has_prefix (fname, roxenloader.server_dir + "/")) |
fname = fname[sizeof (roxenloader.server_dir + "/")..]; |
|
return ({fname, line}); |
} |
|
string filename( program|object o ) |
{ |
[string fname, int line] = filename_2 (o); |
return fname || "(unknown program)"; |
} |
|
protected int once_mode; |
|
|
|
|
|
|
|
array(string) compat_levels = ({"2.1", "2.2", "2.4", "2.5", |
"3.3", "3.4", |
"4.0", "4.5", |
"5.0", "5.1", "5.2", "5.4", "5.5", |
"6.0", |
}); |
|
#ifdef THREADS |
mapping(string:string) thread_names = ([]); |
string thread_name( object thread, int|void skip_auto_name ) |
{ |
string tn; |
if( thread_names[ tn=sprintf("%O",thread) ] || skip_auto_name ) |
return thread_names[tn]; |
return tn; |
} |
|
void name_thread( object thread, string name ) |
{ |
string th_key = sprintf("%O", thread); |
if (name) |
thread_names[th_key] = name; |
else |
m_delete(thread_names, th_key); |
} |
|
|
Thread.Mutex euid_egid_lock = Thread.Mutex(); |
#endif /* THREADS */ |
|
|
|
|
|
|
|
int privs_level; |
|
protected class Privs |
{ |
#if constant(seteuid) |
|
int saved_uid; |
int saved_gid; |
|
int new_uid; |
int new_gid; |
|
#define LOGP (variables && variables->audit && variables->audit->query()) |
|
#if constant(geteuid) && constant(getegid) && constant(seteuid) && constant(setegid) |
#define HAVE_EFFECTIVE_USER |
#endif |
|
private string _getcwd() |
{ |
if (catch{return(getcwd());}) { |
return("Unknown directory (no x-bit on current directory?)"); |
} |
} |
|
private string dbt(array t) |
{ |
if(!arrayp(t) || (sizeof(t)<2)) return ""; |
return (((t[0]||"Unknown program")-(_getcwd()+"/"))-"base_server/")+":"+t[1]+"\n"; |
} |
|
#ifdef THREADS |
protected mixed mutex_key; |
protected object threads_disabled; |
#endif /* THREADS */ |
|
int p_level; |
|
void create(string reason, int|string|void uid, int|string|void gid) |
{ |
|
if(getuid()) return; |
|
#ifdef PRIVS_DEBUG |
report_debug(sprintf("Privs(%O, %O, %O)\n" |
"privs_level: %O\n", |
reason, uid, gid, privs_level)); |
#endif /* PRIVS_DEBUG */ |
|
#ifdef HAVE_EFFECTIVE_USER |
array u; |
|
#ifdef THREADS |
if (euid_egid_lock) { |
if (mixed err = catch { mutex_key = euid_egid_lock->lock(); }) |
master()->handle_error (err); |
} |
threads_disabled = _disable_threads(); |
#endif /* THREADS */ |
|
p_level = privs_level++; |
|
|
|
|
saved_uid = geteuid(); |
saved_gid = getegid(); |
seteuid(0); |
|
|
if(stringp(uid) && (replace(uid,"0123456789"/"",({""})*10)=="")) |
uid = (int)uid; |
|
if(stringp(gid) && (replace(gid, "0123456789"/"", ({"" })*10) == "")) |
gid = (int)gid; |
|
if(!stringp(uid)) |
u = getpwuid(uid); |
else |
{ |
u = getpwnam(uid); |
if(u) |
uid = u[2]; |
} |
|
if(u && !gid) |
gid = u[3]; |
|
if(!u) |
{ |
if (uid && (uid != "root")) |
{ |
if (intp(uid) && (uid >= 60000)) |
{ |
report_warning(sprintf("Privs: User %d is not in the password database.\n" |
"Assuming nobody.\n", uid)); |
|
gid = gid || uid; |
u = ({ "fake-nobody", "x", uid, gid, "A real nobody", "/", "/sbin/sh" }); |
} else { |
error("Unknown user: "+uid+"\n"); |
} |
} else { |
u = ({ "root", "x", 0, gid, "The super-user", "/", "/sbin/sh" }); |
} |
} |
|
if(LOGP) |
report_notice(LOC_M(1, "Change to %s(%d):%d privs wanted (%s), from %s"), |
(string)u[0], (int)uid, (int)gid, |
(string)reason, |
(string)dbt(backtrace()[-2])); |
|
if (u[2]) { |
#if constant(cleargroups) |
if (mixed err = catch { cleargroups(); }) |
master()->handle_error (err); |
#endif /* cleargroups */ |
#if constant(initgroups) |
if (mixed err = catch { initgroups(u[0], u[3]); }) |
master()->handle_error (err); |
#endif |
} |
gid = gid || getgid(); |
int err = (int)setegid(new_gid = gid); |
if (err < 0) { |
report_warning(LOC_M(2, "Privs: WARNING: Failed to set the " |
"effective group id to %d!\n" |
"Check that your password database is correct " |
"for user %s(%d),\n and that your group " |
"database is correct.\n"), |
gid, (string)u[0], (int)uid); |
int gid2 = gid; |
#ifdef HPUX_KLUDGE |
if (gid >= 60000) { |
|
|
|
|
|
|
|
report_debug("Privs: WARNING: Assuming nobody-group.\n" |
"Trying some alternatives...\n"); |
|
foreach(({ 60001, 65534, -2 }), gid2) { |
report_debug("%d... ", gid2); |
if (initgroups(u[0], gid2) >= 0) { |
if ((err = setegid(new_gid = gid2)) >= 0) { |
report_debug("Success!\n"); |
break; |
} |
} |
} |
} |
#endif /* HPUX_KLUDGE */ |
if (err < 0) { |
report_debug("Privs: Failed\n"); |
error ("Failed to set EGID to %d\n", gid); |
} |
report_debug("Privs: WARNING: Set egid to %d instead of %d.\n", |
gid2, gid); |
gid = gid2; |
} |
if(getgid()!=gid) setgid(gid||getgid()); |
seteuid(new_uid = uid); |
enable_coredumps(1); |
#endif /* HAVE_EFFECTIVE_USER */ |
} |
|
void destroy() |
{ |
|
if(getuid()) return; |
|
#ifdef PRIVS_DEBUG |
report_debug(sprintf("Privs->destroy()\n" |
"privs_level: %O\n", |
privs_level)); |
#endif /* PRIVS_DEBUG */ |
|
#ifdef HAVE_EFFECTIVE_USER |
|
if (p_level >= privs_level) { |
report_error(sprintf("Change back to uid#%d gid#%d from uid#%d gid#%d\n" |
"in wrong order! Saved level:%d Current level:%d\n" |
"Occurs in:\n%s\n", |
saved_uid, saved_gid, new_uid, new_gid, |
p_level, privs_level, |
describe_backtrace(backtrace()))); |
return(0); |
} |
if (p_level != privs_level-1) { |
report_error(sprintf("Change back to uid#%d gid#%d from uid#%d gid#%d\n" |
"Skips privs level. Saved level:%d Current level:%d\n" |
"Occurs in:\n%s\n", |
saved_uid, saved_gid, new_uid, new_gid, |
p_level, privs_level, |
describe_backtrace(backtrace()))); |
} |
privs_level = p_level; |
|
if(LOGP) { |
if (mixed err = catch { |
array bt = backtrace(); |
if (sizeof(bt) >= 2) { |
report_notice(LOC_M(3,"Change back to uid#%d gid#%d, from %s")+"\n", |
saved_uid, saved_gid, dbt(bt[-2])); |
} else { |
report_notice(LOC_M(4,"Change back to uid#%d gid#%d, " |
"from backend")+"\n", saved_uid, saved_gid); |
} |
}) |
master()->handle_error (err); |
} |
|
#ifdef PRIVS_DEBUG |
int uid = geteuid(); |
if (uid != new_uid) { |
report_debug("Privs: UID #%d differs from expected #%d\n" |
"%s\n", |
uid, new_uid, describe_backtrace(backtrace())); |
} |
int gid = getegid(); |
if (gid != new_gid) { |
report_debug("Privs: GID #%d differs from expected #%d\n" |
"%s\n", |
gid, new_gid, describe_backtrace(backtrace())); |
} |
#endif /* PRIVS_DEBUG */ |
|
seteuid(0); |
array u = getpwuid(saved_uid); |
#if constant(cleargroups) |
if (mixed err = catch { cleargroups(); }) |
master()->handle_error (err); |
#endif /* cleargroups */ |
if(u && (sizeof(u) > 3)) { |
if (mixed err = catch { initgroups(u[0], u[3]); }) |
master()->handle_error (err); |
} |
setegid(saved_gid); |
seteuid(saved_uid); |
enable_coredumps(1); |
#endif /* HAVE_EFFECTIVE_USER */ |
} |
#else /* constant(seteuid) */ |
void create(string reason, int|string|void uid, int|string|void gid){} |
#endif /* constant(seteuid) */ |
} |
|
|
|
|
protected Privs PRIVS(string r, int|string|void u, int|string|void g) |
{ |
return Privs(r, u, g); |
} |
|
|
Thread.Local current_configuration = Thread.Local(); |
|
|
|
|
|
|
class Fonts |
{ |
class Font |
{ |
Image.Image write( string ... what ); |
array(int) text_extents( string ... what ); |
}; |
array available_font_versions(string name, int size); |
string describe_font_type(string n); |
Font get_font(string f, int size, int bold, int italic, |
string justification, float|int xspace, float|int yspace); |
|
Font resolve_font(string f, string|void justification); |
array(string) available_fonts(int(0..1)|void force_reload); |
} |
Fonts fonts; |
|
|
program _configuration; |
|
array(Configuration) configurations = ({}); |
|
private void stop_all_configurations() |
{ |
configurations->unregister_urls(); |
#ifdef THREADS |
|
|
hold_handler_threads(); |
release_handler_threads(3); |
#endif |
configurations->stop(1); |
} |
|
|
private void really_low_shutdown(int exit_code) |
{ |
|
#ifdef THREADS |
if (mixed err = catch (stop_handler_threads())) |
werror (describe_backtrace (err)); |
#endif /* THREADS */ |
if (!exit_code || once_mode) { |
|
if (mixed err = |
catch { report_notice("Shutting down MySQL.\n"); } || |
catch { |
Sql.Sql db = connect_to_my_mysql(0, "mysql"); |
db->shutdown(); |
}) |
master()->handle_error (err); |
} |
|
destruct (argcache); |
destruct (cache); |
#if 0 |
|
|
|
if (mixed err = catch { |
if (exit_code && !once_mode) |
report_notice("Restarting Roxen.\n"); |
else |
report_notice("Shutting down Roxen.\n"); |
}) |
master()->handle_error (err); |
#endif |
roxenloader.real_exit( exit_code ); |
} |
|
private int shutdown_recurse; |
|
|
|
|
private void low_shutdown(int exit_code, int|void apply_patches) |
{ |
if(shutdown_recurse >= 4) |
{ |
if (mixed err = |
catch (report_notice("Exiting roxen (spurious signals received).\n")) || |
catch (stop_all_configurations())) |
master()->handle_error (err); |
|
destruct(argcache); |
destruct(cache); |
stop_scan_certs(); |
stop_hourly_maintenance(); |
#ifdef THREADS |
#if constant(Filesystem.Monitor.basic) |
stop_fsgarb(); |
#endif |
if (mixed err = catch (stop_handler_threads())) |
master()->handle_error (err); |
#endif /* THREADS */ |
roxenloader.real_exit(exit_code); |
} |
if (shutdown_recurse++) return; |
|
#ifndef NO_SLOW_REQ_BT |
|
slow_be_timeout_changed(); |
#endif |
|
if ((apply_patches || query("patch_on_restart")) > 0) { |
mixed err = catch { |
foreach(plib->file_list_imported(), mapping(string:mixed) item) { |
report_notice("Applying patch %s...\n", item->metadata->id); |
mixed err = catch { |
plib->install_patch(item->metadata->id, |
"Internal Administrator"); |
}; |
if (err) { |
report_error("Failed to install patch %s: %s\n", |
item->metadata->id, |
describe_backtrace(err)); |
} |
} |
}; |
if (err) { |
master()->handle_error(err); |
} |
} |
|
if (mixed err = catch(stop_all_configurations())) |
master()->handle_error (err); |
|
#ifdef SNMP_AGENT |
if(objectp(snmpagent)) { |
snmpagent->stop_trap(); |
snmpagent->disable(); |
} |
#endif |
|
call_out(really_low_shutdown, 0.1, exit_code); |
} |
|
private int shutdown_started; |
|
|
|
|
void restart(float|void i, void|int exit_code, void|int apply_patches) |
|
{ |
shutdown_started = 1; |
call_out(low_shutdown, i, exit_code || -1, apply_patches); |
} |
|
void shutdown(float|void i, void|int apply_patches) |
|
{ |
shutdown_started = 1; |
call_out(low_shutdown, i, 0, apply_patches); |
} |
|
void exit_when_done() |
{ |
shutdown_started = 1; |
report_notice("Interrupt request received.\n"); |
low_shutdown(-1, -1); |
} |
|
int is_shutting_down() |
|
{ |
return shutdown_started; |
} |
|
|
|
|
|
|
#ifdef THREADS |
|
|
Thread.Thread do_thread_create(string id, function f, mixed ... args) |
{ |
Thread.Thread t = thread_create(f, @args); |
name_thread( t, id ); |
return t; |
} |
|
#if 1 |
constant Queue = Thread.Queue; |
#else |
|
|
class Queue |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
{ |
inherit Thread.Condition : r_cond; |
array buffer=allocate(8); |
int r_ptr, w_ptr; |
|
int size() |
{ |
return w_ptr - r_ptr; |
} |
|
mixed read() |
{ |
while(!(w_ptr - r_ptr)) { |
|
|
|
|
Thread.Mutex m = Thread.Mutex(); |
r_cond::wait (m->lock()); |
} |
mixed tmp = buffer[r_ptr]; |
buffer[r_ptr++] = 0; |
return tmp; |
} |
|
mixed try_read() |
{ |
if (!(w_ptr - r_ptr)) return ([])[0]; |
mixed tmp = buffer[r_ptr]; |
buffer[r_ptr++] = 0; |
return tmp; |
} |
|
|
void write(mixed v) |
{ |
if(w_ptr >= sizeof(buffer)) |
{ |
buffer=buffer[r_ptr..]+allocate(8); |
w_ptr-=r_ptr; |
r_ptr=0; |
} |
buffer[w_ptr++]=v; |
r_cond::signal(); |
} |
} |
#endif |
|
#ifndef NO_SLOW_REQ_BT |
|
|
|
protected Pike.Backend slow_req_monitor; |
protected float slow_req_timeout, slow_be_timeout; |
|
protected void slow_req_monitor_thread (Pike.Backend my_monitor) |
{ |
|
|
name_thread(this_thread(), "Slow request monitor"); |
while (slow_req_monitor == my_monitor) |
slow_req_monitor (3600.0); |
name_thread(this_thread(), 0); |
} |
|
protected mixed slow_be_call_out; |
|
protected void slow_be_before_cb() |
{ |
#ifdef DEBUG |
if (this_thread() != backend_thread) error ("Run from wrong thread.\n"); |
#endif |
if (Pike.Backend monitor = slow_be_call_out && slow_req_monitor) { |
monitor->remove_call_out (slow_be_call_out); |
slow_be_call_out = 0; |
|
float backend_rtime = |
(gethrtime() - thread_task_start_times[this_thread()]) / 1E6; |
thread_task_start_times[this_thread()] = 0; |
|
if (backend_rtime > slow_be_timeout) { |
report_slow_thread_finished (this_thread(), backend_rtime); |
} |
} |
} |
|
protected void slow_be_after_cb() |
{ |
|
|
#ifdef DEBUG |
if (this_thread() != backend_thread) error ("Run from wrong thread.\n"); |
#endif |
if (slow_be_timeout > 0.0) { |
if (Pike.Backend monitor = slow_req_monitor) { |
thread_task_start_times[this_thread()] = gethrtime(); |
slow_be_call_out = monitor->call_out (dump_slow_req, slow_be_timeout, |
this_thread(), slow_be_timeout); |
} |
} |
} |
|
void slow_req_count_changed() |
{ |
Pike.Backend monitor = slow_req_monitor; |
int count = query ("slow_req_bt_count"); |
|
if (count && monitor) { |
|
} |
|
else if (count) { |
monitor = slow_req_monitor = Pike.SmallBackend(); |
Thread.thread_create (slow_req_monitor_thread, monitor); |
monitor->call_out (lambda () {}, 0); |
slow_be_timeout_changed(); |
} |
|
else if (monitor) { |
slow_req_monitor = 0; |
monitor->call_out (lambda () {}, 0); |
slow_be_timeout_changed(); |
} |
} |
|
void slow_req_timeout_changed() |
{ |
#ifdef DEBUG |
if (query ("slow_req_bt_timeout") < 0) error ("Invalid timeout.\n"); |
#endif |
slow_req_timeout = query ("slow_req_bt_timeout"); |
} |
|
void slow_be_timeout_changed() |
{ |
#ifdef DEBUG |
if (query ("slow_be_bt_timeout") < 0) error ("Invalid timeout.\n"); |
#endif |
slow_be_timeout = query ("slow_be_bt_timeout"); |
|
#ifdef DEBUG |
if ((Pike.DefaultBackend->before_callback && |
Pike.DefaultBackend->before_callback != slow_be_before_cb) || |
(Pike.DefaultBackend->after_callback && |
Pike.DefaultBackend->after_callback != slow_be_after_cb)) |
werror ("Pike.DefaultBackend already hooked up with " |
"other before/after callbacks - they get overwritten: %O/%O\n", |
Pike.DefaultBackend->before_callback, |
Pike.DefaultBackend->after_callback); |
#endif |
|
if (query ("slow_req_bt_count") && slow_be_timeout > 0.0 && |
|
!shutdown_started) { |
Pike.DefaultBackend->before_callback = slow_be_before_cb; |
Pike.DefaultBackend->after_callback = slow_be_after_cb; |
} |
else { |
Pike.DefaultBackend->before_callback = 0; |
Pike.DefaultBackend->after_callback = 0; |
if (Pike.Backend monitor = slow_be_call_out && slow_req_monitor) { |
monitor->remove_call_out (slow_be_call_out); |
slow_be_call_out = 0; |
} |
} |
} |
|
protected int last_dump_hrtime; |
|
protected void dump_slow_req (Thread.Thread thread, float timeout) |
{ |
object threads_disabled = _disable_threads(); |
|
int count = query ("slow_req_bt_count"); |
if (count > 0) set ("slow_req_bt_count", count - 1); |
|
if (thread == backend_thread && !slow_be_call_out) { |
|
|
|
|
} |
|
else { |
string th_name = |
((thread != backend_thread) && thread_name(thread, 1)) || ""; |
if (sizeof(th_name)) |
th_name = " - " + th_name + " -"; |
report_debug ("###### %s 0x%x%s has been busy for more than %g seconds.\n", |
thread == backend_thread ? "Backend thread" : "Thread", |
thread->id_number(), th_name, timeout); |
int hrnow = gethrtime(); |
if ((hrnow - last_dump_hrtime) / 1E6 < slow_req_timeout / 2) { |
describe_thread (thread); |
} else { |
last_dump_hrtime = hrnow; |
mixed err = catch { |
describe_all_threads(0, 1); |
}; |
if (err) master()->handle_error(err); |
} |
} |
|
threads_disabled = 0; |
} |
|
protected void report_slow_thread_finished (Thread.Thread thread, |
float time_spent) |
{ |
string th_name = |
((thread != backend_thread) && thread_name(thread, 1)) || ""; |
if (sizeof(th_name)) |
th_name = " - " + th_name + " -"; |
|
report_debug ("###### %s 0x%x%s finished after %.2f seconds.\n", |
(thread == backend_thread ? |
"Backend thread" : "Thread"), |
thread->id_number(), th_name, time_spent); |
} |
|
#endif // !NO_SLOW_REQ_BT |
|
|
|
|
|
|
|
|
|
|
|
|
local protected Queue handle_queue = Queue(); |
|
|
|
local protected int thread_reap_cnt; |
|
|
protected int threads_on_hold; |
|
|
|
int handler_num_runs = 0; |
int handler_num_runs_001s = 0; |
int handler_num_runs_005s = 0; |
int handler_num_runs_015s = 0; |
int handler_num_runs_05s = 0; |
int handler_num_runs_1s = 0; |
int handler_num_runs_5s = 0; |
int handler_num_runs_15s = 0; |
int handler_acc_time = 0; |
int handler_acc_cpu_time = 0; |
|
protected string debug_format_queue_task (array(function|array) task) |
|
{ |
return ((functionp (task[0]) ? |
sprintf ("%s: %s", Function.defined (task[0]), |
master()->describe_function (task[0])) : |
programp (task[0]) ? |
sprintf ("%s: %s", Program.defined (task[0]), |
master()->describe_program (task[0])) : |
sprintf ("%O", task[0])) + |
"(" + |
map (task[1], lambda (mixed arg) |
{return RXML.utils.format_short (arg, 200);}) * ", " + |
")"); |
} |
|
protected mapping(Thread.Thread:int) thread_task_start_times = ([]); |
|
mapping(Thread.Thread:int) get_thread_task_start_times() |
{ |
|
return thread_task_start_times + ([ ]); |
} |
|
local protected void handler_thread(int id) |
|
|
|
|
{ |
THREAD_WERR("Handle thread ["+id+"] started"); |
mixed h, q; |
set_u_and_gid (1); |
#ifdef TEST_EUID_CHANGE |
if (test_euid_change) { |
Stdio.File f = Stdio.File(); |
if (f->open ("rootonly", "r") && f->read()) |
werror ("Handler thread %d can read rootonly\n", id); |
else |
werror ("Handler thread %d can't read rootonly\n", id); |
} |
#endif |
while(1) |
{ |
int thread_flagged_as_busy; |
#ifndef NO_SLOW_REQ_BT |
Pike.Backend monitor; |
mixed call_out; |
#endif |
if(q=catch { |
do { |
|
cache_clear_deltas(); |
THREAD_WERR("Handle thread ["+id+"] waiting for next event"); |
if(arrayp(h=handle_queue->read()) && h[0]) { |
THREAD_WERR(sprintf("Handle thread [%O] calling %s", |
id, debug_format_queue_task (h))); |
set_locale(); |
busy_threads++; |
thread_flagged_as_busy = 1; |
handler_num_runs++; |
|
int start_hrtime = gethrtime(); |
thread_task_start_times[this_thread()] = start_hrtime; |
|
float handler_vtime = gauge { |
#ifndef NO_SLOW_REQ_BT |
if (h[0] != bg_process_queue && |
|
|
(monitor = slow_req_monitor) && slow_req_timeout > 0.0) { |
call_out = monitor->call_out (dump_slow_req, slow_req_timeout, |
this_thread(), slow_req_timeout); |
h[0](@h[1]); |
monitor->remove_call_out (call_out); |
} |
else |
#endif |
{ |
h[0](@h[1]); |
} |
}; |
int end_hrtime = gethrtime(); |
|
float handler_rtime = (end_hrtime - start_hrtime)/1E6; |
thread_task_start_times[this_thread()] = 0; |
|
#ifndef NO_SLOW_REQ_BT |
if (slow_req_timeout > 0.0 && |
handler_rtime > slow_req_timeout) { |
report_slow_thread_finished (this_thread(), handler_rtime); |
} |
#endif |
|
h=0; |
busy_threads--; |
thread_flagged_as_busy = 0; |
if (handler_rtime > 0.01) handler_num_runs_001s++; |
if (handler_rtime > 0.05) handler_num_runs_005s++; |
if (handler_rtime > 0.15) handler_num_runs_015s++; |
if (handler_rtime > 0.50) handler_num_runs_05s++; |
if (handler_rtime > 1.00) handler_num_runs_1s++; |
if (handler_rtime > 5.00) handler_num_runs_5s++; |
if (handler_rtime > 15.00) handler_num_runs_15s++; |
handler_acc_cpu_time += (int)(1E6*handler_vtime); |
handler_acc_time += (int)(1E6*handler_rtime); |
} else if(!h) { |
|
report_debug("Handle thread ["+id+"] stopped.\n"); |
thread_reap_cnt--; |
#ifdef NSERIOUS |
if(!thread_reap_cnt) report_debug("+++ATH\n"); |
#endif |
return; |
} |
#ifdef DEBUG |
else if (h != 1) |
error ("Unknown message in handle_queue: %O\n", h); |
#endif |
else { |
num_hold_messages--; |
THREAD_WERR("Handle thread [" + id + "] put on hold"); |
threads_on_hold++; |
if (Thread.Condition cond = hold_wakeup_cond) { |
|
|
|
|
|
Thread.Mutex m = Thread.Mutex(); |
cond->wait (m->lock()); |
} |
threads_on_hold--; |
THREAD_WERR("Handle thread [" + id + "] released"); |
} |
} while(1); |
}) { |
if (thread_flagged_as_busy) |
busy_threads--; |
#ifndef NO_SLOW_REQ_BT |
if (call_out) monitor->remove_call_out (call_out); |
#endif |
if (h = catch { |
master()->handle_error (q); |
if (q = catch {h = 0;}) { |
report_error(LOC_M(5, "Uncaught error in handler thread: %sClient " |
"will not get any response from Roxen.")+"\n", |
describe_backtrace(q)); |
catch (q = 0); |
} |
}) { |
catch { |
report_error("Error reporting error:\n"); |
report_error(sprintf("Raw error: %O\n", h[0])); |
report_error(sprintf("Original raw error: %O\n", q[0])); |
}; |
catch (q = 0); |
catch (h = 0); |
} |
} |
} |
} |
|
void handle(function f, mixed ... args) |
{ |
handle_queue->write(({f, args })); |
} |
|
int handle_queue_length() |
{ |
return handle_queue->size(); |
} |
|
int number_of_threads; |
|
|
int busy_threads; |
|
|
protected array(object) handler_threads = ({}); |
|
|
void start_handler_threads() |
{ |
if (query("numthreads") <= 1) { |
set( "numthreads", 1 ); |
report_warning (LOC_S(1, "Starting one thread to handle requests.")+"\n"); |
} else { |
report_notice (LOC_S(2, "Starting %d threads to handle requests.")+"\n", |
query("numthreads") ); |
} |
array(object) new_threads = ({}); |
for(; number_of_threads < query("numthreads"); number_of_threads++) |
new_threads += ({ do_thread_create( "Handle thread [" + |
number_of_threads + "]", |
handler_thread, number_of_threads ) }); |
handler_threads += new_threads; |
} |
|
protected int num_hold_messages; |
protected Thread.Condition hold_wakeup_cond = Thread.Condition(); |
|
|
|
|
|
|
|
void hold_handler_threads() |
|
|
{ |
if (!hold_wakeup_cond) { |
THREAD_WERR("Ignoring request to hold handler threads during stop"); |
return; |
} |
|
int timeout=10; |
#if constant(_reset_dmalloc) |
|
timeout *= 10; |
#endif /* constant(_reset_dmalloc) */ |
|
THREAD_WERR("Putting " + (number_of_threads - threads_on_hold) + |
" handler threads on hold, " + |
threads_on_hold + " on hold already"); |
|
for (int i = number_of_threads - threads_on_hold - num_hold_messages; i-- > 0;) { |
handle_queue->write (1); |
num_hold_messages++; |
} |
while (threads_on_hold < number_of_threads && timeout--) |
sleep (0.1); |
|
THREAD_WERR(threads_on_hold + " handler threads on hold, " + |
(number_of_threads - threads_on_hold) + " not responding"); |
} |
|
void release_handler_threads (int numthreads) |
|
|
|
|
{ |
if (Thread.Condition cond = hold_wakeup_cond) { |
|
for (int i = handle_queue->size(); i && num_hold_messages; i--) { |
mixed task = handle_queue->try_read(); |
if (task == 1) num_hold_messages--; |
else handle_queue->write (task); |
} |
#ifdef DEBUG |
if (num_hold_messages) |
error ("num_hold_messages is bogus (%d).\n", num_hold_messages); |
#endif |
num_hold_messages = 0; |
|
int blocked_threads = number_of_threads - threads_on_hold; |
int threads_to_create = numthreads - threads_on_hold; |
|
THREAD_WERR("Releasing " + threads_on_hold + " threads on hold"); |
cond->broadcast(); |
|
if (threads_to_create > 0) { |
array(object) new_threads = ({}); |
for (int n = 0; n < threads_to_create; number_of_threads++, n++) |
new_threads += ({ do_thread_create( "Handle thread [" + |
number_of_threads + "]", |
handler_thread, number_of_threads ) }); |
handler_threads += new_threads; |
report_notice ("Created %d new handler threads to compensate " |
"for %d blocked ones.\n", threads_to_create, blocked_threads); |
} |
} |
else { |
THREAD_WERR("Ignoring request to release handler threads during stop"); |
return; |
} |
} |
|
|
int handler_threads_on_hold() {return !!hold_wakeup_cond;} |
|
protected Thread.MutexKey backend_block_lock; |
|
void stop_handler_threads() |
|
|
{ |
int timeout=15; |
int background_run_timeout = 100; |
#if constant(_reset_dmalloc) |
|
timeout *= 10; |
background_run_timeout *= 3; |
#endif /* constant(_reset_dmalloc) */ |
|
report_debug("Stopping all request handler threads.\n"); |
|
|
|
if (Thread.Condition cond = hold_wakeup_cond) { |
hold_wakeup_cond = 0; |
cond->broadcast(); |
} |
|
while(number_of_threads>0) { |
number_of_threads--; |
handle_queue->write(0); |
thread_reap_cnt++; |
} |
handler_threads = ({}); |
|
if (this_thread() != backend_thread && !backend_block_lock) { |
thread_reap_cnt++; |
Thread.Mutex mutex = Thread.Mutex(); |
backend_block_lock = mutex->lock(); |
call_out (lambda () { |
thread_reap_cnt--; |
report_debug("Backend thread stopped.\n"); |
mutex->lock(); |
error("Backend stop failed.\n"); |
}, 0); |
} |
|
int prev_bg_len = bg_queue_length(); |
|
while (thread_reap_cnt) { |
sleep(0.1); |
|
if (--timeout <= 0) { |
int cur_bg_len = bg_queue_length(); |
if (prev_bg_len < cur_bg_len) |
|
timeout = 10; |
prev_bg_len = cur_bg_len; |
} |
|
if(--background_run_timeout <= 0 || timeout <= 0) { |
report_debug("Giving up waiting on threads; " |
"%d threads blocked, %d jobs in the background queue.\n", |
thread_reap_cnt, bg_queue_length()); |
#ifdef DEBUG |
describe_all_threads(); |
#endif |
return; |
} |
} |
} |
|
#else |
|
void handle(function f, mixed ... args) |
{ |
f(@args); |
} |
|
|
|
#endif /* THREADS */ |
|
function async_sig_start( function f, int really ) |
{ |
class SignalAsyncVerifier( function f ) |
{ |
protected int async_called; |
|
void really_call( array args ) |
{ |
async_called = 0; |
f( @args ); |
} |
|
void call( mixed ... args ) |
{ |
if( async_called && async_called-time() ) |
{ |
report_debug("Received signal %s\n", (string) signame( args[0] ) ); |
report_debug("\n\n" |
"Async calling failed for %O, calling synchronous\n", f); |
report_debug("Backtrace at time of hangup:\n%s\n", |
describe_backtrace( backtrace() )); |
f( @args ); |
return; |
} |
if( !async_called ) |
{ |
report_debug("Received signal %s\n", (string) signame( args[0] ) ); |
async_called=time(); |
call_out( really_call, 0, args ); |
} |
} |
}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if( really > 0 ) |
return lambda( mixed ... args ){ call_out( f, 0, @args ); }; |
if( really < 0 ) |
return f; |
return SignalAsyncVerifier( f )->call; |
} |
|
#ifdef THREADS |
protected Thread.Queue bg_queue = Thread.Queue(); |
protected Thread.Thread bg_process_thread; |
|
|
|
|
|
|
|
protected constant bg_time_buffer_max = 30; |
protected constant bg_time_buffer_min = 0; |
protected int bg_last_busy = 0; |
int bg_num_runs = 0; |
int bg_num_runs_001s = 0; |
int bg_num_runs_005s = 0; |
int bg_num_runs_015s = 0; |
int bg_num_runs_05s = 0; |
int bg_num_runs_1s = 0; |
int bg_num_runs_5s = 0; |
int bg_num_runs_15s = 0; |
int bg_acc_time = 0; |
int bg_acc_cpu_time = 0; |
|
int bg_queue_length() |
{ |
return bg_queue->size(); |
} |
|
protected void bg_process_queue() |
{ |
if (bg_process_thread) return; |
|
bg_process_thread = this_thread(); |
|
int maxbeats = |
min (time() - bg_last_busy, bg_time_buffer_max) * (int) (1 / 0.01); |
|
#ifndef NO_SLOW_REQ_BT |
Pike.Backend monitor; |
mixed call_out; |
#endif |
|
if (mixed err = catch { |
|
|
|
|
|
|
|
|
|
|
int jobs_to_process = bg_queue->size(); |
while (hold_wakeup_cond ? jobs_to_process-- : bg_queue->size()) { |
|
array task = bg_queue->read(); |
|
|
if (busy_threads > 1) { |
for (maxbeats = max (maxbeats, (int) (bg_time_buffer_min / 0.01)); |
busy_threads > 1 && maxbeats > 0; |
maxbeats--) |
sleep (0.01); |
bg_last_busy = time(); |
} |
|
#ifdef DEBUG_BACKGROUND_RUN |
report_debug ("background_run run %s [%d jobs left in queue]\n", |
debug_format_queue_task (task), |
bg_queue->size()); |
#endif |
|
float task_vtime, task_rtime; |
bg_num_runs++; |
|
#ifndef NO_SLOW_REQ_BT |
if ((monitor = slow_req_monitor) && slow_req_timeout > 0.0) { |
call_out = monitor->call_out (dump_slow_req, slow_req_timeout, |
this_thread(), slow_req_timeout); |
int start_hrtime = gethrtime(); |
thread_task_start_times[this_thread()] = start_hrtime; |
task_vtime = gauge { |
if (task[0]) |
|
|
task[0] (@task[1]); |
}; |
task_rtime = (gethrtime() - start_hrtime) / 1e6; |
thread_task_start_times[this_thread()] = 0; |
monitor->remove_call_out (call_out); |
} |
else |
#endif |
{ |
int start_hrtime = gethrtime(); |
thread_task_start_times[this_thread()] = start_hrtime; |
task_vtime = gauge { |
if (task[0]) |
task[0] (@task[1]); |
}; |
task_rtime = (gethrtime() - start_hrtime) / 1e6; |
thread_task_start_times[this_thread()] = 0; |
} |
|
if (task_rtime > 0.01) bg_num_runs_001s++; |
if (task_rtime > 0.05) bg_num_runs_005s++; |
if (task_rtime > 0.15) bg_num_runs_015s++; |
if (task_rtime > 0.50) bg_num_runs_05s++; |
if (task_rtime > 1.00) bg_num_runs_1s++; |
if (task_rtime > 5.00) bg_num_runs_5s++; |
if (task_rtime > 15.00) bg_num_runs_15s++; |
bg_acc_cpu_time += (int)(1E6*task_vtime); |
bg_acc_time += (int)(1E6*task_rtime); |
|
if (task_rtime > 60.0) |
report_warning ("Warning: Background job took more than one minute " |
"(%g s real time and %g s cpu time):\n" |
" %s (%s)\n%s", |
task_rtime, task_vtime, |
functionp (task[0]) ? |
sprintf ("%s: %s", Function.defined (task[0]), |
master()->describe_function (task[0])) : |
programp (task[0]) ? |
sprintf ("%s: %s", Program.defined (task[0]), |
master()->describe_program (task[0])) : |
sprintf ("%O", task[0]), |
map (task[1], lambda (mixed arg) |
{return sprintf ("%O", arg);}) * ", ", |
bg_queue->size() ? |
(bg_queue->size() > 1 ? |
" " + bg_queue->size() + " more jobs in the " |
"background queue were delayed.\n" : |
" 1 more job in the background queue was delayed.\n"): |
""); |
#ifdef DEBUG_BACKGROUND_RUN |
else |
report_debug ("background_run done, " |
"took %g ms cpu time and %g ms real time\n", |
task_vtime * 1000, task_rtime * 1000); |
#endif |
|
if (busy_threads > 1) bg_last_busy = time(); |
} |
}) { |
#ifndef NO_SLOW_REQ_BT |
if (call_out) monitor->remove_call_out (call_out); |
#endif |
bg_process_thread = 0; |
handle (bg_process_queue); |
throw (err); |
} |
bg_process_thread = 0; |
if (bg_queue->size()) { |
handle (bg_process_queue); |
|
|
|
sleep (0.001); |
} |
} |
#endif |
|
Thread.Thread background_run_thread() |
|
|
{ |
#ifdef THREADS |
return bg_process_thread; |
#else |
|
|
|
return 0; |
#endif |
} |
|
mixed background_run (int|float delay, function func, mixed... args) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
{ |
|
|
|
#ifdef DEBUG_BACKGROUND_RUN |
report_debug ("background_run enqueue %s (%s) [%d jobs in queue]\n", |
functionp (func) ? |
sprintf ("%s: %s", Function.defined (func), |
master()->describe_function (func)) : |
programp (func) ? |
sprintf ("%s: %s", Program.defined (func), |
master()->describe_program (func)) : |
sprintf ("%O", func), |
map (args, lambda (mixed arg) |
{return sprintf ("%O", arg);}) * ", ", |
bg_queue->size()); |
#endif |
|
#ifdef THREADS |
if (!hold_wakeup_cond) { |
|
#ifdef DEBUG |
report_debug ("Ignoring background job queued during shutdown: %O\n", func); |
#endif |
return 0; |
} |
|
class enqueue(function func, mixed ... args) |
{ |
int __hash() { return hash_value(func); } |
int `==(mixed gunc) { return func == gunc; } |
string _sprintf() { return sprintf("background_run(%O)", func); } |
mixed `()() |
{ |
bg_queue->write (({func, args})); |
if (!bg_process_thread) |
handle (bg_process_queue); |
} |
}; |
|
if (delay) |
return call_out (enqueue(func, @args), delay); |
else { |
enqueue(func, @args)(); |
return 0; |
} |
#else |
|
return call_out (func, delay, @args); |
#endif |
} |
|
class BackgroundProcess |
|
|
|
|
|
|
{ |
int|float period; |
function func; |
array args; |
int stopping = 0; |
|
protected void repeat (function func, array args) |
{ |
|
|
|
|
|
int self_refs = Debug.refs (this); |
#ifdef DEBUG |
if (self_refs < 4) |
error ("Minimum ref calculation wrong - have only %d refs.\n", self_refs); |
#endif |
if (stopping || (self_refs <= 4) || !func) { |
stopping = 2; |
return; |
} |
mixed err = catch { |
func (@args); |
}; |
if (err) |
master()->handle_error (err); |
background_run (period, repeat, func, args); |
} |
|
|
|
|
|
|
|
|
void set_period (int|float period_) |
{ |
period = period_; |
} |
|
|
|
|
|
|
|
|
|
protected void create (int|float period_, function func_, mixed... args_) |
{ |
period = period_; |
func = func_; |
args = args_; |
background_run (period, repeat, func, args); |
} |
|
void stop() |
|
{ |
stopping |= 1; |
} |
|
void start() |
|
{ |
int state = stopping; |
stopping = 0; |
if (state & 2) { |
background_run (period, repeat, func, args); |
} |
} |
|
string _sprintf() |
{ |
return sprintf("BackgroundProcess(%O, %O)", period, func); |
} |
} |
|
|
mapping get_port_options( string key ) |
|
|
{ |
return (query( "port_options" )[ key ] || ([])); |
} |
|
void set_port_options( string key, mapping value ) |
|
|
{ |
mapping q = query("port_options"); |
q[ key ] = value; |
set( "port_options" , q ); |
save( ); |
} |
|
#ifdef DEBUG_URL2CONF |
#define URL2CONF_MSG(X...) report_debug (X) |
#else |
#define URL2CONF_MSG(X...) |
#endif |
|
protected mapping(string:int(0..1)) host_is_local_cache = ([]); |
|
|
int(0..1) host_is_local(string hostname) |
{ |
int(0..1) res; |
if (!zero_type(res = host_is_local_cache[hostname])) return res; |
|
|
string ip = blocking_host_to_ip(hostname); |
|
|
Stdio.Port port = Stdio.Port(); |
|
catch { |
res = port->bind(0, 0, ip); |
}; |
|
destruct(port); |
return host_is_local_cache[hostname] = res; |
} |
|
array(Protocol|mapping(string:mixed)) find_port_for_url ( |
Standards.URI url, void|Configuration only_this_conf) |
|
|
|
|
{ |
|
|
string host = url->host; |
if (has_value (host, ":")) |
host = "[" + (Protocols.IPv6.normalize_addr_basic (host) || host) + "]"; |
string url_with_port = sprintf ("%s://%s:%d%s", url->scheme, host, url->port, |
sizeof (url->path) ? url->path : "/"); |
|
URL2CONF_MSG("URL with port: %s\n", url_with_port); |
|
foreach (urls; string u; mapping(string:mixed) q) |
{ |
URL2CONF_MSG("Trying %O:%O\n", u, q); |
if( glob( u+"*", url_with_port ) ) |
{ |
URL2CONF_MSG("glob match\n"); |
if (Protocol p = q->port) |
if (mapping(string:mixed) url_data = |
p->find_url_data_for_url (url_with_port, 0, 0)) |
{ |
Configuration c = url_data->conf; |
URL2CONF_MSG("Found config: %O\n", url_data->conf); |
|
if ((only_this_conf && (c != only_this_conf)) || |
(sscanf (u, "%*s://%*[^*?]%*c") == 3 && |
|
(!host_is_local(url->host)))) { |
|
URL2CONF_MSG("Bad match: only_this_conf:%O, host_is_local:%O\n", |
(only_this_conf && (c == only_this_conf)), |
(!host_is_local(url->host))); |
c = 0; |
continue; |
} |
|
URL2CONF_MSG("Result: %O\n", c); |
return ({p, url_data}); |
} |
} |
} |
|
return ({0, 0}); |
} |
|
Configuration find_configuration_for_url(Standards.URI url, |
void|Configuration only_this_conf, |
void|array(Protocol) return_port) |
|
|
|
|
|
{ |
[Protocol port_obj, mapping(string:mixed) url_data] = |
find_port_for_url (url, only_this_conf); |
if (return_port) |
return_port[0] = port_obj; |
return url_data && url_data->conf; |
} |
|
class InternalRequestID |
|
{ |
inherit RequestID; |
|
this_program set_path( string f ) |
{ |
raw_url = Roxen.http_encode_invalids( f ); |
|
if( strlen( f ) > 5 ) |
{ |
string a; |
switch( f[1] ) |
{ |
case '<': |
if (sscanf(f, "/<%s>/%s", a, f)==2) |
{ |
config = (multiset)(a/","); |
f = "/"+f; |
} |
|
case '(': |
if(strlen(f) && sscanf(f, "/(%s)/%s", a, f)==2) |
{ |
prestate = (multiset)( a/","-({""}) ); |
f = "/"+f; |
} |
} |
} |
not_query = Roxen.simplify_path( scan_for_query( f ) ); |
return this; |
} |
|
this_program set_url( string url ) |
{ |
object uri = Standards.URI(url); |
prot = upper_case(uri->scheme); |
misc->host = uri->host; |
if ((prot == "HTTP" && uri->port != 80) || |
(prot == "HTTPS" && uri->port != 443)) |
misc->host += ":" + uri->port; |
string path = uri->path; |
raw_url = path; |
method = "GET"; |
raw = "GET " + raw_url + " HTTP/1.1\r\n\r\n"; |
[port_obj, mapping(string:mixed) url_data] = find_port_for_url (uri, 0); |
if (url_data) { |
conf = url_data->conf; |
if (!conf->inited) conf->enable_all_modules(); |
if (string config_path = url_data->path) |
adjust_for_config_path (config_path); |
} |
|
|
uri->path = (misc->site_prefix_path || "") + "/"; |
uri->query = UNDEFINED; |
uri->fragment = UNDEFINED; |
cached_url_base = sprintf("%s", uri); |
return set_path( raw_url ); |
} |
|
protected string _sprintf() |
{ |
return sprintf("InternalRequestID(conf=%O; not_query=%O)", conf, not_query ); |
} |
|
protected void create() |
{ |
client = ({ "Roxen" }); |
prot = "INTERNAL"; |
port_obj = InternalProtocol(); |
method = "GET"; |
real_variables = ([]); |
variables = FakedVariables( real_variables ); |
root_id = this_object(); |
cached_url_base = "internal://0.0.0.0:0/"; |
|
misc = ([ "pref_languages": PrefLanguages(), |
"cacheable": INITIAL_CACHEABLE, |
]); |
connection_misc = ([]); |
cookies = CookieJar(); |
throttle = ([]); |
client_var = ([]); |
request_headers = ([]); |
prestate = (<>); |
config = (<>); |
supports = (<>); |
pragma = (<>); |
rest_query = ""; |
extra_extension = ""; |
remoteaddr = "127.0.0.1"; |
} |
} |
|
class Protocol |
|
|
|
{ |
protected Stdio.Port port_obj; |
|
inherit "basic_defvar"; |
|
int bound; |
|
|
|
|
|
|
|
string path; |
constant name = "unknown"; |
|
constant supports_ipless = 0; |
|
|
constant requesthandlerfile = ""; |
|
|
|
|
constant default_port = 4711; |
|
|
string url_prefix = name + "://"; |
|
int port; |
|
|
string ip; |
|
|
|
|
|
int refs; |
|
|
|
program requesthandler; |
|
|
array(string) sorted_urls = ({}); |
|
|
mapping(string:mapping(string:mixed)) urls = ([]); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
mapping(Configuration:mapping(string:mixed)) conf_data = ([]); |
|
|
void ref(string name, mapping(string:mixed) data) |
|
|
|
|
{ |
if(urls[name]) |
{ |
conf_data[urls[name]->conf] = urls[name] = data; |
return; |
} |
if (!refs) path = data->path; |
else if (path != (data->path || "")) path = 0; |
refs++; |
mu = 0; |
conf_data[data->conf] = urls[name] = data; |
sorted_urls = Array.sort_array(indices(urls), |
lambda(string a, string b) { |
return sizeof(a)<sizeof(b); |
}); |
} |
|
void unref(string _name) |
|
{ |
|
|
|
m_delete(conf_data, urls[_name]->conf); |
m_delete(urls, _name); |
if (!path) { |
array(string) paths = Array.uniq (values (urls)->path); |
if (sizeof (paths) == 1) path = paths[0]; |
} |
sorted_urls -= ({_name}); |
#ifdef PORT_DEBUG |
report_debug("Protocol(%s)->unref(%O): refs:%d\n", |
get_url(), _name, refs); |
#endif /* PORT_DEBUG */ |
if( !--refs ) { |
if (retries) { |
remove_call_out(bind); |
} |
if (port_obj) { |
destruct(port_obj); |
} |
port_obj = 0; |
if (open_ports[name]) { |
if (open_ports[name][ip]) { |
m_delete(open_ports[name][ip], port); |
if(!sizeof(open_ports[name][ip])) { |
|
if (ip && ip != "::") |
m_delete(open_ports[name], ip); |
} |
} |
if (sizeof(open_ports[name]) <= 2) { |
|
int empty = 1; |
foreach(open_ports[name]; string ip; mapping m) { |
if (sizeof(m)) { |
empty = 0; |
break; |
} |
} |
if (empty) |
m_delete(open_ports, name); |
} |
} |
any_port = 0; |
|
} |
} |
|
Stdio.File accept() |
{ |
return port_obj->accept(); |
} |
|
string query_address() |
{ |
return port_obj && port_obj->query_address(); |
} |
|
mapping(string:mixed) mu; |
string rrhf; |
protected void got_connection() |
{ |
Stdio.File q; |
while( q = accept() ) |
{ |
if( !requesthandler && rrhf ) |
{ |
requesthandler = (program)(rrhf); |
} |
Configuration c; |
if( refs < 2 ) |
{ |
if(!mu) |
{ |
mu = get_iterator(urls)->value(); |
if(!(c=mu->conf)->inited ) { |
handle (lambda () { |
c->enable_all_modules(); |
call_out (lambda () |
{ |
|
|
|
|
|
if (q->is_open()) |
requesthandler (q, this, c); |
}, 0); |
}); |
return; |
} |
} else |
c = mu->conf; |
} |
requesthandler( q, this_object(), c ); |
} |
} |
|
private Protocol any_port; |
|
mapping(string:mixed) find_url_data_for_url (string url, int no_default, |
RequestID id) |
{ |
if( refs == 1 ) |
{ |
if (!no_default) { |
if(!mu) mu=get_iterator(urls)->value(); |
URL2CONF_MSG ("%O %O Only one configuration: %O\n", |
this, url, mu->conf); |
return mu; |
} |
} else if (!refs) { |
URL2CONF_MSG("%O %O No active URLS!\n", this, url); |
return 0; |
} |
|
URL2CONF_MSG("sorted_urls: %O\n" |
"url: %O\n", sorted_urls, url); |
|
|
|
foreach( sorted_urls, string in ) |
{ |
if( glob( in+"*", url ) ) |
{ |
URL2CONF_MSG ("%O %O sorted_urls: %O\n", this, url, urls[in]->conf); |
return urls[in]; |
} |
} |
|
if( no_default ) { |
URL2CONF_MSG ("%O %O no default\n", this, url); |
return 0; |
} |
|
|
|
|
|
|
if (!any_port) |
any_port = open_ports[ name ][ 0 ][ port ]; |
if (any_port && any_port != this) |
if (mapping(string:mixed) u = |
any_port->find_url_data_for_url (url, 1, id)) { |
URL2CONF_MSG ("%O %O found on ANY port: %O\n", this, url, u->conf); |
if (id) { |
id->misc->defaulted_conf = 1; |
id->port_obj = any_port; |
} |
return u; |
} |
|
|
|
mapping(Configuration:int(1..1)) choices = ([]); |
foreach( configurations, Configuration c ) |
if( c->query( "default_server" ) ) |
choices[c] = 1; |
|
if( sizeof( choices ) ) |
{ |
|
foreach (urls;; mapping cc) |
if( choices[ cc->conf ] ) |
{ |
URL2CONF_MSG ("%O %O conf in choices: %O\n", this, url, cc->conf); |
if (id) id->misc->defaulted_conf = 2; |
return cc; |
} |
} |
|
return 0; |
} |
|
Configuration find_configuration_for_url( string url, RequestID id ) |
|
|
|
|
{ |
mapping(string:mixed) url_data = find_url_data_for_url (url, 0, id); |
|
if (!url_data) { |
|
|
foreach (configurations, Configuration c) |
if (c->query ("default_server")) { |
URL2CONF_MSG ("%O %O any default server: %O\n", this, url, c); |
if (id) id->misc->defaulted_conf = 3; |
return c; |
} |
|
|
|
|
|
|
url_data = urls[sorted_urls[-1]]; |
if (id) { |
id->misc->defaulted_conf = 4; |
id->misc->defaulted=1; |
} |
URL2CONF_MSG ("%O %O last in sorted_urls: %O\n", this, url, |
url_data->conf); |
} |
|
|
|
|
string config_path = url_data->path; |
if (config_path && id && id->adjust_for_config_path) |
id->adjust_for_config_path (config_path); |
Configuration c = url_data->conf; |
return c; |
} |
|
mixed query_option( string x ) |
|
{ |
return query( x ); |
} |
|
string get_key() |
|
{ |
#if 0 |
if (ip == "::") |
return name + ":0:" + port; |
else |
#endif |
return name+":"+ip+":"+port; |
} |
|
string get_url() |
|
{ |
return (string) name + "://" + |
(!ip ? "*" : has_value (ip, ":") ? "[" + ip + "]" : ip) + |
":" + port + "/"; |
} |
|
void save() |
|
{ |
set_port_options( get_key(), |
mkmapping( indices(variables), |
map(indices(variables),query))); |
} |
|
void restore() |
|
{ |
setvars(get_port_options( get_key() )); |
} |
|
protected int retries; |
protected void bind (void|int ignore_eaddrinuse) |
{ |
if (bound) return; |
if (!port_obj) port_obj = Stdio.Port(); |
Privs privs = Privs (sprintf ("Binding %s", get_url())); |
if (port_obj->bind(port, got_connection, ip)) |
{ |
privs = 0; |
bound = 1; |
return; |
} |
privs = 0; |
#if constant(System.EAFNOSUPPORT) |
if (port_obj->errno() == System.EAFNOSUPPORT) { |
|
error("Invalid address " + ip); |
} |
#endif /* System.EAFNOSUPPORT */ |
#if constant(System.EADDRINUSE) |
if (port_obj->errno() == System.EADDRINUSE) { |
if (ignore_eaddrinuse) { |
|
bound = -1; |
return; |
} |
if (retries++ < 10) { |
|
|
report_error(LOC_M(6, "Failed to bind %s (%s)")+"\n", |
get_url(), strerror(port_obj->errno())); |
report_notice(LOC_M(62, "Attempt %d. Retrying in 1 minute.")+"\n", |
retries); |
call_out(bind, 60); |
} |
} |
else |
#endif /* constant(System.EADDRINUSE) */ |
{ |
report_error(LOC_M(6, "Failed to bind %s (%s)")+"\n", |
get_url(), strerror(port_obj->errno())); |
#if 0 |
werror (describe_backtrace (backtrace())); |
#endif |
} |
} |
|
string canonical_ip(string i) |
{ |
if (!i) return 0; |
if (has_value(i, ":")) |
return Protocols.IPv6.normalize_addr_short (i); |
else { |
|
array(int) segments = array_sscanf(i, "%d.%d.%d.%d"); |
string bytes; |
switch(sizeof(segments)) { |
default: |
case 4: |
bytes = sprintf("%1c%1c%1c%1c", @segments); |
break; |
case 0: return 0; |
case 1: |
|
|
|
bytes = sprintf("%4c", @segments); |
break; |
case 2: |
|
|
|
|
|
|
bytes = sprintf("%1c%3c", @segments); |
break; |
case 3: |
|
|
|
|
|
|
bytes = sprintf("%1c%1c%2c", @segments); |
break; |
} |
if (bytes == "\0\0\0\0") return 0; |
return sprintf("%d.%d.%d.%d", @((array(int))bytes)); |
} |
} |
|
protected void setup (int pn, string i) |
{ |
port = pn; |
ip = canonical_ip(i); |
|
restore(); |
if (sizeof(requesthandlerfile)) { |
if( file_stat( "../local/"+requesthandlerfile ) ) |
rrhf = "../local/"+requesthandlerfile; |
else |
rrhf = requesthandlerfile; |
DDUMP( rrhf ); |
#ifdef DEBUG |
if( !requesthandler ) |
requesthandler = (program)(rrhf); |
#endif |
} |
bound = 0; |
port_obj = 0; |
retries = 0; |
} |
|
protected void create( int pn, string i, void|int ignore_eaddrinuse ) |
|
{ |
setup (pn, i); |
bind (ignore_eaddrinuse); |
} |
|
protected string _sprintf( ) |
{ |
return "Protocol(" + get_url() + ")"; |
} |
} |
|
class InternalProtocol |
|
{ |
inherit Protocol; |
|
constant name = "internal"; |
|
constant prot_name = "internal"; |
|
constant supports_ipless = 1; |
constant default_port = 0; |
|
protected void create() |
{ |
path = ""; |
port = default_port; |
ip = "0.0.0.0"; |
} |
} |
|
#if constant(SSL.File) |
|
|
#if constant(SSL.Constants.fmt_cipher_suites) |
constant fmt_cipher_suite = SSL.Constants.fmt_cipher_suite; |
constant fmt_cipher_suites = SSL.Constants.fmt_cipher_suites; |
#else |
protected mapping(int:string) suite_to_symbol = ([]); |
|
string fmt_cipher_suite(int suite) |
{ |
if (!sizeof(suite_to_symbol)) { |
foreach(indices(SSL.Constants), string id) { |
if (has_prefix(id, "SSL_") || has_prefix(id, "TLS_") || |
has_prefix(id, "SSL2_")) { |
suite_to_symbol[SSL.Constants[id]] = id; |
} |
} |
} |
string res = suite_to_symbol[suite]; |
if (res) return res; |
return suite_to_symbol[suite] = sprintf("unknown(%d)", suite); |
} |
|
string fmt_cipher_suites(array(int) s) |
{ |
String.Buffer b = String.Buffer(); |
foreach(s, int c) { |
b->add(sprintf(" %-6d: %s\n", c, fmt_cipher_suite(c))); |
} |
return (string)b; |
} |
#endif |
|
class SSLContext { |
#if constant(SSL.Context) |
inherit SSL.Context; |
|
#if defined(DEBUG) || defined(SSL3_DEBUG) |
SSL.Alert alert_factory(SSL.Connection con, int level, int description, |
SSL.Constants.ProtocolVersion version, |
string|void debug_message) |
{ |
if (description != SSL.Constants.ALERT_close_notify) { |
if (debug_message) { |
werror("SSL %s: %s: %s", |
(level == SSL.Constants.ALERT_warning)? |
"WARNING":"ERROR", |
SSL.Constants.fmt_constant(description, "ALERT"), |
debug_message); |
} else { |
werror("SSL %s: %s\n", |
(level == SSL.Constants.ALERT_warning)? |
"WARNING":"ERROR", |
SSL.Constants.fmt_constant(description, "ALERT")); |
} |
} |
return ::alert_factory(con, level, description, version, debug_message); |
} |
#endif /* DEBUG || SSL3_DEBUG */ |
|
#else |
inherit SSL.context; |
#endif |
} |
|
|
|
|
class StartTLSProtocol |
{ |
inherit Protocol; |
|
|
SSLContext ctx = SSLContext(); |
|
int cert_failure; |
|
protected void cert_err_unbind() |
{ |
if (bound > 0) { |
port_obj->close(); |
report_warning ("TLS port %s closed.\n", get_url()); |
bound = 0; |
} |
} |
|
#define CERT_WARNING(VAR, MSG, ARGS...) do { \ |
string msg = (MSG); \ |
array args = ({ARGS}); \ |
if (sizeof (args)) msg = sprintf (msg, @args); \ |
report_warning ("TLS port %s: %s", get_url(), msg); \ |
(VAR)->add_warning (msg); \ |
} while (0) |
|
#define CERT_ERROR(VAR, MSG, ARGS...) do { \ |
string msg = (MSG); \ |
array args = ({ARGS}); \ |
if (sizeof (args)) msg = sprintf (msg, @args); \ |
report_error ("TLS port %s: %s", get_url(), msg); \ |
(VAR)->add_warning (msg); \ |
cert_err_unbind(); \ |
cert_failure = 1; \ |
return; \ |
} while (0) |
|
#if constant(SSL.Constants.PROTOCOL_TLS_MAX) |
protected void set_version(SSLContext|void ctx) |
{ |
if (!ctx) ctx = this_program::ctx; |
ctx->min_version = query("ssl_min_version"); |
} |
#endif |
|
protected void filter_preferred_suites(SSLContext|void ctx) |
{ |
if (!ctx) ctx = this_program::ctx; |
#if constant(SSL.ServerConnection) |
int mode = query("ssl_suite_filter"); |
int bits = query("ssl_key_bits"); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
array(int) suites = ({}); |
|
if (!mode) mode = 20; |
|
if ((mode & 8) && !ctx->configure_suite_b) { |
|
mode &= ~8; |
} |
|
if ((mode & 8) && ctx->configure_suite_b) { |
|
switch(mode) { |
case 15: |
|
ctx->configure_suite_b(bits, 2); |
break; |
case 14: |
|
ctx->configure_suite_b(bits, 1); |
break; |
default: |
ctx->configure_suite_b(bits); |
break; |
} |
suites = ctx->preferred_suites; |
|
if (ctx->min_version < query("ssl_min_version")) { |
set_version(ctx); |
} |
} else { |
suites = ctx->get_suites(bits, 1); |
|
|
|
set_version(ctx); |
} |
if (mode & 4) { |
|
suites = filter(suites, |
lambda(int suite) { |
return (< |
<