1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21 package org.waarp.gateway.ftp.control;
22
23 import io.netty.channel.Channel;
24 import org.waarp.common.command.ReplyCode;
25 import org.waarp.common.command.exception.CommandAbstractException;
26 import org.waarp.common.command.exception.Reply421Exception;
27 import org.waarp.common.command.exception.Reply451Exception;
28 import org.waarp.common.command.exception.Reply502Exception;
29 import org.waarp.common.command.exception.Reply504Exception;
30 import org.waarp.common.database.DbSession;
31 import org.waarp.common.database.data.AbstractDbData.UpdatedInfo;
32 import org.waarp.common.database.exception.WaarpDatabaseNoConnectionException;
33 import org.waarp.common.future.WaarpFuture;
34 import org.waarp.common.logging.WaarpLogger;
35 import org.waarp.common.logging.WaarpLoggerFactory;
36 import org.waarp.ftp.core.command.AbstractCommand;
37 import org.waarp.ftp.core.command.FtpCommandCode;
38 import org.waarp.ftp.core.command.access.QUIT;
39 import org.waarp.ftp.core.control.BusinessHandler;
40 import org.waarp.ftp.core.data.FtpTransfer;
41 import org.waarp.ftp.core.exception.FtpNoFileException;
42 import org.waarp.ftp.core.file.FtpFile;
43 import org.waarp.ftp.core.session.FtpSession;
44 import org.waarp.ftp.filesystembased.FilesystemBasedFtpRestart;
45 import org.waarp.gateway.ftp.config.AUTHUPDATE;
46 import org.waarp.gateway.ftp.config.FileBasedConfiguration;
47 import org.waarp.gateway.ftp.database.DbConstantFtp;
48 import org.waarp.gateway.ftp.exec.AbstractExecutor;
49 import org.waarp.gateway.ftp.file.FileBasedAuth;
50 import org.waarp.gateway.ftp.file.FileBasedDir;
51
52 import java.io.File;
53 import java.io.IOException;
54
55
56
57
58
59
60 public class ExecBusinessHandler extends BusinessHandler {
61 private static final String
62 TRANSFER_DONE_BUT_FORCE_DISCONNECTION_SINCE_AN_ERROR_OCCURS_ON_POST_OPERATION =
63 "Transfer done but force disconnection since an error occurs on PostOperation";
64
65 private static final String
66 POST_EXECUTION_IN_ERROR_FOR_TRANSFER_SINCE_NO_FILE_FOUND =
67 "PostExecution in Error for Transfer since No File found";
68
69
70
71
72 private static final WaarpLogger logger =
73 WaarpLoggerFactory.getLogger(ExecBusinessHandler.class);
74
75
76
77
78 private DbSession dbFtpSession;
79
80
81
82 private DbSession dbR66Session;
83 private boolean internalDb;
84
85 @Override
86 public final void afterTransferDoneBeforeAnswer(final FtpTransfer transfer)
87 throws CommandAbstractException {
88
89 if (getFtpSession() == null || getFtpSession().getAuth() == null) {
90 return;
91 }
92 final FileBasedAuth auth = (FileBasedAuth) getFtpSession().getAuth();
93 if (auth.isAdmin()) {
94 return;
95 }
96 final long specialId = auth.getSpecialId();
97 final ReplyCode replyCode = getFtpSession().getReplyCode();
98 logger.debug("Transfer done but action needed: {}", !(replyCode !=
99 ReplyCode.REPLY_250_REQUESTED_FILE_ACTION_OKAY &&
100 replyCode !=
101 ReplyCode.REPLY_226_CLOSING_DATA_CONNECTION));
102 if (replyCode != ReplyCode.REPLY_250_REQUESTED_FILE_ACTION_OKAY &&
103 replyCode != ReplyCode.REPLY_226_CLOSING_DATA_CONNECTION) {
104
105 final String message = "Transfer done with code: " +
106 getFtpSession().getReplyCode().getMesg();
107 WaarpActionLogger.logErrorAction(dbFtpSession, specialId, transfer,
108 message, getFtpSession().getReplyCode(),
109 this);
110 return;
111 }
112
113 final FtpCommandCode code = transfer.getCommand();
114 logger.debug("Checking action vs auth after transfer: {}", code);
115 switch (code) {
116 case RETR:
117
118 WaarpActionLogger.logAction(dbFtpSession, specialId,
119 "Retrieve executed: OK", this,
120 getFtpSession().getReplyCode(),
121 UpdatedInfo.RUNNING);
122 break;
123 case APPE:
124 case STOR:
125 case STOU:
126
127 final WaarpFuture futureCompletion = new WaarpFuture(true);
128 final String[] args = new String[6];
129 args[0] = auth.getUser();
130 args[1] = auth.getAccount();
131 args[2] = auth.getBaseDirectory();
132 final FtpFile file;
133 try {
134 file = transfer.getFtpFile();
135 } catch (final FtpNoFileException e1) {
136
137 final String message =
138 "PostExecution in Error for Transfer since No File found: " +
139 transfer.getCommand() + ' ' + transfer.getStatus() + ' ' +
140 transfer.getPath();
141 final CommandAbstractException exc = new Reply421Exception(
142 POST_EXECUTION_IN_ERROR_FOR_TRANSFER_SINCE_NO_FILE_FOUND);
143 WaarpActionLogger.logErrorAction(dbFtpSession, specialId, transfer,
144 message, exc.code, this);
145 throw exc;
146 }
147 try {
148 args[3] = file.getFile();
149 final File newfile = new File(args[2] + args[3]);
150
151
152
153 if (!newfile.canRead()) {
154 try {
155 if (!newfile.createNewFile()) {
156 logger.error("Cannot create New Empty File");
157 }
158 } catch (final IOException e) {
159 throw new Reply421Exception(
160 POST_EXECUTION_IN_ERROR_FOR_TRANSFER_SINCE_NO_FILE_FOUND);
161 } catch (final SecurityException e) {
162 throw new Reply421Exception(
163 POST_EXECUTION_IN_ERROR_FOR_TRANSFER_SINCE_NO_FILE_FOUND);
164 }
165 }
166
167 if (!newfile.canRead()) {
168
169 final String message =
170 "PostExecution in Error for Transfer since File is not readable: " +
171 transfer.getCommand() + ' ' + newfile.getAbsolutePath() + ':' +
172 newfile.canRead() + ' ' + transfer.getStatus() + ' ' +
173 transfer.getPath();
174 final CommandAbstractException exc = new Reply421Exception(
175 TRANSFER_DONE_BUT_FORCE_DISCONNECTION_SINCE_AN_ERROR_OCCURS_ON_POST_OPERATION);
176 WaarpActionLogger.logErrorAction(dbFtpSession, specialId, transfer,
177 message, exc.code, this);
178 throw exc;
179 }
180 } catch (final CommandAbstractException e1) {
181
182 final String message =
183 "PostExecution in Error for Transfer since No File found: " +
184 transfer.getCommand() + ' ' + transfer.getStatus() + ' ' +
185 transfer.getPath();
186 final CommandAbstractException exc = new Reply421Exception(
187 TRANSFER_DONE_BUT_FORCE_DISCONNECTION_SINCE_AN_ERROR_OCCURS_ON_POST_OPERATION);
188 WaarpActionLogger.logErrorAction(dbFtpSession, specialId, transfer,
189 message, exc.code, this);
190 throw exc;
191 }
192 args[4] = transfer.getCommand().toString();
193 args[5] = Long.toString(specialId);
194 final AbstractExecutor executor =
195 AbstractExecutor.createAbstractExecutor(auth, args, true,
196 futureCompletion);
197 executor.run();
198 futureCompletion.awaitOrInterruptible();
199 if (futureCompletion.isSuccess()) {
200
201 WaarpActionLogger.logAction(dbFtpSession, specialId,
202 "Post-Command executed: OK", this,
203 getFtpSession().getReplyCode(),
204 UpdatedInfo.RUNNING);
205 } else {
206
207 final String message =
208 "PostExecution in Error for Transfer: " + transfer.getCommand() +
209 ' ' + transfer.getStatus() + ' ' + transfer.getPath() + "\n " +
210 (futureCompletion.getCause() != null?
211 futureCompletion.getCause().getMessage() :
212 "Internal error of PostExecution");
213 final CommandAbstractException exc = new Reply421Exception(
214 TRANSFER_DONE_BUT_FORCE_DISCONNECTION_SINCE_AN_ERROR_OCCURS_ON_POST_OPERATION);
215 WaarpActionLogger.logErrorAction(dbFtpSession, specialId, transfer,
216 message, exc.code, this);
217 throw exc;
218 }
219 break;
220 default:
221
222 }
223 }
224
225 @Override
226 public final void afterRunCommandKo(final CommandAbstractException e) {
227 final String message =
228 "ExecHandler: KO: " + getFtpSession() + ' ' + e.getMessage();
229 final long specialId =
230 ((FileBasedAuth) getFtpSession().getAuth()).getSpecialId();
231 WaarpActionLogger.logErrorAction(dbFtpSession, specialId, null, message,
232 e.code, this);
233 ((FileBasedAuth) getFtpSession().getAuth()).setSpecialId(
234 org.waarp.common.database.DbConstant.ILLEGALVALUE);
235 }
236
237 @Override
238 public final void afterRunCommandOk() {
239 if (!(getFtpSession().getCurrentCommand() instanceof QUIT) &&
240 dbR66Session != null) {
241 final long specialId =
242 ((FileBasedAuth) getFtpSession().getAuth()).getSpecialId();
243 WaarpActionLogger.logAction(dbFtpSession, specialId,
244 "Transfer Command fully executed: OK", this,
245 getFtpSession().getReplyCode(),
246 UpdatedInfo.DONE);
247 ((FileBasedAuth) getFtpSession().getAuth()).setSpecialId(
248 org.waarp.common.database.DbConstant.ILLEGALVALUE);
249 }
250 }
251
252 @Override
253 public final void beforeRunCommand() throws CommandAbstractException {
254 long specialId = org.waarp.common.database.DbConstant.ILLEGALVALUE;
255
256 if (getFtpSession() == null || getFtpSession().getAuth() == null) {
257 return;
258 }
259 final FileBasedAuth auth = (FileBasedAuth) getFtpSession().getAuth();
260 if (auth.isAdmin()) {
261 logger.debug("Admin user so all actions are allowed");
262 return;
263 }
264
265 final FtpConstraintLimitHandler constraints =
266 ((FileBasedConfiguration) getFtpSession().getConfiguration()).getConstraintLimitHandler();
267 if (constraints != null) {
268 if (!auth.isIdentified()) {
269
270 } else if (auth.isAdmin()) {
271
272 } else if (!FtpCommandCode.isSpecialCommand(
273 getFtpSession().getCurrentCommand().getCode())) {
274
275 if (constraints.checkConstraintsSleep(1)) {
276 if (constraints.checkConstraints()) {
277
278 logger.info(
279 "Server overloaded. {} Try later... \n" + getFtpSession(),
280 constraints.lastAlert);
281 if (FileBasedConfiguration.fileBasedConfiguration.getFtpMib() !=
282 null) {
283 FileBasedConfiguration.fileBasedConfiguration.getFtpMib()
284 .notifyOverloaded(
285 "Server overloaded",
286 getFtpSession().toString());
287 }
288 throw new Reply451Exception("Server overloaded. Try later...");
289 }
290 }
291 }
292 }
293 final FtpCommandCode code = getFtpSession().getCurrentCommand().getCode();
294 logger.debug("Checking action vs auth before command: {}", code);
295 switch (code) {
296 case APPE:
297 case STOR:
298 case STOU:
299 auth.setSpecialId(specialId);
300 if (!auth.getCommandExecutor().isValidOperation(true)) {
301 throw new Reply504Exception("STORe like operations are not allowed");
302 }
303
304 specialId =
305 WaarpActionLogger.logCreate(dbFtpSession, "PrepareTransfer: OK",
306 getFtpSession().getCurrentCommand()
307 .getArg(), this);
308 auth.setSpecialId(specialId);
309
310 break;
311 case RETR:
312 auth.setSpecialId(specialId);
313 if (!auth.getCommandExecutor().isValidOperation(false)) {
314 throw new Reply504Exception(
315 "RETRieve like operations are not allowed");
316 }
317
318 specialId =
319 WaarpActionLogger.logCreate(dbFtpSession, "PrepareTransfer: OK",
320 getFtpSession().getCurrentCommand()
321 .getArg(), this);
322 auth.setSpecialId(specialId);
323
324 final WaarpFuture futureCompletion = new WaarpFuture(true);
325 final String[] args = new String[6];
326 args[0] = auth.getUser();
327 args[1] = auth.getAccount();
328 args[2] = auth.getBaseDirectory();
329 final String filename = getFtpSession().getCurrentCommand().getArg();
330 final FtpFile file = getFtpSession().getDir().setFile(filename, false);
331 args[3] = file.getFile();
332 args[4] = code.toString();
333 args[5] = Long.toString(specialId);
334 final AbstractExecutor executor =
335 AbstractExecutor.createAbstractExecutor(auth, args, false,
336 futureCompletion);
337 executor.run();
338 futureCompletion.awaitOrInterruptible();
339 if (futureCompletion.isSuccess()) {
340
341 if (!file.canRead()) {
342 logger.error("PreExecution in Error for Transfer since " +
343 "File downloaded but not ready to be retrieved: {} " +
344 " {} \n " + (futureCompletion.getCause() != null?
345 futureCompletion.getCause().getMessage() :
346 "File downloaded but not ready to be retrieved"), args[4],
347 args[3]);
348 throw new Reply421Exception(
349 "File downloaded but not ready to be retrieved");
350 }
351 WaarpActionLogger.logAction(dbFtpSession, specialId,
352 "Pre-Command executed: OK", this,
353 getFtpSession().getReplyCode(),
354 UpdatedInfo.RUNNING);
355 } else {
356
357 logger.error("PreExecution in Error for Transfer since " +
358 "File cannot be prepared to be retrieved: {} " +
359 " {} \n " + (futureCompletion.getCause() != null?
360 futureCompletion.getCause().getMessage() :
361 "File cannot be prepared to be retrieved"), args[4], args[3]);
362 throw new Reply421Exception(
363 "File cannot be prepared to be retrieved");
364 }
365 break;
366 default:
367
368 }
369 }
370
371 @Override
372 protected final void cleanSession() {
373
374 }
375
376 @Override
377 public final void exceptionLocalCaught(final Throwable cause) {
378 if (FileBasedConfiguration.fileBasedConfiguration.getFtpMib() != null) {
379 final String mesg;
380 if (cause != null && cause.getMessage() != null) {
381 mesg = cause.getMessage();
382 } else {
383 if (getFtpSession() != null) {
384 mesg = "Exception while " + getFtpSession().getReplyCode().getMesg();
385 } else {
386 mesg = "Unknown Exception";
387 }
388 }
389 FileBasedConfiguration.fileBasedConfiguration.getFtpMib().notifyError(
390 "Exception trapped", mesg);
391 }
392 if (FileBasedConfiguration.fileBasedConfiguration.getMonitoring() != null) {
393 if (getFtpSession() != null) {
394 FileBasedConfiguration.fileBasedConfiguration.getMonitoring()
395 .updateCodeNoTransfer(
396 getFtpSession().getReplyCode());
397 }
398 }
399 }
400
401 @Override
402 public final void executeChannelClosed() {
403 if (AbstractExecutor.useDatabase && !internalDb && dbR66Session != null) {
404 dbR66Session.disconnect();
405 dbR66Session = null;
406 }
407 if (dbFtpSession != null) {
408 dbFtpSession.disconnect();
409 dbFtpSession = null;
410 }
411 }
412
413 @Override
414 public final void executeChannelConnected(final Channel channel) {
415 if (AbstractExecutor.useDatabase) {
416 if (org.waarp.common.database.DbConstant.admin != null) {
417 try {
418 dbR66Session =
419 new DbSession(org.waarp.common.database.DbConstant.admin, false);
420 } catch (final WaarpDatabaseNoConnectionException e1) {
421 logger.warn("Database not ready due to {}", e1.getMessage());
422 final QUIT command =
423 (QUIT) FtpCommandCode.getFromLine(getFtpSession(),
424 FtpCommandCode.QUIT.name());
425 getFtpSession().setNextCommand(command);
426 dbR66Session = null;
427 internalDb = true;
428 }
429 }
430 }
431 if (DbConstantFtp.gatewayAdmin != null) {
432 try {
433 dbFtpSession = new DbSession(DbConstantFtp.gatewayAdmin, false);
434 } catch (final WaarpDatabaseNoConnectionException e1) {
435 logger.warn("Database not ready due to {}", e1.getMessage());
436 final QUIT command = (QUIT) FtpCommandCode.getFromLine(getFtpSession(),
437 FtpCommandCode.QUIT.name());
438 getFtpSession().setNextCommand(command);
439 dbFtpSession = null;
440 }
441 }
442 }
443
444 @Override
445 public final FileBasedAuth getBusinessNewAuth() {
446 return new FileBasedAuth(getFtpSession());
447 }
448
449 @Override
450 public final FileBasedDir getBusinessNewDir() {
451 return new FileBasedDir(getFtpSession());
452 }
453
454 @Override
455 public final FilesystemBasedFtpRestart getBusinessNewRestart() {
456 return new FilesystemBasedFtpRestart(getFtpSession());
457 }
458
459 @Override
460 public final String getHelpMessage(final String arg) {
461 return
462 "This FTP server is only intend as a Gateway. RETRieve actions may be unallowed.\n" +
463 "This FTP server refers to RFC 959, 775, 2389, 2428, 3659 and " +
464 "supports XDIGEST, XCRC, XMD5 and XSHA1 commands.\n" +
465 "XCRC, XMD5 and XSHA1 take a simple filename as argument, XDIGEST " +
466 "taking algorithm (among CRC32, ADLER32, MD5, MD2, " +
467 "SHA-1, SHA-256, SHA-384, SHA-512) followed by filename " +
468 "as arguments, and return 250 digest-value is the digest of filename.";
469 }
470
471 @Override
472 public final String getFeatMessage() {
473 final StringBuilder builder =
474 new StringBuilder("Extensions supported:").append('\n').append(
475 getDefaultFeatMessage());
476 if (getFtpSession().getConfiguration().getFtpInternalConfiguration()
477 .isAcceptAuthProt()) {
478 builder.append('\n').append(getSslFeatMessage());
479 }
480 builder.append('\n').append(FtpCommandCode.SITE.name()).append(' ')
481 .append("AUTHUPDATE").append("\nEnd");
482 return builder.toString();
483 }
484
485 @Override
486 public final String getOptsMessage(final String[] args)
487 throws CommandAbstractException {
488 if (args.length > 0) {
489 if (args[0].equalsIgnoreCase(FtpCommandCode.MLST.name()) ||
490 args[0].equalsIgnoreCase(FtpCommandCode.MLSD.name())) {
491 return getMLSxOptsMessage(args);
492 }
493 throw new Reply502Exception("OPTS not implemented for " + args[0]);
494 }
495 throw new Reply502Exception("OPTS not implemented");
496 }
497
498 @Override
499 public final AbstractCommand getSpecializedSiteCommand(
500 final FtpSession session, final String line) {
501 if (getFtpSession() == null || getFtpSession().getAuth() == null) {
502 return null;
503 }
504 if (!session.getAuth().isAdmin()) {
505 return null;
506 }
507 if (line == null) {
508 return null;
509 }
510 final String command;
511 String arg;
512 if (line.indexOf(' ') == -1) {
513 command = line;
514 arg = null;
515 } else {
516 command = line.substring(0, line.indexOf(' '));
517 arg = line.substring(line.indexOf(' ') + 1);
518 if (arg.length() == 0) {
519 arg = null;
520 }
521 }
522 final String COMMAND = command.toUpperCase();
523 if (!"AUTHUPDATE".equals(COMMAND)) {
524 return null;
525 }
526 final AbstractCommand abstractCommand = new AUTHUPDATE();
527 abstractCommand.setArgs(session, COMMAND, arg, FtpCommandCode.SITE);
528 return abstractCommand;
529 }
530 }