chiark / gitweb /
Merge pull request #269 from michalbednarski/tmpfile
[termux-packages] / packages / mosh / mosh.cc.patch
1 --- ../mosh/src/frontend/mosh.cc        2016-04-29 00:42:24.837700203 +1000
2 +++ ./src/frontend/mosh.cc      2016-04-29 00:40:13.346294286 +1000
3 @@ -0,0 +1,519 @@
4 +//   Mosh: the mobile shell
5 +//   Copyright 2012 Keith Winstein
6 +//
7 +//   This program is free software: you can redistribute it and/or modify
8 +//   it under the terms of the GNU General Public License as published by
9 +//   the Free Software Foundation, either version 3 of the License, or
10 +//   (at your option) any later version.
11 +//
12 +//   This program is distributed in the hope that it will be useful,
13 +//   but WITHOUT ANY WARRANTY; without even the implied warranty of
14 +//   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 +//   GNU General Public License for more details.
16 +//
17 +//   You should have received a copy of the GNU General Public License
18 +//   along with this program.  If not, see <http://www.gnu.org/licenses/>.
19 +
20 +#include "config.h"
21 +#include <limits.h>
22 +#include <unistd.h>
23 +#include <stdlib.h>
24 +#include <stdarg.h>
25 +#include <string.h>
26 +#include <vector>
27 +#include <map>
28 +#include <stdio.h>
29 +#include <string>
30 +#include <sys/socket.h>
31 +#include <getopt.h>
32 +#include <arpa/inet.h>
33 +#include <netdb.h>
34 +#include <signal.h>
35 +#include <errno.h>
36 +#include <sys/ioctl.h>
37 +#include <sys/types.h>
38 +#include <sys/wait.h>
39 +#include <termios.h>
40 +#if HAVE_PTY_H
41 +#include <pty.h>
42 +#elif HAVE_UTIL_H
43 +#include <util.h>
44 +#endif
45 +
46 +#if OPENPTY_IN_LIBUTIL
47 +#include <libutil.h>
48 +#endif
49 +
50 +#if !HAVE_GETLINE
51 +ssize_t getline( char ** restrict linep,
52 +    size_t * restrict linecapp,
53 +    FILE * restrict stream ) {
54 +  size_t num_read = 0;
55 +  if ( !linep || !linecapp ) {
56 +    errno = EINVAL;
57 +    return -1;
58 +  }
59 +  if ( !*linep ) {
60 +    *linecapp = 0;
61 +  }
62 +  while ( 1 ) {
63 +    int ch = getc( stream );
64 +    if ( ch == EOF ) {
65 +      return num_read ? (ssize_t)num_read : -1;
66 +    }
67 +    if ( num_read + 1 >= *linecapp ) {
68 +      char *new_line = *linep;
69 +      size_t new_linecap = *linecapp;
70 +      if ( *linecapp > SSIZE_MAX - 4096 ) {
71 +        errno = EOVERFLOW;
72 +        return -1;
73 +      }
74 +      new_linecap += 4096;
75 +      new_line = (char *)realloc( new_line, new_linecap );
76 +      if ( !new_line ) {
77 +        return -1;
78 +      }
79 +      *linecapp = new_linecap;
80 +      *linep = new_line;
81 +    }
82 +    (*linep)[num_read++] = ch;
83 +    // so that we can safely return at any time,
84 +    (*linep)[num_read] = '\0';
85 +    if (ch == '\n') {
86 +      return num_read;
87 +    }
88 +  }
89 +  return -1;
90 +}
91 +#endif
92 +
93 +using namespace std;
94 +
95 +inline string shell_quote_string( const string &x )
96 +{
97 +  string result = "'";
98 +  string rest = x;
99 +  while ( rest.size() ) {
100 +    size_t good_part = rest.find( "'" );
101 +    result += rest.substr( 0, good_part );
102 +    if ( good_part != string::npos ) {
103 +      result += "'\\''";
104 +      rest = rest.substr( good_part + 1 );
105 +    } else {
106 +      break;
107 +    }
108 +  }
109 +  return result + "'";
110 +}
111 +
112 +template <typename SequenceT>
113 +inline string shell_quote( const SequenceT &sequence )
114 +{
115 +  string result;
116 +  for ( typename SequenceT::const_iterator i = sequence.begin();
117 +        i != sequence.end();
118 +        i++ ) {
119 +    result += shell_quote_string( *i ) + " ";
120 +  }
121 +  return result.substr( 0, result.size() - 1 );
122 +}
123 +
124 +void die( const char *format, ... ) {
125 +  va_list args;
126 +  va_start( args, format );
127 +  vfprintf( stderr, format, args );
128 +  va_end( args );
129 +  fprintf( stderr, "\n" );
130 +  exit( 255 );
131 +}
132 +
133 +static const char *usage_format =
134 +"Usage: %s [options] [--] [user@]host [command...]\n"
135 +"        --client=PATH        mosh client on local machine\n"
136 +"                                (default: mosh-client)\n"
137 +"        --server=PATH        mosh server on remote machine\n"
138 +"                                (default: mosh-server)\n"
139 +"\n"
140 +"        --predict=adaptive   local echo for slower links [default]\n"
141 +"-a      --predict=always     use local echo even on fast links\n"
142 +"-n      --predict=never      never use local echo\n"
143 +"\n"
144 +"-p NUM  --port=NUM           server-side UDP port\n"
145 +"\n"
146 +"-P NUM  --ssh-port=NUM       ssh server port\n"
147 +"                                (default: let the ssh command choose)\n"
148 +"\n"
149 +"        --ssh=COMMAND        ssh command to run when setting up session\n"
150 +"                                (example: \"ssh -p 2222\")\n"
151 +"                                (default: \"ssh\")\n"
152 +"\n"
153 +"        --help               this message\n"
154 +"        --version            version and copyright information\n"
155 +"\n"
156 +"Please report bugs to mosh-devel@mit.edu.\n"
157 +"Mosh home page: http://mosh.mit.edu";
158 +
159 +static const char *version_format =
160 +"mosh %s\n"
161 +"Copyright 2012 Keith Winstein <mosh-devel@mit.edu>\n"
162 +"License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>.\n"
163 +"This is free software: you are free to change and redistribute it.\n"
164 +"There is NO WARRANTY, to the extent permitted by law.";
165 +
166 +static const char *key_valid_char_set =
167 +"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789/+";
168 +
169 +static char *argv0;
170 +
171 +void predict_check( const string &predict, bool env_set )
172 +{
173 +  if ( predict != "adaptive" &&
174 +       predict != "always" &&
175 +       predict != "never" ) {
176 +    fprintf( stderr, "%s: Unknown mode \"%s\"%s.\n", argv0, predict.c_str(),
177 +        env_set ? " (MOSH_PREDICTION_DISPLAY in environment)" : "" );
178 +    die( usage_format, argv0 );
179 +  }
180 +}
181 +
182 +void cat( int ifd, int ofd )
183 +{
184 +  char buf[4096];
185 +  ssize_t n;
186 +  while ( 1 ) {
187 +    n = read( ifd, buf, sizeof( buf ) );
188 +    if ( n==-1 ) {
189 +      if (errno == EINTR ) {
190 +        continue;
191 +      }
192 +      break;
193 +    }
194 +    if ( n==0 ) {
195 +      break;
196 +    }
197 +    n = write( ofd, buf, n );
198 +    if ( n==-1 ) {
199 +      break;
200 +    }
201 +  }
202 +}
203 +
204 +bool valid_port(string port) {
205 +  if ( port.size() ) {
206 +    return port.find_first_not_of( "0123456789" ) == string::npos &&
207 +         atoi( port.c_str() ) > 0 &&
208 +         atoi( port.c_str() ) <= 65535;
209 +  }
210 +  return true; // consider no port to be the default value
211 +}
212 +
213 +int main( int argc, char *argv[] )
214 +{
215 +  argv0 = argv[0];
216 +  string client = "mosh-client";
217 +  string server = "mosh-server";
218 +  string ssh = "ssh";
219 +  string predict, port_request, ssh_port;
220 +  int help=0, version=0, fake_proxy=0;
221 +
222 +  static struct option long_options[] =
223 +  {
224 +    { "client",      required_argument,  0,              'c' },
225 +    { "server",      required_argument,  0,              's' },
226 +    { "predict",     required_argument,  0,              'r' },
227 +    { "port",        required_argument,  0,              'p' },
228 +    { "ssh-port",    required_argument,  0,              'P' },
229 +    { "ssh",         required_argument,  0,              'S' },
230 +    { "help",        no_argument,        &help,           1  },
231 +    { "version",     no_argument,        &version,        1  },
232 +    { "fake-proxy!", no_argument,        &fake_proxy,     1  },
233 +    { 0, 0, 0, 0 }
234 +  };
235 +  while ( 1 ) {
236 +    int option_index = 0;
237 +    int c = getopt_long( argc, argv, "anp:P:",
238 +        long_options, &option_index );
239 +    if ( c == -1 ) {
240 +      break;
241 +    }
242 +
243 +    switch ( c ) {
244 +      case 0:
245 +        // flag has been set
246 +        break;
247 +      case 'c':
248 +        client = optarg;
249 +        break;
250 +      case 's':
251 +        server = optarg;
252 +        break;
253 +      case 'r':
254 +        predict = optarg;
255 +        break;
256 +      case 'p':
257 +        port_request = optarg;
258 +        break;
259 +      case 'P':
260 +        ssh_port = optarg;
261 +        break;
262 +      case 'S':
263 +        ssh = optarg;
264 +        break;
265 +      case 'a':
266 +        predict = "always";
267 +        break;
268 +      case 'n':
269 +        predict = "never";
270 +        break;
271 +      default:
272 +        die( usage_format, argv[0] );
273 +    }
274 +  }
275 +
276 +  if ( help ) {
277 +    die( usage_format, argv[0] );
278 +  }
279 +  if ( version ) {
280 +    die( version_format, PACKAGE_VERSION );
281 +  }
282 +
283 +  if ( predict.size() ) {
284 +    predict_check( predict, 0 );
285 +  } else if ( getenv( "MOSH_PREDICTION_DELAY" ) ) {
286 +    predict = getenv( "MOSH_PREDICTION_DELAY" );
287 +    predict_check( predict, 1 );
288 +  } else {
289 +    predict = "adaptive";
290 +    predict_check( predict, 0 );
291 +  }
292 +
293 +  if(!valid_port(port_request)) {
294 +    die( "%s: Server-side port (%s) must be within valid range [0..65535].",
295 +        argv[0],
296 +        port_request.c_str() );
297 +  }
298 +
299 +  if(!valid_port(ssh_port)) {
300 +    die( "%s: SSH port (%s) must be within valid range [0..65535].",
301 +        argv[0],
302 +        ssh_port.c_str() );
303 +  }
304 +
305 +  unsetenv( "MOSH_PREDICTION_DISPLAY" );
306 +
307 +  if ( fake_proxy ) {
308 +    string host = argv[optind++];
309 +    string port = argv[optind++];
310 +
311 +    int sockfd = -1;
312 +    struct addrinfo hints, *servinfo, *p;
313 +    int rv;
314 +
315 +    memset( &hints, 0, sizeof( hints ) );
316 +    hints.ai_socktype = SOCK_STREAM;
317 +
318 +    if ( ( rv = getaddrinfo( host.c_str(),
319 +                             port.c_str(),
320 +                             &hints,
321 +                             &servinfo ) ) != 0 ) {
322 +      die( "%s: Could not resolve hostname %s: getaddrinfo: %s",
323 +           argv[0],
324 +           host.c_str(),
325 +           gai_strerror( rv ) );
326 +    }
327 +
328 +    int try_family = AF_INET;
329 +    // loop through all the results and connect to the first we can
330 +    for ( p = servinfo; p != NULL || try_family == AF_INET; p = p->ai_next ) {
331 +      if(p == NULL && try_family == AF_INET) { // start over and try AF_INET6
332 +        p = servinfo;
333 +        try_family = AF_INET6;
334 +      }
335 +      if(p == NULL) {
336 +        break; // servinfo == NULL
337 +      }
338 +
339 +      if(p->ai_family != try_family) {
340 +        continue;
341 +      }
342 +
343 +      if ( ( sockfd = socket( p->ai_family, SOCK_STREAM, IPPROTO_TCP ) ) == -1 ) {
344 +        continue;
345 +      }
346 +
347 +      if ( connect( sockfd, p->ai_addr, p->ai_addrlen ) == -1 ) {
348 +        close( sockfd );
349 +        continue;
350 +      }
351 +
352 +      char host[NI_MAXHOST], service[NI_MAXSERV];
353 +      if ( getnameinfo( p->ai_addr, p->ai_addrlen,
354 +            host, NI_MAXHOST,
355 +            service, NI_MAXSERV,
356 +            NI_NUMERICSERV | NI_NUMERICHOST ) == -1 ) {
357 +        die( "Couldn't get host name info" );
358 +      }
359 +
360 +      fprintf( stderr, "MOSH IP %s\n", host );
361 +      break; // if we get here, we must have connected successfully
362 +    }
363 +
364 +    if ( p == NULL ) {
365 +      // looped off the end of the list with no connection
366 +      die( "%s: failed to connect to host %s port %s",
367 +            argv[0], host.c_str(), port.c_str() );
368 +    }
369 +
370 +    freeaddrinfo( servinfo ); // all done with this structure
371 +
372 +    int pid = fork();
373 +    if ( pid == -1 ) {
374 +      die( "%s: fork: %d", argv[0], errno );
375 +    }
376 +    if ( pid == 0 ) {
377 +      cat( sockfd, 1 );
378 +      shutdown( sockfd, 0 );
379 +      exit( 0 );
380 +    }
381 +    signal( SIGHUP, SIG_IGN );
382 +    cat( 0, sockfd );
383 +    shutdown( sockfd, 1 );
384 +    waitpid( pid, NULL, 0 );
385 +    exit( 0 );
386 +  }
387 +
388 +  if ( argc - optind < 1 ) {
389 +    die( usage_format, argv[0] );
390 +  }
391 +
392 +  string userhost = argv[optind++];
393 +  char **command = &argv[optind];
394 +  int commands = argc - optind;
395 +
396 +  string color_invocation = client + " -c";
397 +  FILE *color_file = popen( color_invocation.c_str(), "r" );
398 +  if ( !color_file ) {
399 +    die( "%s: popen: %d", argv[0], errno );
400 +  }
401 +  char *buf = NULL;
402 +  size_t buf_sz = 0;
403 +  ssize_t n;
404 +  if ( ( n = getline( &buf, &buf_sz, color_file ) ) < 0 ) {
405 +    die( "%s: Can't count colors: %d", argv[0], errno );
406 +  }
407 +  string colors = string( buf, n );
408 +  pclose( color_file );
409 +
410 +  if ( !colors.size() ||
411 +       colors.find_first_not_of( "0123456789" ) != string::npos ||
412 +       atoi( colors.c_str() ) < 0 ) {
413 +    colors = "0";
414 +  }
415 +
416 +  int pty, pty_slave;
417 +  struct winsize ws;
418 +  if ( ioctl( 0, TIOCGWINSZ, &ws ) == -1 ) {
419 +    die( "%s: ioctl: %d", argv[0], errno );
420 +  }
421 +
422 +  if ( openpty( &pty, &pty_slave, NULL, NULL, &ws ) == -1 ) {
423 +    die( "%s: openpty: %d", argv[0], errno );
424 +  }
425 +
426 +  int pid = fork();
427 +  if ( pid == -1 ) {
428 +    die( "%s: fork: %d", argv[0], errno );
429 +  }
430 +  if ( pid == 0 ) {
431 +    close( pty );
432 +    if ( -1 == dup2( pty_slave, 1 ) ||
433 +         -1 == dup2( pty_slave, 2 ) ) {
434 +      die( "%s: dup2: %d", argv[0], errno );
435 +    }
436 +    close( pty_slave );
437 +
438 +    vector<string> server_args;
439 +    server_args.push_back( "new" );
440 +    server_args.push_back( "-s" );
441 +    server_args.push_back( "-c" );
442 +    server_args.push_back( colors );
443 +    if ( port_request.size() ) {
444 +      server_args.push_back( "-p" );
445 +      server_args.push_back( port_request );
446 +    }
447 +    if ( commands ) {
448 +      server_args.insert( server_args.end(), command, command + commands );
449 +    }
450 +    string quoted_self = shell_quote_string( string( argv[0] ) );
451 +    string quoted_server_args = shell_quote( server_args );
452 +    fflush( stdout );
453 +
454 +    string proxy_arg = "ProxyCommand=" + quoted_self + " --fake-proxy -- %h %p";
455 +    string ssh_remote_command = server + " " + quoted_server_args;
456 +
457 +    vector<string> ssh_args;
458 +    ssh_args.push_back( "-S" );
459 +    ssh_args.push_back( "none" );
460 +    ssh_args.push_back( "-o" );
461 +    ssh_args.push_back( proxy_arg );
462 +    ssh_args.push_back( "-t" );
463 +    ssh_args.push_back( userhost );
464 +    if ( ssh_port.size() ) {
465 +      ssh_args.push_back( "-p" );
466 +      ssh_args.push_back( ssh_port );
467 +    }
468 +    ssh_args.push_back( "--" );
469 +    ssh_args.push_back( ssh_remote_command );
470 +
471 +    string ssh_exec_string = ssh + " " + shell_quote( ssh_args );
472 +
473 +    int ret = execlp( "sh", "sh", "-c", ssh_exec_string.c_str(), (char *)NULL );
474 +    if ( ret == -1 ) {
475 +      die( "Cannot exec ssh: %d", errno );
476 +    }
477 +  }
478 +
479 +  close( pty_slave );
480 +  string ip, port, key;
481 +
482 +  FILE *pty_file = fdopen( pty, "r" );
483 +  string line;
484 +  while ( ( n = getline( &buf, &buf_sz, pty_file ) ) >= 0 ) {
485 +    line = string( buf, n );
486 +    line = line.erase( line.find_last_not_of( "\n" ) );
487 +    if ( line.compare( 0, 8, "MOSH IP " ) == 0 ) {
488 +      size_t ip_end = line.find_last_not_of( " \t\n\r" );
489 +      if ( ip_end != string::npos && ip_end >= 8 ) {
490 +        ip = line.substr( 8, ip_end + 1 - 8 );
491 +      }
492 +    } else if ( line.compare( 0, 13, "MOSH CONNECT " ) == 0 ) {
493 +      size_t port_end = line.find_first_not_of( "0123456789", 13 );
494 +      if ( port_end != string::npos && port_end >= 13 ) {
495 +        port = line.substr( 13, port_end - 13 );
496 +      }
497 +      string rest = line.substr( port_end + 1 );
498 +      size_t key_end = rest.find_last_not_of( " \t\n\r" );
499 +      size_t key_valid_end = rest.find_last_of( key_valid_char_set );
500 +      if ( key_valid_end == key_end && key_end + 1 == 22 ) {
501 +        key = rest.substr( 0, key_end + 1 );
502 +      }
503 +      break;
504 +    } else {
505 +      printf( "%s\n", line.c_str() );
506 +    }
507 +  }
508 +  waitpid( pid, NULL, 0 );
509 +
510 +  if ( !ip.size() ) {
511 +    die( "%s: Did not find remote IP address (is SSH ProxyCommand disabled?).",
512 +         argv[0] );
513 +  }
514 +
515 +  if ( !key.size() || !port.size() ) {
516 +    die( "%s: Did not find mosh server startup message.", argv[0] );
517 +  }
518 +
519 +  setenv( "MOSH_KEY", key.c_str(), 1 );
520 +  setenv( "MOSH_PREDICTION_DISPLAY", predict.c_str(), 1 );
521 +  execlp( client.c_str(), client.c_str(), ip.c_str(), port.c_str(), (char *)NULL );
522 +}