1 package org.apache.maven.wagon.providers.ssh.jsch;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 import com.jcraft.jsch.ChannelExec;
23 import com.jcraft.jsch.JSchException;
24 import org.apache.maven.wagon.CommandExecutionException;
25 import org.apache.maven.wagon.InputData;
26 import org.apache.maven.wagon.OutputData;
27 import org.apache.maven.wagon.ResourceDoesNotExistException;
28 import org.apache.maven.wagon.TransferFailedException;
29 import org.apache.maven.wagon.events.TransferEvent;
30 import org.apache.maven.wagon.providers.ssh.ScpHelper;
31 import org.apache.maven.wagon.repository.RepositoryPermissions;
32 import org.apache.maven.wagon.resource.Resource;
33
34 import java.io.EOFException;
35 import java.io.IOException;
36 import java.io.InputStream;
37 import java.io.OutputStream;
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55 public class ScpWagon
56 extends AbstractJschWagon
57 {
58 private static final char COPY_START_CHAR = 'C';
59
60 private static final char ACK_SEPARATOR = ' ';
61
62 private static final String END_OF_FILES_MSG = "E\n";
63
64 private static final int LINE_BUFFER_SIZE = 8192;
65
66 private static final byte LF = '\n';
67
68 private ChannelExec channel;
69
70 private InputStream channelInputStream;
71
72 private OutputStream channelOutputStream;
73
74 private void setFileGroup( RepositoryPermissions permissions, String basedir, Resource resource )
75 throws CommandExecutionException
76 {
77 if ( permissions != null && permissions.getGroup() != null )
78 {
79
80 executeCommand( "chgrp -f " + permissions.getGroup() + " \"" + getPath( basedir, resource.getName() )
81 + "\"" );
82 }
83 }
84
85 protected void cleanupPutTransfer( Resource resource )
86 {
87 if ( channel != null )
88 {
89 channel.disconnect();
90 channel = null;
91 }
92 }
93
94 protected void finishPutTransfer( Resource resource, InputStream input, OutputStream output )
95 throws TransferFailedException
96 {
97 try
98 {
99 sendEom( output );
100
101 checkAck( channelInputStream );
102
103
104 output.write( END_OF_FILES_MSG.getBytes() );
105 output.flush();
106 }
107 catch ( IOException e )
108 {
109 handleIOException( resource, e );
110 }
111
112 String basedir = getRepository().getBasedir();
113 try
114 {
115 setFileGroup( getRepository().getPermissions(), basedir, resource );
116 }
117 catch ( CommandExecutionException e )
118 {
119 fireTransferError( resource, e, TransferEvent.REQUEST_PUT );
120
121 throw new TransferFailedException( e.getMessage(), e );
122 }
123 }
124
125 private void checkAck( InputStream in )
126 throws IOException
127 {
128 int code = in.read();
129 if ( code == -1 )
130 {
131 throw new IOException( "Unexpected end of data" );
132 }
133 else if ( code == 1 )
134 {
135 String line = readLine( in );
136
137 throw new IOException( "SCP terminated with error: '" + line + "'" );
138 }
139 else if ( code == 2 )
140 {
141 throw new IOException( "SCP terminated with error (code: " + code + ")" );
142 }
143 else if ( code != 0 )
144 {
145 throw new IOException( "SCP terminated with unknown error code" );
146 }
147 }
148
149 protected void finishGetTransfer( Resource resource, InputStream input, OutputStream output )
150 throws TransferFailedException
151 {
152 try
153 {
154 checkAck( input );
155
156 sendEom( channelOutputStream );
157 }
158 catch ( IOException e )
159 {
160 handleGetException( resource, e );
161 }
162 }
163
164 protected void cleanupGetTransfer( Resource resource )
165 {
166 if ( channel != null )
167 {
168 channel.disconnect();
169 }
170 }
171
172 @Deprecated
173 protected void getTransfer( Resource resource, OutputStream output, InputStream input, boolean closeInput,
174 int maxSize )
175 throws TransferFailedException
176 {
177 super.getTransfer( resource, output, input, closeInput, (int) resource.getContentLength() );
178 }
179
180 protected void getTransfer( Resource resource, OutputStream output, InputStream input, boolean closeInput,
181 long maxSize )
182 throws TransferFailedException
183 {
184 super.getTransfer( resource, output, input, closeInput, resource.getContentLength() );
185 }
186
187 protected String readLine( InputStream in )
188 throws IOException
189 {
190 StringBuilder sb = new StringBuilder();
191
192 while ( true )
193 {
194 if ( sb.length() > LINE_BUFFER_SIZE )
195 {
196 throw new IOException( "Remote server sent a too long line" );
197 }
198
199 int c = in.read();
200
201 if ( c < 0 )
202 {
203 throw new IOException( "Remote connection terminated unexpectedly." );
204 }
205
206 if ( c == LF )
207 {
208 break;
209 }
210
211 sb.append( (char) c );
212 }
213 return sb.toString();
214 }
215
216 protected static void sendEom( OutputStream out )
217 throws IOException
218 {
219 out.write( 0 );
220
221 out.flush();
222 }
223
224 public void fillInputData( InputData inputData )
225 throws TransferFailedException, ResourceDoesNotExistException
226 {
227 Resource resource = inputData.getResource();
228
229 String path = getPath( getRepository().getBasedir(), resource.getName() );
230
231 String cmd = "scp -p -f \"" + path + "\"";
232
233 fireTransferDebug( "Executing command: " + cmd );
234
235 try
236 {
237 channel = (ChannelExec) session.openChannel( EXEC_CHANNEL );
238
239 channel.setCommand( cmd );
240
241
242 channelOutputStream = channel.getOutputStream();
243
244 InputStream in = channel.getInputStream();
245 inputData.setInputStream( in );
246
247 channel.connect();
248
249 sendEom( channelOutputStream );
250
251 int exitCode = in.read();
252
253 if ( exitCode == 'T' )
254 {
255 String line = readLine( in );
256
257 String[] times = line.split( " " );
258
259 resource.setLastModified( Long.valueOf( times[0] ).longValue() * 1000 );
260
261 sendEom( channelOutputStream );
262
263 exitCode = in.read();
264 }
265
266 String line = readLine( in );
267
268 if ( exitCode != COPY_START_CHAR )
269 {
270 if ( exitCode == 1 && ( line.contains( "No such file or directory" )
271 || line.indexOf( "no such file or directory" ) != 1 ) )
272 {
273 throw new ResourceDoesNotExistException( line );
274 }
275 else
276 {
277 throw new IOException( "Exit code: " + exitCode + " - " + line );
278 }
279 }
280
281 if ( line == null )
282 {
283 throw new EOFException( "Unexpected end of data" );
284 }
285
286 String perms = line.substring( 0, 4 );
287 fireTransferDebug( "Remote file permissions: " + perms );
288
289 if ( line.charAt( 4 ) != ACK_SEPARATOR && line.charAt( 5 ) != ACK_SEPARATOR )
290 {
291 throw new IOException( "Invalid transfer header: " + line );
292 }
293
294 int index = line.indexOf( ACK_SEPARATOR, 5 );
295 if ( index < 0 )
296 {
297 throw new IOException( "Invalid transfer header: " + line );
298 }
299
300 long filesize = Long.parseLong( line.substring( 5, index ) );
301 fireTransferDebug( "Remote file size: " + filesize );
302
303 resource.setContentLength( filesize );
304
305 String filename = line.substring( index + 1 );
306 fireTransferDebug( "Remote filename: " + filename );
307
308 sendEom( channelOutputStream );
309 }
310 catch ( JSchException e )
311 {
312 handleGetException( resource, e );
313 }
314 catch ( IOException e )
315 {
316 handleGetException( resource, e );
317 }
318 }
319
320 public void fillOutputData( OutputData outputData )
321 throws TransferFailedException
322 {
323 Resource resource = outputData.getResource();
324
325 String basedir = getRepository().getBasedir();
326
327 String path = getPath( basedir, resource.getName() );
328
329 String dir = ScpHelper.getResourceDirectory( resource.getName() );
330
331 try
332 {
333 sshTool.createRemoteDirectories( getPath( basedir, dir ), getRepository().getPermissions() );
334 }
335 catch ( CommandExecutionException e )
336 {
337 fireTransferError( resource, e, TransferEvent.REQUEST_PUT );
338
339 throw new TransferFailedException( e.getMessage(), e );
340 }
341
342 String octalMode = getOctalMode( getRepository().getPermissions() );
343
344
345 String command = "scp";
346 if ( octalMode != null )
347 {
348 command += " -p";
349 }
350 command += " -t \"" + path + "\"";
351
352 fireTransferDebug( "Executing command: " + command );
353
354 String resourceName = resource.getName();
355
356 OutputStream out = null;
357 try
358 {
359 channel = (ChannelExec) session.openChannel( EXEC_CHANNEL );
360
361 channel.setCommand( command );
362
363
364 out = channel.getOutputStream();
365 outputData.setOutputStream( out );
366
367 channelInputStream = channel.getInputStream();
368
369 channel.connect();
370
371 checkAck( channelInputStream );
372
373
374 long filesize = resource.getContentLength();
375
376 String mode = octalMode == null ? "0644" : octalMode;
377 command = "C" + mode + " " + filesize + " ";
378
379 if ( resourceName.lastIndexOf( ScpHelper.PATH_SEPARATOR ) > 0 )
380 {
381 command += resourceName.substring( resourceName.lastIndexOf( ScpHelper.PATH_SEPARATOR ) + 1 );
382 }
383 else
384 {
385 command += resourceName;
386 }
387
388 command += "\n";
389
390 out.write( command.getBytes() );
391
392 out.flush();
393
394 checkAck( channelInputStream );
395 }
396 catch ( JSchException e )
397 {
398 fireTransferError( resource, e, TransferEvent.REQUEST_PUT );
399
400 String msg = "Error occurred while deploying '" + resourceName + "' to remote repository: "
401 + getRepository().getUrl() + ": " + e.getMessage();
402
403 throw new TransferFailedException( msg, e );
404 }
405 catch ( IOException e )
406 {
407 handleIOException( resource, e );
408 }
409 }
410
411 private void handleIOException( Resource resource, IOException e )
412 throws TransferFailedException
413 {
414 if ( e.getMessage().contains( "set mode: Operation not permitted" ) )
415 {
416 fireTransferDebug( e.getMessage() );
417 }
418 else
419 {
420 fireTransferError( resource, e, TransferEvent.REQUEST_PUT );
421
422 String msg = "Error occurred while deploying '" + resource.getName() + "' to remote repository: "
423 + getRepository().getUrl() + ": " + e.getMessage();
424
425 throw new TransferFailedException( msg, e );
426 }
427 }
428
429 public String getOctalMode( RepositoryPermissions permissions )
430 {
431 String mode = null;
432 if ( permissions != null && permissions.getFileMode() != null )
433 {
434 if ( permissions.getFileMode().matches( "[0-9]{3,4}" ) )
435 {
436 mode = permissions.getFileMode();
437
438 if ( mode.length() == 3 )
439 {
440 mode = "0" + mode;
441 }
442 }
443 else
444 {
445
446
447 fireSessionDebug( "Not using non-octal permissions: " + permissions.getFileMode() );
448 }
449 }
450 return mode;
451 }
452 }