Move SCP synch calls from sesman tools to library
The sesman tools have some private functions to make syncronous calls to sesman over SCP. This commit moves these calls to a new module scp_sync in libipm so that they can be utilised by other parts of xrdp (i.e. chansrv)
This commit is contained in:
parent
dd1dc7c630
commit
4d80cf6d77
@ -19,6 +19,8 @@ libipm_la_SOURCES = \
|
|||||||
ercp.c \
|
ercp.c \
|
||||||
scp.h \
|
scp.h \
|
||||||
scp.c \
|
scp.c \
|
||||||
|
scp_sync.h \
|
||||||
|
scp_sync.c \
|
||||||
scp_application_types.h \
|
scp_application_types.h \
|
||||||
scp_application_types.c
|
scp_application_types.c
|
||||||
|
|
||||||
|
168
libipm/scp_sync.c
Normal file
168
libipm/scp_sync.c
Normal file
@ -0,0 +1,168 @@
|
|||||||
|
/**
|
||||||
|
* xrdp: A Remote Desktop Protocol server.
|
||||||
|
*
|
||||||
|
* Copyright (C) Jay Sorg 2004-2022
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @file scp_sync.c
|
||||||
|
* @brief scp definitions (synchronous calls)
|
||||||
|
* @author Matt Burt
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#if defined(HAVE_CONFIG_H)
|
||||||
|
#include "config_ac.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
//nclude "tools_common.h"
|
||||||
|
//nclude "trans.h"
|
||||||
|
#include "os_calls.h"
|
||||||
|
#include "log.h"
|
||||||
|
#include "scp.h"
|
||||||
|
#include "scp_sync.h"
|
||||||
|
|
||||||
|
/*****************************************************************************/
|
||||||
|
int
|
||||||
|
scp_sync_wait_specific(struct trans *t, enum scp_msg_code wait_msgno)
|
||||||
|
{
|
||||||
|
|
||||||
|
int rv = 0;
|
||||||
|
int available = 0;
|
||||||
|
|
||||||
|
while (rv == 0 && !available)
|
||||||
|
{
|
||||||
|
if ((rv = scp_msg_in_wait_available(t)) != 0)
|
||||||
|
{
|
||||||
|
LOG(LOG_LEVEL_ERROR, "Error waiting on sesman transport");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
enum scp_msg_code reply_msgno = scp_msg_in_get_msgno(t);
|
||||||
|
|
||||||
|
available = 1;
|
||||||
|
if (reply_msgno != wait_msgno)
|
||||||
|
{
|
||||||
|
char buff[64];
|
||||||
|
scp_msgno_to_str(reply_msgno, buff, sizeof(buff));
|
||||||
|
|
||||||
|
LOG(LOG_LEVEL_WARNING,
|
||||||
|
"Ignoring unexpected message %s", buff);
|
||||||
|
scp_msg_in_reset(t);
|
||||||
|
available = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return rv;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*****************************************************************************/
|
||||||
|
int
|
||||||
|
scp_sync_uds_login_request(struct trans *t)
|
||||||
|
{
|
||||||
|
int rv = scp_send_uds_login_request(t);
|
||||||
|
if (rv == 0)
|
||||||
|
{
|
||||||
|
if ((rv = scp_sync_wait_specific(t, E_SCP_LOGIN_RESPONSE)) == 0)
|
||||||
|
{
|
||||||
|
enum scp_login_status login_result;
|
||||||
|
int server_closed;
|
||||||
|
rv = scp_get_login_response(t, &login_result, &server_closed, NULL);
|
||||||
|
if (rv == 0 && login_result != E_SCP_LOGIN_OK)
|
||||||
|
{
|
||||||
|
char msg[256];
|
||||||
|
scp_login_status_to_str(login_result, msg, sizeof(msg));
|
||||||
|
g_printf("Login failed; %s\n", msg);
|
||||||
|
rv = 1;
|
||||||
|
if (!server_closed)
|
||||||
|
{
|
||||||
|
(void)scp_send_close_connection_request(t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
scp_msg_in_reset(t); // Done with this message
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
return rv;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*****************************************************************************/
|
||||||
|
struct list *
|
||||||
|
scp_sync_list_sessions_request(struct trans *t)
|
||||||
|
{
|
||||||
|
struct list *sessions = list_create();
|
||||||
|
if (sessions == NULL)
|
||||||
|
{
|
||||||
|
LOG(LOG_LEVEL_ERROR, "Out of memory for sessions list");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
int end_of_list = 0;
|
||||||
|
|
||||||
|
enum scp_list_sessions_status status;
|
||||||
|
struct scp_session_info *p;
|
||||||
|
|
||||||
|
int rv = scp_send_list_sessions_request(t);
|
||||||
|
|
||||||
|
sessions->auto_free = 1;
|
||||||
|
|
||||||
|
while (rv == 0 && !end_of_list)
|
||||||
|
{
|
||||||
|
rv = scp_sync_wait_specific(t, E_SCP_LIST_SESSIONS_RESPONSE);
|
||||||
|
if (rv != 0)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
rv = scp_get_list_sessions_response(t, &status, &p);
|
||||||
|
if (rv != 0)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (status)
|
||||||
|
{
|
||||||
|
case E_SCP_LS_SESSION_INFO:
|
||||||
|
if (!list_add_item(sessions, (tintptr)p))
|
||||||
|
{
|
||||||
|
g_free(p);
|
||||||
|
LOG(LOG_LEVEL_ERROR, "Out of memory for session item");
|
||||||
|
rv = 1;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case E_SCP_LS_END_OF_LIST:
|
||||||
|
end_of_list = 1;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
LOG(LOG_LEVEL_ERROR,
|
||||||
|
"Unexpected return code %d for session item", status);
|
||||||
|
rv = 1;
|
||||||
|
}
|
||||||
|
scp_msg_in_reset(t);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rv != 0)
|
||||||
|
{
|
||||||
|
list_delete(sessions);
|
||||||
|
sessions = NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return sessions;
|
||||||
|
}
|
89
libipm/scp_sync.h
Normal file
89
libipm/scp_sync.h
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
/**
|
||||||
|
* xrdp: A Remote Desktop Protocol server.
|
||||||
|
*
|
||||||
|
* Copyright (C) Jay Sorg 2004-2022, all xrdp contributors
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @file libipm/scp_sync.h
|
||||||
|
* @brief scp declarations (synchronous calls)
|
||||||
|
* @author Simone Fedele/ Matt Burt
|
||||||
|
*
|
||||||
|
* This module places a synchronous wrapper on top of some of the
|
||||||
|
* calls in scp.h. It is intended to be used for simple SCP applications
|
||||||
|
* which do not need to handle SCP messages along with other messages
|
||||||
|
* using the xrdp transport mechanism.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef SCP_SYNC_H
|
||||||
|
#define SCP_SYNC_H
|
||||||
|
|
||||||
|
#include <scp.h>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Waits on a single transport for a specific SCP message to be available for
|
||||||
|
* parsing
|
||||||
|
*
|
||||||
|
* @param trans libipm transport
|
||||||
|
* @param wait_msgno Message number to wait for
|
||||||
|
* @return != 0 for error
|
||||||
|
*
|
||||||
|
* This is a convenience function, used to implement synchronous calls.
|
||||||
|
*
|
||||||
|
* While the call is active, data-in callbacks for the transport are
|
||||||
|
* disabled.
|
||||||
|
*
|
||||||
|
* Unexpected messages are ignored and logged.
|
||||||
|
*
|
||||||
|
* Only use this call if you have nothing to do until a message
|
||||||
|
* arrives on the transport.
|
||||||
|
* - If you have other transports to service, use
|
||||||
|
* scp_msg_in_check_available()
|
||||||
|
* - If you can process any incoming message, use
|
||||||
|
* scp_msg_in_wait_available()
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
scp_sync_wait_specific(struct trans *t, enum scp_msg_code wait_msgno);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send UDS login request to sesman and wait for answer
|
||||||
|
*
|
||||||
|
* @param t SCP transport
|
||||||
|
* @return 0 for successful login
|
||||||
|
*
|
||||||
|
* If non-zero is returned, the scp_connection has been closed (if
|
||||||
|
* appropriate) and simply remains to be deleted.
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
scp_sync_uds_login_request(struct trans *t);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send list sessions request to sesman and wait for answer(s)
|
||||||
|
*
|
||||||
|
* @param t SCP transport
|
||||||
|
* @return list of sessions
|
||||||
|
*
|
||||||
|
* If NULL is returned, the scp_connection has been closed (if
|
||||||
|
* appropriate) and simply remains to be deleted.
|
||||||
|
*
|
||||||
|
* If NULL is not returned, the caller must call list_delete() to
|
||||||
|
* free it.
|
||||||
|
*/
|
||||||
|
struct list *
|
||||||
|
scp_sync_list_sessions_request(struct trans *t);
|
||||||
|
|
||||||
|
#endif /* SCP_SYNC_H */
|
@ -17,14 +17,10 @@ noinst_PROGRAMS = \
|
|||||||
xrdp-xcon
|
xrdp-xcon
|
||||||
|
|
||||||
xrdp_sesrun_SOURCES = \
|
xrdp_sesrun_SOURCES = \
|
||||||
sesrun.c \
|
sesrun.c
|
||||||
tools_common.h \
|
|
||||||
tools_common.c
|
|
||||||
|
|
||||||
xrdp_sesadmin_SOURCES = \
|
xrdp_sesadmin_SOURCES = \
|
||||||
sesadmin.c \
|
sesadmin.c
|
||||||
tools_common.h \
|
|
||||||
tools_common.c
|
|
||||||
|
|
||||||
xrdp_dis_SOURCES = \
|
xrdp_dis_SOURCES = \
|
||||||
dis.c
|
dis.c
|
||||||
|
@ -26,11 +26,14 @@
|
|||||||
#include "log.h"
|
#include "log.h"
|
||||||
#include "os_calls.h"
|
#include "os_calls.h"
|
||||||
#include "string_calls.h"
|
#include "string_calls.h"
|
||||||
#include "tools_common.h"
|
|
||||||
|
#include "scp.h"
|
||||||
|
#include "scp_sync.h"
|
||||||
|
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
|
||||||
|
|
||||||
char cmnd[257];
|
char cmnd[257];
|
||||||
char port[257];
|
char port[257];
|
||||||
|
|
||||||
@ -89,25 +92,8 @@ int main(int argc, char **argv)
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
enum scp_login_status login_result;
|
|
||||||
|
|
||||||
/* Log in as the current user */
|
/* Log in as the current user */
|
||||||
if ((rv = scp_send_uds_login_request(t)) == 0 &&
|
rv = scp_sync_uds_login_request(t);
|
||||||
(rv = wait_for_sesman_reply(t, E_SCP_LOGIN_RESPONSE)) == 0)
|
|
||||||
{
|
|
||||||
rv = scp_get_login_response(t, &login_result, NULL, NULL);
|
|
||||||
if (rv == 0)
|
|
||||||
{
|
|
||||||
if (login_result != E_SCP_LOGIN_OK)
|
|
||||||
{
|
|
||||||
char msg[256];
|
|
||||||
scp_login_status_to_str(login_result, msg, sizeof(msg));
|
|
||||||
g_printf("Login failed; %s\n", msg);
|
|
||||||
rv = 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
scp_msg_in_reset(t); // Done with this message
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (rv == 0)
|
if (rv == 0)
|
||||||
@ -169,48 +155,9 @@ print_session(const struct scp_session_info *s)
|
|||||||
static int
|
static int
|
||||||
cmndList(struct trans *t)
|
cmndList(struct trans *t)
|
||||||
{
|
{
|
||||||
struct list *sessions = list_create();
|
int rv = 1;
|
||||||
int end_of_list = 0;
|
struct list *sessions = scp_sync_list_sessions_request(t);
|
||||||
|
if (sessions != NULL)
|
||||||
enum scp_list_sessions_status status;
|
|
||||||
struct scp_session_info *p;
|
|
||||||
|
|
||||||
int rv = scp_send_list_sessions_request(t);
|
|
||||||
|
|
||||||
sessions->auto_free = 1;
|
|
||||||
|
|
||||||
while (rv == 0 && !end_of_list)
|
|
||||||
{
|
|
||||||
rv = wait_for_sesman_reply(t, E_SCP_LIST_SESSIONS_RESPONSE);
|
|
||||||
if (rv != 0)
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
rv = scp_get_list_sessions_response(t, &status, &p);
|
|
||||||
if (rv != 0)
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (status)
|
|
||||||
{
|
|
||||||
case E_SCP_LS_SESSION_INFO:
|
|
||||||
list_add_item(sessions, (tintptr)p);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case E_SCP_LS_END_OF_LIST:
|
|
||||||
end_of_list = 1;
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
printf("Unexpected return code %d\n", status);
|
|
||||||
rv = 1;
|
|
||||||
}
|
|
||||||
scp_msg_in_reset(t);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (rv == 0)
|
|
||||||
{
|
{
|
||||||
if (sessions->count == 0)
|
if (sessions->count == 0)
|
||||||
{
|
{
|
||||||
@ -224,9 +171,10 @@ cmndList(struct trans *t)
|
|||||||
print_session((struct scp_session_info *)sessions->items[i]);
|
print_session((struct scp_session_info *)sessions->items[i]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
(void)scp_send_close_connection_request(t);
|
||||||
|
list_delete(sessions);
|
||||||
}
|
}
|
||||||
|
|
||||||
list_delete(sessions);
|
|
||||||
return rv;
|
return rv;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -40,7 +40,8 @@
|
|||||||
#include "string_calls.h"
|
#include "string_calls.h"
|
||||||
#include "guid.h"
|
#include "guid.h"
|
||||||
|
|
||||||
#include "tools_common.h"
|
#include "scp.h"
|
||||||
|
#include "scp_sync.h"
|
||||||
|
|
||||||
// cppcheck doesn't always set this macro to something in double-quotes
|
// cppcheck doesn't always set this macro to something in double-quotes
|
||||||
#if defined(__cppcheck__)
|
#if defined(__cppcheck__)
|
||||||
@ -453,7 +454,7 @@ handle_login_response(struct trans *t, int *server_closed)
|
|||||||
{
|
{
|
||||||
enum scp_login_status login_result;
|
enum scp_login_status login_result;
|
||||||
|
|
||||||
int rv = wait_for_sesman_reply(t, E_SCP_LOGIN_RESPONSE);
|
int rv = scp_sync_wait_specific(t, E_SCP_LOGIN_RESPONSE);
|
||||||
if (rv != 0)
|
if (rv != 0)
|
||||||
{
|
{
|
||||||
*server_closed = 1;
|
*server_closed = 1;
|
||||||
@ -511,7 +512,7 @@ handle_create_session_response(struct trans *t)
|
|||||||
int display;
|
int display;
|
||||||
struct guid guid;
|
struct guid guid;
|
||||||
|
|
||||||
int rv = wait_for_sesman_reply(t, E_SCP_CREATE_SESSION_RESPONSE);
|
int rv = scp_sync_wait_specific(t, E_SCP_CREATE_SESSION_RESPONSE);
|
||||||
if (rv == 0)
|
if (rv == 0)
|
||||||
{
|
{
|
||||||
rv = scp_get_create_session_response(t, &status,
|
rv = scp_get_create_session_response(t, &status,
|
||||||
|
@ -1,71 +0,0 @@
|
|||||||
/**
|
|
||||||
* xrdp: A Remote Desktop Protocol server.
|
|
||||||
*
|
|
||||||
* Copyright (C) Jay Sorg 2004-2022
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @file tools_common.c
|
|
||||||
* @brief Common definitions for the tools utilities
|
|
||||||
* @author Matt Burt
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
#if defined(HAVE_CONFIG_H)
|
|
||||||
#include "config_ac.h"
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#include "tools_common.h"
|
|
||||||
#include "trans.h"
|
|
||||||
#include "os_calls.h"
|
|
||||||
#include "scp.h"
|
|
||||||
|
|
||||||
|
|
||||||
/*****************************************************************************/
|
|
||||||
int
|
|
||||||
wait_for_sesman_reply(struct trans *t, enum scp_msg_code wait_msgno)
|
|
||||||
{
|
|
||||||
|
|
||||||
int rv = 0;
|
|
||||||
int available = 0;
|
|
||||||
|
|
||||||
while (rv == 0 && !available)
|
|
||||||
{
|
|
||||||
if ((rv = scp_msg_in_wait_available(t)) != 0)
|
|
||||||
{
|
|
||||||
LOG(LOG_LEVEL_ERROR, "Error waiting on sesman transport");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
enum scp_msg_code reply_msgno = scp_msg_in_get_msgno(t);
|
|
||||||
|
|
||||||
available = 1;
|
|
||||||
if (reply_msgno != wait_msgno)
|
|
||||||
{
|
|
||||||
char buff[64];
|
|
||||||
scp_msgno_to_str(reply_msgno, buff, sizeof(buff));
|
|
||||||
|
|
||||||
LOG(LOG_LEVEL_WARNING,
|
|
||||||
"Ignoring unexpected message %s", buff);
|
|
||||||
scp_msg_in_reset(t);
|
|
||||||
available = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return rv;
|
|
||||||
}
|
|
@ -1,48 +0,0 @@
|
|||||||
/**
|
|
||||||
* xrdp: A Remote Desktop Protocol server.
|
|
||||||
*
|
|
||||||
* Copyright (C) Jay Sorg 2004-2022
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @file tools_common.c
|
|
||||||
* @brief Common definitions for the tools utilities
|
|
||||||
* @author Matt Burt
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
#if !defined(TOOLS_COMMON_H)
|
|
||||||
#define TOOLS_COMMON_H
|
|
||||||
|
|
||||||
#include "scp.h"
|
|
||||||
|
|
||||||
struct trans;
|
|
||||||
|
|
||||||
/**************************************************************************//**
|
|
||||||
* Waits for an expected reply from sesman
|
|
||||||
*
|
|
||||||
* Any other incoming messages are ignored.
|
|
||||||
*
|
|
||||||
* Following this call, the message can be parsed in the usual way.
|
|
||||||
*
|
|
||||||
* @param t SCP transport
|
|
||||||
* @param wait_msgno Code of the message we're waiting for.
|
|
||||||
* @result 0 for success
|
|
||||||
*/
|
|
||||||
int
|
|
||||||
wait_for_sesman_reply(struct trans *t, enum scp_msg_code wait_msgno);
|
|
||||||
|
|
||||||
#endif /* TOOLS_COMMON_H */
|
|
Loading…
Reference in New Issue
Block a user