Select Git revision
connections.c
connections.c 31.18 KiB
/*
* Copyright (C) 1991-2005 Lysator Academic Computer Association.
*
* This file is part of the LysKOM server.
*
* LysKOM is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 1, or (at your option)
* any later version.
*
* LysKOM is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* for more details.
*
* You should have received a copy of the GNU General Public License
* along with LysKOM; see the file COPYING. If not, write to
* Lysator, c/o ISY, Linkoping University, S-581 83 Linkoping, SWEDEN,
* or the Free Software Foundation, Inc., 675 Mass Ave, Cambridge,
* MA 02139, USA.
*
* Please report bugs at http://bugzilla.lysator.liu.se/.
*/
/*
* connections.c
*
* Denna fil inneh}ller niv}n ovanf|r isc.
*
* Created by Willf|r 31/3-90. Mostly written by ceder.
*/
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include <errno.h>
#include <stdio.h>
#include <setjmp.h>
#ifdef HAVE_STRING_H
# include <string.h>
#endif
#include <sys/types.h>
#include <sys/stat.h>
#include "timewrap.h"
#include <sys/socket.h>
#include <signal.h>
#include <assert.h>
#include <netdb.h>
#include <stdlib.h>
#include "adns.h"
#include "oop.h"
#include "unused.h"
#include "ldifftime.h"
#include "misc-types.h"
#include "s-string.h"
#include "kom-types.h"
#include "kom-memory.h"
#include "debug.h"
#include "isc-interface.h"
#include "com.h"
#include "async.h"
#include "connections.h"
#include "internal-connections.h"
#include "prot-a-parse-arg.h"
#include "log.h"
#include "lyskomd.h"
#include "services.h"
#include "isc-parse.h"
#include "prot-a.h"
#include "prot-a-parse.h"
#include "server/smalloc.h"
#include "end-of-atomic.h"
#include "send-async.h"
#include "cache.h"
#include "rfc931.h"
#include "param.h"
#include "kom-config.h"
#include "kom-errno.h"
#include "sigflags.h"
#include "server-time.h"
#include "aux-items.h"
#include "eintr.h"
#include "text-garb.h"
#include "timeval-util.h"
#include "stats.h"
#include "string-malloc.h"
#include "manipulate.h"
oop_source_sys * kom_server_oop_src = NULL;
struct isc_mcb * kom_server_mcb = NULL;
Connection * active_connection = NULL;
/*
* This is set TRUE when the server should be closed. It is checked
* each time around the main loop. It is set if someone with enough
* privileges issues a `shutdown', or of lyskomd receives a SIGTERM.
* This not an abort: all data is saved before we exit.
*/
Bool go_and_die = FALSE;
/*
* The number of times that the session penalties has been averaged.
* Beware: this number will wrap around.
*/
static unsigned int penalty_generation = 0;
/*
* These state variables are used to find out if we are busy or not.
* When a packet arrives, data_available_callback() will set work_done
* to TRUE and is_idle to FALSE.
*/
static Bool work_done = FALSE;
static Bool is_idle = FALSE;
jmp_buf parse_env;
const Fnc_descriptor fnc_defs[]={
#include "fnc-def-init.incl"
};
const int num_fnc_defs = sizeof (fnc_defs) / sizeof (Fnc_descriptor);
unsigned long service_statistics[sizeof (fnc_defs) / sizeof (Fnc_descriptor)];
BUGDECL;
static oop_call_fd data_available_callback;
static oop_call_time check_kill_flg;
static oop_call_time check_idle_callback;
static isc_write_error_cb write_err_cb;
static isc_stale_output_cb stale_cb;
static isc_stale_output_cb idle_cb;
/* head and tail pointers for the wait queue that uses the queue_prev,
queue_next and on_queue fields of Connection. */
static Connection *queue_first = NULL;
static Connection *queue_last = NULL;
static void busy(void);
/* Add c to the end of the wait queue. */
static void
queue_add(Connection *c)
{
assert(c->on_queue == FALSE);
c->on_queue = TRUE;
c->queue_prev = queue_last;
c->queue_next = NULL;
if (queue_first == NULL)
queue_first = c;
else
queue_last->queue_next = c;
queue_last = c;
update_stat(STAT_RUN_QUEUE, 1);
}
/* Remove c from the wait queue. Any element on the queue can be removed. */
static void
queue_remove(Connection *c)
{
assert(c->on_queue == TRUE);
c->on_queue = FALSE;
if (c->queue_next != NULL)
c->queue_next->queue_prev = c->queue_prev;
else
queue_last = c->queue_prev;
if (c->queue_prev != NULL)
c->queue_prev->queue_next = c->queue_next;
else
queue_first = c->queue_next;
c->queue_prev = NULL;
c->queue_next = NULL;
update_stat(STAT_RUN_QUEUE, -1);
}
void
set_time(void)
{
struct timeval last_time;
static int limiter = 0;
last_time = current_time;
if (gettimeofday(¤t_time, NULL) < 0)
{
if (limiter < 50)
{
kom_log("WARNING: gettimeofday failed: %s\n", strerror(errno));
if (++limiter == 50)
kom_log("WARNING: will not log the above message again.\n");
}
}
if (timeval_less(current_time, last_time))
{
kom_log("WARNING: Time moved backward at least %g seconds.\n",
timeval_diff_d(last_time, current_time));
/* FIXME (bug 62): Should we take more decisive action here? */
}
}
static void
logout_client(Connection *cp)
{
Connection *real_active_connection;
int ret;
if ( active_connection != NULL )
{
kom_log("BUGCHK: logout_client(%ld): connection %ld is active.\n",
cp->session_no, active_connection->session_no);
}
if ( cp->pers_no != 0 )
{
int ctr = 0;
if (active_connection != NULL)
{
kom_log("WNG: logout_client(): active_connection != NULL\n");
if (ctr < 100)
ctr++;
else
kom_log("WNG: won't log the above message more\n");
}
real_active_connection = active_connection;
active_connection = cp;
logout();
active_connection = real_active_connection;
}
else
{
#if 0
/* FIXME (bug 908): send a new async here instead. This causes the
elisp client to say that a secret (or unknown) person has
left the system. */
async_logout( 0, cp->session_no );
#endif
}
switch(cp->protocol)
{
case 0: /* Hasn't yet allocated any protocol. */
break;
case 'A':
prot_a_destruct(cp);
break;
default:
restart_kom("logout_client(): Bad protocol.\n");
}
ret = isc_destroy(kom_server_mcb, cp->isc_session);
if (ret < 0)
kom_log("logout_client(): isc_destroyed returned %d\n", ret);
cp->isc_session = NULL;
if (cp->on_queue)
queue_remove(cp);
kill_client(cp); /* Free the Connection */
update_stat(STAT_CLIENTS, -1);
}
/*
* This function is part of the shutdown tidy-up sequence.
*/
void
logout_all_clients(void)
{
Session_no sess = 0;
Connection *conn;
while ( (sess = traverse_connections (sess)) != 0)
{
conn = get_conn_by_number (sess);
if ( conn == NULL )
restart_kom("logout_all_clients(): cant get session %ld.\n",
sess);
else
logout_client (conn);
}
if ( traverse_connections (0) != 0)
restart_kom("logout_all_clients(): traverse_connections(0) == %ld.\n",
traverse_connections(0));
}
/*
* Call a function in services.c. A pointer to the result is returned.
* The pointer points to static data which is overwritten on each call.
*/
static Success
call_function(Connection *client,
union result_holder *res)
{
Success status=FAILURE; /* OK if the call was successful. */
if ( active_connection != NULL )
{
kom_log("call_function(%ld): active_connection = %ld\n",
client->session_no, active_connection->session_no);
}
if (client->function == illegal_fnc)
{
err_stat = 0;
kom_errno = KOM_NOT_IMPL;
return FAILURE;
}
active_connection = client;
service_statistics[client->function_index]++;
#include "call-switch.incl"
active_connection = NULL;
return status;
}
static void
parse_packet(Connection *client)
{
if ( client->protocol == '\0' ) /* Not known yet. */
{
client->protocol = parse_char(client);
switch(client->protocol)
{
case 'A':
prot_a_init(client);
break;
default:
client->protocol = '\0';
isc_puts("%%LysKOM unsupported protocol.\n", client->isc_session);
isc_flush(client->isc_session);
BUG(("%%%%Unsupported protocol.\n"));
longjmp(parse_env, KOM_LOGOUT);
}
}
switch(client->protocol)
{
case 'A':
prot_a_parse_packet(client);
break;
default:
restart_kom("parse_packet(): Bad protocol.\n");
break;
}
}
/*
* Free all parsed areas which are no longer needed. Re-initialize all
* parse_pos fields so that the parse will expect a new function.
*
* This function is called
* when a parse error occurs
* when a parse is complete and the function has executed.
*/
static void
free_parsed(Connection *client)
{
s_clear(&client->c_string0);
s_clear(&client->c_string1);
client->string0 = EMPTY_STRING; /* So that no one frees it. */
sfree(client->misc_info_list.misc);
client->misc_info_list.misc = 0;
client->misc_info_list.no_of_misc = 0;
s_clear(&client->aux_item.data);
s_clear(&client->dummy_aux_item.data);
sfree(client->read_range_list.ranges);
client->read_range_list.ranges = NULL;
client->read_range_list.length = 0;
client->parse_pos = 0;
client->fnc_parse_pos = 0;
client->array_parse_index = 0;
client->array_parse_parsed_length = 0;
client->array_parse_pos = 0;
client->struct_parse_pos = 0;
client->string_parse_pos = 0;
client->hunt_parse_pos = 0;
client->array_hunt_num = 0;
client->array_hunt_depth = 0;
sfree(client->num_list.data);
client->num_list.data = NULL;
client->num_list.length = 0;
free_aux_item_list(&client->aux_item_list);
client->info.highest_aux_no = 0;
}
/*
* Send a reply to a call.
*/
static void
reply(Connection *client,
Success status,
union result_holder *result)
{
switch(client->protocol)
{
case 'A':
prot_a_reply(client, status, result);
break;
default:
restart_kom("reply(): Bad protocol.\n");
break;
}
}
/*
* Try to parse enough data from client->unparsed to call a function.
* If more data is needed set client->more_to_parse to FALSE. Returns
* TRUE if anything was (or might have been) written to the client.
*/
static Bool
parse_unparsed(Connection *client)
{
Success status;
union result_holder result;
switch ( setjmp(parse_env) )
{
case 0 :
/* Parse message. If message is complete call function and reply. */
parse_packet(client);
if (client->blocked_by_dns)
return TRUE;
#ifdef DEBUG_CALLS
if (client->blocked_by_disable_client)
return TRUE;
#endif
update_stat(STAT_REQUESTS, 1);
status = call_function(client, &result);
update_stat(STAT_REQUESTS, -1);
reply(client, status, &result);
client->penalty += param.penalty_per_call;
free_parsed(client);
end_of_atomic();
return TRUE;
case KOM_PROTOCOL_ERR:
s_clear(&client->string0);
free_parsed(client);
isc_puts("%% LysKOM protocol error.\n", client->isc_session);
BUG(("%%%% Protocol error.\n"));
client->penalty += param.max_penalty;
s_clear(&client->unparsed);
client->first_to_parse = 0;
client->more_to_parse = FALSE;
end_of_atomic();
return TRUE;
case KOM_MSG_INCOMPLETE:
client->more_to_parse = FALSE;
return FALSE;
case KOM_LOGOUT:
add_to_kill_list(client);
client->more_to_parse = FALSE;
return TRUE;
default:
restart_kom("Bad longjmp return value.\n");
}
/*NOTREACHED*/
}
/* Return 1 if the named file exists, 0 otherwise */
static int
fexists(const char *filename)
{
struct stat buf;
int code;
code = !stat(filename, &buf);
errno = 0;
return code;
}
void
dump_statistics(void)
{
static struct timeval last_dump = {0, 0};
int i;
FILE *fp;
if ((fp = i_fopen(param.statistic_name, "a")) == NULL)
{
kom_log("dump_statistics(): can't open file %s\n",
param.statistic_name);
return;
}
if (timeval_zero(last_dump))
{
fprintf(fp, "RESTART\n");
last_dump = current_time;
}
fprintf(fp, "TIME: %s", ctime(¤t_time.tv_sec));
fprintf(fp, "SECONDS: %ld\n", timeval_diff_sec(current_time, last_dump));
fprintf(fp, "STATISTICS:");
/* The last entry corresponds to the dummy entry that is used to
skip arguments to unimplemented requests. Skip that, since it
contains no useful statistics. */
for (i = 0; i < num_fnc_defs - 1; i++)
{
fprintf(fp, " %d:%lu", fnc_defs[i].function, service_statistics[i]);
service_statistics[i]=0;
}
fprintf(fp, "\n");
i_fclose(fp);
last_dump = current_time;
}
/* List of connections to kill. */
static Session_no *kill_list = NULL;
static int kill_list_size = 0;
static int kill_pending = 0;
/* Schedule this client for termination. */
void
add_to_kill_list(Connection *conn)
{
oop_source *source;
int i;
switch (conn->kill_status)
{
case ks_pending:
/* A kill is already pending. Do nothing--but check that the
the client really is present on the kill_list. */
for (i = 0; i < kill_list_size; i++)
if (kill_list[i] == conn->session_no)
return;
restart_kom("add_to_kill_list(): kill_pending set but not on list.\n");
return;
case ks_dying:
/* Don't add this client to the kill list while it is being
killed. */
return;
case ks_none:
/* The normal case. Do all the work below. */
break;
}
/* Check that the client isn't already present on the kill_list. */
for (i = 0; i < kill_list_size; i++)
if (kill_list[i] == conn->session_no)
restart_kom("add_to_kill_list(): on list but not kill_pending.\n");
if (kill_list == NULL)
{
if (kill_list_size != 0)
restart_kom("add_to_kill_list(): size = %d\n", kill_list_size);
kill_list_size = 1;
kill_list = smalloc(sizeof(Session_no));
}
else
{
kill_list_size++;
kill_list = srealloc(kill_list, kill_list_size * sizeof(Session_no));
}
kill_list[kill_list_size-1] = conn->session_no;
conn->kill_status = ks_pending;
if (!kill_pending)
{
source = isc_getoopsource(conn->isc_session);
source->on_time(source, OOP_TIME_NOW, check_kill_flg, NULL);
kill_pending = 1;
}
}
static void
dump_connections(void)
{
Session_no s;
Connection *conn;
FILE *fp;
if ((fp = i_fopen(param.connection_status_file_tmp, "w")) == NULL)
{
kom_log("dump_connections(): can't open file %s: %s\n",
param.connection_status_file_tmp, strerror(errno));
return;
}
for (s = 0; (s = traverse_connections(s)) != 0;)
{
conn = get_conn_by_number(s);
fprintf(fp, "%d %lu %d %s\n", conn->isc_session->fd, conn->session_no,
handshake_ok(conn, 0),
conn->peer);
}
if (fflush(fp) != 0)
kom_log("dump_connections(): fflush() says an error has occured.\n");
if (ferror(fp))
kom_log("dump_connections(): ferror() says an error has occured.\n");
if (i_fclose(fp) < 0)
{
kom_log("dump_connections(): fclose failed: %s (ignored)\n",
strerror(errno));
}
errno = 0;
if (i_rename(param.connection_status_file_tmp,
param.connection_status_file) < 0)
{
kom_log("dump_connections(): can't rename %s to %s: %s\n",
param.connection_status_file_tmp,
param.connection_status_file,
strerror(errno));
}
}
/*
* check_kill_flg must NEVER be called inside an atomic call!
*/
static void *
check_kill_flg(oop_source *UNUSED(source),
struct timeval UNUSED(tv),
void *UNUSED(user))
{
Connection *conn;
Bool changed = FALSE;
kill_pending = 0;
if ( active_connection != NULL )
{
restart_kom("check_kill_flg: active_connection == %ld",
active_connection->session_no);
}
while (kill_list_size > 0)
{
--kill_list_size;
conn = get_conn_by_number (kill_list[kill_list_size]);
if (conn == NULL)
{
kom_log("check_kill_flg(): Connection %ld doesn't exist.\n",
kill_list[kill_list_size]);
}
else
{
assert(conn->kill_status == ks_pending);
conn->kill_status = ks_dying;
logout_client(conn);
end_of_atomic();
changed = TRUE;
}
}
if (kill_list != NULL)
{
sfree (kill_list);
kill_list = NULL;
}
if (changed == TRUE)
dump_connections();
return OOP_CONTINUE;
}
static void *
dns_resolution(struct isc_scb *scb,
enum isc_resolve_status res,
long errcode)
{
struct timeval after;
double diff = -1.0;
Connection *conn;
char *hostname = NULL;
update_stat(STAT_DNS_QUEUE, -1);
conn = scb->udg;
conn->dns_done = TRUE;
if (conn->blocked_by_dns)
{
conn->blocked_by_dns = FALSE;
if (!conn->on_queue)
queue_add(conn);
}
if (res == isc_resolve_aborted)
return OOP_CONTINUE;
if (gettimeofday(&after, NULL) < 0)
kom_log("gettimeofday failed: %s\n", strerror(errno));
diff = timeval_diff_d(after, conn->connect_time);
busy();
switch (res)
{
case isc_resolve_h_errno:
if (hostname == NULL)
hostname = s_crea_c_str(scb->remote);
if (errcode == HOST_NOT_FOUND)
kom_log("No hostname found for %s.\n", hostname);
else if (errcode == TRY_AGAIN)
kom_log("Lookup of %s timed out.\n", hostname);
else if (errcode == NO_RECOVERY)
kom_log("Non-recoverable error looking up %s.\n", hostname);
else if (errcode == NO_ADDRESS)
kom_log("Got NO_ADDRESS error looking up %s.\n", hostname);
else
kom_log("Unknown resolver error %ld looking up %s.\n",
errcode, hostname);
break;
case isc_resolve_adns_error:
if (hostname == NULL)
hostname = s_crea_c_str(scb->remote);
/* Misconfigurations of localhost are common and harmless.
Don't bother logging them, since that makes the test cases fail. */
if ((errcode != adns_s_inconsistent && errcode != adns_s_nxdomain)
|| strcmp(hostname, "127.0.0.1") != 0)
kom_log("Error looking up %s: %s\n",
hostname, adns_strerror(errcode));
break;
case isc_resolve_aborted:
abort();
case isc_resolve_ok:
break;
}
if (diff > param.dns_log_threshold)
{
if (hostname == NULL)
hostname = s_crea_c_str(scb->remote);
if (res == isc_resolve_ok)
kom_log("Slow DNS: got %s after %f seconds\n", hostname, diff);
else
kom_log("Slow bad DNS: %s failed after %f seconds\n",
hostname, diff);
}
if (hostname != NULL)
string_free(hostname);
if (handshake_ok(conn, 0))
dump_connections();
return OOP_CONTINUE;
}
static void
write_err_cb(struct isc_scb *cb_session,
int saved_errno)
{
Connection * cp = cb_session->udg;
if (saved_errno != ECONNRESET && saved_errno != EPIPE)
kom_log("Failed to write to client %lu from %s: %s\n",
cp->session_no, cp->peer, strerror(saved_errno));
cp->penalty += param.max_penalty;
add_to_kill_list(cp);
}
static void
stale_cb(struct isc_scb *cb_session)
{
Connection *cp = cb_session->udg;
kom_log("Client %lu from %s has stalled. Killing it.\n",
cp->session_no, cp->peer);
cp->penalty += param.max_penalty;
add_to_kill_list(cp);
}
static void
idle_cb(struct isc_scb *cb_session)
{
Connection *cp = cb_session->udg;
kom_log("Client %lu from %s has been idle too long. Killing it.\n",
cp->session_no, cp->peer);
cp->penalty += param.max_penalty;
add_to_kill_list(cp);
}
static void
login_request(struct isc_scb *session)
{
Connection * cp;
const char *realuser;
char portbuf[1+2+3*sizeof(long)];
size_t portlen;
char *remote_ip = NULL;
Bool dns_submitted = FALSE;
/* Supress logins if /etc/nologin exists */
if (fexists(param.nologin_file))
{
isc_puts("%% No logins allowed.\n", session);
isc_flush(session);
isc_destroy(kom_server_mcb, session);
return;
}
/* Create a Connection, and link the Connection and the
isc_session together. */
cp = new_client();
cp->isc_session = session;
session->udg = cp;
update_stat(STAT_CLIENTS, 1);
/* Store the IP address in readable form. */
s_crea_str(&cp->remote_ip, isc_getipnum(session->raddr, NULL, 0));
remote_ip = s_crea_c_str(cp->remote_ip);
/* Initiate DNS lookup. */
if (param.use_dns)
{
int rv = isc_resolve_remote(session, dns_resolution);
if (rv == 0)
{
dns_submitted = TRUE;
update_stat(STAT_DNS_QUEUE, 1);
}
else
{
kom_log("WNG: isc_resolve_remote of %s failed: %s\n",
remote_ip, strerror(rv));
}
}
if (!dns_submitted)
{
s_strcpy(&session->remote, cp->remote_ip);
cp->blocked_by_dns = FALSE;
cp->dns_done = TRUE;
}
/* Update the status file that contains all connection. */
sprintf(portbuf, " %d", isc_getportnum(session->raddr));
portlen = strlen(portbuf);
cp->peer = smalloc(s_strlen(cp->remote_ip) + portlen + 1);
strcpy(cp->peer, remote_ip);
strcpy(cp->peer + s_strlen(cp->remote_ip), portbuf);
dump_connections();
/* Start with max penalty, so that it doesn't pay to make a lot of
new connections. */
cp->penalty = param.max_penalty;
cp->penalty_generation = penalty_generation;
cp->schedule.priority = param.default_priority;
cp->schedule.weight = param.default_weight;
/* Get the real user name, as returned by the Ident protocol (rfc 931). */
realuser = get_real_username(session, remote_ip);
if (realuser == NULL && param.authentication_level == 2)
{
kom_log("Connection from %s rejected - no IDENT available.\n",
remote_ip);
isc_puts("%% No IDENT server reachable at your site.\n",
session);
isc_flush(session);
logout_client(cp);
string_free(remote_ip);
return;
}
if (realuser != NULL)
s_crea_str(&cp->ident_user, realuser);
BUG(("\n[Client %lu from %s is connecting]\n", cp->session_no, remote_ip));
isc_set_read_callback(session, data_available_callback, write_err_cb,
stale_cb, idle_cb);
string_free(remote_ip);
}
static void
adjust_penalty(Connection *conn)
{
unsigned int gens = penalty_generation - conn->penalty_generation;
if (gens > 0)
{
/* The weight is in the range 1-0xffff (inclusive).
The penalty is in the range 0-0x10000 (inclusive).
This means that the multiplication can never overflow. */
if (conn->penalty <= gens)
conn->penalty = 0;
else
{
unsigned int tmp = conn->penalty - gens * conn->schedule.weight;
if (tmp < conn->penalty)
conn->penalty = tmp;
else
conn->penalty = 0;
}
conn->penalty_generation = penalty_generation;
}
}
static Bool
may_read_more(Connection *conn)
{
if (go_and_die)
return FALSE;
if (conn->penalty >= param.max_penalty)
return FALSE;
if (conn->kill_status != ks_none)
return FALSE;
if (conn->blocked_by_dns)
return FALSE;
#ifdef DEBUG_CALLS
if (conn->blocked_by_disable_client == 1)
return FALSE;
#endif
return TRUE;
}
static Bool
should_be_disabled(Connection *conn)
{
#ifdef DEBUG_CALLS
if (conn->blocked_by_disable_client == 1)
return TRUE;
#endif
return conn->penalty >= param.max_penalty && !conn->blocked_by_dns;
}
static void
read_from_connection(Connection *conn)
{
Bool would_block = FALSE;
Bool need_flush = FALSE;
String_size pre;
adjust_penalty(conn);
while (!would_block && may_read_more(conn))
{
pre = s_strlen(conn->unparsed) - conn->first_to_parse;
while (conn->more_to_parse && may_read_more(conn))
need_flush |= parse_unparsed(conn);
update_stat(STAT_RECV_QUEUE,
s_strlen(conn->unparsed) - conn->first_to_parse - pre);
if (!may_read_more(conn))
break;
if (!conn->more_to_parse)
{
pre = s_strlen(conn->unparsed) - conn->first_to_parse;
switch (isc_read_data(conn->isc_session,
&conn->unparsed,
&conn->first_to_parse))
{
case ISC_READ_DATA:
update_stat(STAT_RECV_QUEUE,
s_strlen(conn->unparsed) - conn->first_to_parse
- pre);
conn->penalty += param.penalty_per_read;
conn->more_to_parse = TRUE;
break;
case ISC_READ_ERROR:
if (errno != ECONNRESET && errno != ETIMEDOUT)
kom_log("Error reading from client: %s\n",
strerror(errno));
/*FALLTHROUGH*/
case ISC_READ_LOGOUT:
add_to_kill_list(conn);
break;
case ISC_READ_WOULDBLOCK:
would_block = TRUE;
break;
case ISC_READ_NOMEM:
restart_kom("isc_read_data() reports no memory\n");
}
if (!conn->more_to_parse)
break;
}
}
if (need_flush)
isc_flush(conn->isc_session);
/* Delete the parsed part of 'unparsed' */
if (s_trim_left(&conn->unparsed, conn->first_to_parse) != OK)
restart_kom("parse_unparsed: s_trim_left\n");
conn->first_to_parse = 0;
if (should_be_disabled(conn))
{
/* isc_disable() will fail if we have received EPIPE on
this socket. In that case will soon close it, since
write_err_cb() has added it to the kill list. */
if (isc_disable(conn->isc_session) == 0)
queue_add(conn);
}
}
static void
enable_idle_check(void)
{
oop_source *source = oop_sys_source(kom_server_oop_src);
source->on_time(source, OOP_TIME_NOW, check_idle_callback, NULL);
}
static void *
check_idle_callback(oop_source *UNUSED(source),
struct timeval UNUSED(tv),
void *UNUSED(user))
{
Connection *c;
Connection *next;
Connection *head = NULL;
Connection *tail = NULL;
set_time();
is_idle = !work_done;
work_done = FALSE;
if (is_idle && queue_first != NULL)
{
is_idle = FALSE;
++penalty_generation;
for (next = queue_first; next != NULL; )
{
c = next;
next = next->queue_next;
adjust_penalty(c);
if (!should_be_disabled(c))
{
queue_remove(c);
if (head == NULL)
head = c;
else
tail->queue_next = c;
tail = c;
}
}
}
for (next = head; next != NULL; )
{
c = next;
next = next->queue_next;
c->queue_next = NULL;
if (isc_enable(c->isc_session) < 0)
{
kom_log("failed to re-enable session %ld\n", (long)c->session_no);
}
else if (!go_and_die)
{
/* The call to read_from_connection() might add the
connection to the queue. That's why we remove all entries
first, and use a private queue within this function. */
read_from_connection(c);
}
}
if (!is_idle)
enable_idle_check();
/* Check if a client issued a shutdown command. */
return go_and_die ? OOP_HALT : OOP_CONTINUE;
}
static void *
saver_callback(oop_source *source,
struct timeval UNUSED(tv),
void *user)
{
struct timeval timeout;
struct timeval *next_timer = user;
timeout = end_of_atomic();
if (setup_timer(next_timer, timeout) < 0)
kom_log("gettimeofday failed: %s\n", strerror(errno));
source->on_time(source, *next_timer, saver_callback, user);
return OOP_CONTINUE;
}
static void
busy(void)
{
/* Something arrived, so we are busy. */
if (is_idle)
{
is_idle = FALSE;
enable_idle_check();
}
work_done = TRUE;
}
static void *
data_available_callback(oop_source *source,
int fd,
oop_event event,
void *user)
{
Connection *conn = ((struct isc_scb*)user)->udg;
assert(event == OOP_READ);
assert(conn->isc_session->fd == fd);
assert(isc_getoopsource(conn->isc_session) == source);
assert(conn->on_queue == FALSE);
busy();
set_time();
read_from_connection(conn);
/* Check if the client issued a shutdown command. */
return go_and_die ? OOP_HALT : OOP_CONTINUE;
}
void *
handle_accept_event(struct isc_scb *UNUSED(accepting_session),
struct isc_scb *new_session)
{
new_session->udg = NULL;
set_time();
if (new_session->fd <= PROTECTED_FDS || new_session->fd >= fd_ceiling)
{
BUG(("Connection attempt rejected.\n"));
isc_puts("%% No connections left.\n", new_session);
isc_flush(new_session);
isc_destroy(new_session->master, new_session);
async_rejected_connection();
}
else
login_request(new_session);
return OOP_CONTINUE;
}
void
toploop(void)
{
struct timeval saver_timer;
void *exit_reason;
oop_source *source = oop_sys_source(kom_server_oop_src);
/* Start the garb right away. */
start_garb_thread(source);
/* Hack to find out when we are idle. */
enable_idle_check();
/* Save the database even if we happen to be idle. */
saver_timer = OOP_TIME_NOW;
source->on_time(source, saver_timer, saver_callback, &saver_timer);
exit_reason = oop_sys_run(kom_server_oop_src);
if (exit_reason == OOP_ERROR)
kom_log("ERROR: unexpected error from oop_sys_run: %s\n",
strerror(errno));
else if (exit_reason == OOP_CONTINUE)
kom_log("ERROR: all oop sinks disappeared\n");
else if (exit_reason != OOP_HALT)
kom_log("ERROR: unexpected error from oop_sys_run: %s\n",
strerror(errno));
if (kill_pending)
{
source->cancel_time(source, OOP_TIME_NOW, check_kill_flg, NULL);
check_kill_flg(NULL, OOP_TIME_NOW, NULL);
}
if (is_idle == FALSE)
source->cancel_time(source, OOP_TIME_NOW, check_idle_callback, NULL);
stop_garb_thread(source);
source->cancel_time(source, saver_timer, saver_callback, &saver_timer);
}
Bool
server_idle(void)
{
return is_idle;
}
Success
get_scheduling(Session_no session_no,
Scheduling_info *result)
{
Connection *cptr;
CHK_CONNECTION(FAILURE);
if (session_no != 0 && session_no != active_connection->session_no)
CHK_LOGIN(FAILURE);
if ((cptr = get_conn_by_number(session_no)) == NULL
|| !handshake_ok(cptr, 0))
{
kom_errno = KOM_UNDEF_SESSION;
err_stat = session_no;
return FAILURE;
}
*result = cptr->schedule;
return OK;
}
static Bool
may_change_scheduling(Session_no session_no,
Connection *cptr)
{
if (session_no == 0)
return TRUE;
if (session_no == active_connection->session_no)
return TRUE;
if (cptr->pers_no == 0
&& (ENA_C(active_connection, admin, 2)
|| ENA_C(active_connection, wheel, 8)))
return TRUE;
if (has_access(cptr->pers_no, active_connection, unlimited))
return TRUE;
return FALSE;
}
Success
set_scheduling(Session_no session_no,
unsigned short priority,
unsigned short weight)
{
Connection *cptr;
CHK_CONNECTION(FAILURE);
if (weight == 0)
{
kom_errno = KOM_WEIGHT_ZERO;
err_stat = 0;
return FAILURE;
}
if (session_no != 0 && session_no != active_connection->session_no)
CHK_LOGIN(FAILURE);
if ((cptr = get_conn_by_number(session_no)) == NULL
|| !handshake_ok(cptr, 0))
{
kom_errno = KOM_UNDEF_SESSION;
err_stat = session_no;
return FAILURE;
}
if (!may_change_scheduling(session_no, cptr))
{
kom_errno = KOM_ACCESS;
err_stat = session_no;
return FAILURE;
}
if (priority > param.max_priority)
{
kom_errno = KOM_INDEX_OUT_OF_RANGE;
err_stat = param.max_priority;
return FAILURE;
}
#if 0
/* Since priority is unsigned this can never happen. */
if (priority < 0)
{
kom_errno = KOM_PRIORITY_DENIED;
err_stat = 0;
return FAILURE;
}
#endif
if (weight > param.max_weight)
{
kom_errno = KOM_WEIGHT_DENIED;
err_stat = param.max_weight;
return FAILURE;
}
cptr->schedule.priority = priority;
cptr->schedule.weight = weight;
return OK;
}
#ifdef DEBUG_CALLS
Success
disable_client(Session_no session_no,
int state)
{
Connection *cptr;
CHK_CONNECTION(FAILURE);
if (state != 0 && state != 1)
{
kom_errno = KOM_BAD_BOOL;
err_stat = 0;
return FAILURE;
}
if (session_no != 0 && session_no != active_connection->session_no)
CHK_LOGIN(FAILURE);
if ((cptr = get_conn_by_number(session_no)) == NULL
|| !handshake_ok(cptr, 0))
{
kom_errno = KOM_UNDEF_SESSION;
err_stat = session_no;
return FAILURE;
}
cptr->blocked_by_disable_client = state;
return OK;
}
#endif