Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
N
nxssh
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
2
Merge Requests
2
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
rx-etersoft
nxssh
Commits
68530f0f
Commit
68530f0f
authored
Feb 24, 2026
by
Konstantin Artyushkin
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Added nx-stub.c proxy.c and proxy.h files with new ssh API
parent
22d52905
Show whitespace changes
Inline
Side-by-side
Showing
3 changed files
with
2802 additions
and
0 deletions
+2802
-0
nx-stubs.c
nx-stubs.c
+132
-0
proxy.c
proxy.c
+2510
-0
proxy.h
proxy.h
+160
-0
No files found.
nx-stubs.c
0 → 100644
View file @
68530f0f
/*
* NX proxy stubs for programs that link against libssh.a
* but don't use NX proxy functionality (sshd, ssh-keygen).
*
* These stubs satisfy the linker for NX symbols referenced
* by channels.c and packet.c without pulling in proxy.c
* and its NXcomp dependency.
*/
#include "includes.h"
#include <sys/types.h>
#include <poll.h>
#include <signal.h>
#include <stdlib.h>
#include <string.h>
#include "proxy.h"
/* NX global variables - stub definitions */
int
nx_proxy_log
=
0
;
int
nx_check_switch
=
0
;
int
nx_switch_received
=
0
;
char
nx_switch_cookie
[
256
];
char
nx_switch_host
[
256
];
int
nx_switch_proxy
=
0
;
int
nx_switch_port
=
0
;
int
nx_switch_in
=
-
1
;
int
nx_switch_out
=
-
1
;
char
nx_switch_mode
[
256
];
char
nx_switch_options
[
1024
];
int
nx_switch_internal
=
0
;
int
nx_switch_forward
=
0
;
/* NX global variables used in ssh.c, sshconnect*.c, readpass.c */
int
NxModeEnabled
=
0
;
int
NxAuthOnlyModeEnabled
=
0
;
int
NXStdinPassEnabled
=
0
;
int
NXServerMode
=
0
;
int
NxAdminModeEnabled
=
0
;
/* Stub function implementations */
int
nx_check_channel_input
(
struct
Channel
*
channel
,
char
*
data
,
int
*
length
,
int
limit
)
{
return
0
;
}
void
nx_proxy_init
(
void
)
{
}
int
nx_proxy_select
(
int
maxfds
,
fd_set
*
readfds
,
fd_set
*
writefds
,
fd_set
*
exceptfds
,
struct
timeval
*
timeout
)
{
return
select
(
maxfds
,
readfds
,
writefds
,
exceptfds
,
timeout
);
}
int
nx_proxy_ppoll
(
struct
pollfd
*
fds
,
nfds_t
nfds
,
const
struct
timespec
*
timeout
,
const
sigset_t
*
sigmask
)
{
return
ppoll
(
fds
,
nfds
,
timeout
,
sigmask
);
}
int
nx_open_proxy_connection
(
void
)
{
return
-
1
;
}
int
nx_close_proxy_connection
(
void
)
{
return
0
;
}
int
nx_check_proxy_authentication
(
int
proxy_fd
)
{
return
0
;
}
int
nx_switch_client_side_descriptors
(
struct
Channel
*
channel
,
int
proxy_fd
)
{
return
0
;
}
int
nx_switch_forward_descriptors
(
struct
Channel
*
channel
)
{
return
0
;
}
int
nx_switch_forward_port
(
struct
Channel
*
channel
)
{
return
0
;
}
int
nx_check_standard_input
(
void
)
{
return
0
;
}
void
nx_switch_server_side_descriptors
(
void
)
{
}
void
nx_set_socket_options
(
int
fd
,
int
blocking
)
{
}
const
char
*
nx_get_environment
(
const
char
*
name
)
{
return
getenv
(
name
);
}
int
nx_set_environment
(
const
char
*
name
,
const
char
*
value
)
{
return
setenv
(
name
,
value
,
1
);
}
proxy.c
0 → 100644
View file @
68530f0f
/**************************************************************************/
/* */
/* Copyright (c) 2001, 2011 NoMachine, http://www.nomachine.com/. */
/* */
/* NXSSH, NX protocol compression and NX extensions to this software */
/* are copyright of NoMachine. Redistribution and use of the present */
/* software is allowed according to terms specified in the file LICENSE */
/* which comes in the source distribution. */
/* */
/* Check http://www.nomachine.com/licensing.html for applicability. */
/* */
/* NX and NoMachine are trademarks of Medialogic S.p.A. */
/* */
/* All rights reserved. */
/* */
/**************************************************************************/
#include "includes.h"
#include "log.h"
#include "openbsd-compat/sys-queue.h"
#include "sshbuf.h"
#include "channels.h"
#include "xmalloc.h"
/*
* Used in NX network related functions.
*/
#include "NX.h"
#include "proxy.h"
#include <errno.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <ctype.h>
#include <poll.h>
#include <signal.h>
#include <netdb.h>
#if defined(__CYGWIN32__) || defined(__APPLE__) || defined(__FreeBSD__) || defined(__sun)
#include <netinet/in_systm.h>
#endif
#include <netinet/in.h>
#ifdef __sun
#define INADDR_NONE ((unsigned int) -1)
#endif
/*
* Set here the requested log level.
*/
#define PANIC
#define WARNING
#undef TEST
#undef DEBUG
/*
* Print a line in the log if the time we
* spent inside the select or handling the
* messages exceeded a given time value.
*/
#undef TIME
/*
* The NX version we are advertising to the proxy.
*/
#define NX_ADVERTISE_VERSION "3.0.0"
/*
* Replace stdin and stdout upon the switch. This
* is not supported yet.
*/
#undef NX_REPLACE_STANDARD_DESCRIPTORS
/*
* Close selectively the read and write ends of
* the proxy and channel pipes in the proxy loop.
* This is just for reference as it is better to
* close all the descriptors at the first failure.
*/
#undef NX_SELECTIVELY_CLOSE_DESCRIPTORS
/*
* Use a well-known log file and share it with the
* proxy when the program is forwarding a SSHD con-
* nection to the agent. To be used only for test
* purposes.
*/
#undef NX_SHARE_BINDER_LOG
/*
* Do we check the standard input for the incoming
* switch command? This flag is bound to a command
* line parameter.
*/
int
nx_check_switch
=
0
;
/*
* Did we receive the command? The switch is executed
* at the time the first channel receives the command,
* hence the port forwarding feature of SSH should be
* disabled when enabling the NX proxying.
*/
int
nx_switch_received
=
0
;
/*
* Do we redirect the debug log after the switch
* command?
*/
int
nx_switch_log
=
0
;
/*
* Parameters read from the switch command.
*/
char
nx_switch_cookie
[
256
]
=
{
0
};
char
nx_switch_host
[
256
]
=
{
0
};
int
nx_switch_proxy
=
-
1
;
int
nx_switch_port
=
-
1
;
int
nx_switch_in
=
-
1
;
int
nx_switch_out
=
-
1
;
char
nx_switch_mode
[
256
]
=
{
0
};
char
nx_switch_options
[
1024
]
=
{
0
};
int
nx_switch_internal
=
-
1
;
int
nx_switch_forward
=
-
1
;
/*
* The awaited switch command.
*/
const
char
*
nx_switch_command
=
"NX> 299 Switch connection to: "
;
/*
* Buffer the input while looking for the command.
*/
static
int
nx_check_input
(
struct
sshbuf
*
buffer
,
char
*
data
,
int
*
length
,
int
limit
);
static
int
nx_check_switch_command
(
struct
sshbuf
*
buffer
,
char
*
start
);
static
int
nx_check_switch_parameters
(
char
*
command
);
/*
* Parse the parameters from the switch command.
*/
static
int
nx_check_specifier_mode_and_options
(
char
*
command
,
char
*
mode
,
char
*
options
);
static
int
nx_check_specifier_and_mode
(
char
*
command
,
char
*
mode
);
static
int
nx_check_specifier_and_options
(
char
*
command
,
char
*
options
);
static
int
nx_check_specifier
(
char
*
command
);
static
int
nx_check_host_and_port
(
char
*
command
,
char
*
host
,
char
*
port
);
static
int
nx_check_host_port_and_cookie
(
char
*
command
,
char
*
host
,
char
*
port
,
char
*
cookie
);
static
int
nx_check_host_port_and_descriptors
(
char
*
command
,
char
*
host
,
char
*
port
,
char
*
in
,
char
*
out
);
static
int
nx_check_port_and_accept
(
char
*
command
,
char
*
port
,
char
*
accept
);
static
int
nx_check_descriptors
(
char
*
command
,
char
*
in
,
char
*
out
);
/*
* Connect to the proxy over a socketpair or
* through a network connection.
*/
static
int
nx_open_internal_proxy_connection
(
void
);
static
int
nx_open_external_proxy_connection
(
void
);
/*
* Redirect the log output.
*/
static
void
nx_redirect_log_output
(
void
);
/*
* Forward data read from the SSHD server to an
* agent running on the NX server side.
*/
void
nx_run_server_side_loop
(
int
proxy_fd
);
/*
* Run a simple loop that lets the NX proxy on
* the client connect to an agent running on
* the NX server and make is manage the unenc-
* rypted connection.
*/
void
nx_run_client_side_loop
(
int
proxy_fd
);
/*
* Buffers the standard input when waiting for the
* command without having established any channel.
*/
static
struct
sshbuf
*
nx_input_buffer
;
/*
* Utilities to log in the NX format.
*/
static
void
nx_dump_buffer
(
struct
sshbuf
*
buffer
);
static
void
nx_dump_string
(
char
*
string
);
#if defined(DEBUG) || defined(TIME)
static
char
*
nx_dump_timestamp
(
void
);
static
struct
timeval
nx_get_timestamp
(
void
);
static
int
nx_diff_timestamp
(
struct
timeval
ts1
,
struct
timeval
ts2
);
#endif
/*
* Catch signals used to interrupt the connect or
* detect a broken connection.
*/
static
void
nx_catch_timeout_signal
(
int
number
);
static
void
nx_catch_pipe_signal
(
int
number
);
/*
* Safe version of string functions.
*/
static
char
*
nx_search_string_in_buffer
(
struct
sshbuf
*
buffer
,
char
*
start
,
const
char
*
string
);
static
char
*
nx_remove_string_in_buffer
(
struct
sshbuf
*
buffer
,
char
*
start
,
int
length
);
static
char
*
nx_search_newline_in_buffer
(
struct
sshbuf
*
buffer
,
char
*
start
);
static
char
*
nx_remove_newline_in_string
(
char
*
start
,
int
length
);
static
char
*
nx_toupper_string
(
char
*
start
);
/*
* This is currently unused. Not defined
* static to avoid the warning.
*/
char
*
nx_search_char_in_buffer
(
struct
sshbuf
*
buffer
,
char
*
start
,
int
value
);
/*
* Wait for the given amount of seconds.
*/
static
void
nx_wait_timeout
(
int
seconds
);
/*
* Utility used to set our preferred socket
* options.
*/
static
int
nx_set_nonblocking
(
int
fd
);
static
int
nx_set_blocking
(
int
fd
);
static
int
nx_set_nodelay
(
int
fd
);
static
int
nx_set_keepalive
(
int
fd
);
static
int
nx_set_lowdelay
(
int
fd
);
void
nx_proxy_init
(
void
)
{
nx_input_buffer
=
sshbuf_new
();
if
(
nx_input_buffer
==
NULL
)
fatal
(
"NX> 298 Out of memory allocating NX input buffer"
);
}
int
nx_proxy_select
(
int
maxfds
,
fd_set
*
readfds
,
fd_set
*
writefds
,
fd_set
*
exceptfds
,
struct
timeval
*
timeout
)
{
/*
* Check if we have switched the connection
* to the NX transport.
*/
if
(
nx_switch_internal
==
1
&&
NXTransRunning
(
NX_FD_ANY
)
==
1
)
{
fd_set
t_readfds
,
t_writefds
;
struct
timeval
t_timeout
;
int
n
,
r
,
e
;
if
(
exceptfds
!=
NULL
)
{
fatal
(
"NX> 280 Can't handle exception fds in select"
);
exit
(
1
);
}
#ifdef TEST
debug
(
"NX> 280 Going to run a new NX loop"
);
#endif
if
(
readfds
==
NULL
)
{
FD_ZERO
(
&
t_readfds
);
readfds
=
&
t_readfds
;
}
if
(
writefds
==
NULL
)
{
FD_ZERO
(
&
t_writefds
);
writefds
=
&
t_writefds
;
}
if
(
timeout
==
NULL
)
{
t_timeout
.
tv_sec
=
10
;
t_timeout
.
tv_usec
=
0
;
timeout
=
&
t_timeout
;
}
n
=
maxfds
;
/*
* If the transport is gone avoid
* sleeping until the timeout.
*/
if
(
NXTransPrepare
(
&
n
,
readfds
,
writefds
,
timeout
)
!=
0
)
{
NXTransSelect
(
&
r
,
&
e
,
&
n
,
readfds
,
writefds
,
timeout
);
NXTransExecute
(
&
r
,
&
e
,
&
n
,
readfds
,
writefds
,
timeout
);
errno
=
e
;
return
r
;
}
else
{
return
0
;
}
}
else
{
#ifdef DEBUG
debug
(
"NX> 280 The NX transport is not running"
);
#endif
return
select
(
maxfds
,
readfds
,
writefds
,
exceptfds
,
timeout
);
}
}
/*
* ppoll()-compatible wrapper for NX transport integration.
* Converts pollfd[] <-> fd_set pairs, calls NXTransPrepare/Select/Execute,
* then converts results back to pollfd.revents.
*/
int
nx_proxy_ppoll
(
struct
pollfd
*
fds
,
nfds_t
nfds
,
const
struct
timespec
*
timeout
,
const
sigset_t
*
sigmask
)
{
if
(
nx_switch_internal
==
1
&&
NXTransRunning
(
NX_FD_ANY
)
==
1
)
{
fd_set
readfds
,
writefds
;
struct
timeval
tv
,
*
tvp
;
int
maxfd
=
0
;
int
n
,
r
,
e
;
nfds_t
i
;
FD_ZERO
(
&
readfds
);
FD_ZERO
(
&
writefds
);
/* Convert pollfd[] to fd_set pair */
for
(
i
=
0
;
i
<
nfds
;
i
++
)
{
fds
[
i
].
revents
=
0
;
if
(
fds
[
i
].
fd
<
0
)
continue
;
if
(
fds
[
i
].
events
&
(
POLLIN
|
POLLPRI
))
FD_SET
(
fds
[
i
].
fd
,
&
readfds
);
if
(
fds
[
i
].
events
&
POLLOUT
)
FD_SET
(
fds
[
i
].
fd
,
&
writefds
);
if
(
fds
[
i
].
fd
>=
maxfd
)
maxfd
=
fds
[
i
].
fd
+
1
;
}
/* Convert timespec to timeval */
if
(
timeout
!=
NULL
)
{
tv
.
tv_sec
=
timeout
->
tv_sec
;
tv
.
tv_usec
=
timeout
->
tv_nsec
/
1000
;
tvp
=
&
tv
;
}
else
{
tv
.
tv_sec
=
10
;
tv
.
tv_usec
=
0
;
tvp
=
&
tv
;
}
n
=
maxfd
;
if
(
NXTransPrepare
(
&
n
,
&
readfds
,
&
writefds
,
tvp
)
!=
0
)
{
NXTransSelect
(
&
r
,
&
e
,
&
n
,
&
readfds
,
&
writefds
,
tvp
);
NXTransExecute
(
&
r
,
&
e
,
&
n
,
&
readfds
,
&
writefds
,
tvp
);
/* Convert fd_set results back to pollfd.revents */
r
=
0
;
for
(
i
=
0
;
i
<
nfds
;
i
++
)
{
if
(
fds
[
i
].
fd
<
0
)
continue
;
if
(
FD_ISSET
(
fds
[
i
].
fd
,
&
readfds
))
fds
[
i
].
revents
|=
POLLIN
;
if
(
FD_ISSET
(
fds
[
i
].
fd
,
&
writefds
))
fds
[
i
].
revents
|=
POLLOUT
;
if
(
fds
[
i
].
revents
!=
0
)
r
++
;
}
errno
=
e
;
return
r
;
}
else
{
return
0
;
}
}
else
{
/* NX transport not active, use normal ppoll */
return
ppoll
(
fds
,
nfds
,
timeout
,
sigmask
);
}
}
int
nx_check_channel_input
(
struct
Channel
*
channel
,
char
*
data
,
int
*
length
,
int
limit
)
{
debug
(
"NX> 285 Going to check input for fd: %d"
,
channel
->
rfd
);
/*
* This allows forwarding of connections to
* the authentication agent but it is to be
* better investigated if this is the best
* way to proceed.
*/
if
(
strcmp
(
channel
->
ctype
,
"authentication agent connection"
)
==
0
)
{
debug
(
"NX> 285 Detected authentication agent connection"
);
return
0
;
}
/*
* It returns true if more data has been
* produced for the channel. The switch
* command is removed from the buffer.
*/
return
nx_check_input
(
channel
->
nx_buffer
,
data
,
length
,
limit
);
}
int
nx_check_standard_input
(
void
)
{
char
data
[
1024
];
fd_set
set
;
int
fd
=
fileno
(
stdin
);
int
result
;
debug
(
"NX> 285 Going to check input for fd: %d"
,
fd
);
FD_ZERO
(
&
set
);
for
(;;)
{
FD_SET
(
fd
,
&
set
);
result
=
select
(
fd
+
1
,
&
set
,
NULL
,
NULL
,
NULL
);
if
(
result
<
0
&&
errno
==
EINTR
)
{
continue
;
}
if
(
result
<=
0
)
{
goto
nx_check_standard_input_error
;
}
for
(;;)
{
result
=
read
(
fd
,
data
,
1024
-
1
);
if
(
result
<
0
&&
errno
==
EINTR
)
{
continue
;
}
if
(
result
<=
0
)
{
goto
nx_check_standard_input_error
;
}
/*
* No echo performed. Just print a warning.
*/
nx_check_input
(
nx_input_buffer
,
data
,
&
result
,
1024
-
1
);
if
(
result
>
0
)
{
*
(
data
+
result
)
=
'\0'
;
nx_remove_newline_in_string
(
data
,
result
);
error
(
"NX> 289 Discarding spurious data: '%s'"
,
data
);
sshbuf_reset
(
nx_input_buffer
);
}
break
;
}
break
;
}
return
1
;
nx_check_standard_input_error:
fatal
(
"NX> 290 End of input received before switch command"
);
return
-
1
;
}
int
nx_check_input
(
struct
sshbuf
*
buffer
,
char
*
data
,
int
*
length
,
int
limit
)
{
char
*
start
;
char
*
current
;
char
*
end
;
int
r
;
/*
* Append data to the buffer.
*/
if
(
sshbuf_len
(
buffer
)
+
*
length
>=
(
16
*
1024
-
1
))
{
error
(
"
\r\n
NX> 288 Buffer length of: %d bytes exceeded"
,
16
*
1024
);
fatal
(
"NX> 290 Don't use the -nxswitch option with binary data"
);
}
debug
(
"NX> 280 Adding: %d bytes to the temporary buffer"
,
*
length
);
if
((
r
=
sshbuf_put
(
buffer
,
data
,
*
length
))
!=
0
)
fatal
(
"NX> 298 sshbuf_put failed: %d"
,
r
);
debug
(
"NX> 280 Dumping content of the temporary buffer:"
);
nx_dump_buffer
(
buffer
);
/*
* Check if buffer contains the switch command.
*/
start
=
nx_search_string_in_buffer
(
buffer
,
NULL
,
nx_switch_command
);
if
(
start
!=
NULL
)
{
debug
(
"NX> 280 Switch command maybe found at position: %d"
,
(
int
)
(
start
-
(
char
*
)
sshbuf_ptr
(
buffer
)));
nx_switch_received
=
nx_check_switch_command
(
buffer
,
start
);
}
/*
* Place back the remaining data in
* the original buffer.
*/
current
=
data
;
*
length
=
0
;
for
(;;)
{
start
=
(
char
*
)
sshbuf_ptr
(
buffer
);
end
=
nx_search_newline_in_buffer
(
buffer
,
start
);
if
(
end
==
NULL
||
*
length
+
(
end
-
start
+
1
)
>
limit
)
{
break
;
}
debug
(
"NX> 280 Adding: %d bytes to the returned buffer"
,
(
int
)
(
end
-
start
+
1
));
if
((
r
=
sshbuf_get
(
buffer
,
current
,
end
-
start
+
1
))
!=
0
)
fatal
(
"NX> 298 sshbuf_get failed: %d"
,
r
);
current
+=
end
-
start
+
1
;
*
length
+=
end
-
start
+
1
;
}
debug
(
"NX> 280 Left: %zu bytes in the temporary buffer"
,
sshbuf_len
(
buffer
));
return
1
;
}
int
nx_check_switch_command
(
struct
sshbuf
*
buffer
,
char
*
start
)
{
char
*
command
;
char
*
end
;
int
length
=
sshbuf_len
(
buffer
)
-
(
start
-
(
char
*
)
sshbuf_ptr
(
buffer
));
debug
(
"NX> 280 Searching newline with: %d bytes remaining"
,
length
);
if
((
end
=
nx_search_newline_in_buffer
(
buffer
,
start
))
==
NULL
)
{
fatal
(
"NX> 290 Can't find the terminating newline in buffer:"
);
return
0
;
}
/*
* Copy the command string to a temporary.
*/
length
=
end
-
start
;
debug
(
"NX> 280 Checking command with length: %d"
,
length
);
command
=
xmalloc
(
length
+
1
);
if
(
command
==
NULL
)
{
fatal
(
"NX> 298 Out of memory checking the command string"
);
return
-
1
;
}
memcpy
(
command
,
start
,
length
);
*
(
command
+
length
)
=
'\0'
;
debug
(
"NX> 280 Analyzing command string:"
);
nx_dump_string
(
command
);
/*
* Get rid of the command fingerprint from
* buffer until the terminating newline.
*/
nx_remove_string_in_buffer
(
buffer
,
start
,
length
);
debug
(
"NX> 280 Content of the temporary buffer:"
);
nx_dump_buffer
(
buffer
);
if
(
nx_check_switch_parameters
(
command
)
<=
0
)
{
fatal
(
"
\r\n
NX> 298 Parse error in the switch parameters"
);
return
-
1
;
}
return
1
;
}
int
nx_check_switch_parameters
(
char
*
command
)
{
char
host
[
256
]
=
{
0
};
char
accept
[
256
]
=
{
0
};
char
cookie
[
256
]
=
{
0
};
char
mode
[
256
]
=
{
0
};
char
options
[
1024
]
=
{
0
};
char
port
[
16
]
=
{
0
};
char
in
[
16
]
=
{
0
};
char
out
[
16
]
=
{
0
};
if
(
command
==
NULL
||
*
command
==
'\0'
)
{
return
-
1
;
}
else
if
(
strlen
(
command
)
>=
256
)
{
return
-
1
;
}
debug
(
"NX> 285 Searching switch parameters in command:"
);
nx_dump_string
(
command
);
if
(
nx_check_specifier_mode_and_options
(
command
,
mode
,
options
)
<=
0
&&
nx_check_specifier_and_options
(
command
,
options
)
<=
0
&&
nx_check_specifier_and_mode
(
command
,
mode
)
<=
0
&&
nx_check_port_and_accept
(
command
,
port
,
accept
)
<=
0
&&
nx_check_host_port_and_descriptors
(
command
,
host
,
port
,
in
,
out
)
<=
0
&&
nx_check_descriptors
(
command
,
in
,
out
)
<=
0
&&
nx_check_host_port_and_cookie
(
command
,
host
,
port
,
cookie
)
<=
0
&&
nx_check_host_and_port
(
command
,
host
,
port
)
<=
0
&&
nx_check_specifier
(
command
)
<=
0
)
{
return
-
1
;
}
/*
* If at least host and port were given
* we need an outbound connection.
*/
if
(
*
host
!=
'\0'
&&
*
port
!=
'\0'
)
{
strcpy
(
nx_switch_host
,
host
);
nx_switch_port
=
atoi
(
port
);
logit
(
"NX> 285 Identified host: %s port: %d"
,
nx_switch_host
,
nx_switch_port
);
if
(
*
cookie
!=
'\0'
)
{
strcpy
(
nx_switch_cookie
,
cookie
);
logit
(
"NX> 285 Identified cookie: %s"
,
nx_switch_cookie
);
}
if
(
*
in
!=
'\0'
&&
*
out
!=
'\0'
)
{
nx_switch_in
=
atoi
(
in
);
nx_switch_out
=
atoi
(
out
);
logit
(
"NX> 285 Identified descriptors in: %d out: %d"
,
nx_switch_in
,
nx_switch_out
);
}
nx_switch_internal
=
0
;
}
else
if
(
*
in
!=
'\0'
&&
*
out
!=
'\0'
)
{
nx_switch_in
=
atoi
(
in
);
nx_switch_out
=
atoi
(
out
);
logit
(
"NX> 285 Identified descriptors in: %d out: %d"
,
nx_switch_in
,
nx_switch_out
);
nx_switch_forward
=
1
;
}
else
if
(
*
accept
!=
'\0'
&&
*
port
!=
'\0'
)
{
strcpy
(
nx_switch_host
,
accept
);
nx_switch_port
=
atoi
(
port
);
logit
(
"NX> 285 Identified port: %d accept: %s"
,
nx_switch_port
,
nx_switch_host
);
nx_switch_forward
=
2
;
}
else
{
logit
(
"NX> 285 Identified internal connection"
);
if
(
*
mode
!=
'\0'
)
{
if
(
strcmp
(
"encrypted"
,
mode
)
!=
0
&&
strcmp
(
"unencrypted"
,
mode
)
!=
0
&&
strcmp
(
"default"
,
mode
)
!=
0
)
{
error
(
"NX> 290 Not supported mode: %s"
,
mode
);
return
-
1
;
}
if
(
strcmp
(
"unencrypted"
,
mode
)
==
0
)
{
strcpy
(
nx_switch_mode
,
mode
);
logit
(
"NX> 285 Identified mode: %s"
,
nx_switch_mode
);
}
else
{
logit
(
"NX> 285 Using default mode encrypted"
);
}
}
if
(
*
options
!=
'\0'
)
{
strcpy
(
nx_switch_options
,
options
);
logit
(
"NX> 285 Identified options: %s"
,
nx_switch_options
);
}
nx_switch_internal
=
1
;
}
return
1
;
}
int
nx_check_host_port_and_descriptors
(
char
*
command
,
char
*
host
,
char
*
port
,
char
*
in
,
char
*
out
)
{
char
match
[
256
]
=
{
0
};
int
result
;
snprintf
(
match
,
255
,
"%s%%128[^:]:%%5[0-9] in: %%5[0-9] out: %%5[0-9]"
,
nx_switch_command
);
debug
(
"NX> 280 Searching with matching string:"
);
nx_dump_string
(
match
);
result
=
sscanf
(
command
,
match
,
host
,
port
,
in
,
out
);
if
(
result
!=
4
)
{
return
-
1
;
}
return
1
;
}
int
nx_check_port_and_accept
(
char
*
command
,
char
*
port
,
char
*
accept
)
{
char
match
[
256
]
=
{
0
};
int
result
;
snprintf
(
match
,
255
,
"%s SSH port: %%5[0-9] accept: %%128s"
,
nx_switch_command
);
debug
(
"NX> 280 Searching with matching string:"
);
nx_dump_string
(
match
);
result
=
sscanf
(
command
,
match
,
port
,
accept
);
if
(
result
!=
2
)
{
return
-
1
;
}
return
1
;
}
int
nx_check_descriptors
(
char
*
command
,
char
*
in
,
char
*
out
)
{
char
match
[
256
]
=
{
0
};
int
result
;
snprintf
(
match
,
255
,
"%s SSH in: %%5[0-9] out: %%5[0-9]"
,
nx_switch_command
);
debug
(
"NX> 280 Searching with matching string:"
);
nx_dump_string
(
match
);
result
=
sscanf
(
command
,
match
,
in
,
out
);
if
(
result
!=
2
)
{
return
-
1
;
}
return
1
;
}
int
nx_check_host_port_and_cookie
(
char
*
command
,
char
*
host
,
char
*
port
,
char
*
cookie
)
{
char
match
[
256
]
=
{
0
};
int
result
;
snprintf
(
match
,
255
,
"%s%%128[^:]:%%5[0-9] cookie: %%32s"
,
nx_switch_command
);
debug
(
"NX> 280 Searching with matching string:"
);
nx_dump_string
(
match
);
result
=
sscanf
(
command
,
match
,
host
,
port
,
cookie
);
if
(
result
!=
3
)
{
return
-
1
;
}
return
1
;
}
int
nx_check_host_and_port
(
char
*
command
,
char
*
host
,
char
*
port
)
{
char
match
[
256
]
=
{
0
};
int
result
;
snprintf
(
match
,
255
,
"%s%%128[^:]:%%5[0-9]"
,
nx_switch_command
);
debug
(
"NX> 280 Searching with matching string:"
);
nx_dump_string
(
match
);
result
=
sscanf
(
command
,
match
,
host
,
port
);
if
(
result
!=
2
)
{
return
-
1
;
}
return
1
;
}
int
nx_check_specifier_mode_and_options
(
char
*
command
,
char
*
mode
,
char
*
options
)
{
char
match
[
256
]
=
{
0
};
int
result
;
snprintf
(
match
,
255
,
"%s NX mode: %%16s options: %%1023[^']"
,
nx_switch_command
);
debug
(
"NX> 280 Searching with matching string:"
);
nx_dump_string
(
match
);
result
=
sscanf
(
command
,
match
,
mode
,
options
);
if
(
result
!=
2
)
{
return
-
1
;
}
return
1
;
}
int
nx_check_specifier_and_mode
(
char
*
command
,
char
*
mode
)
{
char
match
[
256
]
=
{
0
};
int
result
;
snprintf
(
match
,
255
,
"%s NX mode: %%16s"
,
nx_switch_command
);
debug
(
"NX> 280 Searching with matching string:"
);
nx_dump_string
(
match
);
result
=
sscanf
(
command
,
match
,
mode
);
if
(
result
!=
1
)
{
return
-
1
;
}
return
1
;
}
int
nx_check_specifier_and_options
(
char
*
command
,
char
*
options
)
{
char
match
[
256
]
=
{
0
};
int
result
;
snprintf
(
match
,
255
,
"%s NX options: %%1023s"
,
nx_switch_command
);
debug
(
"NX> 280 Searching with matching string:"
);
nx_dump_string
(
match
);
result
=
sscanf
(
command
,
match
,
options
);
if
(
result
!=
1
)
{
return
-
1
;
}
return
1
;
}
int
nx_check_specifier
(
char
*
command
)
{
char
match
[
256
]
=
{
0
};
char
nx
[
256
]
=
{
0
};
int
result
;
snprintf
(
match
,
255
,
"%s%%2s"
,
nx_switch_command
);
debug
(
"NX> 280 Searching with matching string:"
);
nx_dump_string
(
match
);
result
=
sscanf
(
command
,
match
,
nx
);
if
(
result
!=
1
||
strcmp
(
"NX"
,
nx_toupper_string
(
nx
))
!=
0
)
{
return
-
1
;
}
return
1
;
}
int
nx_open_proxy_connection
(
void
)
{
if
(
*
nx_switch_host
!=
'\0'
&&
nx_switch_port
!=
-
1
)
{
return
nx_open_external_proxy_connection
();
}
else
if
(
nx_switch_internal
==
1
)
{
return
nx_open_internal_proxy_connection
();
}
else
{
fatal
(
"
\r\n
NX> 297 Missing data to perform the switch"
);
return
-
1
;
}
}
int
nx_close_proxy_connection
(
void
)
{
debug
(
"NX> 280 Switch proxy is: %d"
,
nx_switch_proxy
);
if
(
nx_switch_proxy
!=
-
1
)
{
debug
(
"NX> 280 Waiting for the NX transport to terminate"
);
NXTransDestroy
(
nx_switch_proxy
);
debug
(
"NX> 280 NX transport successfully terminated"
);
}
return
1
;
}
/*
* Let the proxy create a socketpair and receive
* back the end we'll use to communicate with it.
*/
int
nx_open_internal_proxy_connection
(
void
)
{
char
*
proxy_options
;
int
proxy_pair
[
2
];
if
(
*
nx_switch_options
!=
'\0'
)
{
proxy_options
=
nx_switch_options
;
debug
(
"NX> 280 Creating proxy with options: %s"
,
nx_switch_options
);
}
else
{
proxy_options
=
NULL
;
debug
(
"NX> 280 Creating proxy with null options"
);
}
if
(
socketpair
(
PF_UNIX
,
SOCK_STREAM
,
0
,
proxy_pair
)
<
0
)
{
fatal
(
"NX> 290 Failure creating the socket pair: %s"
,
strerror
(
errno
));
return
-
1
;
}
if
(
strcmp
(
"unencrypted"
,
nx_switch_mode
)
==
0
)
{
close
(
proxy_pair
[
1
]);
proxy_pair
[
1
]
=
NX_FD_ANY
;
}
else
{
nx_set_socket_options
(
proxy_pair
[
0
],
0
);
}
debug
(
"NX> 280 Using local end: %d remote end: %d"
,
proxy_pair
[
0
],
proxy_pair
[
1
]);
if
(
NXTransCreate
(
proxy_pair
[
1
],
NX_MODE_SERVER
,
proxy_options
)
<
0
)
{
fatal
(
"NX> 290 Failed to create the internal connection"
);
return
-
1
;
}
logit
(
"NX> 280 Proxy opened with local: %d remote: %d"
,
proxy_pair
[
0
],
proxy_pair
[
1
]);
debug
(
"NX> 280 Should enable NX memory-to-memory agent transport"
);
nx_switch_proxy
=
proxy_pair
[
1
];
return
proxy_pair
[
0
];
}
/*
* Connect to an external proxy process.
*/
int
nx_open_external_proxy_connection
(
void
)
{
int
proxy_fd
;
int
remote_ip_addr
;
void
(
*
handler
)(
int
)
=
signal
(
SIGALRM
,
nx_catch_timeout_signal
);
int
retry_connect
=
4
;
struct
sockaddr_in
addr
;
struct
hostent
*
host
=
gethostbyname
(
nx_switch_host
);
int
flag
=
1
;
int
result
;
int
timer
=
alarm
(
0
);
logit
(
"
\r\n
NX> 291 Connecting to: %s:%d"
,
nx_switch_host
,
nx_switch_port
);
if
(
host
==
NULL
)
{
unsigned
int
address
;
address
=
(
unsigned
int
)
inet_addr
(
nx_switch_host
);
if
(
address
==
INADDR_NONE
)
{
fatal
(
"
\r\n
NX> 297 Can't resolve address of host: %s"
,
nx_switch_host
);
goto
nx_open_proxy_connection_error
;
}
remote_ip_addr
=
(
int
)
address
;
}
else
{
remote_ip_addr
=
(
*
((
int
*
)
host
->
h_addr_list
[
0
]));
}
addr
.
sin_family
=
AF_INET
;
addr
.
sin_port
=
htons
(
nx_switch_port
);
addr
.
sin_addr
.
s_addr
=
remote_ip_addr
;
for
(;;)
{
proxy_fd
=
socket
(
AF_INET
,
SOCK_STREAM
,
PF_UNSPEC
);
if
(
proxy_fd
==
-
1
)
{
fatal
(
"
\r\n
NX> 296 Can't create the connecting socket"
);
goto
nx_open_proxy_connection_error
;
}
else
if
(
setsockopt
(
proxy_fd
,
SOL_SOCKET
,
SO_REUSEADDR
,
(
char
*
)
&
flag
,
sizeof
(
flag
))
<
0
)
{
fatal
(
"
\r\n
NX> 295 Can't set the SO_REUSEADDR on socket"
);
goto
nx_open_proxy_connection_error
;
}
alarm
(
retry_connect
);
result
=
connect
(
proxy_fd
,
(
struct
sockaddr
*
)
&
addr
,
sizeof
(
struct
sockaddr_in
));
if
(
result
<
0
)
{
retry_connect
--
;
if
(
retry_connect
>
0
)
{
error
(
"NX> 294 Connection to: %s:%d failed. Retrying"
,
nx_switch_host
,
nx_switch_port
);
close
(
proxy_fd
);
}
else
{
fatal
(
"NX> 290 Failed connection to: %s:%d"
,
nx_switch_host
,
nx_switch_port
);
goto
nx_open_proxy_connection_error
;
}
nx_wait_timeout
(
3
);
}
else
{
break
;
}
}
debug
(
"NX> 294 Connected to proxy at: %s:%d"
,
nx_switch_host
,
nx_switch_port
);
signal
(
SIGALRM
,
handler
);
alarm
(
timer
);
nx_set_socket_options
(
proxy_fd
,
0
);
return
proxy_fd
;
nx_open_proxy_connection_error:
signal
(
SIGALRM
,
handler
);
alarm
(
timer
);
return
-
1
;
}
int
nx_check_proxy_authentication
(
int
proxy_fd
)
{
if
(
*
nx_switch_cookie
!=
'\0'
)
{
char
string
[
256
];
snprintf
(
string
,
255
,
"NXSSH-%s cookie=%s "
,
NX_ADVERTISE_VERSION
,
nx_toupper_string
(
nx_switch_cookie
));
*
(
string
+
255
)
=
'\0'
;
logit
(
"NX> 285 Sending authentication cookie: %s"
,
nx_switch_cookie
);
write
(
proxy_fd
,
string
,
strlen
(
string
));
return
1
;
}
return
0
;
}
int
nx_switch_client_side_descriptors
(
struct
Channel
*
channel
,
int
proxy_fd
)
{
if
(
nx_switch_internal
==
1
&&
strcmp
(
"unencrypted"
,
nx_switch_mode
)
==
0
)
{
nx_check_switch
=
0
;
nx_run_client_side_loop
(
proxy_fd
);
logit
(
"NX> 285 Client side loop completed"
);
exit
(
0
);
}
else
{
logit
(
"NX> 285 Switching descriptors: %d and: %d to: %d"
,
channel
->
rfd
,
channel
->
wfd
,
proxy_fd
);
if
(
dup2
(
proxy_fd
,
channel
->
rfd
)
<
0
||
dup2
(
proxy_fd
,
channel
->
wfd
)
<
0
)
{
fatal
(
"
\r\n
NX> 292 Can't redirect I/O to channel descriptors"
);
}
close
(
proxy_fd
);
nx_set_socket_options
(
channel
->
rfd
,
0
);
nx_set_socket_options
(
channel
->
wfd
,
0
);
logit
(
"
\r\n
NX> 287 Redirected I/O to channel descriptors"
);
logit
(
"NX> 280 Proxy in: %d out: %d transport in: %d out: %d"
,
channel
->
rfd
,
channel
->
wfd
,
nx_switch_proxy
,
nx_switch_proxy
);
if
(
nx_switch_log
==
1
)
{
nx_redirect_log_output
();
}
nx_check_switch
=
0
;
return
1
;
}
}
void
nx_switch_server_side_descriptors
(
void
)
{
int
proxy_fd
;
proxy_fd
=
nx_open_proxy_connection
();
if
(
proxy_fd
<
0
)
{
fatal
(
"NX> 290 Can't switch communication to: %s:%d"
,
nx_switch_host
,
nx_switch_port
);
}
nx_check_proxy_authentication
(
proxy_fd
);
if
(
nx_switch_in
>=
0
&&
nx_switch_out
>=
0
)
{
#ifdef NX_REPLACE_STANDARD_DESCRIPTORS
if
(
dup2
(
nx_switch_in
,
fileno
(
stdin
))
<
0
)
{
fatal
(
"NX> 290 Can't duplicate descriptor: %d to: %d error: '%s'"
,
nx_switch_in
,
fileno
(
stdin
),
strerror
(
errno
));
}
else
{
logit
(
"NX> 285 Duplicated descriptor: %d to: %d"
,
nx_switch_in
,
fileno
(
stdin
));
}
if
(
dup2
(
nx_switch_out
,
fileno
(
stdout
))
<
0
)
{
fatal
(
"NX> 290 Can't duplicate descriptor: %d to: %d error: '%s'"
,
nx_switch_out
,
fileno
(
stdout
),
strerror
(
errno
));
}
else
{
logit
(
"NX> 285 Duplicated descriptor: %d to: %d"
,
nx_switch_out
,
fileno
(
stdout
));
}
#endif
}
else
{
fatal
(
"NX> 290 Can't switch communication to in: %d out: %d"
,
nx_switch_in
,
nx_switch_out
);
}
nx_check_switch
=
0
;
nx_run_server_side_loop
(
proxy_fd
);
}
int
nx_switch_forward_descriptors
(
struct
Channel
*
channel
)
{
if
(
nx_switch_in
>=
0
&&
nx_switch_out
>=
0
)
{
if
(
dup2
(
nx_switch_in
,
channel
->
rfd
)
<
0
)
{
fatal
(
"NX> 290 Can't duplicate descriptor: %d to: %d error: '%s'"
,
nx_switch_in
,
channel
->
rfd
,
strerror
(
errno
));
}
else
{
logit
(
"NX> 285 Duplicated descriptor: %d to: %d"
,
nx_switch_in
,
channel
->
rfd
);
}
if
(
dup2
(
nx_switch_out
,
channel
->
wfd
)
<
0
)
{
fatal
(
"NX> 290 Can't duplicate descriptor: %d to: %d error: '%s'"
,
nx_switch_out
,
channel
->
wfd
,
strerror
(
errno
));
}
else
{
logit
(
"NX> 285 Duplicated descriptor: %d to: %d"
,
nx_switch_out
,
channel
->
wfd
);
}
}
else
{
fatal
(
"NX> 290 Can't switch communication to in: %d out: %d"
,
nx_switch_in
,
nx_switch_out
);
}
nx_check_switch
=
0
;
return
1
;
}
int
nx_switch_forward_port
(
struct
Channel
*
channel
)
{
int
retry_accept
=
4
;
int
proxy_fd
=
-
1
;
int
new_fd
=
-
1
;
int
flag
=
1
;
int
remote_ip_addr
;
struct
sockaddr_in
tcp_addr
;
struct
hostent
*
host
=
gethostbyname
(
nx_switch_host
);
if
(
host
==
NULL
)
{
unsigned
int
address
;
address
=
(
unsigned
int
)
inet_addr
(
nx_switch_host
);
if
(
address
==
INADDR_NONE
)
{
fatal
(
"
\r\n
NX> 297 Can't resolve address of host: %s"
,
nx_switch_host
);
goto
nx_switch_forward_port_error
;
}
remote_ip_addr
=
(
int
)
address
;
}
else
{
remote_ip_addr
=
(
*
((
int
*
)
host
->
h_addr_list
[
0
]));
}
if
(
remote_ip_addr
==
0
)
{
fatal
(
"
\r\n
NX> 297 Cannot accept connections from unknown host: %s"
,
nx_switch_host
);
goto
nx_switch_forward_port_error
;
}
proxy_fd
=
socket
(
AF_INET
,
SOCK_STREAM
,
PF_UNSPEC
);
if
(
proxy_fd
==
-
1
)
{
fatal
(
"
\r\n
NX> 297 Can't create socket error: %s"
,
strerror
(
errno
));
goto
nx_switch_forward_port_error
;
}
else
if
(
setsockopt
(
proxy_fd
,
SOL_SOCKET
,
SO_REUSEADDR
,
(
char
*
)
&
flag
,
sizeof
(
flag
))
<
0
)
{
fatal
(
"
\r\n
NX> 295 Can't set the SO_REUSEADDR on socket"
);
goto
nx_switch_forward_port_error
;
}
tcp_addr
.
sin_family
=
AF_INET
;
tcp_addr
.
sin_port
=
htons
(
nx_switch_port
);
tcp_addr
.
sin_addr
.
s_addr
=
htonl
(
INADDR_ANY
);
if
(
bind
(
proxy_fd
,
(
struct
sockaddr
*
)
&
tcp_addr
,
sizeof
(
tcp_addr
))
==
-
1
)
{
fatal
(
"
\r\n
NX> 297 Can't bind to port: %i error: %s"
,
nx_switch_port
,
strerror
(
errno
));
goto
nx_switch_forward_port_error
;
}
if
(
listen
(
proxy_fd
,
4
)
==
-
1
)
{
fatal
(
"
\r\n
NX> 297 Can't listen on port: %i error: %s"
,
nx_switch_port
,
strerror
(
errno
));
goto
nx_switch_forward_port_error
;
}
logit
(
"NX> 291 Waiting for remote connection on port: %d from: %s"
,
nx_switch_port
,
nx_switch_host
);
for
(;;)
{
fd_set
readSet
;
int
result
;
struct
timeval
selectTs
;
FD_ZERO
(
&
readSet
);
FD_SET
(
proxy_fd
,
&
readSet
);
selectTs
.
tv_sec
=
20
;
selectTs
.
tv_usec
=
0
;
result
=
select
(
proxy_fd
+
1
,
&
readSet
,
NULL
,
NULL
,
&
selectTs
);
if
(
result
==
-
1
)
{
fatal
(
"
\r\n
NX> 297 Call to select failed error: %s"
,
strerror
(
errno
));
goto
nx_switch_forward_port_error
;
}
else
if
(
result
>
0
&&
FD_ISSET
(
proxy_fd
,
&
readSet
))
{
struct
sockaddr_in
newAddr
;
char
*
connected_host
=
inet_ntoa
(
newAddr
.
sin_addr
);
unsigned
int
connected_port
=
ntohs
(
newAddr
.
sin_port
);
socklen_t
addrLen
=
sizeof
(
struct
sockaddr_in
);
new_fd
=
accept
(
proxy_fd
,
(
struct
sockaddr
*
)
&
newAddr
,
&
addrLen
);
if
(
new_fd
==
-
1
)
{
fatal
(
"
\r\n
NX> 297 Call to accept failed error: %s"
,
strerror
(
errno
));
goto
nx_switch_forward_port_error
;
}
if
((
int
)
newAddr
.
sin_addr
.
s_addr
==
remote_ip_addr
)
{
#ifdef TEST
logit
(
"NX> 291 Accepted connection from: %s with port: %d"
,
connected_host
,
connected_port
);
#endif
break
;
}
else
{
fatal
(
"NX> 297 Refused connection from: %s with port: %d"
,
connected_host
,
connected_port
);
goto
nx_switch_forward_port_error
;
}
sleep
(
5
);
close
(
new_fd
);
}
if
(
--
retry_accept
==
0
)
{
fatal
(
"
\r\n
NX> 297 Connection with remote host: %s could not be established"
,
nx_switch_host
);
goto
nx_switch_forward_port_error
;
}
}
close
(
proxy_fd
);
nx_set_socket_options
(
new_fd
,
0
);
if
(
dup2
(
new_fd
,
channel
->
rfd
)
<
0
||
dup2
(
new_fd
,
channel
->
wfd
)
<
0
)
{
fatal
(
"
\r\n
NX> 297 Can't redirect port to channel descriptors"
);
}
nx_check_switch
=
0
;
return
1
;
nx_switch_forward_port_error:
close
(
proxy_fd
);
return
-
1
;
}
void
nx_run_server_side_loop
(
int
proxy_fd
)
{
char
data
[
64
*
1024
];
fd_set
set
;
int
proxy_in
=
dup
(
proxy_fd
);
int
proxy_out
=
dup
(
proxy_fd
);
#ifdef NX_REPLACE_STANDARD_DESCRIPTORS
int
channel_in
=
fileno
(
stdin
);
int
channel_out
=
fileno
(
stdout
);
#else
int
channel_in
=
nx_switch_in
;
int
channel_out
=
nx_switch_out
;
#endif
int
proxy_in_open
=
1
;
int
channel_in_open
=
1
;
int
in_fd
;
int
out_fd
;
int
failed_fd
;
int
selected
;
int
written
;
int
length
;
int
result
;
#if defined(DEBUG) || defined(TIME)
struct
timeval
last_ts
=
nx_get_timestamp
();
int
diff_ts
;
#endif
if
(
nx_switch_log
==
1
)
{
nx_redirect_log_output
();
}
close
(
proxy_fd
);
nx_set_socket_options
(
proxy_in
,
1
);
nx_set_socket_options
(
proxy_out
,
1
);
nx_set_socket_options
(
channel_in
,
1
);
nx_set_socket_options
(
channel_out
,
1
);
signal
(
SIGPIPE
,
nx_catch_pipe_signal
);
logit
(
"NX> 285 Entering the server side NX loop with proxy at: %s:%d"
,
nx_switch_host
,
nx_switch_port
);
FD_ZERO
(
&
set
);
nx_run_server_side_loop_start:
while
(
proxy_in_open
||
channel_in_open
)
{
selected
=
0
;
if
(
proxy_in_open
)
{
#ifdef DEBUG
logit
(
"NX> 280 Selecting proxy descriptor"
);
#endif
FD_SET
(
proxy_in
,
&
set
);
if
(
proxy_in
>=
selected
)
{
selected
=
proxy_in
+
1
;
}
}
if
(
channel_in_open
)
{
#ifdef DEBUG
logit
(
"NX> 280 Selecting channel descriptor"
);
#endif
FD_SET
(
channel_in
,
&
set
);
if
(
channel_in
>=
selected
)
{
selected
=
channel_in
+
1
;
}
}
#if defined(DEBUG) || defined(TIME)
diff_ts
=
nx_diff_timestamp
(
last_ts
,
nx_get_timestamp
());
#ifdef TIME
if
(
diff_ts
>
20
)
{
logit
(
"NX> 280 TIME! Spent: %d ms handling messages"
,
diff_ts
);
}
#endif
last_ts
=
nx_get_timestamp
();
logit
(
"NX> 280 Entering select at: %s"
,
nx_dump_timestamp
());
#endif
selected
=
select
(
selected
,
&
set
,
NULL
,
NULL
,
NULL
);
#if defined(DEBUG) || defined(TIME)
diff_ts
=
nx_diff_timestamp
(
last_ts
,
nx_get_timestamp
());
logit
(
"NX> 280 Out of select with result: %d at: %s after: %d ms"
,
selected
,
nx_dump_timestamp
(),
diff_ts
);
#ifdef TIME
if
(
diff_ts
>
20
)
{
logit
(
"NX> 280 TIME! Spent: %d ms waiting for data"
,
diff_ts
);
}
#endif
last_ts
=
nx_get_timestamp
();
#endif
if
(
selected
<=
0
)
{
if
(
selected
<
0
)
{
#ifdef DEBUG
logit
(
"NX> 280 Got error: %d in select: '%s' at: %s"
,
errno
,
strerror
(
errno
),
nx_dump_timestamp
());
#endif
if
(
errno
==
EINTR
)
{
continue
;
}
}
error
(
"NX> 280 Failed select on input descriptors"
);
goto
nx_run_server_side_loop_end
;
}
while
(
selected
--
>
0
)
{
if
(
FD_ISSET
(
channel_in
,
&
set
))
{
#ifdef DEBUG
logit
(
"NX> 280 Channel in descriptor is selected"
);
#endif
in_fd
=
channel_in
;
out_fd
=
proxy_out
;
}
else
{
#ifdef DEBUG
logit
(
"NX> 280 Proxy in descriptor is selected"
);
#endif
in_fd
=
proxy_in
;
out_fd
=
channel_out
;
}
FD_CLR
(
in_fd
,
&
set
);
for
(;;)
{
#ifdef DEBUG
logit
(
"NX> 280 Going to read from descriptor: %d at: %s"
,
in_fd
,
nx_dump_timestamp
());
#endif
length
=
read
(
in_fd
,
data
,
sizeof
(
data
));
#ifdef DEBUG
logit
(
"NX> 280 Read: %d bytes from descriptor: %d at: %s"
,
length
,
in_fd
,
nx_dump_timestamp
());
#endif
if
(
length
<=
0
)
{
if
(
length
<
0
)
{
#ifdef DEBUG
logit
(
"NX> 280 Got error: %d in read: '%s' at: %s"
,
errno
,
strerror
(
errno
),
nx_dump_timestamp
());
#endif
if
(
errno
==
EINTR
)
{
continue
;
}
}
#ifdef DEBUG
logit
(
"NX> 280 Error reading from descriptor: %d at: %s"
,
in_fd
,
nx_dump_timestamp
());
#endif
failed_fd
=
in_fd
;
goto
nx_run_server_side_loop_error
;
}
written
=
0
;
for
(;;)
{
result
=
write
(
out_fd
,
data
+
written
,
length
-
written
);
if
(
result
<=
0
)
{
if
(
result
<
0
)
{
#ifdef DEBUG
logit
(
"NX> 280 Got error: %d in write: '%s' at: %s"
,
errno
,
strerror
(
errno
),
nx_dump_timestamp
());
#endif
if
(
errno
==
EINTR
||
errno
==
EAGAIN
)
{
continue
;
}
}
#ifdef DEBUG
logit
(
"NX> 280 Error writing to descriptor: %d at: %s"
,
out_fd
,
nx_dump_timestamp
());
#endif
failed_fd
=
out_fd
;
goto
nx_run_server_side_loop_error
;
}
#ifdef DEBUG
logit
(
"NX> 280 Written: %d bytes to descriptor: %d at: %s"
,
result
,
out_fd
,
nx_dump_timestamp
());
#endif
written
+=
result
;
if
(
written
==
length
)
{
break
;
}
}
break
;
}
}
}
nx_run_server_side_loop_end:
error
(
"NX> 280 Exiting from the server side loop"
);
return
;
nx_run_server_side_loop_error:
#ifdef NX_SELECTIVELY_CLOSE_DESCRIPTORS
if
(
failed_fd
==
proxy_in
)
{
error
(
"NX> 290 Failed read on proxy descriptor"
);
close
(
proxy_in
);
close
(
channel_out
);
proxy_in_open
=
0
;
}
else
if
(
failed_fd
==
proxy_out
)
{
error
(
"NX> 290 Failed write on proxy descriptor"
);
close
(
proxy_out
);
close
(
channel_in
);
channel_in_open
=
0
;
}
else
if
(
failed_fd
==
channel_in
)
{
error
(
"NX> 290 Failed read on channel descriptor"
);
close
(
proxy_out
);
close
(
channel_in
);
channel_in_open
=
0
;
}
else
{
error
(
"NX> 290 Failed write on channel descriptor"
);
close
(
proxy_in
);
close
(
channel_out
);
proxy_in_open
=
0
;
}
#else
/* #ifdef NX_SELECTIVELY_CLOSE_DESCRIPTORS */
if
(
failed_fd
==
proxy_in
)
{
error
(
"NX> 290 Failed read on proxy descriptor"
);
}
else
if
(
failed_fd
==
proxy_out
)
{
error
(
"NX> 290 Failed write on proxy descriptor"
);
}
else
if
(
failed_fd
==
channel_in
)
{
error
(
"NX> 290 Failed read on channel descriptor"
);
}
else
{
error
(
"NX> 290 Failed write on channel descriptor"
);
}
close
(
proxy_in
);
close
(
proxy_out
);
close
(
channel_out
);
close
(
channel_in
);
proxy_in_open
=
0
;
channel_in_open
=
0
;
#endif
/* #ifdef NX_SELECTIVELY_CLOSE_DESCRIPTORS */
signal
(
SIGPIPE
,
nx_catch_pipe_signal
);
goto
nx_run_server_side_loop_start
;
}
void
nx_run_client_side_loop
(
int
proxy_fd
)
{
struct
timeval
timeout
;
close
(
proxy_fd
);
debug
(
"NX> 285 Entering the client side NX loop"
);
while
(
NXTransRunning
(
NX_FD_ANY
))
{
#ifdef DEBUG
debug
(
"NX> 280 Going to prepare a new NX loop"
);
#endif
timeout
.
tv_sec
=
10
;
timeout
.
tv_usec
=
0
;
NXTransContinue
(
&
timeout
);
#ifdef DEBUG
debug
(
"NX> 280 Completed execution of the NX loop"
);
#endif
}
debug
(
"NX> 285 Exiting from the client side loop"
);
return
;
}
void
nx_catch_timeout_signal
(
int
number
)
{
}
void
nx_catch_pipe_signal
(
int
number
)
{
}
void
nx_dump_buffer
(
struct
sshbuf
*
buffer
)
{
unsigned
int
i
;
unsigned
int
l
;
const
unsigned
char
*
p
=
sshbuf_ptr
(
buffer
);
size_t
blen
=
sshbuf_len
(
buffer
);
char
line
[
136
];
debug
(
"---"
);
for
(
i
=
0
,
l
=
0
;
i
<
blen
;
i
++
,
l
++
)
{
line
[
l
]
=
p
[
i
];
if
(
line
[
l
]
==
'%'
)
{
line
[
l
]
=
'?'
;
}
if
(
l
==
134
)
{
line
[
l
]
=
'\\'
;
line
[
l
+
1
]
=
'\0'
;
debug
(
"%s"
,
line
);
l
=
0
;
}
}
line
[
l
]
=
'\0'
;
if
(
line
[
0
]
!=
'\0'
)
{
debug
(
"%s"
,
line
);
}
debug
(
"---"
);
}
void
nx_dump_string
(
char
*
string
)
{
int
l
;
char
*
p
;
char
line
[
136
];
debug
(
"---"
);
for
(
p
=
string
,
l
=
0
;
*
p
!=
'\0'
;
p
++
,
l
++
)
{
line
[
l
]
=
*
p
;
if
(
line
[
l
]
==
'%'
)
{
line
[
l
]
=
'?'
;
}
if
(
l
==
134
)
{
line
[
l
]
=
'\\'
;
line
[
l
+
1
]
=
'\0'
;
debug
(
"%s"
,
line
);
l
=
0
;
}
}
line
[
l
]
=
'\0'
;
if
(
line
[
0
]
!=
'\0'
)
{
debug
(
"%s"
,
line
);
}
debug
(
"---"
);
}
#if defined(DEBUG) || defined(TIME)
struct
timeval
nx_get_timestamp
(
void
)
{
struct
timeval
ts
;
gettimeofday
(
&
ts
,
NULL
);
return
ts
;
}
int
nx_diff_timestamp
(
struct
timeval
ts1
,
struct
timeval
ts2
)
{
long
ms
;
if
(
ts1
.
tv_sec
==
0
&&
ts1
.
tv_usec
==
0
)
{
return
-
1
;
}
ms
=
((
ts2
.
tv_sec
*
1000
+
(
ts2
.
tv_usec
+
500
)
/
1000
)
-
(
ts1
.
tv_sec
*
1000
+
(
ts1
.
tv_usec
+
500
)
/
1000
));
return
(
ms
<
0
?
-
1
:
ms
);
}
char
*
nx_dump_timestamp
(
void
)
{
char
ctime_new
[
25
];
char
*
ctime_now
;
struct
timeval
ts
=
nx_get_timestamp
();
ctime_now
=
ctime
((
time_t
*
)
&
ts
.
tv_sec
);
sprintf
(
ctime_new
,
"%.8s:%3.3f"
,
ctime_now
+
11
,
(
float
)
ts
.
tv_usec
/
1000
);
strncpy
(
ctime_now
,
ctime_new
,
24
);
return
ctime_now
;
}
#endif
char
*
nx_search_string_in_buffer
(
struct
sshbuf
*
buffer
,
char
*
start
,
const
char
*
string
)
{
char
*
end
;
char
*
found
;
int
temporary
;
if
(
start
==
NULL
)
{
start
=
(
char
*
)
sshbuf_ptr
(
buffer
);
}
end
=
nx_search_newline_in_buffer
(
buffer
,
start
);
if
(
end
==
NULL
)
{
return
NULL
;
}
temporary
=
*
end
;
*
end
=
'\0'
;
found
=
strstr
(
start
,
string
);
*
end
=
temporary
;
return
found
;
}
char
*
nx_remove_string_in_buffer
(
struct
sshbuf
*
buffer
,
char
*
start
,
int
length
)
{
int
left
;
int
right
;
int
r
;
char
*
temporary
=
xmalloc
(
sshbuf_len
(
buffer
));
if
(
temporary
==
NULL
)
{
fatal
(
"NX> 298 Out of memory creating the temporary string"
);
return
NULL
;
}
left
=
start
-
(
char
*
)
sshbuf_ptr
(
buffer
);
debug
(
"NX> 280 Lefthand remaining bytes are: %d"
,
left
);
memcpy
(
temporary
,
sshbuf_ptr
(
buffer
),
left
);
debug
(
"NX> 280 Righthand remaining bytes are: %d"
,
left
);
right
=
sshbuf_len
(
buffer
)
-
left
-
length
-
1
;
memcpy
(
temporary
+
left
,
start
+
length
+
1
,
right
);
left
+=
right
;
debug
(
"NX> 280 Total remaining bytes are: %d"
,
left
);
sshbuf_reset
(
buffer
);
if
((
r
=
sshbuf_put
(
buffer
,
temporary
,
left
))
!=
0
)
fatal
(
"NX> 298 sshbuf_put failed: %d"
,
r
);
free
(
temporary
);
return
(
char
*
)
sshbuf_ptr
(
buffer
);
}
char
*
nx_search_char_in_buffer
(
struct
sshbuf
*
buffer
,
char
*
start
,
int
value
)
{
char
*
end
;
char
*
found
;
int
temporary
;
if
(
start
==
NULL
)
{
start
=
(
char
*
)
sshbuf_ptr
(
buffer
);
}
end
=
nx_search_newline_in_buffer
(
buffer
,
start
);
if
(
end
==
NULL
)
{
return
NULL
;
}
temporary
=
*
end
;
*
end
=
'\0'
;
found
=
strchr
(
start
,
value
);
*
end
=
temporary
;
return
found
;
}
char
*
nx_search_newline_in_buffer
(
struct
sshbuf
*
buffer
,
char
*
start
)
{
char
*
p
;
char
*
end
;
if
(
start
==
NULL
)
{
start
=
(
char
*
)
sshbuf_ptr
(
buffer
);
}
end
=
(
char
*
)
sshbuf_ptr
(
buffer
)
+
sshbuf_len
(
buffer
);
for
(
p
=
start
;
p
<
end
;
p
++
)
{
if
(
*
p
==
'\n'
||
*
p
==
'\r'
||
*
p
==
'\0'
)
{
return
p
;
}
}
return
NULL
;
}
char
*
nx_remove_newline_in_string
(
char
*
start
,
int
length
)
{
char
*
p
;
char
*
end
=
start
+
length
;
for
(
p
=
start
;
p
<
end
;
p
++
)
{
if
(
*
p
==
'\n'
||
*
p
==
'\r'
)
{
*
p
=
'\0'
;
}
}
return
start
;
}
char
*
nx_toupper_string
(
char
*
start
)
{
char
*
p
;
for
(
p
=
start
;
*
p
!=
'\0'
;
p
++
)
{
*
p
=
toupper
(
*
p
);
}
return
start
;
}
void
nx_wait_timeout
(
int
seconds
)
{
struct
timeval
timeout
;
timeout
.
tv_sec
=
seconds
;
timeout
.
tv_usec
=
0
;
select
(
0
,
NULL
,
NULL
,
NULL
,
&
timeout
);
return
;
}
void
nx_set_socket_options
(
int
fd
,
int
blocking
)
{
if
(
0
)
{
nx_set_keepalive
(
fd
);
}
if
(
blocking
==
1
)
{
nx_set_blocking
(
fd
);
}
else
{
nx_set_nonblocking
(
fd
);
}
nx_set_nodelay
(
fd
);
nx_set_lowdelay
(
fd
);
}
static
int
nx_set_nonblocking
(
int
fd
)
{
int
flags
;
debug
(
"NX> 286 Setting O_NONBLOCK on descriptor: %d"
,
fd
);
flags
=
fcntl
(
fd
,
F_GETFL
);
if
(
flags
>=
0
)
{
flags
|=
O_NONBLOCK
;
}
if
(
flags
<
0
||
fcntl
(
fd
,
F_SETFL
,
flags
)
<
0
)
{
error
(
"NX> 286 Failed to set O_NONBLOCK on descriptor: %d"
,
fd
);
return
-
1
;
}
debug
(
"NX> 286 Set O_NONBLOCK on descriptor: %d"
,
fd
);
return
1
;
}
static
int
nx_set_blocking
(
int
fd
)
{
int
flags
;
debug
(
"NX> 286 Resetting O_NONBLOCK on descriptor: %d"
,
fd
);
flags
=
fcntl
(
fd
,
F_GETFL
);
if
(
flags
>=
0
)
{
flags
&=
~
O_NONBLOCK
;
}
if
(
flags
<
0
||
fcntl
(
fd
,
F_SETFL
,
flags
)
<
0
)
{
error
(
"NX> 286 Failed to reset O_NONBLOCK on descriptor: %d"
,
fd
);
return
-
1
;
}
debug
(
"NX> 286 Reset O_NONBLOCK on descriptor: %d"
,
fd
);
return
1
;
}
static
int
nx_set_nodelay
(
int
fd
)
{
int
result
;
int
flag
=
1
;
debug
(
"NX> 286 Trying TCP_NODELAY on descriptor: %d"
,
fd
);
result
=
setsockopt
(
fd
,
IPPROTO_TCP
,
TCP_NODELAY
,
&
flag
,
sizeof
(
flag
));
if
(
result
==
0
)
{
result
=
1
;
}
else
if
(
result
<
0
)
{
#if defined(__APPLE__) || defined(__sun)
if
(
errno
==
ENOPROTOOPT
)
{
result
=
0
;
}
#else
if
(
errno
==
EOPNOTSUPP
)
{
result
=
0
;
}
#endif
}
if
(
result
<
0
)
{
error
(
"NX> 286 Failed to set TCP_NODELAY on descriptor: %d"
,
fd
);
}
else
if
(
result
==
0
)
{
debug
(
"NX> 286 Option TCP_NODELAY not supported on: %d"
,
fd
);
}
else
{
debug
(
"NX> 286 Set TCP_NODELAY on descriptor: %d"
,
fd
);
}
return
result
;
}
static
int
nx_set_keepalive
(
int
fd
)
{
int
result
;
int
flag
=
1
;
debug
(
"NX> 286 Trying SO_KEEPALIVE on descriptor: %d"
,
fd
);
result
=
setsockopt
(
fd
,
SOL_SOCKET
,
SO_KEEPALIVE
,
&
flag
,
sizeof
(
flag
));
if
(
result
<
0
)
{
error
(
"NX> 286 Failed to set SO_KEEPALIVE on descriptor: %d"
,
fd
);
}
else
{
debug
(
"NX> 286 Set SO_KEEPALIVE on descriptor: %d"
,
fd
);
}
return
result
;
}
static
int
nx_set_lowdelay
(
int
fd
)
{
int
result
;
int
flag
=
IPTOS_LOWDELAY
;
#if defined(__CYGWIN32__)
return
0
;
#endif
debug
(
"NX> 286 Trying IPTOS_LOWDELAY on descriptor: %d"
,
fd
);
result
=
setsockopt
(
fd
,
IPPROTO_IP
,
IP_TOS
,
&
flag
,
sizeof
(
flag
));
if
(
result
==
0
)
{
result
=
1
;
}
else
if
(
result
<
0
)
{
#if defined(__APPLE__) || defined(__sun)
if
(
errno
==
ENOPROTOOPT
)
{
result
=
0
;
}
#else
if
(
errno
==
EOPNOTSUPP
)
{
result
=
0
;
}
#endif
}
if
(
result
<
0
)
{
error
(
"NX> 286 Failed to set IPTOS_LOWDELAY on descriptor: %d"
,
fd
);
}
else
if
(
result
==
0
)
{
debug
(
"NX> 286 Option IPTOS_LOWDELAY not supported on: %d"
,
fd
);
}
else
{
debug
(
"NX> 286 Set IPTOS_LOWDELAY on descriptor: %d"
,
fd
);
}
return
result
;
}
void
nx_redirect_log_output
(
void
)
{
FILE
*
file
;
char
name
[
1024
];
const
char
*
proxy_log
;
logit
(
"NX> 285 Enabling redirection of debug output"
);
proxy_log
=
NXTransFile
(
NX_FILE_ERRORS
);
if
(
proxy_log
!=
NULL
)
{
logit
(
"NX> 280 Using proxy log file: %s"
,
proxy_log
);
strncpy
(
name
,
proxy_log
,
1023
);
*
(
name
+
1023
)
=
'\0'
;
}
else
{
#ifdef NX_SHARE_BINDER_LOG
strcpy
(
name
,
"/tmp/errors"
);
logit
(
"NX> 280 Using log file: %s"
,
name
);
#else
logit
(
"NX> 280 Can't determine the name of the proxy log"
);
return
;
#endif
}
file
=
fopen
(
name
,
"a"
);
if
(
file
==
NULL
)
{
logit
(
"NX> 280 Can't open log file: %s error is: %d, '%s'"
,
name
,
errno
,
strerror
(
errno
));
}
else
{
if
(
dup2
(
fileno
(
file
),
fileno
(
stderr
))
==
-
1
)
{
logit
(
"NX> 280 Can't redirect the log to file: %s"
,
name
);
}
}
}
const
char
*
nx_get_environment
(
const
char
*
name
)
{
return
getenv
(
name
);
}
int
nx_set_environment
(
const
char
*
name
,
const
char
*
value
)
{
#ifdef __sun
static
char
buffer
[
1024
];
snprintf
(
buffer
,
1024
-
1
,
"%s=%s"
,
name
,
value
);
*
(
buffer
+
1023
)
=
'\0'
;
return
putenv
(
buffer
);
#else
return
setenv
(
name
,
value
,
1
);
#endif
}
proxy.h
0 → 100644
View file @
68530f0f
/**************************************************************************/
/* */
/* Copyright (c) 2001, 2011 NoMachine, http://www.nomachine.com/. */
/* */
/* NXSSH, NX protocol compression and NX extensions to this software */
/* are copyright of NoMachine. Redistribution and use of the present */
/* software is allowed according to terms specified in the file LICENSE */
/* which comes in the source distribution. */
/* */
/* Check http://www.nomachine.com/licensing.html for applicability. */
/* */
/* NX and NoMachine are trademarks of Medialogic S.p.A. */
/* */
/* All rights reserved. */
/* */
/**************************************************************************/
#ifndef _NX_PROXY_H
#define _NX_PROXY_H
#include <poll.h>
struct
Channel
;
/*
* Do we share the debug log with the proxy?
*/
extern
int
nx_proxy_log
;
/*
* Do we check for the incoming command? This flag
* should be bound to a command line parameter.
*/
extern
int
nx_check_switch
;
/*
* Did we receive the command? Switch is executed at
* the time the first channel receives the command,
* thus it is important to note that port forwarding
* should always be disabled when enabling the check.
*/
extern
int
nx_switch_received
;
/*
* Parameters read from the switch command.
*/
extern
char
nx_switch_cookie
[
256
];
extern
char
nx_switch_host
[
256
];
extern
int
nx_switch_proxy
;
extern
int
nx_switch_port
;
extern
int
nx_switch_in
;
extern
int
nx_switch_out
;
extern
char
nx_switch_mode
[
256
];
extern
char
nx_switch_options
[
1024
];
extern
int
nx_switch_internal
;
extern
int
nx_switch_forward
;
/*
* Buffer the input while looking for the command.
* The buffering happens by flushing the input when
* a newline is received. This means that all input
* should be terminated with a newline or it will
* remain in the buffer and will never be sent to
* the packet interface.
*/
int
nx_check_channel_input
(
struct
Channel
*
channel
,
char
*
data
,
int
*
length
,
int
limit
);
/*
* Init buffer
*/
void
nx_proxy_init
(
void
);
/*
* Replace the select() with the version managing
* the NX descriptors.
*/
int
nx_proxy_select
(
int
maxfds
,
fd_set
*
readfds
,
fd_set
*
writefds
,
fd_set
*
exceptfds
,
struct
timeval
*
timeout
);
/*
* ppoll()-compatible wrapper that integrates NX transport.
* Converts pollfd[] <-> fd_set, calls NXTransPrepare/Select/Execute.
*/
int
nx_proxy_ppoll
(
struct
pollfd
*
fds
,
nfds_t
nfds
,
const
struct
timespec
*
timeout
,
const
sigset_t
*
sigmask
);
/*
* Connect to the NX transport.
*/
int
nx_open_proxy_connection
(
void
);
/*
* Wait for the NX transport to terminate.
*/
int
nx_close_proxy_connection
(
void
);
/*
* If cookie was passed, manage authentication.
*/
int
nx_check_proxy_authentication
(
int
proxy_fd
);
/*
* Reassign the descriptors.
*/
int
nx_switch_client_side_descriptors
(
struct
Channel
*
channel
,
int
proxy_fd
);
/*
* Reassign the descriptors.
*/
int
nx_switch_forward_descriptors
(
struct
Channel
*
channel
);
/*
* Forward port to remote sshd.
*/
int
nx_switch_forward_port
(
struct
Channel
*
channel
);
/*
* Used in ssh.c to monitor the standard input
* until the switch command is received.
*/
int
nx_check_standard_input
(
void
);
/*
* Used in ssh.c. Connect to the proxy and run the
* restricted loop forwarding the traffic to the
* local proxy.
*/
void
nx_switch_server_side_descriptors
(
void
);
/*
* Set the preferred options for using the socket
* with NX.
*/
void
nx_set_socket_options
(
int
fd
,
int
blocking
);
/*
* Wrappers used to get and set the environment.
*/
const
char
*
nx_get_environment
(
const
char
*
name
);
int
nx_set_environment
(
const
char
*
name
,
const
char
*
value
);
#endif
/* _NX_PROXY_H */
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment