View Javadoc
1   package org.apache.maven.wagon.providers.ssh;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *   http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  import org.apache.sshd.common.util.DirectoryScanner;
23  import org.apache.sshd.server.Command;
24  import org.apache.sshd.server.Environment;
25  import org.apache.sshd.server.ExitCallback;
26  import org.apache.sshd.server.FileSystemAware;
27  import org.apache.sshd.server.FileSystemView;
28  import org.apache.sshd.server.SshFile;
29  import org.slf4j.Logger;
30  import org.slf4j.LoggerFactory;
31  
32  import java.io.ByteArrayOutputStream;
33  import java.io.EOFException;
34  import java.io.IOException;
35  import java.io.InputStream;
36  import java.io.OutputStream;
37  import java.util.Arrays;
38  
39  /**
40   * This commands provide SCP support on both server and client side.
41   * Permissions and preservation of access / modification times on files
42   * are not supported.
43   * olamy : copy of a class from mina for changing return codes in case of file not found 1 warning instead of 2
44   *
45   * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
46   */
47  public class ScpCommand
48      implements Command, Runnable, FileSystemAware
49  {
50  
51      protected static final Logger LOG = LoggerFactory.getLogger( ScpCommand.class );
52  
53      protected static final int OK = 0;
54  
55      protected static final int WARNING = 1;
56  
57      protected static final int ERROR = 2;
58  
59      protected String name;
60  
61      protected boolean optR;
62  
63      protected boolean optT;
64  
65      protected boolean optF;
66  
67      protected boolean optV;
68  
69      protected boolean optD;
70  
71      protected boolean optP;
72  
73      protected FileSystemView root;
74  
75      protected String path;
76  
77      protected InputStream in;
78  
79      protected OutputStream out;
80  
81      protected OutputStream err;
82  
83      protected ExitCallback callback;
84  
85      protected IOException error;
86  
87      public ScpCommand( String[] args )
88      {
89          name = Arrays.asList( args ).toString();
90          if ( LOG.isDebugEnabled() )
91          {
92              LOG.debug( "Executing command {}", name );
93          }
94          path = ".";
95          for ( int i = 1; i < args.length; i++ )
96          {
97              if ( args[i].charAt( 0 ) == '-' )
98              {
99                  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 }