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