GUACAMOLE-5: Merge read-only server side parameter.
This commit is contained in:
commit
4734d15fb6
@ -91,6 +91,7 @@ const char* GUAC_RDP_CLIENT_ARGS[] = {
|
|||||||
"create-recording-path",
|
"create-recording-path",
|
||||||
"resize-method",
|
"resize-method",
|
||||||
"enable-audio-input",
|
"enable-audio-input",
|
||||||
|
"read-only",
|
||||||
|
|
||||||
NULL
|
NULL
|
||||||
};
|
};
|
||||||
@ -385,6 +386,12 @@ enum RDP_ARGS_IDX {
|
|||||||
*/
|
*/
|
||||||
IDX_ENABLE_AUDIO_INPUT,
|
IDX_ENABLE_AUDIO_INPUT,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* "true" if this connection should be read-only (user input should be
|
||||||
|
* dropped), "false" or blank otherwise.
|
||||||
|
*/
|
||||||
|
IDX_READ_ONLY,
|
||||||
|
|
||||||
RDP_ARGS_COUNT
|
RDP_ARGS_COUNT
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -528,6 +535,11 @@ guac_rdp_settings* guac_rdp_parse_args(guac_user* user,
|
|||||||
guac_user_parse_args_string(user, GUAC_RDP_CLIENT_ARGS, argv,
|
guac_user_parse_args_string(user, GUAC_RDP_CLIENT_ARGS, argv,
|
||||||
IDX_PASSWORD, NULL);
|
IDX_PASSWORD, NULL);
|
||||||
|
|
||||||
|
/* Read-only mode */
|
||||||
|
settings->read_only =
|
||||||
|
guac_user_parse_args_boolean(user, GUAC_RDP_CLIENT_ARGS, argv,
|
||||||
|
IDX_READ_ONLY, 0);
|
||||||
|
|
||||||
/* Client name */
|
/* Client name */
|
||||||
settings->client_name =
|
settings->client_name =
|
||||||
guac_user_parse_args_string(user, GUAC_RDP_CLIENT_ARGS, argv,
|
guac_user_parse_args_string(user, GUAC_RDP_CLIENT_ARGS, argv,
|
||||||
|
@ -141,6 +141,11 @@ typedef struct guac_rdp_settings {
|
|||||||
*/
|
*/
|
||||||
char* password;
|
char* password;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether this connection is read-only, and user input should be dropped.
|
||||||
|
*/
|
||||||
|
int read_only;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The color depth of the display to request, in bits.
|
* The color depth of the display to request, in bits.
|
||||||
*/
|
*/
|
||||||
|
@ -44,12 +44,9 @@ int guac_rdp_user_join_handler(guac_user* user, int argc, char** argv) {
|
|||||||
|
|
||||||
guac_rdp_client* rdp_client = (guac_rdp_client*) user->client->data;
|
guac_rdp_client* rdp_client = (guac_rdp_client*) user->client->data;
|
||||||
|
|
||||||
/* Connect via RDP if owner */
|
/* Parse provided arguments */
|
||||||
if (user->owner) {
|
guac_rdp_settings* settings = guac_rdp_parse_args(user,
|
||||||
|
argc, (const char**) argv);
|
||||||
/* Parse arguments into client */
|
|
||||||
guac_rdp_settings* settings = rdp_client->settings =
|
|
||||||
guac_rdp_parse_args(user, argc, (const char**) argv);
|
|
||||||
|
|
||||||
/* Fail if settings cannot be parsed */
|
/* Fail if settings cannot be parsed */
|
||||||
if (settings == NULL) {
|
if (settings == NULL) {
|
||||||
@ -58,6 +55,15 @@ int guac_rdp_user_join_handler(guac_user* user, int argc, char** argv) {
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Store settings at user level */
|
||||||
|
user->data = settings;
|
||||||
|
|
||||||
|
/* Connect via RDP if owner */
|
||||||
|
if (user->owner) {
|
||||||
|
|
||||||
|
/* Store owner's settings at client level */
|
||||||
|
rdp_client->settings = settings;
|
||||||
|
|
||||||
/* Start client thread */
|
/* Start client thread */
|
||||||
if (pthread_create(&rdp_client->client_thread, NULL,
|
if (pthread_create(&rdp_client->client_thread, NULL,
|
||||||
guac_rdp_client_thread, user->client)) {
|
guac_rdp_client_thread, user->client)) {
|
||||||
@ -88,13 +94,25 @@ int guac_rdp_user_join_handler(guac_user* user, int argc, char** argv) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
user->file_handler = guac_rdp_user_file_handler;
|
/* Only handle events if not read-only */
|
||||||
|
if (!settings->read_only) {
|
||||||
|
|
||||||
|
/* General mouse/keyboard/clipboard events */
|
||||||
user->mouse_handler = guac_rdp_user_mouse_handler;
|
user->mouse_handler = guac_rdp_user_mouse_handler;
|
||||||
user->key_handler = guac_rdp_user_key_handler;
|
user->key_handler = guac_rdp_user_key_handler;
|
||||||
user->size_handler = guac_rdp_user_size_handler;
|
|
||||||
user->pipe_handler = guac_rdp_svc_pipe_handler;
|
|
||||||
user->clipboard_handler = guac_rdp_clipboard_handler;
|
user->clipboard_handler = guac_rdp_clipboard_handler;
|
||||||
|
|
||||||
|
/* Display size change events */
|
||||||
|
user->size_handler = guac_rdp_user_size_handler;
|
||||||
|
|
||||||
|
/* Set generic (non-filesystem) file upload handler */
|
||||||
|
user->file_handler = guac_rdp_user_file_handler;
|
||||||
|
|
||||||
|
/* Inbound arbitrary named pipes */
|
||||||
|
user->pipe_handler = guac_rdp_svc_pipe_handler;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -131,8 +149,15 @@ int guac_rdp_user_leave_handler(guac_user* user) {
|
|||||||
|
|
||||||
guac_rdp_client* rdp_client = (guac_rdp_client*) user->client->data;
|
guac_rdp_client* rdp_client = (guac_rdp_client*) user->client->data;
|
||||||
|
|
||||||
|
/* Update shared cursor state */
|
||||||
guac_common_cursor_remove_user(rdp_client->display->cursor, user);
|
guac_common_cursor_remove_user(rdp_client->display->cursor, user);
|
||||||
|
|
||||||
|
/* Free settings if not owner (owner settings will be freed with client) */
|
||||||
|
if (!user->owner) {
|
||||||
|
guac_rdp_settings* settings = (guac_rdp_settings*) user->data;
|
||||||
|
guac_rdp_settings_free(settings);
|
||||||
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -50,6 +50,7 @@ const char* GUAC_SSH_CLIENT_ARGS[] = {
|
|||||||
"recording-path",
|
"recording-path",
|
||||||
"recording-name",
|
"recording-name",
|
||||||
"create-recording-path",
|
"create-recording-path",
|
||||||
|
"read-only",
|
||||||
NULL
|
NULL
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -158,6 +159,12 @@ enum SSH_ARGS_IDX {
|
|||||||
*/
|
*/
|
||||||
IDX_CREATE_RECORDING_PATH,
|
IDX_CREATE_RECORDING_PATH,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* "true" if this connection should be read-only (user input should be
|
||||||
|
* dropped), "false" or blank otherwise.
|
||||||
|
*/
|
||||||
|
IDX_READ_ONLY,
|
||||||
|
|
||||||
SSH_ARGS_COUNT
|
SSH_ARGS_COUNT
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -232,6 +239,11 @@ guac_ssh_settings* guac_ssh_parse_args(guac_user* user,
|
|||||||
guac_user_parse_args_string(user, GUAC_SSH_CLIENT_ARGS, argv,
|
guac_user_parse_args_string(user, GUAC_SSH_CLIENT_ARGS, argv,
|
||||||
IDX_PORT, GUAC_SSH_DEFAULT_PORT);
|
IDX_PORT, GUAC_SSH_DEFAULT_PORT);
|
||||||
|
|
||||||
|
/* Read-only mode */
|
||||||
|
settings->read_only =
|
||||||
|
guac_user_parse_args_boolean(user, GUAC_SSH_CLIENT_ARGS, argv,
|
||||||
|
IDX_READ_ONLY, false);
|
||||||
|
|
||||||
/* Read command, if any */
|
/* Read command, if any */
|
||||||
settings->command =
|
settings->command =
|
||||||
guac_user_parse_args_string(user, GUAC_SSH_CLIENT_ARGS, argv,
|
guac_user_parse_args_string(user, GUAC_SSH_CLIENT_ARGS, argv,
|
||||||
|
@ -94,6 +94,11 @@ typedef struct guac_ssh_settings {
|
|||||||
*/
|
*/
|
||||||
char* key_passphrase;
|
char* key_passphrase;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether this connection is read-only, and user input should be dropped.
|
||||||
|
*/
|
||||||
|
bool read_only;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The command to run instead of the default shell. If a normal shell
|
* The command to run instead of the default shell. If a normal shell
|
||||||
* session is desired, this will be NULL.
|
* session is desired, this will be NULL.
|
||||||
|
@ -39,12 +39,9 @@ int guac_ssh_user_join_handler(guac_user* user, int argc, char** argv) {
|
|||||||
guac_client* client = user->client;
|
guac_client* client = user->client;
|
||||||
guac_ssh_client* ssh_client = (guac_ssh_client*) client->data;
|
guac_ssh_client* ssh_client = (guac_ssh_client*) client->data;
|
||||||
|
|
||||||
/* Connect via SSH if owner */
|
/* Parse provided arguments */
|
||||||
if (user->owner) {
|
guac_ssh_settings* settings = guac_ssh_parse_args(user,
|
||||||
|
argc, (const char**) argv);
|
||||||
/* Parse arguments into client */
|
|
||||||
guac_ssh_settings* settings = ssh_client->settings =
|
|
||||||
guac_ssh_parse_args(user, argc, (const char**) argv);
|
|
||||||
|
|
||||||
/* Fail if settings cannot be parsed */
|
/* Fail if settings cannot be parsed */
|
||||||
if (settings == NULL) {
|
if (settings == NULL) {
|
||||||
@ -53,6 +50,15 @@ int guac_ssh_user_join_handler(guac_user* user, int argc, char** argv) {
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Store settings at user level */
|
||||||
|
user->data = settings;
|
||||||
|
|
||||||
|
/* Connect via SSH if owner */
|
||||||
|
if (user->owner) {
|
||||||
|
|
||||||
|
/* Store owner's settings at client level */
|
||||||
|
ssh_client->settings = settings;
|
||||||
|
|
||||||
/* Start client thread */
|
/* Start client thread */
|
||||||
if (pthread_create(&(ssh_client->client_thread), NULL,
|
if (pthread_create(&(ssh_client->client_thread), NULL,
|
||||||
ssh_client_thread, (void*) client)) {
|
ssh_client_thread, (void*) client)) {
|
||||||
@ -69,15 +75,22 @@ int guac_ssh_user_join_handler(guac_user* user, int argc, char** argv) {
|
|||||||
guac_socket_flush(user->socket);
|
guac_socket_flush(user->socket);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Set per-user event handlers */
|
/* Only handle events if not read-only */
|
||||||
|
if (!settings->read_only) {
|
||||||
|
|
||||||
|
/* General mouse/keyboard/clipboard events */
|
||||||
user->key_handler = guac_ssh_user_key_handler;
|
user->key_handler = guac_ssh_user_key_handler;
|
||||||
user->mouse_handler = guac_ssh_user_mouse_handler;
|
user->mouse_handler = guac_ssh_user_mouse_handler;
|
||||||
user->size_handler = guac_ssh_user_size_handler;
|
|
||||||
user->clipboard_handler = guac_ssh_clipboard_handler;
|
user->clipboard_handler = guac_ssh_clipboard_handler;
|
||||||
|
|
||||||
|
/* Display size change events */
|
||||||
|
user->size_handler = guac_ssh_user_size_handler;
|
||||||
|
|
||||||
/* Set generic (non-filesystem) file upload handler */
|
/* Set generic (non-filesystem) file upload handler */
|
||||||
user->file_handler = guac_sftp_file_handler;
|
user->file_handler = guac_sftp_file_handler;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -86,8 +99,15 @@ int guac_ssh_user_leave_handler(guac_user* user) {
|
|||||||
|
|
||||||
guac_ssh_client* ssh_client = (guac_ssh_client*) user->client->data;
|
guac_ssh_client* ssh_client = (guac_ssh_client*) user->client->data;
|
||||||
|
|
||||||
|
/* Update shared cursor state */
|
||||||
guac_common_cursor_remove_user(ssh_client->term->cursor, user);
|
guac_common_cursor_remove_user(ssh_client->term->cursor, user);
|
||||||
|
|
||||||
|
/* Free settings if not owner (owner settings will be freed with client) */
|
||||||
|
if (!user->owner) {
|
||||||
|
guac_ssh_settings* settings = (guac_ssh_settings*) user->data;
|
||||||
|
guac_ssh_settings_free(settings);
|
||||||
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -46,6 +46,7 @@ const char* GUAC_TELNET_CLIENT_ARGS[] = {
|
|||||||
"recording-path",
|
"recording-path",
|
||||||
"recording-name",
|
"recording-name",
|
||||||
"create-recording-path",
|
"create-recording-path",
|
||||||
|
"read-only",
|
||||||
NULL
|
NULL
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -138,6 +139,12 @@ enum TELNET_ARGS_IDX {
|
|||||||
*/
|
*/
|
||||||
IDX_CREATE_RECORDING_PATH,
|
IDX_CREATE_RECORDING_PATH,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* "true" if this connection should be read-only (user input should be
|
||||||
|
* dropped), "false" or blank otherwise.
|
||||||
|
*/
|
||||||
|
IDX_READ_ONLY,
|
||||||
|
|
||||||
TELNET_ARGS_COUNT
|
TELNET_ARGS_COUNT
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -217,6 +224,11 @@ guac_telnet_settings* guac_telnet_parse_args(guac_user* user,
|
|||||||
IDX_PASSWORD_REGEX, GUAC_TELNET_DEFAULT_PASSWORD_REGEX));
|
IDX_PASSWORD_REGEX, GUAC_TELNET_DEFAULT_PASSWORD_REGEX));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Read-only mode */
|
||||||
|
settings->read_only =
|
||||||
|
guac_user_parse_args_boolean(user, GUAC_TELNET_CLIENT_ARGS, argv,
|
||||||
|
IDX_READ_ONLY, false);
|
||||||
|
|
||||||
/* Read font name */
|
/* Read font name */
|
||||||
settings->font_name =
|
settings->font_name =
|
||||||
guac_user_parse_args_string(user, GUAC_TELNET_CLIENT_ARGS, argv,
|
guac_user_parse_args_string(user, GUAC_TELNET_CLIENT_ARGS, argv,
|
||||||
|
@ -112,6 +112,11 @@ typedef struct guac_telnet_settings {
|
|||||||
*/
|
*/
|
||||||
regex_t* password_regex;
|
regex_t* password_regex;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether this connection is read-only, and user input should be dropped.
|
||||||
|
*/
|
||||||
|
bool read_only;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The name of the font to use for display rendering.
|
* The name of the font to use for display rendering.
|
||||||
*/
|
*/
|
||||||
|
@ -38,12 +38,9 @@ int guac_telnet_user_join_handler(guac_user* user, int argc, char** argv) {
|
|||||||
guac_client* client = user->client;
|
guac_client* client = user->client;
|
||||||
guac_telnet_client* telnet_client = (guac_telnet_client*) client->data;
|
guac_telnet_client* telnet_client = (guac_telnet_client*) client->data;
|
||||||
|
|
||||||
/* Connect via telnet if owner */
|
/* Parse provided arguments */
|
||||||
if (user->owner) {
|
guac_telnet_settings* settings = guac_telnet_parse_args(user,
|
||||||
|
argc, (const char**) argv);
|
||||||
/* Parse arguments into client */
|
|
||||||
guac_telnet_settings* settings = telnet_client->settings =
|
|
||||||
guac_telnet_parse_args(user, argc, (const char**) argv);
|
|
||||||
|
|
||||||
/* Fail if settings cannot be parsed */
|
/* Fail if settings cannot be parsed */
|
||||||
if (settings == NULL) {
|
if (settings == NULL) {
|
||||||
@ -52,6 +49,15 @@ int guac_telnet_user_join_handler(guac_user* user, int argc, char** argv) {
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Store settings at user level */
|
||||||
|
user->data = settings;
|
||||||
|
|
||||||
|
/* Connect via telnet if owner */
|
||||||
|
if (user->owner) {
|
||||||
|
|
||||||
|
/* Store owner's settings at client level */
|
||||||
|
telnet_client->settings = settings;
|
||||||
|
|
||||||
/* Start client thread */
|
/* Start client thread */
|
||||||
if (pthread_create(&(telnet_client->client_thread), NULL,
|
if (pthread_create(&(telnet_client->client_thread), NULL,
|
||||||
guac_telnet_client_thread, (void*) client)) {
|
guac_telnet_client_thread, (void*) client)) {
|
||||||
@ -68,12 +74,19 @@ int guac_telnet_user_join_handler(guac_user* user, int argc, char** argv) {
|
|||||||
guac_socket_flush(user->socket);
|
guac_socket_flush(user->socket);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Set per-user event handlers */
|
/* Only handle events if not read-only */
|
||||||
|
if (!settings->read_only) {
|
||||||
|
|
||||||
|
/* General mouse/keyboard/clipboard events */
|
||||||
user->key_handler = guac_telnet_user_key_handler;
|
user->key_handler = guac_telnet_user_key_handler;
|
||||||
user->mouse_handler = guac_telnet_user_mouse_handler;
|
user->mouse_handler = guac_telnet_user_mouse_handler;
|
||||||
user->size_handler = guac_telnet_user_size_handler;
|
|
||||||
user->clipboard_handler = guac_telnet_clipboard_handler;
|
user->clipboard_handler = guac_telnet_clipboard_handler;
|
||||||
|
|
||||||
|
/* Display size change events */
|
||||||
|
user->size_handler = guac_telnet_user_size_handler;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -83,8 +96,15 @@ int guac_telnet_user_leave_handler(guac_user* user) {
|
|||||||
guac_telnet_client* telnet_client =
|
guac_telnet_client* telnet_client =
|
||||||
(guac_telnet_client*) user->client->data;
|
(guac_telnet_client*) user->client->data;
|
||||||
|
|
||||||
|
/* Update shared cursor state */
|
||||||
guac_common_cursor_remove_user(telnet_client->term->cursor, user);
|
guac_common_cursor_remove_user(telnet_client->term->cursor, user);
|
||||||
|
|
||||||
|
/* Free settings if not owner (owner settings will be freed with client) */
|
||||||
|
if (!user->owner) {
|
||||||
|
guac_telnet_settings* settings = (guac_telnet_settings*) user->data;
|
||||||
|
guac_telnet_settings_free(settings);
|
||||||
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -41,12 +41,9 @@ int guac_vnc_user_join_handler(guac_user* user, int argc, char** argv) {
|
|||||||
|
|
||||||
guac_vnc_client* vnc_client = (guac_vnc_client*) user->client->data;
|
guac_vnc_client* vnc_client = (guac_vnc_client*) user->client->data;
|
||||||
|
|
||||||
/* Connect via VNC if owner */
|
/* Parse provided arguments */
|
||||||
if (user->owner) {
|
guac_vnc_settings* settings = guac_vnc_parse_args(user,
|
||||||
|
argc, (const char**) argv);
|
||||||
/* Parse arguments into client */
|
|
||||||
guac_vnc_settings* settings = vnc_client->settings =
|
|
||||||
guac_vnc_parse_args(user, argc, (const char**) argv);
|
|
||||||
|
|
||||||
/* Fail if settings cannot be parsed */
|
/* Fail if settings cannot be parsed */
|
||||||
if (settings == NULL) {
|
if (settings == NULL) {
|
||||||
@ -55,6 +52,15 @@ int guac_vnc_user_join_handler(guac_user* user, int argc, char** argv) {
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Store settings at user level */
|
||||||
|
user->data = settings;
|
||||||
|
|
||||||
|
/* Connect via VNC if owner */
|
||||||
|
if (user->owner) {
|
||||||
|
|
||||||
|
/* Store owner's settings at client level */
|
||||||
|
vnc_client->settings = settings;
|
||||||
|
|
||||||
/* Start client thread */
|
/* Start client thread */
|
||||||
if (pthread_create(&vnc_client->client_thread, NULL, guac_vnc_client_thread, user->client)) {
|
if (pthread_create(&vnc_client->client_thread, NULL, guac_vnc_client_thread, user->client)) {
|
||||||
guac_user_log(user, GUAC_LOG_ERROR, "Unable to start VNC client thread.");
|
guac_user_log(user, GUAC_LOG_ERROR, "Unable to start VNC client thread.");
|
||||||
@ -79,7 +85,7 @@ int guac_vnc_user_join_handler(guac_user* user, int argc, char** argv) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Only handle events if not read-only */
|
/* Only handle events if not read-only */
|
||||||
if (vnc_client->settings->read_only == 0) {
|
if (!settings->read_only) {
|
||||||
|
|
||||||
/* General mouse/keyboard/clipboard events */
|
/* General mouse/keyboard/clipboard events */
|
||||||
user->mouse_handler = guac_vnc_user_mouse_handler;
|
user->mouse_handler = guac_vnc_user_mouse_handler;
|
||||||
@ -101,8 +107,15 @@ int guac_vnc_user_leave_handler(guac_user* user) {
|
|||||||
|
|
||||||
guac_vnc_client* vnc_client = (guac_vnc_client*) user->client->data;
|
guac_vnc_client* vnc_client = (guac_vnc_client*) user->client->data;
|
||||||
|
|
||||||
|
/* Update shared cursor state */
|
||||||
guac_common_cursor_remove_user(vnc_client->display->cursor, user);
|
guac_common_cursor_remove_user(vnc_client->display->cursor, user);
|
||||||
|
|
||||||
|
/* Free settings if not owner (owner settings will be freed with client) */
|
||||||
|
if (!user->owner) {
|
||||||
|
guac_vnc_settings* settings = (guac_vnc_settings*) user->data;
|
||||||
|
guac_vnc_settings_free(settings);
|
||||||
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user