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