Phone

/* $Id$ */
/* Copyright (c) 2014-2020 Pierre Pronchery <khorben@defora.org> */
/* This file is part of DeforaOS Desktop Phone */
/* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY ITS AUTHORS AND CONTRIBUTORS "AS IS" AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <string.h>
#include <signal.h>
#include <libgen.h>
#include <errno.h>
#include <net/if.h>
#include <System/string.h>
/* constants */
#ifndef PROGNAME_PPPD
# define PROGNAME_PPPD "pppd"
#endif
/* private */
/* types */
struct _Phone
{
Config * config;
PhonePluginHelper helper;
PhonePluginDefinition * plugind;
PhonePlugin * plugin;
char * username;
char * password;
int fd;
guint source;
};
/* prototypes */
static int _phone_init(Phone * phone, PhonePluginDefinition * plugind);
static void _phone_destroy(Phone * phone);
/* helpers */
static char const * _helper_config_get(Phone * phone, char const * section,
char const * variable);
static int _helper_config_set(Phone * phone, char const * section,
char const * variable, char const * value);
static int _helper_error(Phone * phone, char const * message, int ret);
static int _helper_request(Phone * phone, ModemRequest * request);
static int _helper_trigger(Phone * phone, ModemEventType event);
/* functions */
/* phone_init */
static int _phone_init(Phone * phone, PhonePluginDefinition * plugind)
{
char const * homedir;
String * path;
if((phone->config = config_new()) == NULL)
return -1;
if((homedir = g_getenv("HOME")) == NULL)
homedir = g_get_home_dir();
if((path = string_new_append(homedir, "/.phone", NULL)) != 0)
{
if(config_load(phone->config, path) != 0)
error_print(PROGNAME);
string_delete(path);
}
memset(&phone->helper, 0, sizeof(phone->helper));
phone->helper.phone = phone;
phone->helper.config_get = _helper_config_get;
phone->helper.config_set = _helper_config_set;
phone->helper.error = _helper_error;
phone->helper.request = _helper_request;
phone->helper.trigger = _helper_trigger;
phone->plugind = plugind;
phone->plugin = NULL;
phone->username = NULL;
phone->password = NULL;
phone->fd = -1;
phone->source = 0;
if((phone->plugin = plugind->init(&phone->helper)) == NULL)
{
_phone_destroy(phone);
return -1;
}
return 0;
}
/* phone_destroy */
static void _phone_destroy(Phone * phone)
{
free(phone->username);
if(phone->password != NULL)
string_clear(phone->password);
free(phone->password);
if(phone->fd >= 0)
close(phone->fd);
if(phone->source != 0)
g_source_remove(phone->source);
}
/* helpers */
/* helper_config_get */
static char const * _helper_config_get(Phone * phone, char const * section,
char const * variable)
{
char const * ret;
String * s;
if((s = string_new_append("plugin::", section, NULL)) == NULL)
return NULL;
ret = config_get(phone->config, s, variable);
string_delete(s);
return ret;
}
/* helper_config_set */
static int _helper_config_set(Phone * phone, char const * section,
char const * variable, char const * value)
{
int ret;
String * s;
if((s = string_new_append("plugin::", section, NULL)) == NULL)
return -1;
ret = config_set(phone->config, section, variable, value);
string_delete(s);
/* FIXME save the configuration if successful */
return ret;
}
/* helper_error */
static int _error_text(char const * message, int ret);
static int _helper_error(Phone * phone, char const * message, int ret)
{
GtkWidget * dialog;
if(phone == NULL)
return _error_text(message, ret);
dialog = gtk_message_dialog_new(NULL, 0, GTK_MESSAGE_ERROR,
GTK_BUTTONS_CLOSE,
#if GTK_CHECK_VERSION(2, 6, 0)
"%s", "Error");
gtk_message_dialog_format_secondary_text(GTK_MESSAGE_DIALOG(dialog),
#endif
"%s", message);
gtk_window_set_title(GTK_WINDOW(dialog), "Error");
gtk_dialog_run(GTK_DIALOG(dialog));
gtk_widget_destroy(dialog);
return ret;
}
static int _error_text(char const * message, int ret)
{
fprintf(stderr, "%s: %s\n", PROGNAME, message);
return ret;
}
/* helper_request */
static int _request_call(Phone * phone, ModemRequest * request);
static void _request_call_child(gpointer data);
static void _request_call_child_watch(GPid pid, gint status, gpointer data);
static int _request_call_hangup(Phone * phone, ModemRequest * request);
static int _request_authenticate(Phone * phone, ModemRequest * request);
static int _helper_request(Phone * phone, ModemRequest * request)
{
switch(request->type)
{
case MODEM_REQUEST_AUTHENTICATE:
return _request_authenticate(phone, request);
case MODEM_REQUEST_CALL:
return _request_call(phone, request);
case MODEM_REQUEST_CALL_HANGUP:
return _request_call_hangup(phone, request);
default:
/* FIXME implement more */
return -error_set_code(1, "Not implemented");
}
}
static int _request_authenticate(Phone * phone, ModemRequest * request)
{
char const * p;
if(request->authenticate.name == NULL)
return -error_set_code(1, "Unknown authentication");
if(strcmp(request->authenticate.name, "APN") == 0)
/* FIXME really implement */
return 0;
else if(strcmp(request->authenticate.name, "GPRS") == 0)
{
free(phone->username);
if(phone->password != NULL)
string_clear(phone->password);
free(phone->password);
p = (request->authenticate.username != NULL)
? request->authenticate.username : "";
phone->username = strdup(p);
p = (request->authenticate.password != NULL)
? request->authenticate.password : "";
phone->password = strdup(p);
if(phone->username == NULL || phone->password == NULL)
{
free(phone->username);
phone->username = NULL;
if(phone->password != NULL)
string_clear(phone->password);
free(phone->password);
phone->password = NULL;
return -error_set_code(1, "%s", strerror(errno));
}
return 0;
}
return -error_set_code(1, "Unknown authentication");
}
static int _request_call(Phone * phone, ModemRequest * request)
{
char * argv[] = { "/usr/sbin/" PROGNAME_PPPD, PROGNAME_PPPD,
"call", "gprs", "user", NULL, "password", NULL, NULL };
char const * p;
gboolean res;
const GSpawnFlags flags = G_SPAWN_FILE_AND_ARGV_ZERO
| G_SPAWN_DO_NOT_REAP_CHILD;
GPid pid;
GError * error = NULL;
if(request->call.call_type != MODEM_CALL_TYPE_DATA)
return -error_set_code(1, "Unknown call type");
/* pppd */
if((p = _helper_config_get(phone, "gprs", "pppd")) != NULL)
{
if((argv[0] = strdup(p)) == NULL)
return -error_set_code(1, "%s", strerror(errno));
argv[1] = basename(argv[0]);
}
argv[5] = phone->username;
argv[7] = phone->password;
res = g_spawn_async(NULL, argv, NULL, flags, _request_call_child, NULL,
&pid, &error);
if(p != NULL)
free(argv[0]);
if(res == FALSE)
{
error_set_code(1, "%s", error->message);
g_error_free(error);
return -1;
}
if(phone->source != 0)
g_source_remove(phone->source);
phone->source = g_child_watch_add(pid, _request_call_child_watch,
phone);
return 0;
}
static void _request_call_child(gpointer data)
{
(void) data;
/* XXX lets the PID file readable with higher privileges */
umask(022);
}
static void _request_call_child_watch(GPid pid, gint status, gpointer data)
{
Phone * phone = data;
(void) status;
phone->source = 0;
g_spawn_close_pid(pid);
_helper_trigger(phone, MODEM_EVENT_TYPE_CONNECTION);
}
static int _request_call_hangup(Phone * phone, ModemRequest * request)
{
int ret = 0;
char const * interface;
String * path;
FILE * fp;
char buf[16];
pid_t pid;
(void) request;
if((interface = _helper_config_get(phone, "gprs", "interface")) == NULL)
return -error_set_code(1, "Unknown interface");
if((path = string_new_append("/var/run/", interface, ".pid", NULL))
== NULL)
return -1;
if((fp = fopen(path, "r")) == NULL)
ret = -error_set_code(1, "%s: %s", path, strerror(errno));
else if(fread(buf, sizeof(*buf), sizeof(buf), fp) == 0)
ret = -error_set_code(1, "%s: %s", path, strerror(errno));
else
{
buf[sizeof(buf) - 1] = '\0';
if(sscanf(buf, "%d", &pid) != 1)
ret = -error_set_code(1, "%s", strerror(errno));
else if(pid <= 0)
ret = -error_set_code(1, "%s", strerror(ERANGE));
else if(kill(pid, SIGHUP) != 0)
ret = -error_set_code(1, "%d: %s", pid,
strerror(errno));
}
if(fp != NULL)
fclose(fp);
string_delete(path);
return ret;
}
/* helper_trigger */
static int _trigger_connection(Phone * phone, ModemEventType type);
#if defined(SIOCGIFDATA) || defined(SIOCGIFFLAGS)
static int _trigger_connection_interface(Phone * phone, PhoneEvent * event,
char const * interface);
#endif
static int _helper_trigger(Phone * phone, ModemEventType event)
{
switch(event)
{
case MODEM_EVENT_TYPE_CONNECTION:
return _trigger_connection(phone, event);
default:
/* FIXME implement more */
return 0;
}
}
static int _trigger_connection(Phone * phone, ModemEventType type)
{
PhoneEvent pevent;
ModemEvent mevent;
#if defined(SIOCGIFDATA) || defined(SIOCGIFFLAGS)
char const * p;
#endif
if(phone->source != 0)
/* wait for the result of the connection */
return 0;
memset(&pevent, 0, sizeof(pevent));
memset(&mevent, 0, sizeof(mevent));
pevent.type = PHONE_EVENT_TYPE_MODEM_EVENT;
pevent.modem_event.event = &mevent;
mevent.type = type;
mevent.connection.connected = FALSE;
mevent.connection.in = 0;
mevent.connection.out = 0;
#if defined(SIOCGIFDATA) || defined(SIOCGIFFLAGS)
if((p = _helper_config_get(phone, "gprs", "interface")) != NULL)
/* XXX ignore errors */
_trigger_connection_interface(phone, &pevent, p);
#endif
return phone->plugind->event(phone->plugin, &pevent);
}
#if defined(SIOCGIFDATA) || defined(SIOCGIFFLAGS)
static int _trigger_connection_interface(Phone * phone, PhoneEvent * event,
char const * interface)
{
ModemEvent * mevent = event->modem_event.event;
# ifdef SIOCGIFDATA
struct ifdatareq ifdr;
# endif
# ifdef SIOCGIFFLAGS
struct ifreq ifr;
# endif
if(phone->fd < 0 && (phone->fd = socket(AF_INET, SOCK_DGRAM, 0)) < 0)
return -error_set_print(PROGNAME, 1, "%s", strerror(errno));
# ifdef SIOCGIFDATA
memset(&ifdr, 0, sizeof(ifdr));
strncpy(ifdr.ifdr_name, interface, sizeof(ifdr.ifdr_name));
if(ioctl(phone->fd, SIOCGIFDATA, &ifdr) == -1)
error_set_print(PROGNAME, 1, "%s: %s", interface,
strerror(errno));
else
{
mevent->connection.connected = TRUE;
mevent->connection.in = ifdr.ifdr_data.ifi_ibytes;
mevent->connection.out = ifdr.ifdr_data.ifi_obytes;
}
# endif
# ifdef SIOCGIFFLAGS
memset(&ifr, 0, sizeof(ifr));
strncpy(ifr.ifr_name, interface, sizeof(ifr.ifr_name));
if(ioctl(phone->fd, SIOCGIFFLAGS, &ifr) == -1)
error_set_print(PROGNAME, 1, "%s: %s", interface,
strerror(errno));
else
{
# ifdef IFF_UP
mevent->connection.connected = (ifr.ifr_flags & IFF_UP)
? TRUE : FALSE;
# endif
}
# endif
return 0;
}
#endif