/* * $Id: connections.c,v 0.11 1991/09/21 13:07:03 ceder Exp $ * Copyright (C) 1991 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 mail bug reports to bug-lyskom@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. */ static char *rcsid = "$Id: connections.c,v 0.11 1991/09/21 13:07:03 ceder Exp $"; #include <errno.h> #include <setjmp.h> #include <sys/types.h> #include <sys/file.h> #include <sys/stat.h> #include <time.h> #include <sys/time.h> #include "s-string.h" #include <kom-types.h> #include "lyskomd.h" #include <config.h> #include <debug.h> #include "end-of-atomic.h" #include "log.h" #include <services.h> #include "isc-interface.h" #include <kom-errno.h> #include "com.h" #include "connections.h" #include "prot-a-parse.h" #include "prot-a-output.h" #include "prot-a.h" #include <server/smalloc.h> #include "prot-a-parse-arg.h" #include "isc-parse.h" #include "cache.h" #include "send-async.h" #include "mux.h" #include "mux-parse.h" #include "internal-connections.h" ISCMCB * 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 SIGHUP. * This not an abort: all data is saved before we exit. */ volatile Bool go_and_die = FALSE; /* * Once upon a time the entire database was always held in core, and * saved to disk when we got a SIGUSR1 signal. Nowadays the server * keeps track of the time itself and saves at appropriate intervalls. * It does still write some statistics when it get a SIGUSR1 signal. * The signal handler sets do_sync_db. The name is retained for * historical reasons. */ volatile Bool do_sync_db = FALSE; jmp_buf parse_env; const Fnc_descriptor fnc_defs[]={ #include "fnc-def-init.incl" }; u_long service_statistics[sizeof (fnc_defs) / sizeof (Fnc_descriptor)]; BUGDECL; static void logout_client(Connection *cp) { Connection *real_active_connection; if ( active_connection != NULL ) { log("BUGCHK: logout_client(%d): connection %d is active.\n", cp->session_no, active_connection->session_no); } switch (cp->magic) { case CONN_MAGIC_ALLOC: break; case CONN_MAGIC_FREE: log("LOGOUT_CLIENT: Trying to free freed Connection - ignored!\n"); return; default: restart_kom("LOGOUT_CLIENT: Bad magic number\n"); } if ( cp->pers_no != 0 ) { real_active_connection = active_connection; /* Shouldn't be needed +++ */ active_connection = cp; logout(); active_connection = real_active_connection; } 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"); } mux_close(cp); /* Close fd's. Clear mux and isc structures. */ kill_client(cp); /* Free the Connection */ } /* * 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 %d.\n", sess); else logout_client (conn); } if ( traverse_connections (0) != 0) restart_kom("logout_all_clients(): traverse_connections(0) == %d.\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, Result_holder * res) /* This is a union. */ { Success status=FAILURE; /* OK if the call was successful. */ if ( active_connection != NULL ) { log("call_function(%d): active_connection = %d", client->session_no, active_connection->session_no); } active_connection = client; service_statistics[client->function]++; #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'; mux_printf(client, "%%%%LysKOM unsupported protocol.\n"); mux_flush(client); BUG(("%%%%Unsupported protocol.\n")); longjmp(parse_env, ISC_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->c_misc_info_p); client->c_misc_info_p = NULL; sfree( client->c_local_text_no_p); client->c_local_text_no_p = NULL; client->parse_pos = 0; client->fnc_parse_pos = 0; client->array_parse_pos = 0; client->struct_parse_pos = 0; client->string_parse_pos = 0; } /* * Send a reply to a call. */ static void reply(Connection *client, Success status, 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. */ static void parse_unparsed(Connection *client) { String tmp_str = EMPTY_STRING; Success status; Result_holder result; switch ( setjmp(parse_env) ) { case 0 : /* Parse message. If message is complete call function and reply. */ parse_packet(client); client->last_request = time(NULL); status = call_function(client, &result); reply(client, status, &result); free_parsed(client); end_of_atomic(FALSE); break; case ISC_PROTOCOL_ERR: s_clear(&client->string0); free_parsed(client); mux_printf(client, "%% LysKOM protocol error.\n"); mux_flush(client); BUG(("%%%% Protocol error.\n")); s_clear(&client->unparsed); client->first_to_parse = 0; client->more_to_parse = FALSE; end_of_atomic(FALSE); break; case ISC_MSG_INCOMPLETE: client->more_to_parse = FALSE; break; case ISC_LOGOUT: client->kill_me = TRUE; break; } /* Delete the parsed part of 'unparsed' */ if ( s_substr(&tmp_str, client->unparsed, client->first_to_parse, END_OF_STRING) != OK ) restart_kom("parse_unparsed: s_substr\n"); s_clear(&client->unparsed); client->unparsed = tmp_str; client->first_to_parse = 0; } /* * There is a message in the event. Parse it. */ static void parse_message(Connection *cp, String tmp_str) { VBUGSTR(tmp_str); cp->more_to_parse = TRUE; if ( s_strcat(&cp->unparsed, tmp_str) != OK ) restart_kom("parse_message(): s_strcat\n"); tmp_str = EMPTY_STRING; /* Parse this packet, but leave the last token if it isn't complete. */ parse_unparsed(cp); return; } static void mux_handle_packet(Mux *mux) { Mux_client *mcp = NULL; Connection *cp = NULL; switch(mux->parse.function) { case 0: /* ping */ isc_printf(mux->scb, "0\n"); isc_flush(mux->scb); break; case 1: /* login */ mcp = mux_getclientbyid(mux, mux->parse.num); if (mcp) { /* ERROR %d: Already logged in\n */ isc_printf(mux->scb, "4\n"); isc_flush(mux->scb); return; } cp = new_client(); cp->mux = mux; cp->hostname = mux->parse.string; mux->parse.string = EMPTY_STRING; mux_addclient(mux, mux->parse.num, cp); BUG(("\n[Client %d from %.*s via MUX %s", cp->session_no, cp->hostname.len, cp->hostname.string, mux->scb->info.tcp.hostname)); BUG((" is connecting]\n")); break; case 2: /* logout */ mcp = mux_getclientbyid(mux, mux->parse.num); if (!mcp) { /* ERROR %d: No such mux-session */ isc_printf(mux->scb, "4\n"); isc_flush(mux->scb); return; } cp = mcp->conn; BUG(("\n[Client %d via MUX(%s)", cp->session_no, mux->scb->info.tcp.hostname)); BUG((" is logging out by request]\n")); #if 0 logout_client(cp); end_of_atomic(FALSE); /* ??? */ #else cp->kill_me = TRUE; #endif break; case 3: /* msg */ mcp = mux_getclientbyid(mux, mux->parse.num); if (!mcp) { /* ERROR %d: No such mux-session */ isc_printf(mux->scb, "4\n"); isc_flush(mux->scb); return; } cp = mcp->conn; VBUG(("\n[Message client %d via MUX #%d]\n", cp->session_no, mux->scb)); parse_message(cp, mux->parse.string); break; default: VBUG(("\nMUX Protocol error from MUX #%d]\n", mux->scb)); isc_printf(mux->scb, "5\n"); isc_flush(mux->scb); break; } } void mux_logout(Mux *mp) { int i; for (i = 0; i < mp->client_c; i++) if (mp->client_v[i].conn) { BUG(("\n[Client %d via MUX(%s)", mp->client_v[i].conn->session_no, mp->scb->info.tcp.hostname)); BUG((" is logging out by MUX shutdown]\n")); logout_client(mp->client_v[i].conn); end_of_atomic(FALSE); } isc_close(mp->scb); mux_destruct(mp); } static void mux_parse_unparsed(Mux *mux) { String tmp_str; while ( mux->parse.more_to_parse ) { switch ( setjmp(mux_parse_env) ) { case 0 : /* Parse message. Take apropriate action if message is complete. */ mux_parse_packet(mux); mux_handle_packet(mux); mux_free_parsed(mux); break; case MUX_PROTOCOL_ERR: s_clear(&mux->parse.string); mux_free_parsed(mux); isc_printf(mux->scb, "5\n"); isc_flush(mux->scb); BUG(("%%%% Mux protocol error.\n")); s_clear(&mux->parse.unparsed); mux->parse.first_to_parse = 0; mux->parse.more_to_parse = FALSE; break; case MUX_MSG_INCOMPLETE: mux->parse.more_to_parse = FALSE; break; case MUX_LOGOUT: mux_logout(mux); return; } /* Delete the parsed part of 'unparsed' */ tmp_str = EMPTY_STRING; if ( s_substr(&tmp_str, mux->parse.unparsed, mux->parse.first_to_parse, END_OF_STRING) != OK ) restart_kom("mux_parse_unparsed: s_substr\n"); s_clear(&mux->parse.unparsed); mux->parse.unparsed = tmp_str; mux->parse.first_to_parse = 0; } } static void mux_parse_message(Mux *mp, String tmp_str) { VBUGSTR(tmp_str); BUG(("mux_parse_message(): mp->parse.unparsed = { len = %d, " "string = %p }\n", mp->parse.unparsed.len, mp->parse.unparsed.string)); if ( s_strcat(&mp->parse.unparsed, tmp_str) != OK ) restart_kom("mux_parse_message(): s_strcat\n"); tmp_str = EMPTY_STRING; /* Parse this packet, but leave the last token if it isn't complete. */ mux_parse_unparsed(mp); return; } /* * parse data in client->unparsed and mux->unparsed if there * is data that has not yet been taken care of. Return TRUE * if there was no data to take care of. */ Bool parse_forgotten() { Bool flg = FALSE; /* Was there anything? */ Connection *ci; Session_no session_no = 0; if ( traverse_connections(0) == 0 ) return TRUE; while ( ( session_no = traverse_connections( session_no ) ) != 0 ) { ci = get_conn_by_number (session_no); if ( ci->more_to_parse ) { parse_unparsed(ci); flg = TRUE; } } return !flg; } /* Return 1 if the named file exists, 0 otherwise */ static int fexists(char *filename) { extern int stat(char *filename, struct stat *bufp); struct stat buf; int code; code = !stat(filename, &buf); errno = 0; return code; } void dump_statistics(void) { static time_t last_dump = NO_TIME; int i; time_t now; FILE *fp; extern char statisticfile[1024]; /* Defined in ramkomd.c */ if ( (fp = fopen(statisticfile, "a")) == NULL ) { log(__FILE__ ": dump_statistics(): can't open file %s\n", statisticfile); return; } time(&now); if (last_dump == NO_TIME) { fprintf(fp, "RESTART\n"); last_dump = now; } fprintf(fp, "TIME: %s", ctime(&now)); fprintf(fp, "SECONDS: %d\n", (int)difftime(now, last_dump)); fprintf(fp, "STATISTICS:"); for ( i = 0; i < sizeof(service_statistics) / sizeof(u_long); i++) { fprintf(fp, " %lu", service_statistics[i]); service_statistics[i]=0; } fprintf(fp, "\n"); fclose(fp); last_dump = now; } /* * check_kill_flg must NEVER be called inside an atomic call! */ static void check_kill_flg(void) { Session_no i = 0; Connection *conn; if ( active_connection != NULL ) { restart_kom("check_kill_flg: active_connection == %d", active_connection->session_no); } while ( (i = traverse_connections (i)) != 0 ) { conn = get_conn_by_number(i); if ( conn->kill_me == TRUE ) { logout_client(conn); end_of_atomic(FALSE); } } } static void login_request(ISCECB *event) { Connection * cp; if (isc_sessions(kom_server_mcb) >= MAX_NO_OF_CONNECTIONS) { BUG(("Connection attempt rejected.\n")); isc_printf(event->session, "%s", "%% No connections left.\n"); isc_flush(event->session); isc_close(event->session); async_rejected_connection(); return; } /* Supress logins if /etc/nologin exists */ if (fexists("/etc/nologin")) { isc_printf(event->session, "%s", "%% No logins allowed.\n"); isc_flush(event->session); isc_close(event->session); return; } if (event->session->info.tcp.listen == listen_mux) { /* Multiplexer requesting connection */ /* ** Create, and setup the MUX for the MUX case */ event->session->udg = mux_create(MUX_TYPE_MUX, event->session); BUG(("MUX #%d at %s connecting.\n", event->session, event->session->info.tcp.hostname)); } else { /* Client requesting connection */ /* ** Create, and setup the MUX for the CLIENT case */ event->session->udg = mux_create(MUX_TYPE_CLIENT, event->session); cp = new_client(); cp->mux = event->session->udg; s_crea_str(&cp->hostname, event->session->info.tcp.hostname); mux_addclient(event->session->udg, 0, cp); BUG(("\n[Client %d from %s", cp->session_no, event->session->info.tcp.hostname)); BUG((" is connecting]\n")); } } static void logout_request(ISCECB *event) { Connection * cp; if (event->session->udg->type == MUX_TYPE_MUX) { BUG(("\n[MUX #%d at %s", event->session, event->session->info.tcp.hostname)); BUG((" is logging out]\n")); /* ** Logout any remaining clients */ mux_logout(event->session->udg); } else { cp = event->session->udg->client_v[0].conn; BUG(("\n[Client %d from %s", cp->session_no, event->session->info.tcp.hostname)); BUG((" is logging out]\n")); cp->kill_me = TRUE; } } static void message_request(ISCECB *event) { Connection *cp; String tmp_str; if (event->session->udg->type == MUX_TYPE_MUX) { event->session->udg->parse.more_to_parse = TRUE; tmp_str.string = (unsigned char *)event->msg->buffer; tmp_str.len = event->msg->length; mux_parse_message(event->session->udg, tmp_str); } else { cp = event->session->udg->client_v[0].conn; VBUG(("\n[Message from client %d]\n", cp->session_no)); /* ** Pass the message on to the parser */ tmp_str.string = (unsigned char *)event->msg->buffer; tmp_str.len = event->msg->length; parse_message(cp, tmp_str); } } static long timevaldiff(struct timeval a, struct timeval b) { return 1000000 * (a.tv_sec - b.tv_sec) + a.tv_usec - b.tv_usec; } void toploop(void) { ISCECB * event; Bool pending_input = TRUE; /* Should be TRUE whenever there is or might be pending input from any client in the unparsed buffer. */ long timeout = TIMEOUT; /* In milliseconds. */ struct timeval before, after; while ( !go_and_die ) { if (do_sync_db) { /* * do_sync_db used to save the entire database. Nowadays * all it does is to print some statistics. */ cache_sync(); /* A noop in lyskomd. Actually stops the server and dumps everything in ramkomd. */ dump_statistics(); do_sync_db = FALSE; } gettimeofday(&before, NULL); event = isc_getnextevent(kom_server_mcb, pending_input ? 0 : timeout ); gettimeofday(&after, NULL); timeout -= timevaldiff(after, before) / 1000; if ( timeout < 0 ) timeout = 0; if ( event == NULL ) restart_kom("toploop(): Got NULL event - shouldn't happen...\n"); switch ( event->event ) { case ISC_EVENT_ERROR: if (errno != EINTR) { log("toploop: ISC_EVENT_ERROR (error = '%s'):\n '%s'\n", strerror(errno), event->msg ? event->msg->buffer : "(unknown)"); } break; case ISC_EVENT_TIMEOUT: BUG((">")); #ifdef DEBUG fflush(stdout); #endif if ( pending_input == FALSE ) timeout = end_of_atomic(TRUE); /* Idle. Do some cleaning up. */ else { if ( timeout == 0 ) end_of_atomic(FALSE); pending_input = !parse_forgotten(); } break; case ISC_EVENT_LOGIN: pending_input = TRUE; login_request(event); break; case ISC_EVENT_LOGOUT: pending_input = TRUE; logout_request(event); break; case ISC_EVENT_MESSAGE: pending_input = TRUE; message_request(event); break; default: pending_input = TRUE; log("ERROR: toploop(): Unknown ISC_EVENT\n"); break; } isc_dispose(event); check_kill_flg(); } }