Added session recording with https://github.com/kilobyte/termrec
This commit is contained in:
parent
4da77dcb10
commit
beff818f25
13 changed files with 291 additions and 25 deletions
|
@ -8,8 +8,7 @@ OBJ=$(sources:.c=.o)
|
|||
all: bastion
|
||||
|
||||
bastion: $(OBJ)
|
||||
#$(CC) ${DEBUG} -o bastion $(OBJ) -lssh -lutil -lpthread -lssh_threads
|
||||
$(CC) ${DEBUG} -o bastion $(OBJ) -lssh -lutil
|
||||
$(CC) ${DEBUG} -o bastion $(OBJ) -lssh -lutil -ltty
|
||||
|
||||
clean:
|
||||
$(RM) bastion *.[do] src/*.[do]
|
||||
|
|
13
config.h
13
config.h
|
@ -1,14 +1,23 @@
|
|||
#ifndef CONFIG_H_
|
||||
#define CONFIG_H_
|
||||
|
||||
#define USER_TO_LOGIN_AS "root"
|
||||
|
||||
#define LISTEN_PORT 2222
|
||||
#define MAX_HOSTNAME_LENGTH 48
|
||||
#define MAX_HOSTNAME_LENGTH 255
|
||||
#define MAX_USERNAME_LENGTH 255
|
||||
#define USER_RSA_PUBKEY "AAAAB3NzaC1yc2EAAAADAQABAAACAQDMdBAFjENiPMTtq90GT3+NZ68nfGxQiRExaYYnLzm1ecmulCvsuA4AOpeLY6f+FWe+ludiw7nhrXzssDdsKBy0QL+XQyvjjjW4X+k9MYhP1gAWXEOGJnjJ/1ovEsMt++6fLyNKLUTA46kErbEehDs22r+rIiEKatrn0BNrJcRI94H44oEL1/ImzVam0cSBL0tPiaJxe60sBs7M76zfyFtVdMGkeuBpS7ee+FLA58fsS3/sEZmkas8MT0QdvZz1y/66MknXYbIaqDSOUACXGF4yVKpogLRRJ1SgNo1Ujo/U3VOR1O4CiQczsZOcbSdjgl0x3fJb7BaIxrZy9iW2I7G/L/chfTvRws+x1s1y5FNZOOiXMCdZjhgLaRwb6p5gMsMVn9sJbhDjmejcAkBKQDkzbvxxhfVkH225FoVXA9YF0msWLyOEyZQYbA8autLDJsAOT5RDfw/G82DQBufAPEBR/bPby0Hl5kjqW75bpSVxDvzmKwt3EpITg9iuYEhvYZ/Zq5qC1UJ54ZfOvaf0PsTUzFePty6ve/JzfxCV1XgFQ+B8l4NSz11loDfNXSUngf7lL4qu5X4aN6WmLFO1YbyFlfpvt3K1CekJmWVeE5mV9EFTUJ4ParVWRGiA4W+zaCOsHgRkcGkp4eYGyWW8gOR/lVxYU2IFl9mbMrC9bkdRbQ=="
|
||||
#define PRIVKEY_PATH "./id_rsa"
|
||||
#define USER_TO_LOGIN_AS "root"
|
||||
|
||||
#define DSAKEY_PATH "./ssh_host_dsa_key"
|
||||
#define RSAKEY_PATH "./ssh_host_rsa_key"
|
||||
#define ECDSAKEY_PATH "./ssh_host_ecdsa_key"
|
||||
|
||||
#define SESSION_RECORDING // comment this to deactivate
|
||||
#define LOG_FILENAME_FORMAT "./log/$d/$h/$u/$i.gz" // $d : date in iso format, $h : hostname, $u : username : $i session id
|
||||
#define LOG_FILENAME_MAX_LEN 255
|
||||
#define LOG_DIRECTORY_MODE S_IRUSR | S_IWUSR | S_IXUSR
|
||||
|
||||
#define LIBSSH_VERBOSE_OUTPOUT // comment this to deactivate
|
||||
|
||||
#endif
|
||||
|
|
29
src/client.c
29
src/client.c
|
@ -4,6 +4,10 @@
|
|||
|
||||
#include "../config.h"
|
||||
#include "client.h"
|
||||
#ifdef SESSION_RECORDING
|
||||
#include "recording.h"
|
||||
#endif
|
||||
#include "state.h"
|
||||
|
||||
// callback function for channel data and exceptions
|
||||
static int client_data_function(ssh_session session, ssh_channel channel, void *data,
|
||||
|
@ -13,9 +17,12 @@ static int client_data_function(ssh_session session, ssh_channel channel, void *
|
|||
(void) channel;
|
||||
(void) is_stderr;
|
||||
|
||||
if (ssh_channel_is_open(cdata->proxy_channel))
|
||||
if (ssh_channel_is_open(cdata->proxy_channel)) {
|
||||
#ifdef SESSION_RECORDING
|
||||
record(data, len);
|
||||
#endif
|
||||
return ssh_channel_write(cdata->proxy_channel, (char*) data, len);
|
||||
else
|
||||
} else
|
||||
return SSH_ERROR;
|
||||
}
|
||||
|
||||
|
@ -69,8 +76,9 @@ static void client_channel_exit_signal_callback (ssh_session session, ssh_channe
|
|||
printf("client exit signal callback\n");
|
||||
}
|
||||
|
||||
struct client_channel_data_struct* client_dial(ssh_event event, struct proxy_channel_data_struct *pdata, const char * hostname)
|
||||
struct client_channel_data_struct* client_dial(ssh_event event, struct proxy_channel_data_struct *pdata)
|
||||
{
|
||||
const char * hostname = state_get_ssh_destination();
|
||||
struct client_channel_data_struct *cdata = malloc(sizeof(*cdata));
|
||||
cdata->event = event;
|
||||
cdata->my_session = NULL;
|
||||
|
@ -90,9 +98,11 @@ struct client_channel_data_struct* client_dial(ssh_event event, struct proxy_cha
|
|||
cdata->my_session = ssh_new();
|
||||
|
||||
ssh_options_set(cdata->my_session, SSH_OPTIONS_HOST, hostname);
|
||||
ssh_options_set(cdata->my_session, SSH_OPTIONS_USER, USER_TO_LOGIN_AS);
|
||||
ssh_options_set(cdata->my_session, SSH_OPTIONS_USER, state_get_username());
|
||||
#ifdef LIBSSH_VERBOSE_OUTPOUT
|
||||
int verbosity = SSH_LOG_PROTOCOL;
|
||||
ssh_options_set(cdata->my_session, SSH_OPTIONS_LOG_VERBOSITY, &verbosity);
|
||||
#endif
|
||||
|
||||
if (ssh_connect(cdata->my_session) != SSH_OK) {
|
||||
printf("Error connecting to %s: %s\n", hostname, ssh_get_error(cdata->my_session));
|
||||
|
@ -152,6 +162,14 @@ struct client_channel_data_struct* client_dial(ssh_event event, struct proxy_cha
|
|||
ssh_set_channel_callbacks(cdata->my_channel, cdata->client_channel_cb);
|
||||
ssh_event_add_session(event, cdata->my_session);
|
||||
|
||||
// TODO only start recording upong shell_exec or pty_request in proxy.c.
|
||||
// It will be important when we start supporting scp
|
||||
#ifdef SESSION_RECORDING
|
||||
if (init_recorder() != 0) {
|
||||
goto channel_clean;
|
||||
}
|
||||
#endif
|
||||
|
||||
ssh_string_free_char(hexa);
|
||||
ssh_clean_pubkey_hash(&hash);
|
||||
ssh_key_free(server_pub_key);
|
||||
|
@ -179,6 +197,9 @@ privkey_clean:
|
|||
|
||||
void client_cleanup(struct client_channel_data_struct *cdata)
|
||||
{
|
||||
#ifdef SESSION_RECORDING
|
||||
clean_recorder();
|
||||
#endif
|
||||
ssh_event_remove_session(cdata->event, cdata->my_session);
|
||||
ssh_channel_free(cdata->my_channel);
|
||||
ssh_disconnect(cdata->my_session);
|
||||
|
|
|
@ -16,7 +16,7 @@ struct client_channel_data_struct {
|
|||
struct ssh_channel_callbacks_struct * client_channel_cb;
|
||||
};
|
||||
|
||||
struct client_channel_data_struct* client_dial(ssh_event event, struct proxy_channel_data_struct *pdata, const char * hostname);
|
||||
struct client_channel_data_struct* client_dial(ssh_event event, struct proxy_channel_data_struct *pdata);
|
||||
void client_cleanup(struct client_channel_data_struct *cdata);
|
||||
|
||||
#endif
|
||||
|
|
|
@ -14,12 +14,13 @@ static void sigchld_handler(int signo) {
|
|||
}
|
||||
|
||||
/* SIGINT handler for cleaning up on forced exit. */
|
||||
static ssh_bind sshbind;
|
||||
static ssh_session session;
|
||||
static ssh_bind sshbind = NULL;
|
||||
static ssh_session session = NULL;
|
||||
|
||||
__attribute__((noreturn)) static void sigint_handler(int signo)
|
||||
{
|
||||
(void) signo;
|
||||
ssh_disconnect(session);
|
||||
ssh_free(session);
|
||||
ssh_bind_free(sshbind);
|
||||
ssh_finalize();
|
||||
|
@ -75,8 +76,10 @@ int main()
|
|||
fprintf(stderr, "Error initializing ssh_session\n");
|
||||
break;
|
||||
}
|
||||
#ifdef LIBSSH_VERBOSE_OUTPOUT
|
||||
int verbosity = SSH_LOG_PROTOCOL;
|
||||
ssh_options_set(session, SSH_OPTIONS_LOG_VERBOSITY, &verbosity);
|
||||
#endif
|
||||
|
||||
// Blocks until there is a new incoming connection
|
||||
if (ssh_bind_accept(sshbind,session) == SSH_OK){
|
||||
|
@ -87,6 +90,7 @@ int main()
|
|||
sigaction(SIGCHLD, &sa, NULL);
|
||||
/* Remove socket binding, which allows us to restart the parent process, without terminating existing sessions. */
|
||||
ssh_bind_free(sshbind);
|
||||
sshbind = NULL;
|
||||
|
||||
ssh_event event = ssh_event_new();
|
||||
if (event != NULL) {
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
#include "client.h"
|
||||
#include "proxy.h"
|
||||
#include "state.h"
|
||||
|
||||
// callback function for channel data and exceptions
|
||||
static int proxy_data_function(ssh_session session, ssh_channel channel, void *data,
|
||||
|
@ -30,6 +31,7 @@ static int proxy_pty_request(ssh_session session, ssh_channel channel,
|
|||
(void) py;
|
||||
(void) px;
|
||||
|
||||
// TODO record pty size in recorder
|
||||
if (ssh_channel_is_open(pdata->client_channel)) {
|
||||
if (ssh_channel_request_pty_size(pdata->client_channel, term, cols, rows) == SSH_OK)
|
||||
return SSH_OK;
|
||||
|
@ -51,6 +53,7 @@ static int proxy_pty_resize(ssh_session session, ssh_channel channel, int cols,
|
|||
(void) py;
|
||||
(void) px;
|
||||
|
||||
// TODO record pty size in recorder
|
||||
if (ssh_channel_is_open(pdata->client_channel)) {
|
||||
if (ssh_channel_change_pty_size(pdata->client_channel, cols, rows) == SSH_OK)
|
||||
return SSH_OK;
|
||||
|
@ -158,7 +161,7 @@ static void proxy_channel_exit_signal_callback (ssh_session session, ssh_channel
|
|||
printf("proxy exit signal callback\n");
|
||||
}
|
||||
|
||||
void handle_proxy_session(ssh_event event, ssh_session session, ssh_channel my_channel, const char * hostname)
|
||||
void handle_proxy_session(ssh_event event, ssh_session session, ssh_channel my_channel)
|
||||
{
|
||||
struct client_channel_data_struct * cdata;
|
||||
|
||||
|
@ -169,7 +172,7 @@ void handle_proxy_session(ssh_event event, ssh_session session, ssh_channel my_c
|
|||
.client_channel = NULL,
|
||||
};
|
||||
|
||||
cdata = client_dial(event, &pdata, hostname);
|
||||
cdata = client_dial(event, &pdata);
|
||||
|
||||
if (cdata == NULL) {
|
||||
return;
|
||||
|
|
|
@ -13,6 +13,6 @@ struct proxy_channel_data_struct {
|
|||
ssh_channel my_channel;
|
||||
ssh_channel client_channel;
|
||||
};
|
||||
void handle_proxy_session(ssh_event event, ssh_session session, ssh_channel my_channel, const char * hostname);
|
||||
void handle_proxy_session(ssh_event event, ssh_session session, ssh_channel my_channel);
|
||||
|
||||
#endif
|
||||
|
|
126
src/recording.c
Normal file
126
src/recording.c
Normal file
|
@ -0,0 +1,126 @@
|
|||
#include <dirent.h>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/time.h>
|
||||
#include <time.h>
|
||||
#include <ttyrec.h>
|
||||
|
||||
#include "../config.h"
|
||||
#include "recording.h"
|
||||
#include "state.h"
|
||||
|
||||
#ifdef SESSION_RECORDING
|
||||
static recorder recorder_handle = NULL;
|
||||
|
||||
void clean_recorder(void)
|
||||
{
|
||||
ttyrec_w_close(recorder_handle);
|
||||
recorder_handle = NULL;
|
||||
}
|
||||
|
||||
static char * // this char * is to be freed from the calling code
|
||||
make_filename(void)
|
||||
{
|
||||
char * format = LOG_FILENAME_FORMAT;
|
||||
char * filename = NULL;
|
||||
unsigned int fname_pos = 0;
|
||||
unsigned int format_pos = 0;
|
||||
|
||||
filename = malloc(LOG_FILENAME_MAX_LEN+1);
|
||||
|
||||
size_t format_len = strlen(format);
|
||||
while (format_pos < format_len + 1 && fname_pos < LOG_FILENAME_MAX_LEN +1) {
|
||||
if (format[format_pos] == '$') {
|
||||
format_pos++;
|
||||
if (format[format_pos] == 'd') {
|
||||
time_t t;
|
||||
struct tm * tm;
|
||||
time(&t);
|
||||
tm = localtime(&t);
|
||||
fname_pos += strftime(filename + fname_pos, LOG_FILENAME_MAX_LEN - fname_pos, "%F", tm);
|
||||
} else if (format[format_pos] == 'h') {
|
||||
const char * hostname = state_get_ssh_destination();
|
||||
size_t len = strlen(hostname);
|
||||
strcpy(filename + fname_pos, hostname);
|
||||
fname_pos += len;
|
||||
} else if (format[format_pos] == 'u') {
|
||||
const char * username = state_get_username();
|
||||
size_t len = strlen(username);
|
||||
strcpy(filename + fname_pos, username);
|
||||
fname_pos += len;
|
||||
} else if (format[format_pos] == 'i') {
|
||||
sprintf(filename + fname_pos, "%d", state_get_session_id());
|
||||
fname_pos += strlen(filename + fname_pos);
|
||||
}
|
||||
format_pos++;
|
||||
} else {
|
||||
filename[fname_pos] = format[format_pos];
|
||||
if (filename[fname_pos] == '/') { // We create the corresponding directory if it doesn't exist
|
||||
filename[fname_pos+1] = '\0';
|
||||
DIR* dir = opendir(filename);
|
||||
if (dir)
|
||||
closedir(dir);
|
||||
else {
|
||||
int ret = mkdir(filename, LOG_DIRECTORY_MODE);
|
||||
if (ret != 0) {
|
||||
fprintf(stderr, "Couldn't create log directory %s : %s\n", filename, strerror( errno ));
|
||||
}
|
||||
}
|
||||
}
|
||||
format_pos++;
|
||||
fname_pos++;
|
||||
}
|
||||
}
|
||||
|
||||
if (filename[fname_pos-1] != '\0') {
|
||||
fprintf(stderr, "Log file name is too long, check LOG_FILENAME_FORMAT and LOG_FILENAME_MAX_LEN\n");
|
||||
free(filename);
|
||||
filename = NULL;
|
||||
}
|
||||
return filename;
|
||||
}
|
||||
|
||||
char // returns 0 if ok, 1 otherwise
|
||||
init_recorder(void)
|
||||
{
|
||||
char * filename = make_filename();
|
||||
if (filename == NULL)
|
||||
return 1;
|
||||
struct timeval tm;
|
||||
if (gettimeofday(&tm, NULL) != 0) {
|
||||
fprintf(stderr, "OUPS gettimeofday failed!\n");
|
||||
return 1;
|
||||
}
|
||||
recorder_handle = ttyrec_w_open(-1, "ttyrec", filename, &tm);
|
||||
free(filename);
|
||||
if (recorder_handle == NULL) {
|
||||
fprintf(stderr, "Couldn't open the session termrec log file.\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
char // returns 0 if ok, greater than 0 otherwise
|
||||
record(void* data, size_t len)
|
||||
{
|
||||
if(recorder_handle == NULL)
|
||||
return 0;
|
||||
|
||||
struct timeval tm;
|
||||
if (gettimeofday(&tm, NULL) != 0) {
|
||||
fprintf(stderr, "OUPS gettimeofday failed!\n");
|
||||
return 1;
|
||||
}
|
||||
if (ttyrec_w_write(recorder_handle, &tm, data, (int) len) == 0) {
|
||||
fprintf(stderr, "OUPS ttyrec_w_write failed!\n");
|
||||
return 2;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
#endif
|
12
src/recording.h
Normal file
12
src/recording.h
Normal file
|
@ -0,0 +1,12 @@
|
|||
#include "../config.h"
|
||||
|
||||
#ifdef SESSION_RECORDING
|
||||
#ifndef RECORDING_H_
|
||||
#define RECORDING_H_
|
||||
|
||||
void clean_recorder(void);
|
||||
char init_recorder(void);
|
||||
char record(void* data, size_t len);
|
||||
|
||||
#endif
|
||||
#endif
|
|
@ -11,6 +11,7 @@
|
|||
#include "../config.h"
|
||||
#include "proxy.h"
|
||||
#include "session.h"
|
||||
#include "state.h"
|
||||
|
||||
static int auth_pubkey(ssh_session session, const char *user,
|
||||
struct ssh_key_struct *pubkey,
|
||||
|
@ -35,13 +36,12 @@ static int auth_pubkey(ssh_session session, const char *user,
|
|||
if (!ssh_key_cmp(pubkey, reference_key, SSH_KEY_CMP_PUBLIC)) {
|
||||
sdata->authenticated = 1;
|
||||
ssh_key_free(reference_key);
|
||||
size_t len = strnlen(user, MAX_HOSTNAME_LENGTH + 1);
|
||||
if (len == MAX_HOSTNAME_LENGTH + 1) {
|
||||
fprintf(stderr, "Hostname too long, max length is %d.\n", MAX_HOSTNAME_LENGTH);
|
||||
if (state_set_ssh_destination(user) != 0)
|
||||
return SSH_ERROR;
|
||||
}
|
||||
sdata->login_username = malloc(len+1);
|
||||
strncpy(sdata->login_username, user, len+1);
|
||||
// TODO check access rights and host configs
|
||||
state_set_username(USER_TO_LOGIN_AS);
|
||||
// TODO log session creation in db
|
||||
state_set_session_id(1337);
|
||||
return SSH_AUTH_SUCCESS;
|
||||
} else {
|
||||
ssh_key_free(reference_key);
|
||||
|
@ -68,7 +68,6 @@ void handle_session(ssh_event event, ssh_session session) {
|
|||
.channel = NULL,
|
||||
.auth_attempts = 0,
|
||||
.authenticated = 0,
|
||||
.login_username = NULL
|
||||
};
|
||||
|
||||
struct ssh_server_callbacks_struct server_cb = {
|
||||
|
@ -105,7 +104,7 @@ void handle_session(ssh_event event, ssh_session session) {
|
|||
}
|
||||
}
|
||||
|
||||
handle_proxy_session(event, session, sdata.channel, sdata.login_username);
|
||||
handle_proxy_session(event, session, sdata.channel);
|
||||
|
||||
if (ssh_channel_is_open(sdata.channel)) {
|
||||
ssh_channel_close(sdata.channel);
|
||||
|
@ -115,6 +114,6 @@ void handle_session(ssh_event event, ssh_session session) {
|
|||
for (int n = 0; n < 50 && (ssh_get_status(session) & SESSION_END) == 0; n++) {
|
||||
ssh_event_dopoll(event, 100);
|
||||
}
|
||||
free(sdata.login_username);
|
||||
state_clean();
|
||||
ssh_event_remove_session(event, session);
|
||||
}
|
||||
|
|
|
@ -11,8 +11,6 @@ struct session_data_struct {
|
|||
ssh_channel channel;
|
||||
int auth_attempts;
|
||||
int authenticated;
|
||||
// ssh user name when login
|
||||
char * login_username;
|
||||
};
|
||||
|
||||
void handle_session(ssh_event event, ssh_session session);
|
||||
|
|
83
src/state.c
Normal file
83
src/state.c
Normal file
|
@ -0,0 +1,83 @@
|
|||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "../config.h"
|
||||
#include "state.h"
|
||||
|
||||
struct state {
|
||||
char * destination;
|
||||
char * username;
|
||||
int session_id;
|
||||
int padding;
|
||||
};
|
||||
|
||||
static struct state state = {0};
|
||||
|
||||
char // returns 0 if ok, greater than 0 otherwise
|
||||
state_set_ssh_destination(const char * name)
|
||||
{
|
||||
if (state.destination != NULL) {
|
||||
fprintf(stderr, "BUG found, attempting to overwrite state.destination that has already been set\n");
|
||||
return 1;
|
||||
}
|
||||
size_t len = strnlen(name, MAX_HOSTNAME_LENGTH + 1);
|
||||
if (len >= MAX_HOSTNAME_LENGTH + 1) {
|
||||
fprintf(stderr, "Hostname too long, max length is %d.\n", MAX_HOSTNAME_LENGTH);
|
||||
return 2;
|
||||
}
|
||||
state.destination = malloc(len+1);
|
||||
strncpy(state.destination, name, len+1);
|
||||
return 0;
|
||||
}
|
||||
|
||||
const char * state_get_ssh_destination(void)
|
||||
{
|
||||
return state.destination;
|
||||
}
|
||||
|
||||
char // return 0 if ok, greater than 0 otherwise
|
||||
state_set_username(const char * name)
|
||||
{
|
||||
if (state.username != NULL) {
|
||||
fprintf(stderr, "BUG found, attempting to overwrite state.username that has already been set\n");
|
||||
return 1;
|
||||
}
|
||||
size_t len = strnlen(name, MAX_USERNAME_LENGTH + 1);
|
||||
if (len >= MAX_USERNAME_LENGTH + 1) {
|
||||
fprintf(stderr, "Username too long, max length is %d.\n", MAX_USERNAME_LENGTH);
|
||||
return 1;
|
||||
}
|
||||
state.username = malloc(len+1);
|
||||
strncpy(state.username, name, len+1);
|
||||
return 0;
|
||||
}
|
||||
|
||||
const char * state_get_username(void)
|
||||
{
|
||||
return state.username;
|
||||
}
|
||||
|
||||
char // return 0 if ok, greater than 0 otherwise
|
||||
state_set_session_id(const int id)
|
||||
{
|
||||
if (state.session_id != 0) {
|
||||
fprintf(stderr, "BUG found, attempting to overwrite state.username that has already been set\n");
|
||||
return 1;
|
||||
}
|
||||
state.session_id = id;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int state_get_session_id(void)
|
||||
{
|
||||
return state.session_id;
|
||||
}
|
||||
|
||||
void state_clean(void)
|
||||
{
|
||||
free(state.destination);
|
||||
state.destination = NULL;
|
||||
free(state.username);
|
||||
state.username = NULL;
|
||||
}
|
12
src/state.h
Normal file
12
src/state.h
Normal file
|
@ -0,0 +1,12 @@
|
|||
#ifndef STATE_H_
|
||||
#define STATE_H_
|
||||
|
||||
char state_set_ssh_destination(const char * dest);
|
||||
const char * state_get_ssh_destination(void);
|
||||
char state_set_username(const char * name);
|
||||
const char * state_get_username(void);
|
||||
char state_set_session_id(const int id);
|
||||
int state_get_session_id(void);
|
||||
void state_clean(void);
|
||||
|
||||
#endif
|
Reference in a new issue