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