001package org.apache.maven.wagon.providers.ssh; 002 003/* 004 * Licensed to the Apache Software Foundation (ASF) under one 005 * or more contributor license agreements. See the NOTICE file 006 * distributed with this work for additional information 007 * regarding copyright ownership. The ASF licenses this file 008 * to you under the Apache License, Version 2.0 (the 009 * "License"); you may not use this file except in compliance 010 * with the License. You may obtain a copy of the License at 011 * 012 * http://www.apache.org/licenses/LICENSE-2.0 013 * 014 * Unless required by applicable law or agreed to in writing, 015 * software distributed under the License is distributed on an 016 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 017 * KIND, either express or implied. See the License for the 018 * specific language governing permissions and limitations 019 * under the License. 020 */ 021 022import org.apache.sshd.common.util.DirectoryScanner; 023import org.apache.sshd.server.Command; 024import org.apache.sshd.server.Environment; 025import org.apache.sshd.server.ExitCallback; 026import org.apache.sshd.server.FileSystemAware; 027import org.apache.sshd.server.FileSystemView; 028import org.apache.sshd.server.SshFile; 029import org.slf4j.Logger; 030import org.slf4j.LoggerFactory; 031 032import java.io.ByteArrayOutputStream; 033import java.io.EOFException; 034import java.io.IOException; 035import java.io.InputStream; 036import java.io.OutputStream; 037import java.util.Arrays; 038 039/** 040 * This commands provide SCP support on both server and client side. 041 * Permissions and preservation of access / modification times on files 042 * are not supported. 043 * olamy : copy of a class from mina for changing return codes in case of file not found 1 warning instead of 2 044 * 045 * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a> 046 */ 047public class ScpCommand 048 implements Command, Runnable, FileSystemAware 049{ 050 051 protected static final Logger LOG = LoggerFactory.getLogger( ScpCommand.class ); 052 053 protected static final int OK = 0; 054 055 protected static final int WARNING = 1; 056 057 protected static final int ERROR = 2; 058 059 protected String name; 060 061 protected boolean optR; 062 063 protected boolean optT; 064 065 protected boolean optF; 066 067 protected boolean optV; 068 069 protected boolean optD; 070 071 protected boolean optP; 072 073 protected FileSystemView root; 074 075 protected String path; 076 077 protected InputStream in; 078 079 protected OutputStream out; 080 081 protected OutputStream err; 082 083 protected ExitCallback callback; 084 085 protected IOException error; 086 087 public ScpCommand( String[] args ) 088 { 089 name = Arrays.asList( args ).toString(); 090 if ( LOG.isDebugEnabled() ) 091 { 092 LOG.debug( "Executing command {}", name ); 093 } 094 path = "."; 095 for ( int i = 1; i < args.length; i++ ) 096 { 097 if ( args[i].charAt( 0 ) == '-' ) 098 { 099 for ( int j = 1; j < args[i].length(); j++ ) 100 { 101 switch ( args[i].charAt( j ) ) 102 { 103 case 'f': 104 optF = true; 105 break; 106 case 'p': 107 optP = true; 108 break; 109 case 'r': 110 optR = true; 111 break; 112 case 't': 113 optT = true; 114 break; 115 case 'v': 116 optV = true; 117 break; 118 case 'd': 119 optD = true; 120 break; 121 default: 122// error = new IOException("Unsupported option: " + args[i].charAt(j)); 123// return; 124 } 125 } 126 } 127 else if ( i == args.length - 1 ) 128 { 129 path = args[args.length - 1]; 130 } 131 } 132 if ( !optF && !optT ) 133 { 134 error = new IOException( "Either -f or -t option should be set" ); 135 } 136 } 137 138 public void setInputStream( InputStream in ) 139 { 140 this.in = in; 141 } 142 143 public void setOutputStream( OutputStream out ) 144 { 145 this.out = out; 146 } 147 148 public void setErrorStream( OutputStream err ) 149 { 150 this.err = err; 151 } 152 153 public void setExitCallback( ExitCallback callback ) 154 { 155 this.callback = callback; 156 } 157 158 public void start( Environment env ) 159 throws IOException 160 { 161 if ( error != null ) 162 { 163 throw error; 164 } 165 new Thread( this, "ScpCommand: " + name ).start(); 166 } 167 168 public void destroy() 169 { 170 } 171 172 public void run() 173 { 174 int exitValue = OK; 175 String exitMessage = null; 176 177 try 178 { 179 if ( optT ) 180 { 181 ack(); 182 for (; ; ) 183 { 184 String line; 185 boolean isDir = false; 186 int c = readAck( true ); 187 switch ( c ) 188 { 189 case -1: 190 return; 191 case 'D': 192 isDir = true; 193 case 'C': 194 line = ( (char) c ) + readLine(); 195 break; 196 case 'E': 197 readLine(); 198 return; 199 default: 200 //a real ack that has been acted upon already 201 continue; 202 } 203 204 if ( optR && isDir ) 205 { 206 writeDir( line, root.getFile( path ) ); 207 } 208 else 209 { 210 writeFile( line, root.getFile( path ) ); 211 } 212 } 213 } 214 else if ( optF ) 215 { 216 String pattern = path; 217 int idx = pattern.indexOf( '*' ); 218 if ( idx >= 0 ) 219 { 220 String basedir = ""; 221 int lastSep = pattern.substring( 0, idx ).lastIndexOf( '/' ); 222 if ( lastSep >= 0 ) 223 { 224 basedir = pattern.substring( 0, lastSep ); 225 pattern = pattern.substring( lastSep + 1 ); 226 } 227 String[] included = new DirectoryScanner( basedir, pattern ).scan(); 228 for ( String path : included ) 229 { 230 SshFile file = root.getFile( basedir + "/" + path ); 231 if ( file.isFile() ) 232 { 233 readFile( file ); 234 } 235 else if ( file.isDirectory() ) 236 { 237 if ( !optR ) 238 { 239 out.write( WARNING ); 240 out.write( ( path + " not a regular file\n" ).getBytes() ); 241 } 242 else 243 { 244 readDir( file ); 245 } 246 } 247 else 248 { 249 out.write( WARNING ); 250 out.write( ( path + " unknown file type\n" ).getBytes() ); 251 } 252 } 253 } 254 else 255 { 256 String basedir = ""; 257 int lastSep = pattern.lastIndexOf( '/' ); 258 if ( lastSep >= 0 ) 259 { 260 basedir = pattern.substring( 0, lastSep ); 261 pattern = pattern.substring( lastSep + 1 ); 262 } 263 SshFile file = root.getFile( basedir + "/" + pattern ); 264 if ( !file.doesExist() ) 265 { 266 exitValue = WARNING; 267 throw new IOException( file + ": no such file or directory" ); 268 } 269 if ( file.isFile() ) 270 { 271 readFile( file ); 272 } 273 else if ( file.isDirectory() ) 274 { 275 if ( !optR ) 276 { 277 throw new IOException( file + " not a regular file" ); 278 } 279 else 280 { 281 readDir( file ); 282 } 283 } 284 else 285 { 286 throw new IOException( file + ": unknown file type" ); 287 } 288 } 289 } 290 else 291 { 292 throw new IOException( "Unsupported mode" ); 293 } 294 } 295 catch ( IOException e ) 296 { 297 try 298 { 299 exitValue = ( exitValue != OK ? exitValue : ERROR ); 300 exitMessage = e.getMessage(); 301 out.write( exitValue ); 302 out.write( exitMessage.getBytes() ); 303 out.write( '\n' ); 304 out.flush(); 305 } 306 catch ( IOException e2 ) 307 { 308 // Ignore 309 } 310 LOG.info( "Error in scp command", e ); 311 } 312 finally 313 { 314 if ( callback != null ) 315 { 316 callback.onExit( exitValue, exitMessage ); 317 } 318 } 319 } 320 321 protected void writeDir( String header, SshFile path ) 322 throws IOException 323 { 324 if ( LOG.isDebugEnabled() ) 325 { 326 LOG.debug( "Writing dir {}", path ); 327 } 328 if ( !header.startsWith( "D" ) ) 329 { 330 throw new IOException( "Expected a D message but got '" + header + "'" ); 331 } 332 333 String perms = header.substring( 1, 5 ); 334 int length = Integer.parseInt( header.substring( 6, header.indexOf( ' ', 6 ) ) ); 335 String name = header.substring( header.indexOf( ' ', 6 ) + 1 ); 336 337 if ( length != 0 ) 338 { 339 throw new IOException( "Expected 0 length for directory but got " + length ); 340 } 341 SshFile file; 342 if ( path.doesExist() && path.isDirectory() ) 343 { 344 file = root.getFile( path, name ); 345 } 346 else if ( !path.doesExist() && path.getParentFile().doesExist() && path.getParentFile().isDirectory() ) 347 { 348 file = path; 349 } 350 else 351 { 352 throw new IOException( "Can not write to " + path ); 353 } 354 if ( !( file.doesExist() && file.isDirectory() ) && !file.mkdir() ) 355 { 356 throw new IOException( "Could not create directory " + file ); 357 } 358 359 ack(); 360 361 for (; ; ) 362 { 363 header = readLine(); 364 if ( header.startsWith( "C" ) ) 365 { 366 writeFile( header, file ); 367 } 368 else if ( header.startsWith( "D" ) ) 369 { 370 writeDir( header, file ); 371 } 372 else if ( header.equals( "E" ) ) 373 { 374 ack(); 375 break; 376 } 377 else 378 { 379 throw new IOException( "Unexpected message: '" + header + "'" ); 380 } 381 } 382 383 } 384 385 protected void writeFile( String header, SshFile path ) 386 throws IOException 387 { 388 if ( LOG.isDebugEnabled() ) 389 { 390 LOG.debug( "Writing file {}", path ); 391 } 392 if ( !header.startsWith( "C" ) ) 393 { 394 throw new IOException( "Expected a C message but got '" + header + "'" ); 395 } 396 397 String perms = header.substring( 1, 5 ); 398 long length = Long.parseLong( header.substring( 6, header.indexOf( ' ', 6 ) ) ); 399 String name = header.substring( header.indexOf( ' ', 6 ) + 1 ); 400 401 SshFile file; 402 if ( path.doesExist() && path.isDirectory() ) 403 { 404 file = root.getFile( path, name ); 405 } 406 else if ( path.doesExist() && path.isFile() ) 407 { 408 file = path; 409 } 410 else if ( !path.doesExist() && path.getParentFile().doesExist() && path.getParentFile().isDirectory() ) 411 { 412 file = path; 413 } 414 else 415 { 416 throw new IOException( "Can not write to " + path ); 417 } 418 if ( file.doesExist() && file.isDirectory() ) 419 { 420 throw new IOException( "File is a directory: " + file ); 421 } 422 else if ( file.doesExist() && !file.isWritable() ) 423 { 424 throw new IOException( "Can not write to file: " + file ); 425 } 426 OutputStream os = file.createOutputStream( 0 ); 427 try 428 { 429 ack(); 430 431 byte[] buffer = new byte[8 * 1024]; 432 while ( length > 0 ) 433 { 434 int len = (int) Math.min( length, buffer.length ); 435 len = in.read( buffer, 0, len ); 436 if ( len <= 0 ) 437 { 438 throw new IOException( "End of stream reached" ); 439 } 440 os.write( buffer, 0, len ); 441 length -= len; 442 } 443 } 444 finally 445 { 446 os.close(); 447 } 448 449 ack(); 450 readAck( false ); 451 } 452 453 protected String readLine() 454 throws IOException 455 { 456 ByteArrayOutputStream baos = new ByteArrayOutputStream(); 457 for (; ; ) 458 { 459 int c = in.read(); 460 if ( c == '\n' ) 461 { 462 return baos.toString(); 463 } 464 else if ( c == -1 ) 465 { 466 throw new IOException( "End of stream" ); 467 } 468 else 469 { 470 baos.write( c ); 471 } 472 } 473 } 474 475 protected void readFile( SshFile path ) 476 throws IOException 477 { 478 if ( LOG.isDebugEnabled() ) 479 { 480 LOG.debug( "Reading file {}", path ); 481 } 482 StringBuilder buf = new StringBuilder(); 483 buf.append( "C" ); 484 buf.append( "0644" ); // what about perms 485 buf.append( " " ); 486 buf.append( path.getSize() ); // length 487 buf.append( " " ); 488 buf.append( path.getName() ); 489 buf.append( "\n" ); 490 out.write( buf.toString().getBytes() ); 491 out.flush(); 492 readAck( false ); 493 494 InputStream is = path.createInputStream( 0 ); 495 try 496 { 497 byte[] buffer = new byte[8 * 1024]; 498 for (; ; ) 499 { 500 int len = is.read( buffer, 0, buffer.length ); 501 if ( len == -1 ) 502 { 503 break; 504 } 505 out.write( buffer, 0, len ); 506 } 507 } 508 finally 509 { 510 is.close(); 511 } 512 ack(); 513 readAck( false ); 514 } 515 516 protected void readDir( SshFile path ) 517 throws IOException 518 { 519 if ( LOG.isDebugEnabled() ) 520 { 521 LOG.debug( "Reading directory {}", path ); 522 } 523 StringBuilder buf = new StringBuilder(); 524 buf.append( "D" ); 525 buf.append( "0755" ); // what about perms 526 buf.append( " " ); 527 buf.append( "0" ); // length 528 buf.append( " " ); 529 buf.append( path.getName() ); 530 buf.append( "\n" ); 531 out.write( buf.toString().getBytes() ); 532 out.flush(); 533 readAck( false ); 534 535 for ( SshFile child : path.listSshFiles() ) 536 { 537 if ( child.isFile() ) 538 { 539 readFile( child ); 540 } 541 else if ( child.isDirectory() ) 542 { 543 readDir( child ); 544 } 545 } 546 547 out.write( "E\n".getBytes() ); 548 out.flush(); 549 readAck( false ); 550 } 551 552 protected void ack() 553 throws IOException 554 { 555 out.write( 0 ); 556 out.flush(); 557 } 558 559 protected int readAck( boolean canEof ) 560 throws IOException 561 { 562 int c = in.read(); 563 switch ( c ) 564 { 565 case -1: 566 if ( !canEof ) 567 { 568 throw new EOFException(); 569 } 570 break; 571 case OK: 572 break; 573 case WARNING: 574 LOG.warn( "Received warning: " + readLine() ); 575 break; 576 case ERROR: 577 throw new IOException( "Received nack: " + readLine() ); 578 default: 579 break; 580 } 581 return c; 582 } 583 584 public void setFileSystemView( FileSystemView view ) 585 { 586 this.root = view; 587 } 588}