1
0
mirror of https://github.com/haiwen/ccnet-server.git synced 2025-06-13 15:44:42 +00:00
ccnet-server/net/common/session.c
2016-08-19 13:54:34 +08:00

629 lines
17 KiB
C

/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
#include "common.h"
#include <signal.h>
#include <dirent.h>
#include <sys/stat.h>
#include <time.h>
#include "getgateway.h"
#include "utils.h"
#include "net.h"
#include "rsa.h"
#include "ccnet-config.h"
#include "session.h"
#include "peer.h"
#include "peer-mgr.h"
#include "perm-mgr.h"
#include "packet-io.h"
#include "connect-mgr.h"
#include "message.h"
#include "message-manager.h"
#include "algorithms.h"
#include "proc-factory.h"
#define DEBUG_FLAG CCNET_DEBUG_OTHER
#include "log.h"
#define THREAD_POOL_SIZE 50
static void ccnet_service_free (CcnetService *service);
G_DEFINE_TYPE (CcnetSession, ccnet_session, CCNET_TYPE_SESSION_BASE);
static void on_peer_auth_done (CcnetPeerManager *manager,
CcnetPeer *peer, gpointer user_data);
static void
ccnet_session_class_init (CcnetSessionClass *klass)
{
/* GObjectClass *gobject_class = G_OBJECT_CLASS (klass); */
}
static void
ccnet_session_init (CcnetSession *session)
{
/* note, the order is important. */
session->service_hash = g_hash_table_new_full (
g_str_hash, g_str_equal,
g_free, (GDestroyNotify)ccnet_service_free);
session->proc_factory = ccnet_proc_factory_new (session);
session->peer_mgr = ccnet_peer_manager_new (session);
session->connMgr = ccnet_conn_manager_new (session);
session->msg_mgr = ccnet_message_manager_new (session);
session->perm_mgr = ccnet_perm_manager_new (session);
session->job_mgr = ccnet_job_manager_new (THREAD_POOL_SIZE);
}
static int load_rsakey(CcnetSession *session)
{
char *path;
FILE *fp;
RSA *key;
path = g_build_filename(session->config_dir, PEER_KEYFILE, NULL);
if (!g_file_test(path, G_FILE_TEST_EXISTS))
ccnet_error ("Can't load rsa private key from %s\n", path);
if ((fp = g_fopen(path, "rb")) == NULL)
ccnet_error ("Can't open private key file %s: %s\n", path,
strerror(errno));
if ((key = PEM_read_RSAPrivateKey(fp, NULL, NULL, NULL)) == NULL)
ccnet_error ("Can't open load key file %s: format error\n", path);
fclose(fp);
session->privkey = key;
session->pubkey = private_key_to_pub(key);
g_free(path);
return 0;
}
static void listen_on_localhost (CcnetSession *session);
static void listen_on_pipe (CcnetSession *session);
static void save_pubinfo (CcnetSession *session);
CcnetSession *
ccnet_session_new ()
{
return g_object_new (CCNET_TYPE_SESSION, NULL);
}
int
ccnet_session_load_config (CcnetSession *session,
const char *central_config_dir_r,
const char *config_dir_r)
{
int ret = 0;
char *config_file = NULL, *config_dir = NULL, *central_config_dir = NULL;
char *id = NULL, *name = NULL, *port_str = NULL,
*lport_str = NULL, *un_path = NULL,
*user_name = NULL;
#ifdef CCNET_SERVER
char *service_url;
#endif
int port, local_port = 0;
unsigned char sha1[20];
GKeyFile *key_file;
config_dir = ccnet_expand_path (config_dir_r);
if (checkdir(config_dir) < 0) {
ccnet_error ("Config dir %s does not exist or is not "
"a directory.\n", config_dir);
return -1;
}
#ifdef CCNET_SERVER
if (central_config_dir_r) {
central_config_dir = ccnet_expand_path (central_config_dir_r);
if (checkdir(central_config_dir) < 0) {
ccnet_error ("Server Config dir %s does not exist or is not "
"a directory.\n", central_config_dir);
return -1;
}
}
#endif
config_file = g_build_filename (central_config_dir ? central_config_dir : config_dir,
SESSION_CONFIG_FILENAME, NULL);
ccnet_message ("using config file %s\n", config_file);
key_file = g_key_file_new ();
g_key_file_set_list_separator (key_file, ',');
if (!g_key_file_load_from_file (key_file, config_file,
G_KEY_FILE_KEEP_COMMENTS, NULL))
{
ccnet_warning ("Can't load config file %s.\n", config_file);
return -1;
}
id = ccnet_key_file_get_string (key_file, "General", "ID");
user_name = ccnet_key_file_get_string (key_file, "General", "USER_NAME");
name = ccnet_key_file_get_string (key_file, "General", "NAME");
#ifdef CCNET_SERVER
service_url = ccnet_key_file_get_string (key_file, "General", "SERVICE_URL");
#endif
port_str = ccnet_key_file_get_string (key_file, "Network", "PORT");
lport_str = ccnet_key_file_get_string (key_file, "Client", "PORT");
un_path = ccnet_key_file_get_string (key_file, "Client", "UNIX_SOCKET");
if (port_str == NULL) {
port = 0;
} else {
port = atoi (port_str);
if (port <= 0 || port > 65535) {
port = DEFAULT_PORT;
}
}
if ( (id == NULL) || (strlen (id) != SESSION_ID_LENGTH)
|| (hex_to_sha1 (id, sha1) < 0) ) {
ccnet_error ("Wrong ID\n");
ret = -1;
goto onerror;
}
if (lport_str)
local_port = atoi (lport_str);
memcpy (session->base.id, id, 40);
session->base.id[40] = '\0';
session->base.name = g_strdup(name);
session->base.user_name = g_strdup(user_name);
session->base.public_port = port;
#ifdef CCNET_SERVER
session->base.service_url = g_strdup(service_url);
#endif
session->config_file = config_file;
session->config_dir = config_dir;
session->central_config_dir = central_config_dir;
session->un_path = un_path;
session->local_port = local_port;
session->keyf = key_file;
load_rsakey(session);
ret = 0;
onerror:
g_free (id);
g_free (name);
g_free (user_name);
g_free (port_str);
g_free (lport_str);
#ifdef CCNET_SERVER
g_free (service_url);
#endif
return ret;
}
void
ccnet_session_free (CcnetSession *session)
{
ccnet_peer_manager_free (session->peer_mgr);
g_object_unref (session);
}
int
ccnet_session_prepare (CcnetSession *session,
const char *central_config_dir,
const char *config_dir_r,
gboolean test_config)
{
char *misc_path;
int ret;
if (ccnet_session_load_config (session, central_config_dir, config_dir_r) < 0)
return -1;
misc_path = g_build_filename (session->config_dir, "misc", NULL);
if (checkdir_with_mkdir (misc_path) < 0) {
ccnet_error ("mkdir %s error", misc_path);
return -1;
}
/* config db */
session->config_db = ccnet_session_config_open_db (misc_path);
if (!session->config_db) {
ccnet_warning ("Failed to open config db.\n");
return -1;
}
/* call subclass prepare */
ret = CCNET_SESSION_GET_CLASS (session)->prepare(session);
if (ret < 0)
return ret;
/* peer */
ccnet_peer_manager_prepare(session->peer_mgr);
g_signal_connect (session->peer_mgr, "peer-auth-done",
G_CALLBACK(on_peer_auth_done), session);
/* permission manager */
ccnet_perm_manager_prepare (session->perm_mgr);
g_free (misc_path);
if (test_config) {
return 0;
} else {
/* Open localhost, if failed, then the program will exists. This is used
* to prevent two instance of ccnet on the same port.
*/
#ifdef WIN32
listen_on_localhost (session);
#else
listen_on_pipe (session);
#endif
/* refresh pubinfo on every startup */
save_pubinfo (session);
}
return 0;
}
static void
save_peerinfo (CcnetSession *session)
{
FILE *fp;
char *path;
char filename[64];
sprintf(filename, "%s%s", session->base.id, ".peer");
path = g_build_filename (session->config_dir, "misc", filename, NULL);
if ((fp = g_fopen(path, "wb")) == NULL) {
ccnet_warning ("Open public info file %s error: %s\n",
path, strerror(errno));
g_free (path);
return;
}
g_free (path);
ccnet_message ("Update pubinfo file\n");
session->myself->public_port = session->base.public_port;
GString *str = ccnet_peer_to_string (session->myself);
fputs (str->str, fp);
fclose (fp);
g_string_free (str, TRUE);
}
static void save_pubinfo (CcnetSession *session)
{
save_peerinfo(session);
}
void
ccnet_session_save_config (CcnetSession *session)
{
GError *error = NULL;
char *str;
FILE *fp;
ccnet_message ("[Session] Saving configure file\n");
if (session->saving_pub) {
/* only update timestamp when pubinfo changes */
save_pubinfo (session);
session->saving_pub = 0;
}
g_key_file_set_string (session->keyf, "General", "NAME",
session->base.name);
g_key_file_set_string (session->keyf, "General", "ID",
session->base.id);
g_key_file_set_string (session->keyf, "General", "USER_NAME",
session->base.user_name);
#ifdef CCNET_SERVER
g_key_file_set_string (session->keyf, "General", "SERVICE_URL",
session->base.service_url?session->base.service_url:"");
#endif
g_key_file_set_integer (session->keyf, "Network", "PORT",
session->base.public_port);
g_key_file_set_integer (session->keyf, "Client", "PORT", session->local_port);
str = g_key_file_to_data (session->keyf, NULL, &error);
if (error) {
ccnet_warning ("Can't save unauth peer info: %s\n",
error->message);
return;
}
if ((fp = g_fopen (session->config_file, "wb")) == NULL) {
ccnet_warning ("Can't save session conf: %s\n", strerror(errno));
g_free (str);
return;
}
fputs (str, fp);
fclose (fp);
g_free (str);
return;
}
void
ccnet_session_save (CcnetSession *session)
{
/* ccnet_req_manager_backup_requirements (session->reqMgr); */
}
void
ccnet_session_on_exit (CcnetSession *session)
{
time_t t;
ccnet_peer_manager_on_exit (session->peer_mgr);
ccnet_session_save (session);
t = time(NULL);
ccnet_message ("Exit at %s\n", ctime(&t));
ccnet_session_free (session);
}
static const char *net_status_string (int status)
{
switch (status) {
case NET_STATUS_DOWN:
return "Down";
case NET_STATUS_INNAT:
return "In nat";
case NET_STATUS_FULL:
return "Full";
default:
return "Unknown";
}
}
static void accept_local_client (evutil_socket_t fd, short event, void *vsession)
{
CcnetSession *session = vsession;
CcnetPacketIO *io;
int connfd;
CcnetPeer *peer;
static int local_id = 0;
connfd = accept (fd, NULL, 0);
ccnet_message ("Accepted a local client\n");
io = ccnet_packet_io_new_incoming (session, NULL, connfd);
peer = ccnet_peer_new (session->base.id);
peer->name = g_strdup_printf("local-%d", local_id++);
peer->is_local = TRUE;
ccnet_peer_set_io (peer, io);
ccnet_peer_set_net_state (peer, PEER_CONNECTED);
ccnet_peer_manager_add_local_peer (session->peer_mgr, peer);
g_object_unref (peer);
}
static void listen_on_localhost (CcnetSession *session)
{
int sockfd;
if ( (sockfd = ccnet_net_bind_v4 ("127.0.0.1", &session->local_port)) < 0) {
printf ("listen on localhost failed\n");
exit (1);
}
ccnet_message ("Listen on 127.0.0.1 %d\n", session->local_port);
listen (sockfd, 5);
event_set (&session->local_event, sockfd, EV_READ | EV_PERSIST,
accept_local_client, session);
event_add (&session->local_event, NULL);
}
#ifndef WIN32
static void listen_on_pipe (CcnetSession *session)
{
int pipe_fd = socket (AF_UNIX, SOCK_STREAM, 0);
char *un_path = NULL;
if (pipe_fd < 0) {
ccnet_warning ("Failed to create unix socket fd : %s\n",
strerror(errno));
goto failed;
}
struct sockaddr_un saddr;
saddr.sun_family = AF_UNIX;
if (!session->un_path)
un_path = g_build_filename (session->config_dir, CCNET_PIPE_NAME, NULL);
else
un_path = g_strdup(session->un_path);
if (strlen(un_path) > sizeof(saddr.sun_path)-1) {
ccnet_warning ("Unix socket path %s is too long."
"Please set or modify UNIX_SOCKET option in ccnet.conf.\n",
un_path);
g_free (un_path);
goto failed;
}
if (g_file_test (un_path, G_FILE_TEST_EXISTS)) {
ccnet_warning ("socket file exists, delete it anyway\n");
if (g_unlink (un_path) < 0) {
ccnet_warning ("delete socket file failed : %s\n", strerror(errno));
goto failed;
}
}
g_strlcpy (saddr.sun_path, un_path, sizeof(saddr.sun_path));
if (bind(pipe_fd, (struct sockaddr *)&saddr, sizeof(saddr)) < 0) {
ccnet_warning ("failed to bind unix socket fd to %s : %s\n",
un_path, strerror(errno));
goto failed;
}
if (listen(pipe_fd, 3) < 0) {
ccnet_warning ("failed to listen to unix socket: %s\n", strerror(errno));
goto failed;
}
if (chmod(un_path, 0700) < 0) {
ccnet_warning ("failed to set permisson for unix socket %s: %s\n",
un_path, strerror(errno));
goto failed;
}
event_set (&session->local_pipe_event, pipe_fd, EV_READ | EV_PERSIST,
accept_local_client, session);
event_add (&session->local_pipe_event, NULL);
ccnet_message ("Listen on %s for local clients\n", un_path);
g_free (un_path);
return;
failed:
ccnet_warning ("listen on unix socket failed\n");
exit (1);
}
#endif // WIN32
void
ccnet_session_start_network (CcnetSession *session)
{
session->base.net_status = NET_STATUS_FULL;
ccnet_conn_manager_start (session->connMgr);
session->myself->addr_str = NULL;
}
void
ccnet_session_shutdown_network (CcnetSession *session)
{
GList *peers, *ptr;
peers = ccnet_peer_manager_get_peer_list(session->peer_mgr);
for (ptr = peers; ptr; ptr = ptr->next)
ccnet_peer_shutdown ((CcnetPeer *)ptr->data);
g_list_free (peers);
ccnet_conn_manager_stop (session->connMgr);
}
static int
restart_network (CcnetSession *session)
{
ccnet_session_start_network (session);
if (session->base.net_status != NET_STATUS_DOWN) {
session->start_failure = 0;
return FALSE;
}
if (++session->start_failure > 3)
return FALSE;
return TRUE;
}
void
ccnet_session_start (CcnetSession *session)
{
ccnet_proc_factory_start (session->proc_factory);
ccnet_message_manager_start (session->msg_mgr);
if (session->base.public_port == 0) {
return;
}
ccnet_session_start_network (session);
if (session->base.net_status == NET_STATUS_DOWN) {
ccnet_timer_new ((TimerCB)restart_network, session, 10000);
}
ccnet_peer_manager_start (session->peer_mgr);
}
static void
ccnet_service_free (CcnetService *service)
{
g_free (service->svc_name);
g_free (service);
}
int
ccnet_session_register_service (CcnetSession *session,
const char *svc_name,
const char *group,
CcnetPeer *peer)
{
CcnetService *service = g_new0 (CcnetService, 1);
if (g_hash_table_lookup (session->service_hash, svc_name)) {
ccnet_debug ("[Service] Service %s has already been registered\n",
svc_name);
return -1;
}
ccnet_debug ("[Service] Service %s registered\n", svc_name);
ccnet_perm_manager_register_service (session->perm_mgr, svc_name,
group, peer);
service->svc_name = g_strdup(svc_name);
service->provider = peer;
g_hash_table_insert (session->service_hash, g_strdup(svc_name), service);
return 0;
}
CcnetService*
ccnet_session_get_service (CcnetSession *session,
const char *svc_name)
{
return g_hash_table_lookup (session->service_hash, svc_name);
}
gboolean remove_service_cmp (gpointer key,
gpointer value,
gpointer user_data)
{
CcnetService *service = value;
if (service->provider == user_data) {
ccnet_debug ("[Service] Service %s un-registered\n", (char *)key);
return TRUE;
}
return FALSE;
}
void
ccnet_session_unregister_service (CcnetSession *session,
CcnetPeer *peer)
{
g_hash_table_foreach_remove (session->service_hash,
remove_service_cmp, peer);
}
static void on_peer_auth_done (CcnetPeerManager *manager,
CcnetPeer *peer, gpointer user_data)
{
CcnetSession *session = (CcnetSession *)user_data;
CCNET_SESSION_GET_CLASS (session)->on_peer_auth_done(session, peer);
}
gboolean
ccnet_session_should_encrypt_channel (CcnetSession *session)
{
return session->encrypt_channel;
}