]> git.proxmox.com Git - pve-xtermjs.git/blame - src/main.rs
bump version to 4.12.0-1
[pve-xtermjs.git] / src / main.rs
CommitLineData
3e4311fe
DC
1use std::cmp::min;
2use std::collections::HashMap;
3use std::ffi::{OsStr, OsString};
9c147346 4use std::io::{ErrorKind, Result, Write};
3e4311fe
DC
5use std::os::unix::io::{AsRawFd, FromRawFd};
6use std::os::unix::process::CommandExt;
7use std::process::Command;
8use std::time::{Duration, Instant};
9
10use clap::{App, AppSettings, Arg};
11use curl::easy::Easy;
c2d63828 12use mio::net::{TcpListener, TcpStream};
c2d63828 13use mio::unix::SourceFd;
3c586bd8 14use mio::{Events, Interest, Poll, Token};
3e4311fe
DC
15
16use proxmox::sys::error::io_err_other;
17use proxmox::sys::linux::pty::{make_controlling_terminal, PTY};
18use proxmox::tools::byte_buffer::ByteBuffer;
19use proxmox::{io_bail, io_format_err};
20
21const MSG_TYPE_DATA: u8 = 0;
22const MSG_TYPE_RESIZE: u8 = 1;
23//const MSG_TYPE_PING: u8 = 2;
24
25fn remove_number(buf: &mut ByteBuffer) -> Option<usize> {
26 loop {
27 if let Some(pos) = &buf.iter().position(|&x| x == b':') {
28 let data = buf.remove_data(*pos);
29 buf.consume(1); // the ':'
30 let len = match std::str::from_utf8(&data) {
31 Ok(lenstring) => match lenstring.parse() {
32 Ok(len) => len,
33 Err(err) => {
34 eprintln!("error parsing number: '{}'", err);
35 break;
36 }
37 },
38 Err(err) => {
39 eprintln!("error parsing number: '{}'", err);
40 break;
41 }
42 };
43 return Some(len);
44 } else if buf.len() > 20 {
45 buf.consume(20);
46 } else {
47 break;
48 }
49 }
50 None
51}
52
53fn process_queue(buf: &mut ByteBuffer, pty: &mut PTY) -> Option<usize> {
54 if buf.is_empty() {
55 return None;
56 }
57
58 loop {
59 if buf.len() < 2 {
60 break;
61 }
62
63 let msgtype = buf[0] - b'0';
64
65 if msgtype == MSG_TYPE_DATA {
66 buf.consume(2);
67 if let Some(len) = remove_number(buf) {
68 return Some(len);
69 }
70 } else if msgtype == MSG_TYPE_RESIZE {
71 buf.consume(2);
72 if let Some(cols) = remove_number(buf) {
73 if let Some(rows) = remove_number(buf) {
74 pty.set_size(cols as u16, rows as u16).ok()?;
75 }
76 }
77 // ignore incomplete messages
78 } else {
79 buf.consume(1);
80 // ignore invalid or ping (msgtype 2)
81 }
82 }
83
84 None
85}
86
87type TicketResult = Result<(Box<[u8]>, Box<[u8]>)>;
88
89/// Reads from the stream and returns the first line and the rest
90fn read_ticket_line(
91 stream: &mut TcpStream,
92 buf: &mut ByteBuffer,
93 timeout: Duration,
94) -> TicketResult {
7739b7e8 95 let mut poll = Poll::new()?;
3c586bd8
FG
96 poll.registry()
97 .register(stream, Token(0), Interest::READABLE)?;
7739b7e8 98 let mut events = Events::with_capacity(1);
41f32a28
FG
99
100 let now = Instant::now();
101 let mut elapsed = Duration::new(0, 0);
7739b7e8
DC
102
103 loop {
41f32a28 104 poll.poll(&mut events, Some(timeout - elapsed))?;
7739b7e8
DC
105 if !events.is_empty() {
106 match buf.read_from(stream) {
107 Ok(n) => {
108 if n == 0 {
109 io_bail!("connection closed before authentication");
110 }
3e4311fe 111 }
7739b7e8
DC
112 Err(err) if err.kind() == ErrorKind::WouldBlock => {}
113 Err(err) => return Err(err),
114 }
115
116 if buf[..].contains(&b'\n') {
117 break;
118 }
119
120 if buf.is_full() {
121 io_bail!("authentication data is incomplete: {:?}", &buf[..]);
3e4311fe 122 }
7739b7e8
DC
123 }
124
41f32a28
FG
125 elapsed = now.elapsed();
126 if elapsed > timeout {
7739b7e8 127 io_bail!("timed out");
3e4311fe
DC
128 }
129 }
130
3e4311fe
DC
131 let newline_idx = &buf[..].iter().position(|&x| x == b'\n').unwrap();
132
133 let line = buf.remove_data(*newline_idx);
134 buf.consume(1); // discard newline
135
136 match line.iter().position(|&b| b == b':') {
137 Some(pos) => {
138 let (username, ticket) = line.split_at(pos);
139 Ok((username.into(), ticket[1..].into()))
140 }
141 None => io_bail!("authentication data is invalid"),
142 }
143}
144
145fn authenticate(
146 username: &[u8],
147 ticket: &[u8],
148 path: &str,
149 perm: Option<&str>,
150 authport: u16,
151 port: Option<u16>,
152) -> Result<()> {
153 let mut curl = Easy::new();
154 curl.url(&format!(
155 "http://localhost:{}/api2/json/access/ticket",
156 authport
157 ))?;
158
159 let username = curl.url_encode(username);
160 let ticket = curl.url_encode(ticket);
161 let path = curl.url_encode(path.as_bytes());
162
163 let mut post_fields = Vec::with_capacity(5);
164 post_fields.push(format!("username={}", username));
165 post_fields.push(format!("password={}", ticket));
166 post_fields.push(format!("path={}", path));
167
168 if let Some(perm) = perm {
169 let perm = curl.url_encode(perm.as_bytes());
170 post_fields.push(format!("privs={}", perm));
171 }
172
173 if let Some(port) = port {
174 post_fields.push(format!("port={}", port));
175 }
176
177 curl.post_fields_copy(post_fields.join("&").as_bytes())?;
178 curl.post(true)?;
179 curl.perform()?;
180
181 let response_code = curl.response_code()?;
182
183 if response_code != 200 {
184 io_bail!("invalid authentication, code {}", response_code);
185 }
186
187 Ok(())
188}
189
190fn listen_and_accept(
191 hostname: &str,
192 port: u64,
193 port_as_fd: bool,
194 timeout: Duration,
195) -> Result<(TcpStream, u16)> {
196 let listener = if port_as_fd {
197 unsafe { std::net::TcpListener::from_raw_fd(port as i32) }
198 } else {
199 std::net::TcpListener::bind((hostname, port as u16))?
200 };
201 let port = listener.local_addr()?.port();
c2d63828
DC
202 let mut listener = TcpListener::from_std(listener);
203 let mut poll = Poll::new()?;
3e4311fe 204
3c586bd8
FG
205 poll.registry()
206 .register(&mut listener, Token(0), Interest::READABLE)?;
3e4311fe
DC
207
208 let mut events = Events::with_capacity(1);
209
41f32a28
FG
210 let now = Instant::now();
211 let mut elapsed = Duration::new(0, 0);
212
3e4311fe 213 loop {
41f32a28 214 poll.poll(&mut events, Some(timeout - elapsed))?;
3e4311fe 215 if !events.is_empty() {
c2d63828 216 let (stream, client) = listener.accept()?;
3e4311fe
DC
217 println!("client connection: {:?}", client);
218 return Ok((stream, port));
219 }
41f32a28
FG
220
221 elapsed = now.elapsed();
222 if elapsed > timeout {
3e4311fe
DC
223 io_bail!("timed out");
224 }
225 }
226}
227
228fn run_pty(cmd: &OsStr, params: clap::OsValues) -> Result<PTY> {
229 let (mut pty, secondary_name) = PTY::new().map_err(io_err_other)?;
230
231 let mut filtered_env: HashMap<OsString, OsString> = std::env::vars_os()
232 .filter(|&(ref k, _)| {
233 k == "PATH"
234 || k == "USER"
235 || k == "HOME"
236 || k == "LANG"
237 || k == "LANGUAGE"
238 || k.to_string_lossy().starts_with("LC_")
239 })
240 .collect();
241 filtered_env.insert("TERM".into(), "xterm-256color".into());
242
243 let mut command = Command::new(cmd);
244
245 command.args(params).env_clear().envs(&filtered_env);
246
247 unsafe {
248 command.pre_exec(move || {
249 make_controlling_terminal(&secondary_name).map_err(io_err_other)?;
250 Ok(())
251 });
252 }
253
254 command.spawn()?;
255
256 pty.set_size(80, 20).map_err(|x| x.as_errno().unwrap())?;
257 Ok(pty)
258}
259
260const TCP: Token = Token(0);
261const PTY: Token = Token(1);
262
263fn do_main() -> Result<()> {
264 let matches = App::new("termproxy")
265 .setting(AppSettings::TrailingVarArg)
266 .arg(Arg::with_name("port").takes_value(true).required(true))
267 .arg(
268 Arg::with_name("authport")
269 .takes_value(true)
270 .long("authport"),
271 )
272 .arg(Arg::with_name("use-port-as-fd").long("port-as-fd"))
273 .arg(
274 Arg::with_name("path")
275 .takes_value(true)
276 .long("path")
277 .required(true),
278 )
279 .arg(Arg::with_name("perm").takes_value(true).long("perm"))
280 .arg(Arg::with_name("cmd").multiple(true).required(true))
281 .get_matches();
282
283 let port: u64 = matches
284 .value_of("port")
285 .unwrap()
286 .parse()
287 .map_err(io_err_other)?;
288 let path = matches.value_of("path").unwrap();
289 let perm: Option<&str> = matches.value_of("perm");
290 let mut cmdparams = matches.values_of_os("cmd").unwrap();
291 let cmd = cmdparams.next().unwrap();
292 let authport: u16 = matches
293 .value_of("authport")
294 .unwrap_or("85")
295 .parse()
296 .map_err(io_err_other)?;
297 let mut pty_buf = ByteBuffer::new();
298 let mut tcp_buf = ByteBuffer::new();
299
300 let use_port_as_fd = matches.is_present("use-port-as-fd");
301
302 if use_port_as_fd && port > u16::MAX as u64 {
303 return Err(io_format_err!("port too big"));
304 } else if port > i32::MAX as u64 {
305 return Err(io_format_err!("Invalid FD number"));
306 }
307
c2d63828 308 let (mut tcp_handle, port) =
3e4311fe
DC
309 listen_and_accept("localhost", port, use_port_as_fd, Duration::new(10, 0))
310 .map_err(|err| io_format_err!("failed waiting for client: {}", err))?;
311
c2d63828 312 let (username, ticket) = read_ticket_line(&mut tcp_handle, &mut pty_buf, Duration::new(10, 0))
3e4311fe
DC
313 .map_err(|err| io_format_err!("failed reading ticket: {}", err))?;
314 let port = if use_port_as_fd { Some(port) } else { None };
315 authenticate(&username, &ticket, path, perm, authport, port)?;
c2d63828 316 tcp_handle.write_all(b"OK").expect("error writing response");
3e4311fe 317
c2d63828 318 let mut poll = Poll::new()?;
3e4311fe
DC
319 let mut events = Events::with_capacity(128);
320
321 let mut pty = run_pty(cmd, cmdparams)?;
322
c2d63828
DC
323 poll.registry().register(
324 &mut tcp_handle,
3e4311fe 325 TCP,
bb32b0c6 326 Interest::READABLE | Interest::WRITABLE,
3e4311fe 327 )?;
c2d63828
DC
328 poll.registry().register(
329 &mut SourceFd(&pty.as_raw_fd()),
3e4311fe 330 PTY,
bb32b0c6 331 Interest::READABLE | Interest::WRITABLE,
3e4311fe
DC
332 )?;
333
334 let mut tcp_writable = true;
335 let mut pty_writable = true;
336 let mut tcp_readable = true;
337 let mut pty_readable = true;
338 let mut remaining = 0;
339 let mut finished = false;
340
341 while !finished {
342 if tcp_readable && !pty_buf.is_full() || pty_readable && !tcp_buf.is_full() {
343 poll.poll(&mut events, Some(Duration::new(0, 0)))?;
344 } else {
345 poll.poll(&mut events, None)?;
346 }
347
348 for event in &events {
c2d63828
DC
349 let writable = event.is_writable();
350 let readable = event.is_readable();
351 if event.is_read_closed() {
3e4311fe
DC
352 finished = true;
353 }
354 match event.token() {
355 TCP => {
356 if readable {
357 tcp_readable = true;
358 }
359 if writable {
360 tcp_writable = true;
361 }
362 }
363 PTY => {
364 if readable {
365 pty_readable = true;
366 }
367 if writable {
368 pty_writable = true;
369 }
370 }
371 _ => unreachable!(),
372 }
373 }
374
375 while tcp_readable && !pty_buf.is_full() {
376 let bytes = match pty_buf.read_from(&mut tcp_handle) {
377 Ok(bytes) => bytes,
378 Err(err) if err.kind() == ErrorKind::WouldBlock => {
379 tcp_readable = false;
380 break;
381 }
382 Err(err) => {
383 if !finished {
384 return Err(io_format_err!("error reading from tcp: {}", err));
385 }
386 break;
387 }
388 };
389 if bytes == 0 {
390 finished = true;
391 break;
392 }
393 }
394
395 while pty_readable && !tcp_buf.is_full() {
396 let bytes = match tcp_buf.read_from(&mut pty) {
397 Ok(bytes) => bytes,
398 Err(err) if err.kind() == ErrorKind::WouldBlock => {
399 pty_readable = false;
400 break;
401 }
402 Err(err) => {
403 if !finished {
404 return Err(io_format_err!("error reading from pty: {}", err));
405 }
406 break;
407 }
408 };
409 if bytes == 0 {
410 finished = true;
411 break;
412 }
413 }
414
415 while !tcp_buf.is_empty() && tcp_writable {
416 let bytes = match tcp_handle.write(&tcp_buf[..]) {
417 Ok(bytes) => bytes,
418 Err(err) if err.kind() == ErrorKind::WouldBlock => {
419 tcp_writable = false;
420 break;
421 }
422 Err(err) => {
423 if !finished {
424 return Err(io_format_err!("error writing to tcp : {}", err));
425 }
426 break;
427 }
428 };
429 tcp_buf.consume(bytes);
430 }
431
432 while !pty_buf.is_empty() && pty_writable {
433 if remaining == 0 {
434 remaining = match process_queue(&mut pty_buf, &mut pty) {
435 Some(val) => val,
436 None => break,
437 };
438 }
439 let len = min(remaining, pty_buf.len());
440 let bytes = match pty.write(&pty_buf[..len]) {
441 Ok(bytes) => bytes,
442 Err(err) if err.kind() == ErrorKind::WouldBlock => {
443 pty_writable = false;
444 break;
445 }
446 Err(err) => {
447 if !finished {
448 return Err(io_format_err!("error writing to pty : {}", err));
449 }
450 break;
451 }
452 };
453 remaining -= bytes;
454 pty_buf.consume(bytes);
455 }
456 }
457
458 Ok(())
459}
460
461fn main() {
462 std::process::exit(match do_main() {
463 Ok(_) => 0,
464 Err(err) => {
465 eprintln!("{}", err);
466 1
467 }
468 });
469}