/*===========================================================================*
* *
* xiddns.c - *
* *
* Copyright (c) 1991-2003 iMatix Corporation *
* *
* ------------------ GPL Licensed Source Code ------------------ *
* iMatix makes this software available under the GNU General *
* Public License (GPL) license for open source projects. For *
* details of the GPL license please see www.gnu.org or read the *
* file license.gpl provided in this package. *
* *
* This program 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 2 of *
* the License, or (at your option) any later version. *
* *
* This program 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 this program in the file 'license.gpl'; if *
* not, write to the Free Software Foundation, Inc., 59 Temple *
* Place - Suite 330, Boston, MA 02111-1307, USA. *
* *
* You can also license this software under iMatix's General Terms *
* of Business (GTB) for commercial projects. If you have not *
* explicitly licensed this software under the iMatix GTB you may *
* only use it under the terms of the GNU General Public License. *
* *
* For more information, send an email to info@imatix.com. *
* -------------------------------------------------------------- *
*===========================================================================*/
/*
Synopsis: Handles connection/disconnection to DDNS server
*/
#include "smtdefn.h" /* SMT definitions */
#include "smthttpl.h" /* SMTHTTPL prototypes & globals */
/*- Definitions -------------------------------------------------------------*/
#undef AGENT_NAME
#define AGENT_NAME "XIDDNS" /* Our public name */
#define SINGLE_THREADED TRUE /* Single-threaded agent */
#define DDNS_TIMEOUT 120 /* DDNS connect timeout, seconds */
#define BUFFER_MAX 512 /* For working buffer */
#define AUTHEN_MAX 100 /* Size of username + password */
/*- Function prototypes -----------------------------------------------------*/
static void send_ddns_request (THREAD *thread, Bool signon);
/*- Global variables used in this source file only --------------------------*/
static QID
sockq, /* Socket agent event queue */
timeq, /* Timer agent event queue */
operq; /* Operator console event queue */
static XML_ITEM
*services, /* List of DDNS services */
*service_item; /* Service item in XML tree */
static char
*ddns_service, /* Server name */
*ddns_username, /* User name or e-mail address */
*ddns_password, /* Password or key */
*ddns_domain, /* Requested domain name */
*ddns_host, /* DDNS host to contact */
*ddns_port, /* Port to connect to */
*ddns_req, /* Request string to server */
*ddns_ack; /* Acknowledgment string */
static Bool
ddns_verbose = FALSE, /* Show conversation? */
ddns_trace = FALSE; /* Trace conversation? */
static struct_smt_sock_ok
*connection; /* Connection data */
static int
ddns_index; /* "ddns..." index number */
static char
buffer [BUFFER_MAX + 1]; /* For queries and responses */
#include "xiddns.d" /* Include dialog data */
/******************** INITIALISE AGENT - ENTRY POINT ********************/
/* ---------------------------------------------------------------------[<]-
Function: xiddns_init
Synopsis: Initialises the Xitami DDNS agent. Returns 0 if initialised
okay, -1 if there was an error. Supports these public methods:
SIGNON Sign-on to all DDNS hosts (done when agent starts-up)
SIGNOFF Sign-off from all DDNS hosts
Does not send reply events.
---------------------------------------------------------------------[>]-*/
int
xiddns_init (void)
{
AGENT *agent; /* Handle for our agent */
THREAD *thread; /* Handle to console thread */
# include "xiddns.i" /* Include dialog interpreter */
/* Method name Event value Priority */
/* Shutdown event comes from Kernel */
method_declare (agent, "SHUTDOWN", shutdown_event, SMT_PRIORITY_MAX);
/* Public methods supported by this agent */
method_declare (agent, "SIGNON", signon_event, 0);
method_declare (agent, "SIGNOFF", signoff_event, 0);
/* Reply events from socket agent */
method_declare (agent, "SOCK_INPUT_OK", ok_event, 0);
method_declare (agent, "SOCK_OUTPUT_OK", ok_event, 0);
method_declare (agent, "SOCK_READ_OK", ok_event, 0);
method_declare (agent, "SOCK_WRITE_OK", ok_event, 0);
method_declare (agent, "SOCK_CLOSED", ok_event, 0);
method_declare (agent, "SOCK_ERROR", sock_error_event, 0);
method_declare (agent, "SOCK_TIMEOUT", sock_timeout_event, 0);
/* Reply events from timer agent */
method_declare (agent, "TIME_ALARM", signon_event, 0);
/* Ensure that operator console is running, else start it up */
smtoper_init ();
if ((thread = thread_lookup (SMT_OPERATOR, "")) != NULL)
operq = thread-> queue-> qid;
else
return (-1);
/* Ensure that timer agent is running, else start it up */
smttime_init ();
if ((thread = thread_lookup (SMT_TIMER, "")) != NULL)
timeq = thread-> queue-> qid;
else
return (-1);
/* Ensure that socket i/o agent is running, else start it up */
smtsock_init ();
if ((thread = thread_lookup (SMT_SOCKET, "")) != NULL)
sockq = thread-> queue-> qid;
else
return (-1);
/* Create single master thread */
thread_create (AGENT_NAME, "main");
/* Signal okay to caller that we initialised okay */
return (0);
}
/************************* INITIALISE THE THREAD *************************/
MODULE initialise_the_thread (THREAD *thread)
{
the_next_event = ok_event;
}
/************************ INITIALISE SIGNON TIMER ************************/
MODULE initialise_signon_timer (THREAD *thread)
{
qbyte
refresh; /* Server refresh rate */
refresh = atol (CONFIG ("ddns:refresh")) * 100;
if (refresh == 0)
return; /* Then do nothing */
if (refresh < 6000)
refresh = 6000; /* 1 minute minimum rate */
send_clock (&timeq, 0, refresh, 0, 0, NULL);
}
/*********************** CHECK IF AUTOMATIC SIGNON ***********************/
MODULE check_if_automatic_signon (THREAD *thread)
{
if (*CONFIG ("ddns:enabled") == '1')
the_next_event = ok_event;
else
the_next_event = ignore_event;
}
/************************ LOAD DDNS SERVICES LIST ************************/
MODULE load_ddns_services_list (THREAD *thread)
{
char
*filename;
filename = CONFIG ("ddns:filename");
services = NULL;
switch (xml_load_file (&services, "PATH", filename, FALSE))
{
case XML_FILEERROR:
sendfmt (&operq, "ERROR",
"xiddns: cannot read '%s': %s", filename, xml_error ());
raise_exception (exception_event);
break;
case XML_LOADERROR:
sendfmt (&operq, "ERROR",
"xiddns: error in '%s': %s", filename, xml_error ());
raise_exception (exception_event);
break;
}
}
/************************ DROP DDNS SERVICES LIST ************************/
MODULE drop_ddns_services_list (THREAD *thread)
{
xml_free (services);
}
/************************* GET FIRST DDNS PROFILE ************************/
MODULE get_first_ddns_profile (THREAD *thread)
{
ddns_index = 0;
get_next_ddns_profile (thread);
}
/************************* GET NEXT DDNS PROFILE *************************/
MODULE get_next_ddns_profile (THREAD *thread)
{
char
ddns_prefix [10]; /* "ddns.." config table prefix */
XML_ATTR
*attr; /* XML attribute */
/* If we have a symbol with the name ddns... then we can assume that
* this is the start of a DDNS entry.
*/
sprintf (ddns_prefix, "ddns%.0d", ddns_index++);
ddns_service = ini_dyn_value (config, ddns_prefix, "service", NULL);
if (ddns_service)
{
the_next_event = ok_event;
ddns_username = ini_dyn_value (config, ddns_prefix, "username", "");
ddns_password = ini_dyn_value (config, ddns_prefix, "password", "");
ddns_domain = ini_dyn_value (config, ddns_prefix, "domain", "");
/* Now find service properties */
FORCHILDREN (service_item, xml_first_child (services))
{
attr = xml_attr (service_item, "NAME");
if (attr == NULL)
sendfmt (&operq, "ERROR",
"xiddns: syntax error in definition file - no NAME");
else
if (streq (ddns_service, xml_attr_value (attr)))
break;
}
if (service_item == NULL)
{
sendfmt (&operq, "ERROR",
"xiddns: service '%s' not defined", ddns_service);
the_next_event = error_event;
}
}
else
the_next_event = no_more_event;
}
/************************* CONNECT TO DDNS SERVER ************************/
MODULE connect_to_ddns_server (THREAD *thread)
{
ddns_host = xml_get_attr (service_item, "HOST", "localhost");
ddns_port = xml_get_attr (service_item, "PORT", "80");
ddns_verbose = *xml_get_attr (service_item, "VERBOSE", "1") == '1';
ddns_trace = *xml_get_attr (service_item, "TRACE", "0") == '1';
if (ddns_verbose)
sendfmt (&operq, "INFO",
"xiddns: connecting to %s...", ddns_host);
send_connect (&sockq, 0, "TCP", ddns_host, ddns_port, 0, 0, 0);
}
/************************** GET CONNECTED SOCKET *************************/
MODULE get_connected_socket (THREAD *thread)
{
get_smt_sock_ok (thread-> event-> body, &connection);
if (ddns_trace)
coprintf ("xiddns: connected on socket %d", connection-> socket);
}
/************************ SEND DDNS SIGNON REQUEST ***********************/
MODULE send_ddns_signon_request (THREAD *thread)
{
if (ddns_verbose && !ddns_trace)
sendfmt (&operq, "INFO", "xiddns: sending DDNS sign-on");
send_ddns_request (thread, TRUE);
}
static void
send_ddns_request (THREAD *thread, Bool signon)
{
SYMTAB
*symtab; /* Token insertion table */
XML_ITEM
*xml_item; /* SIGNON or SIGNOFF item */
XML_ATTR
*xml_attr; /* Attributes for item */
int
auth_length; /* Length of authorisation string */
char
authorisation [AUTHEN_MAX * 2], /* HTTP authorisation, base 64 */
*attr_name, /* XML attribute name */
*attr_value, /* XML attribute value */
*expanded; /* String after token expansion */
/* Create symbol table for insertion symbols */
symtab = sym_create_table ();
/* Set insertion symbols */
sym_assume_symbol (symtab, "username", ddns_username);
sym_assume_symbol (symtab, "password", ddns_password);
sym_assume_symbol (symtab, "domain", ddns_domain);
sym_assume_symbol (symtab, "ipaddr", socket_localaddr (connection-> socket));
/* Format HTTP authentication string */
sprintf (buffer, "%s:%s", ddns_username, ddns_password);
buffer [AUTHEN_MAX] = '\0'; /* Truncate brutally */
auth_length = encode_base64 ((byte *) buffer, (byte *) authorisation,
strlen (buffer));
authorisation [auth_length] = '\0';
sprintf (buffer, "Authorization: Basic %s\r\n", authorisation);
sym_assume_symbol (symtab, "httpauth", buffer);
/* Format other HTTP headers */
sprintf (buffer, "User-Agent: Xitami DDNS Agent\r\nHost: %s\r\n\r\n",
ddns_host);
sym_assume_symbol (symtab, "httpheaders", buffer);
/* Get signon / signoff item as required */
FORCHILDREN (xml_item, service_item)
{
if (lexcmp (xml_item_name (xml_item), signon? "SIGNON": "SIGNOFF") == 0)
break;
}
/* If none is defined, we ignore this service */
if (xml_item == NULL)
{
raise_exception (ignore_event);
return;
}
/* Format symbols from attributes in signon item */
ddns_req = NULL; /* Required attribute */
ddns_ack = "HTTP/1.? 200*"; /* Optional attribute */
FORATTRIBUTES (xml_attr, xml_item)
{
attr_name = xml_attr_name (xml_attr);
attr_value = xml_attr_value (xml_attr);
if (lexcmp (attr_name, "SEND") == 0)
ddns_req = attr_value;
else
if (lexcmp (attr_name, "EXPECT") == 0)
ddns_ack = attr_value;
else
{
expanded = tok_subst (attr_value, symtab);
sym_assume_symbol (symtab, attr_name, expanded);
mem_free (expanded);
}
}
/* The SEND attribute is required */
if (ddns_req == NULL)
{
sendfmt (&operq, "ERROR", "xiddns: SEND missing for '%s'", ddns_host);
raise_exception (ignore_event);
return;
}
/* Now format request string and send it off */
expanded = tok_subst (ddns_req, symtab);
searchreplace (expanded, "\\r", "\r");
searchreplace (expanded, "\\n", "\n");
if (ddns_trace)
{
coprintf ("xiddns: sending sign-%s request", signon? "on": "off");
coprintf (expanded);
}
send_write (&sockq, 0, connection-> socket,
(word) strlen (expanded), (byte *) expanded, 0);
mem_free (expanded);
sym_delete_table (symtab);
}
/*********************** SEND DDNS SIGNOFF REQUEST ***********************/
MODULE send_ddns_signoff_request (THREAD *thread)
{
if (ddns_verbose && !ddns_trace)
sendfmt (&operq, "INFO", "xiddns: sending DDNS sign-off");
send_ddns_request (thread, FALSE);
}
/************************ GET DDNS SERVER RESPONSE ***********************/
MODULE get_ddns_server_response (THREAD *thread)
{
if (ddns_trace)
coprintf ("xiddns: waiting for response from %s...", ddns_host);
send_read (&sockq, 0, connection-> socket, BUFFER_MAX, 0, 0);
}
/************************ CHECK DDNS CONFIRMATION ************************/
MODULE check_ddns_confirmation (THREAD *thread)
{
word
read_size; /* Amount of data read from socket */
char
*read_data = buffer; /* Data read from socket */
/* Get reply from server into buffer */
exdr_read (thread-> event-> body, SMT_SOCK_READ_OK,
NULL, NULL, &read_size, &read_data, NULL);
ASSERT (read_size <= BUFFER_MAX);
buffer [read_size] = '\0';
if (ddns_trace)
{
coprintf ("xiddns: have response from DDNS server");
coprintf (buffer);
}
/* Check return code */
searchreplace (ddns_ack, "\\r", "\r");
searchreplace (ddns_ack, "\\n", "\n");
if (lexwcmp (buffer, ddns_ack) == 0)
{
if (ddns_verbose)
sendfmt (&operq, "INFO",
"xiddns: domain '%s' registered", ddns_domain);
}
else
{
/* Error - show first line of response */
strconvch (buffer, '\r', '\0');
strconvch (buffer, '\n', '\0');
sendfmt (&operq, "Error", "xiddns: '%s': %s", ddns_host, buffer);
}
}
/*************************** CLOSE DDNS SOCKET ***************************/
MODULE close_ddns_socket (THREAD *thread)
{
close_socket (connection-> socket);
send_flush (&sockq, connection-> socket);
free_smt_sock_ok (&connection);
}
/************************ SIGNAL CONNECTION FAILED ***********************/
MODULE signal_connection_failed (THREAD *thread)
{
sendfmt (&operq, "ERROR", "xiddns: connection to %s failed", ddns_host);
}
/************************ SIGNAL DDNS SOCK TIMEOUT ***********************/
MODULE signal_ddns_sock_timeout (THREAD *thread)
{
sendfmt (&operq, "ERROR", "xiddns: no response from %s", ddns_host);
}
/************************* SIGNAL DDNS SOCK ERROR ************************/
MODULE signal_ddns_sock_error (THREAD *thread)
{
char
*message = NULL;
/* Pick up message from event body, encoded as SMT_SOCK_ERROR */
exdr_read (thread-> event-> body, SMT_SOCK_ERROR, &message, NULL, NULL);
sendfmt (&operq, "ERROR", "xiddns: TCP/IP error: %s", message);
mem_free (message);
}
/************************* TERMINATE THE THREAD **************************/
MODULE terminate_the_thread (THREAD *thread)
{
the_next_event = terminate_event;
}