mirror of
https://git.libssh.org/projects/libssh.git
synced 2026-02-09 09:54:25 +09:00
kex, known_hosts: Use new tokens functions
Replace the old tokens handling functions usage with the new implementation. Signed-off-by: Anderson Toshiyuki Sasaki <ansasaki@redhat.com> Reviewed-by: Jakub Jelen <jjelen@redhat.com>
This commit is contained in:
committed by
Andreas Schneider
parent
2c4850cbbd
commit
bc95a51710
126
src/kex.c
126
src/kex.c
@@ -186,92 +186,6 @@ static const char *ssh_kex_descriptions[] = {
|
|||||||
NULL
|
NULL
|
||||||
};
|
};
|
||||||
|
|
||||||
/* tokenize will return a token of strings delimited by ",". the first element has to be freed */
|
|
||||||
static char **tokenize(const char *chain){
|
|
||||||
char **tokens;
|
|
||||||
size_t n=1;
|
|
||||||
size_t i=0;
|
|
||||||
char *tmp;
|
|
||||||
char *ptr;
|
|
||||||
|
|
||||||
tmp = strdup(chain);
|
|
||||||
if (tmp == NULL) {
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
ptr = tmp;
|
|
||||||
while(*ptr){
|
|
||||||
if(*ptr==','){
|
|
||||||
n++;
|
|
||||||
*ptr=0;
|
|
||||||
}
|
|
||||||
ptr++;
|
|
||||||
}
|
|
||||||
/* now n contains the number of tokens, the first possibly empty if the list was empty too e.g. "" */
|
|
||||||
tokens = calloc(n + 1, sizeof(char *)); /* +1 for the null */
|
|
||||||
if (tokens == NULL) {
|
|
||||||
SAFE_FREE(tmp);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
ptr=tmp;
|
|
||||||
for(i=0;i<n;i++){
|
|
||||||
tokens[i]=ptr;
|
|
||||||
while(*ptr)
|
|
||||||
ptr++; // find a zero
|
|
||||||
ptr++; // then go one step further
|
|
||||||
}
|
|
||||||
tokens[i]=NULL;
|
|
||||||
return tokens;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* same as tokenize(), but with spaces instead of ',' */
|
|
||||||
/* TODO FIXME rewrite me! */
|
|
||||||
char **ssh_space_tokenize(const char *chain){
|
|
||||||
char **tokens;
|
|
||||||
size_t n=1;
|
|
||||||
size_t i=0;
|
|
||||||
char *tmp;
|
|
||||||
char *ptr;
|
|
||||||
|
|
||||||
tmp = strdup(chain);
|
|
||||||
if (tmp == NULL) {
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
ptr = tmp;
|
|
||||||
|
|
||||||
while(*ptr==' ')
|
|
||||||
++ptr; /* skip initial spaces */
|
|
||||||
while(*ptr){
|
|
||||||
if(*ptr==' '){
|
|
||||||
n++; /* count one token per word */
|
|
||||||
*ptr=0;
|
|
||||||
while(*(ptr+1)==' '){ /* don't count if the tokens have more than 2 spaces */
|
|
||||||
*(ptr++)=0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ptr++;
|
|
||||||
}
|
|
||||||
/* now n contains the number of tokens, the first possibly empty if the list was empty too e.g. "" */
|
|
||||||
tokens = calloc(n + 1, sizeof(char *)); /* +1 for the null */
|
|
||||||
if (tokens == NULL) {
|
|
||||||
SAFE_FREE(tmp);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
ptr=tmp; /* we don't pass the initial spaces because the "tmp" pointer is needed by the caller */
|
|
||||||
/* function to free the tokens. */
|
|
||||||
for(i=0;i<n;i++){
|
|
||||||
tokens[i]=ptr;
|
|
||||||
if(i!=n-1){
|
|
||||||
while(*ptr)
|
|
||||||
ptr++; // find a zero
|
|
||||||
while(!*(ptr+1))
|
|
||||||
++ptr; /* if the zero is followed by other zeros, go through them */
|
|
||||||
ptr++; // then go one step further
|
|
||||||
}
|
|
||||||
}
|
|
||||||
tokens[i]=NULL;
|
|
||||||
return tokens;
|
|
||||||
}
|
|
||||||
|
|
||||||
const char *ssh_kex_get_default_methods(uint32_t algo)
|
const char *ssh_kex_get_default_methods(uint32_t algo)
|
||||||
{
|
{
|
||||||
if (algo >= KEX_METHODS_SIZE) {
|
if (algo >= KEX_METHODS_SIZE) {
|
||||||
@@ -306,37 +220,33 @@ const char *ssh_kex_get_description(uint32_t algo) {
|
|||||||
*/
|
*/
|
||||||
static int cmp_first_kex_algo(const char *client_str,
|
static int cmp_first_kex_algo(const char *client_str,
|
||||||
const char *server_str) {
|
const char *server_str) {
|
||||||
|
size_t client_kex_len;
|
||||||
|
size_t server_kex_len;
|
||||||
|
|
||||||
|
char *colon;
|
||||||
|
|
||||||
int is_wrong = 1;
|
int is_wrong = 1;
|
||||||
char **server_str_tokens = NULL;
|
|
||||||
char **client_str_tokens = NULL;
|
|
||||||
|
|
||||||
if ((client_str == NULL) || (server_str == NULL)) {
|
colon = strchr(client_str, ',');
|
||||||
goto out;
|
if (colon == NULL) {
|
||||||
|
client_kex_len = strlen(client_str);
|
||||||
|
} else {
|
||||||
|
client_kex_len = colon - client_str;
|
||||||
}
|
}
|
||||||
|
|
||||||
client_str_tokens = tokenize(client_str);
|
colon = strchr(server_str, ',');
|
||||||
|
if (colon == NULL) {
|
||||||
if (client_str_tokens == NULL) {
|
server_kex_len = strlen(server_str);
|
||||||
goto out;
|
} else {
|
||||||
|
server_kex_len = colon - server_str;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (client_str_tokens[0] == NULL) {
|
if (client_kex_len != server_kex_len) {
|
||||||
goto freeout;
|
return is_wrong;
|
||||||
}
|
}
|
||||||
|
|
||||||
server_str_tokens = tokenize(server_str);
|
is_wrong = (strncmp(client_str, server_str, client_kex_len) != 0);
|
||||||
if (server_str_tokens == NULL) {
|
|
||||||
goto freeout;
|
|
||||||
}
|
|
||||||
|
|
||||||
is_wrong = (strcmp(client_str_tokens[0], server_str_tokens[0]) != 0);
|
|
||||||
|
|
||||||
SAFE_FREE(server_str_tokens[0]);
|
|
||||||
SAFE_FREE(server_str_tokens);
|
|
||||||
freeout:
|
|
||||||
SAFE_FREE(client_str_tokens[0]);
|
|
||||||
SAFE_FREE(client_str_tokens);
|
|
||||||
out:
|
|
||||||
return is_wrong;
|
return is_wrong;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -38,6 +38,7 @@
|
|||||||
#include "libssh/knownhosts.h"
|
#include "libssh/knownhosts.h"
|
||||||
/*todo: remove this include */
|
/*todo: remove this include */
|
||||||
#include "libssh/string.h"
|
#include "libssh/string.h"
|
||||||
|
#include "libssh/token.h"
|
||||||
|
|
||||||
#ifndef _WIN32
|
#ifndef _WIN32
|
||||||
# include <netinet/in.h>
|
# include <netinet/in.h>
|
||||||
@@ -50,23 +51,6 @@
|
|||||||
* @{
|
* @{
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
|
||||||
* @internal
|
|
||||||
*
|
|
||||||
* @brief Free a token array.
|
|
||||||
*/
|
|
||||||
static void tokens_free(char **tokens) {
|
|
||||||
if (tokens == NULL) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
SAFE_FREE(tokens[0]);
|
|
||||||
/* It's not needed to free other pointers because tokens generated by
|
|
||||||
* space_tokenize fit all in one malloc
|
|
||||||
*/
|
|
||||||
SAFE_FREE(tokens);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @internal
|
* @internal
|
||||||
*
|
*
|
||||||
@@ -86,58 +70,63 @@ static void tokens_free(char **tokens) {
|
|||||||
* free that value. NULL if no match was found or the file
|
* free that value. NULL if no match was found or the file
|
||||||
* was not found.
|
* was not found.
|
||||||
*/
|
*/
|
||||||
static char **ssh_get_knownhost_line(FILE **file, const char *filename,
|
static struct ssh_tokens_st *ssh_get_knownhost_line(FILE **file,
|
||||||
const char **found_type) {
|
const char *filename,
|
||||||
char buffer[4096] = {0};
|
const char **found_type)
|
||||||
char *ptr;
|
{
|
||||||
char **tokens;
|
char buffer[4096] = {0};
|
||||||
|
char *ptr;
|
||||||
|
struct ssh_tokens_st *tokens;
|
||||||
|
|
||||||
if(*file == NULL){
|
|
||||||
*file = fopen(filename,"r");
|
|
||||||
if (*file == NULL) {
|
if (*file == NULL) {
|
||||||
return NULL;
|
*file = fopen(filename,"r");
|
||||||
}
|
if (*file == NULL) {
|
||||||
}
|
return NULL;
|
||||||
|
}
|
||||||
while (fgets(buffer, sizeof(buffer), *file)) {
|
|
||||||
ptr = strchr(buffer, '\n');
|
|
||||||
if (ptr) {
|
|
||||||
*ptr = '\0';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ptr = strchr(buffer,'\r');
|
while (fgets(buffer, sizeof(buffer), *file)) {
|
||||||
if (ptr) {
|
ptr = strchr(buffer, '\n');
|
||||||
*ptr = '\0';
|
if (ptr) {
|
||||||
|
*ptr = '\0';
|
||||||
|
}
|
||||||
|
|
||||||
|
ptr = strchr(buffer,'\r');
|
||||||
|
if (ptr) {
|
||||||
|
*ptr = '\0';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (buffer[0] == '\0' || buffer[0] == '#') {
|
||||||
|
continue; /* skip empty lines */
|
||||||
|
}
|
||||||
|
|
||||||
|
tokens = ssh_tokenize(buffer, ' ');
|
||||||
|
if (tokens == NULL) {
|
||||||
|
fclose(*file);
|
||||||
|
*file = NULL;
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tokens->tokens[0] == NULL ||
|
||||||
|
tokens->tokens[1] == NULL ||
|
||||||
|
tokens->tokens[2] == NULL)
|
||||||
|
{
|
||||||
|
/* it should have at least 3 tokens */
|
||||||
|
ssh_tokens_free(tokens);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
*found_type = tokens->tokens[1];
|
||||||
|
|
||||||
|
return tokens;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (buffer[0] == '\0' || buffer[0] == '#') {
|
fclose(*file);
|
||||||
continue; /* skip empty lines */
|
*file = NULL;
|
||||||
}
|
|
||||||
|
|
||||||
tokens = ssh_space_tokenize(buffer);
|
/* we did not find anything, end of file*/
|
||||||
if (tokens == NULL) {
|
return NULL;
|
||||||
fclose(*file);
|
|
||||||
*file = NULL;
|
|
||||||
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(tokens[0] == NULL || tokens[1] == NULL || tokens[2] == NULL) {
|
|
||||||
/* it should have at least 3 tokens */
|
|
||||||
tokens_free(tokens);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
*found_type = tokens[1];
|
|
||||||
|
|
||||||
return tokens;
|
|
||||||
}
|
|
||||||
|
|
||||||
fclose(*file);
|
|
||||||
*file = NULL;
|
|
||||||
|
|
||||||
/* we did not find anything, end of file*/
|
|
||||||
return NULL;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -291,137 +280,143 @@ static int match_hashed_host(const char *host, const char *sourcehash)
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief This function is depcrecated
|
* @brief This function is deprecated
|
||||||
*
|
*
|
||||||
* @deprecated Please use ssh_session_is_known_server()
|
* @deprecated Please use ssh_session_is_known_server()
|
||||||
* @see ssh_session_is_known_server()
|
* @see ssh_session_is_known_server()
|
||||||
*/
|
*/
|
||||||
int ssh_is_server_known(ssh_session session) {
|
int ssh_is_server_known(ssh_session session)
|
||||||
FILE *file = NULL;
|
{
|
||||||
char **tokens;
|
FILE *file = NULL;
|
||||||
char *host;
|
char *host;
|
||||||
char *hostport;
|
char *hostport;
|
||||||
const char *type;
|
const char *type;
|
||||||
int match;
|
int match;
|
||||||
int i=0;
|
int i = 0;
|
||||||
char * files[3];
|
char *files[3];
|
||||||
int ret = SSH_SERVER_NOT_KNOWN;
|
|
||||||
|
|
||||||
if (session->opts.knownhosts == NULL) {
|
struct ssh_tokens_st *tokens;
|
||||||
if (ssh_options_apply(session) < 0) {
|
|
||||||
ssh_set_error(session, SSH_REQUEST_DENIED,
|
|
||||||
"Can't find a known_hosts file");
|
|
||||||
|
|
||||||
return SSH_SERVER_FILE_NOT_FOUND;
|
int ret = SSH_SERVER_NOT_KNOWN;
|
||||||
|
|
||||||
|
if (session->opts.knownhosts == NULL) {
|
||||||
|
if (ssh_options_apply(session) < 0) {
|
||||||
|
ssh_set_error(session, SSH_REQUEST_DENIED,
|
||||||
|
"Can't find a known_hosts file");
|
||||||
|
|
||||||
|
return SSH_SERVER_FILE_NOT_FOUND;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (session->opts.host == NULL) {
|
if (session->opts.host == NULL) {
|
||||||
ssh_set_error(session, SSH_FATAL,
|
ssh_set_error(session, SSH_FATAL,
|
||||||
"Can't verify host in known hosts if the hostname isn't known");
|
"Can't verify host in known hosts if the hostname isn't known");
|
||||||
|
|
||||||
return SSH_SERVER_ERROR;
|
return SSH_SERVER_ERROR;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (session->current_crypto == NULL){
|
if (session->current_crypto == NULL){
|
||||||
ssh_set_error(session, SSH_FATAL,
|
ssh_set_error(session, SSH_FATAL,
|
||||||
"ssh_is_host_known called without cryptographic context");
|
"ssh_is_host_known called without cryptographic context");
|
||||||
|
|
||||||
|
return SSH_SERVER_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
host = ssh_lowercase(session->opts.host);
|
||||||
|
hostport = ssh_hostport(host, session->opts.port > 0 ? session->opts.port : 22);
|
||||||
|
if (host == NULL || hostport == NULL) {
|
||||||
|
ssh_set_error_oom(session);
|
||||||
|
SAFE_FREE(host);
|
||||||
|
SAFE_FREE(hostport);
|
||||||
|
|
||||||
|
return SSH_SERVER_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Set the list of known hosts files */
|
||||||
|
i = 0;
|
||||||
|
if (session->opts.global_knownhosts != NULL){
|
||||||
|
files[i++] = session->opts.global_knownhosts;
|
||||||
|
}
|
||||||
|
files[i++] = session->opts.knownhosts;
|
||||||
|
files[i] = NULL;
|
||||||
|
i = 0;
|
||||||
|
|
||||||
|
do {
|
||||||
|
tokens = ssh_get_knownhost_line(&file,
|
||||||
|
files[i],
|
||||||
|
&type);
|
||||||
|
|
||||||
|
/* End of file, return the current state or use next file */
|
||||||
|
if (tokens == NULL) {
|
||||||
|
++i;
|
||||||
|
if(files[i] == NULL)
|
||||||
|
break;
|
||||||
|
else
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
match = match_hashed_host(host, tokens->tokens[0]);
|
||||||
|
if (match == 0){
|
||||||
|
match = match_hostname(hostport, tokens->tokens[0],
|
||||||
|
strlen(tokens->tokens[0]));
|
||||||
|
}
|
||||||
|
if (match == 0) {
|
||||||
|
match = match_hostname(host, tokens->tokens[0],
|
||||||
|
strlen(tokens->tokens[0]));
|
||||||
|
}
|
||||||
|
if (match == 0) {
|
||||||
|
match = match_hashed_host(hostport, tokens->tokens[0]);
|
||||||
|
}
|
||||||
|
if (match) {
|
||||||
|
ssh_key pubkey = ssh_dh_get_current_server_publickey(session);
|
||||||
|
const char *pubkey_type = ssh_key_type_to_char(ssh_key_type(pubkey));
|
||||||
|
|
||||||
|
/* We got a match. Now check the key type */
|
||||||
|
if (strcmp(pubkey_type, type) != 0) {
|
||||||
|
SSH_LOG(SSH_LOG_PACKET,
|
||||||
|
"ssh_is_server_known: server type [%s] doesn't match the "
|
||||||
|
"type [%s] in known_hosts file",
|
||||||
|
pubkey_type,
|
||||||
|
type);
|
||||||
|
/* Different type. We don't override the known_changed error which is
|
||||||
|
* more important */
|
||||||
|
if (ret != SSH_SERVER_KNOWN_CHANGED)
|
||||||
|
ret = SSH_SERVER_FOUND_OTHER;
|
||||||
|
ssh_tokens_free(tokens);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
/* so we know the key type is good. We may get a good key or a bad key. */
|
||||||
|
match = check_public_key(session, tokens->tokens);
|
||||||
|
ssh_tokens_free(tokens);
|
||||||
|
|
||||||
|
if (match < 0) {
|
||||||
|
ret = SSH_SERVER_ERROR;
|
||||||
|
break;
|
||||||
|
} else if (match == 1) {
|
||||||
|
ret = SSH_SERVER_KNOWN_OK;
|
||||||
|
break;
|
||||||
|
} else if(match == 0) {
|
||||||
|
/* We override the status with the wrong key state */
|
||||||
|
ret = SSH_SERVER_KNOWN_CHANGED;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ssh_tokens_free(tokens);
|
||||||
|
}
|
||||||
|
} while (1);
|
||||||
|
|
||||||
|
if ((ret == SSH_SERVER_NOT_KNOWN) &&
|
||||||
|
(session->opts.StrictHostKeyChecking == 0)) {
|
||||||
|
ssh_write_knownhost(session);
|
||||||
|
ret = SSH_SERVER_KNOWN_OK;
|
||||||
|
}
|
||||||
|
|
||||||
return SSH_SERVER_ERROR;
|
|
||||||
}
|
|
||||||
host = ssh_lowercase(session->opts.host);
|
|
||||||
hostport = ssh_hostport(host, session->opts.port > 0 ? session->opts.port : 22);
|
|
||||||
if (host == NULL || hostport == NULL) {
|
|
||||||
ssh_set_error_oom(session);
|
|
||||||
SAFE_FREE(host);
|
SAFE_FREE(host);
|
||||||
SAFE_FREE(hostport);
|
SAFE_FREE(hostport);
|
||||||
|
if (file != NULL) {
|
||||||
return SSH_SERVER_ERROR;
|
fclose(file);
|
||||||
}
|
|
||||||
|
|
||||||
/* set the list of known hosts */
|
|
||||||
i = 0;
|
|
||||||
if (session->opts.global_knownhosts != NULL){
|
|
||||||
files[i++]=session->opts.global_knownhosts;
|
|
||||||
}
|
|
||||||
files[i++] = session->opts.knownhosts;
|
|
||||||
files[i] = NULL;
|
|
||||||
i = 0;
|
|
||||||
|
|
||||||
do {
|
|
||||||
tokens = ssh_get_knownhost_line(&file,
|
|
||||||
files[i],
|
|
||||||
&type);
|
|
||||||
|
|
||||||
/* End of file, return the current state or use next file */
|
|
||||||
if (tokens == NULL) {
|
|
||||||
++i;
|
|
||||||
if(files[i] == NULL)
|
|
||||||
break;
|
|
||||||
else
|
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
match = match_hashed_host(host, tokens[0]);
|
|
||||||
if (match == 0){
|
|
||||||
match = match_hostname(hostport, tokens[0], strlen(tokens[0]));
|
|
||||||
}
|
|
||||||
if (match == 0) {
|
|
||||||
match = match_hostname(host, tokens[0], strlen(tokens[0]));
|
|
||||||
}
|
|
||||||
if (match == 0) {
|
|
||||||
match = match_hashed_host(hostport, tokens[0]);
|
|
||||||
}
|
|
||||||
if (match) {
|
|
||||||
ssh_key pubkey = ssh_dh_get_current_server_publickey(session);
|
|
||||||
const char *pubkey_type = ssh_key_type_to_char(ssh_key_type(pubkey));
|
|
||||||
|
|
||||||
/* We got a match. Now check the key type */
|
/* Return the current state at end of file */
|
||||||
if (strcmp(pubkey_type, type) != 0) {
|
return ret;
|
||||||
SSH_LOG(SSH_LOG_PACKET,
|
|
||||||
"ssh_is_server_known: server type [%s] doesn't match the "
|
|
||||||
"type [%s] in known_hosts file",
|
|
||||||
pubkey_type,
|
|
||||||
type);
|
|
||||||
/* Different type. We don't override the known_changed error which is
|
|
||||||
* more important */
|
|
||||||
if (ret != SSH_SERVER_KNOWN_CHANGED)
|
|
||||||
ret = SSH_SERVER_FOUND_OTHER;
|
|
||||||
tokens_free(tokens);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
/* so we know the key type is good. We may get a good key or a bad key. */
|
|
||||||
match = check_public_key(session, tokens);
|
|
||||||
tokens_free(tokens);
|
|
||||||
|
|
||||||
if (match < 0) {
|
|
||||||
ret = SSH_SERVER_ERROR;
|
|
||||||
break;
|
|
||||||
} else if (match == 1) {
|
|
||||||
ret = SSH_SERVER_KNOWN_OK;
|
|
||||||
break;
|
|
||||||
} else if(match == 0) {
|
|
||||||
/* We override the status with the wrong key state */
|
|
||||||
ret = SSH_SERVER_KNOWN_CHANGED;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
tokens_free(tokens);
|
|
||||||
}
|
|
||||||
} while (1);
|
|
||||||
|
|
||||||
if ((ret == SSH_SERVER_NOT_KNOWN) &&
|
|
||||||
(session->opts.StrictHostKeyChecking == 0)) {
|
|
||||||
ssh_write_knownhost(session);
|
|
||||||
ret = SSH_SERVER_KNOWN_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
SAFE_FREE(host);
|
|
||||||
SAFE_FREE(hostport);
|
|
||||||
if (file != NULL) {
|
|
||||||
fclose(file);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Return the current state at end of file */
|
|
||||||
return ret;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Reference in New Issue
Block a user