2 use std
::collections
::HashMap
;
3 use std
::ffi
::OsString
;
4 use std
::io
::{ErrorKind, Write}
;
5 use std
::os
::unix
::io
::{AsRawFd, FromRawFd}
;
6 use std
::os
::unix
::process
::CommandExt
;
7 use std
::process
::Command
;
8 use std
::time
::{Duration, Instant}
;
10 use anyhow
::{bail, format_err, Result}
;
12 use mio
::net
::{TcpListener, TcpStream}
;
13 use mio
::unix
::SourceFd
;
14 use mio
::{Events, Interest, Poll, Token}
;
16 use proxmox_io
::ByteBuffer
;
17 use proxmox_lang
::error
::io_err_other
;
18 use proxmox_sys
::linux
::pty
::{make_controlling_terminal, PTY}
;
20 const MSG_TYPE_DATA
: u8 = 0;
21 const MSG_TYPE_RESIZE
: u8 = 1;
22 //const MSG_TYPE_PING: u8 = 2;
24 fn remove_number(buf
: &mut ByteBuffer
) -> Option
<usize> {
26 if let Some(pos
) = &buf
.iter().position(|&x
| x
== b'
:'
) {
27 let data
= buf
.remove_data(*pos
);
28 buf
.consume(1); // the ':'
29 let len
= match std
::str::from_utf8(&data
) {
30 Ok(lenstring
) => match lenstring
.parse() {
33 eprintln
!("error parsing number: '{}'", err
);
38 eprintln
!("error parsing number: '{}'", err
);
43 } else if buf
.len() > 20 {
52 fn process_queue(buf
: &mut ByteBuffer
, pty
: &mut PTY
) -> Option
<usize> {
62 let msgtype
= buf
[0] - b'
0'
;
64 if msgtype
== MSG_TYPE_DATA
{
66 if let Some(len
) = remove_number(buf
) {
69 } else if msgtype
== MSG_TYPE_RESIZE
{
71 if let Some(cols
) = remove_number(buf
) {
72 if let Some(rows
) = remove_number(buf
) {
73 pty
.set_size(cols
as u16, rows
as u16).ok()?
;
76 // ignore incomplete messages
79 // ignore invalid or ping (msgtype 2)
86 type TicketResult
= Result
<(Box
<[u8]>, Box
<[u8]>)>;
88 /// Reads from the stream and returns the first line and the rest
90 stream
: &mut TcpStream
,
94 let mut poll
= Poll
::new()?
;
96 .register(stream
, Token(0), Interest
::READABLE
)?
;
97 let mut events
= Events
::with_capacity(1);
99 let now
= Instant
::now();
100 let mut elapsed
= Duration
::new(0, 0);
103 poll
.poll(&mut events
, Some(timeout
- elapsed
))?
;
104 if !events
.is_empty() {
105 match buf
.read_from(stream
) {
108 bail
!("connection closed before authentication");
111 Err(err
) if err
.kind() == ErrorKind
::WouldBlock
=> {}
112 Err(err
) => return Err(err
.into()),
115 if buf
[..].contains(&b'
\n'
) {
120 bail
!("authentication data is incomplete: {:?}", &buf
[..]);
124 elapsed
= now
.elapsed();
125 if elapsed
> timeout
{
130 let newline_idx
= &buf
[..].iter().position(|&x
| x
== b'
\n'
).unwrap();
132 let line
= buf
.remove_data(*newline_idx
);
133 buf
.consume(1); // discard newline
135 match line
.iter().position(|&b
| b
== b'
:'
) {
137 let (username
, ticket
) = line
.split_at(pos
);
138 Ok((username
.into(), ticket
[1..].into()))
140 None
=> bail
!("authentication data is invalid"),
152 let mut post_fields
: Vec
<(&str, &str)> = Vec
::with_capacity(5);
153 post_fields
.push(("username", std
::str::from_utf8(username
)?
));
154 post_fields
.push(("password", std
::str::from_utf8(ticket
)?
));
155 post_fields
.push(("path", path
));
156 if let Some(perm
) = perm
{
157 post_fields
.push(("privs", perm
));
160 if let Some(port
) = port
{
161 port_str
= port
.to_string();
162 post_fields
.push(("port", &port_str
));
165 let url
= format
!("http://localhost:{}/api2/json/access/ticket", authport
);
167 match ureq
::post(&url
).send_form(&post_fields
[..]) {
168 Ok(res
) if res
.status() == 200 => Ok(()),
169 Ok(res
) | Err(ureq
::Error
::Status(_
, res
)) => {
170 let code
= res
.status();
171 bail
!("invalid authentication - {} {}", code
, res
.status_text())
173 Err(err
) => bail
!("authentication request failed - {}", err
),
177 fn listen_and_accept(
182 ) -> Result
<(TcpStream
, u16)> {
183 let listener
= if port_as_fd
{
184 unsafe { std::net::TcpListener::from_raw_fd(port as i32) }
186 std
::net
::TcpListener
::bind((hostname
, port
as u16))?
188 let port
= listener
.local_addr()?
.port();
189 let mut listener
= TcpListener
::from_std(listener
);
190 let mut poll
= Poll
::new()?
;
193 .register(&mut listener
, Token(0), Interest
::READABLE
)?
;
195 let mut events
= Events
::with_capacity(1);
197 let now
= Instant
::now();
198 let mut elapsed
= Duration
::new(0, 0);
201 poll
.poll(&mut events
, Some(timeout
- elapsed
))?
;
202 if !events
.is_empty() {
203 let (stream
, client
) = listener
.accept()?
;
204 println
!("client connection: {:?}", client
);
205 return Ok((stream
, port
));
208 elapsed
= now
.elapsed();
209 if elapsed
> timeout
{
215 fn run_pty
<'a
>(mut full_cmd
: impl Iterator
<Item
= &'a OsString
>) -> Result
<PTY
> {
216 let cmd_exe
= full_cmd
.next().unwrap();
217 let params
= full_cmd
; // rest
219 let (mut pty
, secondary_name
) = PTY
::new().map_err(io_err_other
)?
;
221 let mut filtered_env
: HashMap
<OsString
, OsString
> = std
::env
::vars_os()
222 .filter(|&(ref k
, _
)| {
228 || k
.to_string_lossy().starts_with("LC_")
231 filtered_env
.insert("TERM".into(), "xterm-256color".into());
233 let mut command
= Command
::new(cmd_exe
);
235 command
.args(params
).env_clear().envs(&filtered_env
);
238 command
.pre_exec(move || {
239 make_controlling_terminal(&secondary_name
).map_err(io_err_other
)?
;
246 pty
.set_size(80, 20)?
;
250 const TCP
: Token
= Token(0);
251 const PTY
: Token
= Token(1);
253 fn do_main() -> Result
<()> {
254 let matches
= clap
::builder
::Command
::new("termproxy")
255 .trailing_var_arg(true)
260 .value_parser(clap
::value_parser
!(u64)),
262 .arg(Arg
::new("authport").num_args(1).long("authport"))
263 .arg(Arg
::new("use-port-as-fd").long("port-as-fd"))
264 .arg(Arg
::new("path").num_args(1).long("path").required(true))
265 .arg(Arg
::new("perm").num_args(1).long("perm"))
268 .value_parser(clap
::value_parser
!(OsString
))
274 let port
: u64 = *matches
.get_one("port").unwrap();
275 let path
= matches
.get_one
::<String
>("path").unwrap();
276 let perm
= matches
.get_one
::<String
>("perm").map(|x
| x
.as_str());
277 let full_cmd
: clap
::parser
::ValuesRef
<OsString
> = matches
.get_many("cmd").unwrap();
278 let authport
: u16 = *matches
.get_one("authport").unwrap_or(&85);
279 let use_port_as_fd
= matches
.contains_id("use-port-as-fd");
281 if use_port_as_fd
&& port
> u16::MAX
as u64 {
282 return Err(format_err
!("port too big"));
283 } else if port
> i32::MAX
as u64 {
284 return Err(format_err
!("Invalid FD number"));
287 let (mut tcp_handle
, port
) =
288 listen_and_accept("localhost", port
, use_port_as_fd
, Duration
::new(10, 0))
289 .map_err(|err
| format_err
!("failed waiting for client: {}", err
))?
;
291 let mut pty_buf
= ByteBuffer
::new();
292 let mut tcp_buf
= ByteBuffer
::new();
294 let (username
, ticket
) = read_ticket_line(&mut tcp_handle
, &mut pty_buf
, Duration
::new(10, 0))
295 .map_err(|err
| format_err
!("failed reading ticket: {}", err
))?
;
296 let port
= if use_port_as_fd { Some(port) }
else { None }
;
297 authenticate(&username
, &ticket
, &path
, perm
.as_deref(), authport
, port
)?
;
298 tcp_handle
.write_all(b
"OK").expect("error writing response");
300 let mut poll
= Poll
::new()?
;
301 let mut events
= Events
::with_capacity(128);
303 let mut pty
= run_pty(full_cmd
)?
;
305 poll
.registry().register(
308 Interest
::READABLE
| Interest
::WRITABLE
,
310 poll
.registry().register(
311 &mut SourceFd(&pty
.as_raw_fd()),
313 Interest
::READABLE
| Interest
::WRITABLE
,
316 let mut tcp_writable
= true;
317 let mut pty_writable
= true;
318 let mut tcp_readable
= true;
319 let mut pty_readable
= true;
320 let mut remaining
= 0;
321 let mut finished
= false;
324 if tcp_readable
&& !pty_buf
.is_full() || pty_readable
&& !tcp_buf
.is_full() {
325 poll
.poll(&mut events
, Some(Duration
::new(0, 0)))?
;
327 poll
.poll(&mut events
, None
)?
;
330 for event
in &events
{
331 let writable
= event
.is_writable();
332 let readable
= event
.is_readable();
333 if event
.is_read_closed() {
336 match event
.token() {
357 while tcp_readable
&& !pty_buf
.is_full() {
358 let bytes
= match pty_buf
.read_from(&mut tcp_handle
) {
360 Err(err
) if err
.kind() == ErrorKind
::WouldBlock
=> {
361 tcp_readable
= false;
366 return Err(format_err
!("error reading from tcp: {}", err
));
377 while pty_readable
&& !tcp_buf
.is_full() {
378 let bytes
= match tcp_buf
.read_from(&mut pty
) {
380 Err(err
) if err
.kind() == ErrorKind
::WouldBlock
=> {
381 pty_readable
= false;
386 return Err(format_err
!("error reading from pty: {}", err
));
397 while !tcp_buf
.is_empty() && tcp_writable
{
398 let bytes
= match tcp_handle
.write(&tcp_buf
[..]) {
400 Err(err
) if err
.kind() == ErrorKind
::WouldBlock
=> {
401 tcp_writable
= false;
406 return Err(format_err
!("error writing to tcp : {}", err
));
411 tcp_buf
.consume(bytes
);
414 while !pty_buf
.is_empty() && pty_writable
{
416 remaining
= match process_queue(&mut pty_buf
, &mut pty
) {
421 let len
= min(remaining
, pty_buf
.len());
422 let bytes
= match pty
.write(&pty_buf
[..len
]) {
424 Err(err
) if err
.kind() == ErrorKind
::WouldBlock
=> {
425 pty_writable
= false;
430 return Err(format_err
!("error writing to pty : {}", err
));
436 pty_buf
.consume(bytes
);
444 std
::process
::exit(match do_main() {
447 eprintln
!("{}", err
);