blob: d5f9341fc9e2314a25735878e536ccee7db1534a [file] [log] [blame]
/*
*
* Connection Manager
*
* Copyright (C) 2007-2012 Intel Corporation. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* 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; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#define _GNU_SOURCE
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <termios.h>
#include <netdb.h>
#include <gweb/gweb.h>
#define DEFAULT_URL "http://www.connman.net/online/status.html"
static GTimer *timer;
static GMainLoop *main_loop;
static void web_debug(const char *str, void *data)
{
g_print("%s: %s\n", (const char *) data, str);
}
static void sig_term(int sig)
{
g_main_loop_quit(main_loop);
}
static const char *message_type_to_string(int message_type)
{
switch (message_type) {
case 100:
return "Initial redirect message";
case 110:
return "Proxy notification";
case 120:
return "Authentication notification";
case 130:
return "Logoff notification";
case 140:
return "Response to Authentication Poll";
case 150:
return "Response to Abort Login";
}
return NULL;
}
static const char *response_code_to_string(int response_code)
{
switch (response_code) {
case 0:
return "No error";
case 50:
return "Login succeeded";
case 100:
return "Login failed";
case 102:
return "RADIUS server error/timeout";
case 105:
return "RADIUS server not enabled";
case 150:
return "Logoff succeeded";
case 151:
return "Login aborted";
case 200:
return "Proxy detection/repeat operation";
case 201:
return "Authentication pending";
case 255:
return "Access gateway internal error";
}
return NULL;
}
struct wispr_msg {
bool has_error;
const char *current_element;
int message_type;
int response_code;
char *login_url;
char *abort_login_url;
char *logoff_url;
char *access_procedure;
char *access_location;
char *location_name;
};
static inline void wispr_msg_init(struct wispr_msg *msg)
{
msg->has_error = false;
msg->current_element = NULL;
msg->message_type = -1;
msg->response_code = -1;
g_free(msg->login_url);
msg->login_url = NULL;
g_free(msg->abort_login_url);
msg->abort_login_url = NULL;
g_free(msg->logoff_url);
msg->logoff_url = NULL;
g_free(msg->access_procedure);
msg->access_procedure = NULL;
g_free(msg->access_location);
msg->access_location = NULL;
g_free(msg->location_name);
msg->location_name = NULL;
}
struct wispr_session {
GWeb *web;
GWebParser *parser;
guint request;
struct wispr_msg msg;
char *username;
char *password;
char *originurl;
char *formdata;
};
static gboolean execute_login(gpointer user_data);
static struct {
const char *str;
enum {
WISPR_ELEMENT_NONE,
WISPR_ELEMENT_ACCESS_PROCEDURE,
WISPR_ELEMENT_ACCESS_LOCATION,
WISPR_ELEMENT_LOCATION_NAME,
WISPR_ELEMENT_LOGIN_URL,
WISPR_ELEMENT_ABORT_LOGIN_URL,
WISPR_ELEMENT_MESSAGE_TYPE,
WISPR_ELEMENT_RESPONSE_CODE,
WISPR_ELEMENT_NEXT_URL,
WISPR_ELEMENT_DELAY,
WISPR_ELEMENT_REPLY_MESSAGE,
WISPR_ELEMENT_LOGIN_RESULTS_URL,
WISPR_ELEMENT_LOGOFF_URL,
} element;
} wispr_element_map[] = {
{ "AccessProcedure", WISPR_ELEMENT_ACCESS_PROCEDURE },
{ "AccessLocation", WISPR_ELEMENT_ACCESS_LOCATION },
{ "LocationName", WISPR_ELEMENT_LOCATION_NAME },
{ "LoginURL", WISPR_ELEMENT_LOGIN_URL },
{ "AbortLoginURL", WISPR_ELEMENT_ABORT_LOGIN_URL },
{ "MessageType", WISPR_ELEMENT_MESSAGE_TYPE },
{ "ResponseCode", WISPR_ELEMENT_RESPONSE_CODE },
{ "NextURL", WISPR_ELEMENT_NEXT_URL },
{ "Delay", WISPR_ELEMENT_DELAY },
{ "ReplyMessage", WISPR_ELEMENT_REPLY_MESSAGE },
{ "LoginResultsURL", WISPR_ELEMENT_LOGIN_RESULTS_URL },
{ "LogoffURL", WISPR_ELEMENT_LOGOFF_URL },
{ NULL, WISPR_ELEMENT_NONE },
};
static void start_element_handler(GMarkupParseContext *context,
const gchar *element_name,
const gchar **attribute_names,
const gchar **attribute_values,
gpointer user_data, GError **error)
{
struct wispr_msg *msg = user_data;
msg->current_element = element_name;
}
static void end_element_handler(GMarkupParseContext *context,
const gchar *element_name,
gpointer user_data, GError **error)
{
struct wispr_msg *msg = user_data;
msg->current_element = NULL;
}
static void text_handler(GMarkupParseContext *context,
const gchar *text, gsize text_len,
gpointer user_data, GError **error)
{
struct wispr_msg *msg = user_data;
int i;
if (!msg->current_element)
return;
for (i = 0; wispr_element_map[i].str; i++) {
if (!g_str_equal(wispr_element_map[i].str, msg->current_element))
continue;
switch (wispr_element_map[i].element) {
case WISPR_ELEMENT_NONE:
case WISPR_ELEMENT_ACCESS_PROCEDURE:
g_free(msg->access_procedure);
msg->access_procedure = g_strdup(text);
break;
case WISPR_ELEMENT_ACCESS_LOCATION:
g_free(msg->access_location);
msg->access_location = g_strdup(text);
break;
case WISPR_ELEMENT_LOCATION_NAME:
g_free(msg->location_name);
msg->location_name = g_strdup(text);
break;
case WISPR_ELEMENT_LOGIN_URL:
g_free(msg->login_url);
msg->login_url = g_strdup(text);
break;
case WISPR_ELEMENT_ABORT_LOGIN_URL:
g_free(msg->abort_login_url);
msg->abort_login_url = g_strdup(text);
break;
case WISPR_ELEMENT_MESSAGE_TYPE:
msg->message_type = atoi(text);
break;
case WISPR_ELEMENT_RESPONSE_CODE:
msg->response_code = atoi(text);
break;
case WISPR_ELEMENT_NEXT_URL:
case WISPR_ELEMENT_DELAY:
case WISPR_ELEMENT_REPLY_MESSAGE:
case WISPR_ELEMENT_LOGIN_RESULTS_URL:
break;
case WISPR_ELEMENT_LOGOFF_URL:
g_free(msg->logoff_url);
msg->logoff_url = g_strdup(text);
break;
}
}
}
static void error_handler(GMarkupParseContext *context,
GError *error, gpointer user_data)
{
struct wispr_msg *msg = user_data;
msg->has_error = true;
}
static const GMarkupParser wispr_parser = {
start_element_handler,
end_element_handler,
text_handler,
NULL,
error_handler,
};
static void parser_callback(const char *str, gpointer user_data)
{
struct wispr_session *wispr = user_data;
GMarkupParseContext *context;
bool result;
//printf("%s\n", str);
context = g_markup_parse_context_new(&wispr_parser,
G_MARKUP_TREAT_CDATA_AS_TEXT, &wispr->msg, NULL);
result = g_markup_parse_context_parse(context, str, strlen(str), NULL);
if (result)
g_markup_parse_context_end_parse(context, NULL);
g_markup_parse_context_free(context);
}
typedef void (*user_input_cb)(const char *value, gpointer user_data);
struct user_input_data {
GString *str;
user_input_cb cb;
gpointer user_data;
bool hidden;
int fd;
struct termios saved_termios;
};
static void user_callback(struct user_input_data *data)
{
char *value;
if (data->hidden) {
ssize_t len;
len = write(data->fd, "\n", 1);
if (len < 0)
return;
}
tcsetattr(data->fd, TCSADRAIN, &data->saved_termios);
close(data->fd);
value = g_string_free(data->str, FALSE);
if (data->cb)
data->cb(value, data->user_data);
g_free(value);
g_free(data);
}
static gboolean keyboard_input(GIOChannel *channel, GIOCondition condition,
gpointer user_data)
{
struct user_input_data *data = user_data;
char buf[1];
int len;
len = read(data->fd, buf, 1);
if (len != 1)
return TRUE;
if (buf[0] == '\n') {
user_callback(data);
return FALSE;
}
g_string_append_c(data->str, buf[0]);
if (data->hidden)
len = write(data->fd, "*", 1);
return TRUE;
}
static bool user_input(const char *label, bool hidden,
user_input_cb func, gpointer user_data)
{
struct user_input_data *data;
struct termios new_termios;
GIOChannel *channel;
guint watch;
ssize_t len;
data = g_try_new0(struct user_input_data, 1);
if (!data)
return false;
data->str = g_string_sized_new(32);
data->cb = func;
data->user_data = user_data;
data->hidden = hidden;
data->fd = open("/dev/tty", O_RDWR | O_NOCTTY | O_CLOEXEC);
if (data->fd < 0)
goto error;
if (tcgetattr(data->fd, &data->saved_termios) < 0) {
close(data->fd);
goto error;
}
new_termios = data->saved_termios;
if (data->hidden)
new_termios.c_lflag &= ~(ICANON|ECHO);
else
new_termios.c_lflag &= ~ICANON;
new_termios.c_cc[VMIN] = 1;
new_termios.c_cc[VTIME] = 0;
tcsetattr(data->fd, TCSADRAIN, &new_termios);
channel = g_io_channel_unix_new(data->fd);
g_io_channel_set_encoding(channel, NULL, NULL);
g_io_channel_set_buffered(channel, FALSE);
watch = g_io_add_watch(channel, G_IO_IN, keyboard_input, data);
g_io_channel_unref(channel);
if (watch == 0)
goto error;
len = write(data->fd, label, strlen(label));
if (len < 0)
goto error;
len = write(data->fd, ": ", 2);
if (len < 0)
goto error;
return true;
error:
g_string_free(data->str, TRUE);
g_free(data);
return false;
}
static void password_callback(const char *value, gpointer user_data)
{
struct wispr_session *wispr = user_data;
g_free(wispr->password);
wispr->password = g_strdup(value);
printf("\n");
execute_login(wispr);
}
static void username_callback(const char *value, gpointer user_data)
{
struct wispr_session *wispr = user_data;
g_free(wispr->username);
wispr->username = g_strdup(value);
if (!wispr->password) {
user_input("Password", true, password_callback, wispr);
return;
}
printf("\n");
execute_login(wispr);
}
static bool wispr_input(const guint8 **data, gsize *length,
gpointer user_data)
{
struct wispr_session *wispr = user_data;
GString *buf;
gsize count;
buf = g_string_sized_new(100);
g_string_append(buf, "button=Login&UserName=");
g_string_append_uri_escaped(buf, wispr->username, NULL, FALSE);
g_string_append(buf, "&Password=");
g_string_append_uri_escaped(buf, wispr->password, NULL, FALSE);
g_string_append(buf, "&FNAME=0&OriginatingServer=");
g_string_append_uri_escaped(buf, wispr->originurl, NULL, FALSE);
count = buf->len;
g_free(wispr->formdata);
wispr->formdata = g_string_free(buf, FALSE);
*data = (guint8 *) wispr->formdata;
*length = count;
return false;
}
static bool wispr_route(const char *addr, int ai_family, int if_index,
gpointer user_data)
{
char *family = "unknown";
if (ai_family == AF_INET)
family = "IPv4";
else if (ai_family == AF_INET6)
family = "IPv6";
printf("Route request: %s %s index %d\n", family, addr, if_index);
if (ai_family != AF_INET && ai_family != AF_INET6)
return false;
return true;
}
static bool wispr_result(GWebResult *result, gpointer user_data)
{
struct wispr_session *wispr = user_data;
const guint8 *chunk;
gsize length;
guint16 status;
gdouble elapsed;
g_web_result_get_chunk(result, &chunk, &length);
if (length > 0) {
//printf("%s\n", (char *) chunk);
g_web_parser_feed_data(wispr->parser, chunk, length);
return true;
}
g_web_parser_end_data(wispr->parser);
status = g_web_result_get_status(result);
g_print("status: %03u\n", status);
elapsed = g_timer_elapsed(timer, NULL);
g_print("elapse: %f seconds\n", elapsed);
if (wispr->msg.message_type < 0) {
const char *redirect;
if (status != 302)
goto done;
if (!g_web_result_get_header(result, "Location", &redirect))
goto done;
printf("Redirect URL: %s\n", redirect);
printf("\n");
wispr->request = g_web_request_get(wispr->web, redirect,
wispr_result, wispr_route, wispr);
return false;
}
printf("Message type: %s (%d)\n",
message_type_to_string(wispr->msg.message_type),
wispr->msg.message_type);
printf("Response code: %s (%d)\n",
response_code_to_string(wispr->msg.response_code),
wispr->msg.response_code);
if (wispr->msg.access_procedure)
printf("Access procedure: %s\n", wispr->msg.access_procedure);
if (wispr->msg.access_location)
printf("Access location: %s\n", wispr->msg.access_location);
if (wispr->msg.location_name)
printf("Location name: %s\n", wispr->msg.location_name);
if (wispr->msg.login_url)
printf("Login URL: %s\n", wispr->msg.login_url);
if (wispr->msg.abort_login_url)
printf("Abort login URL: %s\n", wispr->msg.abort_login_url);
if (wispr->msg.logoff_url)
printf("Logoff URL: %s\n", wispr->msg.logoff_url);
printf("\n");
if (status != 200 && status != 302 && status != 404)
goto done;
if (wispr->msg.message_type == 100) {
if (!wispr->username) {
user_input("Username", false,
username_callback, wispr);
return false;
}
if (!wispr->password) {
user_input("Password", true, password_callback, wispr);
return false;
}
g_idle_add(execute_login, wispr);
return false;
} else if (wispr->msg.message_type == 120 ||
wispr->msg.message_type == 140) {
int code = wispr->msg.response_code;
printf("Login process: %s\n",
code == 50 ? "SUCCESS" : "FAILURE");
}
if (status == 302) {
const char *redirect;
if (!g_web_result_get_header(result, "Location", &redirect))
goto done;
printf("\n");
printf("Redirect URL: %s\n", redirect);
printf("\n");
wispr->request = g_web_request_get(wispr->web, redirect,
wispr_result, NULL, wispr);
return false;
}
done:
g_main_loop_quit(main_loop);
return false;
}
static gboolean execute_login(gpointer user_data)
{
struct wispr_session *wispr = user_data;
wispr->request = g_web_request_post(wispr->web, wispr->msg.login_url,
"application/x-www-form-urlencoded",
wispr_input, wispr_result, wispr);
wispr_msg_init(&wispr->msg);
return FALSE;
}
static bool option_debug = false;
static gchar *option_nameserver = NULL;
static gchar *option_username = NULL;
static gchar *option_password = NULL;
static gchar *option_url = NULL;
static GOptionEntry options[] = {
{ "debug", 'd', 0, G_OPTION_ARG_NONE, &option_debug,
"Enable debug output" },
{ "nameserver", 'n', 0, G_OPTION_ARG_STRING, &option_nameserver,
"Specify nameserver", "ADDRESS" },
{ "username", 'u', 0, G_OPTION_ARG_STRING, &option_username,
"Specify username", "USERNAME" },
{ "password", 'p', 0, G_OPTION_ARG_STRING, &option_password,
"Specify password", "PASSWORD" },
{ "url", 'U', 0, G_OPTION_ARG_STRING, &option_url,
"Specify arbitrary request", "URL" },
{ NULL },
};
int main(int argc, char *argv[])
{
GOptionContext *context;
GError *error = NULL;
struct sigaction sa;
struct wispr_session wispr;
int index = 0;
context = g_option_context_new(NULL);
g_option_context_add_main_entries(context, options, NULL);
if (!g_option_context_parse(context, &argc, &argv, &error)) {
if (error) {
g_printerr("%s\n", error->message);
g_error_free(error);
} else
g_printerr("An unknown error occurred\n");
return 1;
}
g_option_context_free(context);
memset(&wispr, 0, sizeof(wispr));
wispr_msg_init(&wispr.msg);
wispr.web = g_web_new(index);
if (!wispr.web) {
fprintf(stderr, "Failed to create web service\n");
return 1;
}
if (option_debug)
g_web_set_debug(wispr.web, web_debug, "WEB");
main_loop = g_main_loop_new(NULL, FALSE);
if (option_nameserver) {
g_web_add_nameserver(wispr.web, option_nameserver);
g_free(option_nameserver);
}
g_web_set_accept(wispr.web, NULL);
g_web_set_user_agent(wispr.web, "SmartClient/%s wispr", VERSION);
g_web_set_close_connection(wispr.web, TRUE);
if (!option_url)
option_url = g_strdup(DEFAULT_URL);
wispr.username = option_username;
wispr.password = option_password;
wispr.originurl = option_url;
timer = g_timer_new();
wispr.parser = g_web_parser_new("<WISPAccessGatewayParam",
"WISPAccessGatewayParam>",
parser_callback, &wispr);
wispr.request = g_web_request_get(wispr.web, option_url,
wispr_result, wispr_route, &wispr);
if (wispr.request == 0) {
fprintf(stderr, "Failed to start request\n");
return 1;
}
memset(&sa, 0, sizeof(sa));
sa.sa_handler = sig_term;
sigaction(SIGINT, &sa, NULL);
sigaction(SIGTERM, &sa, NULL);
g_main_loop_run(main_loop);
g_timer_destroy(timer);
if (wispr.request > 0)
g_web_cancel_request(wispr.web, wispr.request);
g_web_parser_unref(wispr.parser);
g_web_unref(wispr.web);
g_main_loop_unref(main_loop);
g_free(wispr.username);
g_free(wispr.password);
g_free(wispr.originurl);
return 0;
}