/*
* Copyright (c) 2001 Sun Microsystems, Inc. All rights
* reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* 3. The end-user documentation included with the redistribution,
* if any, must include the following acknowledgment:
* "This product includes software developed by the
* Sun Microsystems, Inc. for Project JXTA."
* Alternately, this acknowledgment may appear in the software itself,
* if and wherever such third-party acknowledgments normally appear.
*
* 4. The names "Sun", "Sun Microsystems, Inc.", "JXTA" and "Project JXTA" must
* not be used to endorse or promote products derived from this
* software without prior written permission. For written
* permission, please contact Project JXTA at http://www.jxta.org.
*
* 5. Products derived from this software may not be called "JXTA",
* nor may "JXTA" appear in their name, without prior written
* permission of Sun.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL SUN MICROSYSTEMS OR
* ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
* ====================================================================
*
* This software consists of voluntary contributions made by many
* individuals on behalf of Project JXTA. For more
* information on Project JXTA, please see
* <http://www.jxta.org/>.
*
* This license is based on the BSD license adopted by the Apache Foundation.
*
* $Id: sftp.java,v 1.11 2007/02/09 23:12:43 hamada Exp $
*/
package net.jxta.impl.shell.bin.sftp;
import net.jxta.discovery.DiscoveryService;
import net.jxta.document.AdvertisementFactory;
import net.jxta.endpoint.ByteArrayMessageElement;
import net.jxta.endpoint.Message;
import net.jxta.id.IDFactory;
import net.jxta.impl.shell.ShellApp;
import net.jxta.impl.shell.ShellEnv;
import net.jxta.impl.shell.ShellObject;
import net.jxta.peergroup.PeerGroup;
import net.jxta.pipe.InputPipe;
import net.jxta.pipe.OutputPipe;
import net.jxta.pipe.PipeService;
import net.jxta.protocol.PipeAdvertisement;
import java.io.*;
import java.util.Enumeration;
/**
* fsend Shell command: send a file to a user.
*/
public class sftp extends ShellApp implements Runnable {
private DiscoveryService discovery = null;
private ShellEnv env = null;
private PipeAdvertisement userAdv = null;
private static final int WaitingTime = 2000;
private static final int MAXRETRIES = 5;
public static final String SftpNameTag = "JxtaSftpUserName";
public static final String SftpIDTag = "JxtaSftpPipeID";
private static final String EnvName = "sftpd";
// local user
private String userName = null;
private static final String SenderName = "JxtaSftpSenderName";
private static final String JftpData = "JxtaSftpData";
private static final String FileInfo = "JxtaSftpInfo";
private Thread thread = null;
// Some file stuff
private String fsep = "";
private static final String fdirname = "sftp";
private String fdir = null;
public sftp() {
}
private int syntaxError() {
println("Usage: sftp -register userName");
println(" sftp -login userName ");
println(" sftp -logout userName ");
println(" sftp -s userName destUserName filename");
println(" sftp -search");
return ShellApp.appParamError;
}
public int startApp(String[] args) {
fdir = fdirname + File.separator;
File f = new File(fdir);
if (!f.exists()) {
try {
if (!f.mkdir()) {
// try "home" directory as root
fdir = "";
}
} catch (Exception ex) {
fdir = null;
printStackTrace("Failed creating directory " + fdir, ex);
}
}
if ((args == null) || (args.length == 0)) {
return syntaxError();
}
env = getEnv();
discovery = getGroup().getDiscoveryService();
if (args[0].equals("-register")) {
return registerNewUser(args);
}
if (args[0].equals("-login")) {
return login(args);
}
if (args[0].equals("-logout")) {
return logout(args);
}
if (args[0].equals("-search")) {
return findUsers();
}
return sendFile(args);
}
private boolean daemonRunning(String name) {
ShellObject obj = env.get(EnvName + "." + name + "@" +
(getGroup().getPeerGroupAdvertisement()).getName());
return obj != null;
}
private int login(String[] args) {
if (args.length != 2) {
return syntaxError();
}
String name = args[1];
if (daemonRunning(name)) {
consoleMessage("user " + name + " is already listening");
return ShellApp.appMiscError;
}
PipeAdvertisement adv = findUserAdv(name);
if (adv == null) {
consoleMessage( name + " is not a registered user");
return ShellApp.appMiscError;
} else
discovery.remotePublish(adv);
runDaemon(name, adv);
return ShellApp.appNoError;
}
private int findUsers() {
Enumeration each;
int i = 0;
discovery.getRemoteAdvertisements(null, DiscoveryService.ADV,
PipeAdvertisement.NameTag,
SftpNameTag + ".*",
20, null);
while (true) {
try {
if (i > MAXRETRIES) {
println("");
break;
}
Thread.sleep(WaitingTime);
print(".");
i++;
} catch (Exception e) {
printStackTrace("findUsers failed ", e);
}
}
try {
each = discovery.getLocalAdvertisements(DiscoveryService.ADV,
PipeAdvertisement.NameTag,
SftpNameTag + ".*");
if (each.hasMoreElements()) {
PipeAdvertisement adv;
consoleMessage("found the following registrations:");
while (each.hasMoreElements()) {
try {
adv = (PipeAdvertisement) each.nextElement();
println(adv.getName());
} catch (Exception e) {
printStackTrace("findUsers failed ", e );
}
}
}
} catch (Exception e) {
printStackTrace("findUsers failed ", e );
}
return ShellApp.appNoError;
}
private int logout(String[] args) {
if (args.length != 2) {
return syntaxError();
}
String name = args[1];
if (!daemonRunning(name)) {
consoleMessage("user " + name + " is not listening");
return ShellApp.appMiscError;
}
stopDaemon(name);
return ShellApp.appNoError;
}
// sftp -s <source pipe> <dest pipe> <filename>
// is required
private static final int OBUFLEN = 15000;
private int sendFile(String[] args) {
if (!args[0].equals("-s") || args.length != 4) {
return syntaxError();
}
String srcName = args[1];
String name = args[2];
String fileName = args[3];
// check if the srcName is registered
if (!daemonRunning(srcName)) {
consoleMessage("user " + srcName + " is not logged in");
return ShellApp.appMiscError;
}
// Locate target name
PipeAdvertisement adv = findUserAdv(name);
if (adv == null) {
consoleMessage(name + " is not a registered user");
return ShellApp.appMiscError;
}
// Try and connect to target
OutputPipe pipeOut;
try {
consoleMessage("found user's advertisement attempting to connect");
pipeOut = getGroup().getPipeService().createOutputPipe(adv, 120000);
println("Please be patient ...");
if (pipeOut == null) {
consoleMessage("user " + name + " is not listening. Try again later");
return ShellApp.appMiscError;
}
} catch (Exception e) {
printStackTrace( "user " + name + " is not listening. Try again later", e );
return ShellApp.appMiscError;
}
// Try and open the file
File f;
DataInputStream fs;
try {
f = new File(fileName);
fs = new DataInputStream(new FileInputStream(f));
} catch (Exception e) {
printStackTrace("Could not open " + fileName, e);
pipeOut.close();
return ShellApp.appMiscError;
}
// Get the user text
long fileSize = f.length();
// fileinfo message
// Strip any path info
int pix = fileName.lastIndexOf(fsep);
String stripped;
if (pix != -1)
stripped = fileName.substring(pix + 1);
else
stripped = fileName;
// Make file info element for first message only
String start = stripped + "\n" + fileSize;
Message msg;
byte[] buf = new byte[OBUFLEN];
int n16kbufs = (int) (fileSize / OBUFLEN);
int lastBufSize = (int) (fileSize % OBUFLEN);
boolean infoDone = false;
long stime = System.currentTimeMillis();
int chunks = n16kbufs + (lastBufSize == 0 ? 0 : 1);
consoleMessage("connected to user " + name);
println("Sending file " + fileName + ", size = " + fileSize + " bytes" +
" in " + chunks + " chunks");
for (int i = 0; i < n16kbufs; i++) {
try {
// Build a message
msg = new Message();
// set file info element in first message
if (i == 0) {
infoDone = true;
// file info
msg.addMessageElement(new ByteArrayMessageElement(FileInfo, null, start.getBytes(), null));
}
// read the data
fs.readFully(buf, 0, OBUFLEN);
// data elements
msg.addMessageElement(new ByteArrayMessageElement(JftpData, null, buf, null));
// src name
msg.addMessageElement(new ByteArrayMessageElement(SenderName, null, srcName.getBytes(), null));
pipeOut.send(msg);
print("!");
} catch (Exception e) {
printStackTrace("Failed to send file " + fileName + " to user :" + name, e);
try {
fs.close();
} catch (Exception ex) {
printStackTrace("Close failed for " + fileName, ex);
}
pipeOut.close();
return ShellApp.appMiscError;
}
}
// See if we have one more buffer to send
if (lastBufSize != 0) {
try {
// Build a message
msg = new Message();
// set file info element in first message only
if (!infoDone) {
// file info
msg.addMessageElement(new ByteArrayMessageElement(FileInfo, null, start.getBytes(), null));
}
// read the data
fs.readFully(buf, 0, lastBufSize);
// data element
msg.addMessageElement(new ByteArrayMessageElement(JftpData, null, buf, 0, lastBufSize, null));
// src name element
msg.addMessageElement(new ByteArrayMessageElement(SenderName, null, srcName.getBytes(), null));
pipeOut.send(msg);
print("!");
} catch (Exception e) {
printStackTrace("Failed to send file " + fileName + " to user :" + name, e);
try {
fs.close();
} catch (Exception ex) {
printStackTrace("Close failed for " + fileName, ex);
}
pipeOut.close();
return ShellApp.appMiscError;
}
}
// K bytes per second calcul
long elapsedTime = System.currentTimeMillis() - stime;
// protect agains div by 0
if (elapsedTime == 0)
elapsedTime = 1;
long kbpsec = fileSize / elapsedTime;
float secs = (float) (((float) elapsedTime) / 1000.0);
println("\nSent: " + fileSize + " bytes in " + secs + " secs[" +
kbpsec + "Kbytes/sec]");
try {
fs.close();
} catch (Exception ex) {
printStackTrace("Close failed for " + fileName, ex);
}
pipeOut.close();
return ShellApp.appNoError;
}
private int registerNewUser(String[] args) {
boolean secure = true;
if ((args.length == 3) && ("-insecure".equals(args[2])))
secure = false;
else if ((args.length != 2)) {
return syntaxError();
}
String name = args[1];
// Always secure
String type;
if (secure)
type = PipeService.UnicastSecureType;
else
type = PipeService.UnicastType;
// Check if there is already a registered user of the
// same name.
PipeAdvertisement adv = findUserAdv(name);
if (adv != null) {
consoleMessage("Sorry, user " + name + " is already registered");
return ShellApp.appMiscError;
}
try {
// Create a pipe advertisement for this pipe.
adv = (PipeAdvertisement)
AdvertisementFactory.newAdvertisement(PipeAdvertisement.getAdvertisementType());
} catch (Exception all) {
printStackTrace("Advertisement document could not be created", all);
return ShellApp.appMiscError;
}
ShellObject obj = env.get("stdgroup");
// extract the advertisement
PeerGroup group = (PeerGroup) obj.getObject();
adv.setPipeID(IDFactory.newPipeID(group.getPeerGroupID()));
adv.setName(SftpNameTag + "." + name);
adv.setType(type);
try {
// Save the document into the public folder
discovery.publish(adv);
discovery.remotePublish(adv);
} catch (Exception e2) {
printStackTrace("Advertisement could not be saved", e2);
return ShellApp.appMiscError;
}
println("User : " + name + " is now registered");
return ShellApp.appNoError;
}
// Make sure "name" is the advertised name
private boolean checkUserAdv(PipeAdvertisement adv, String name) {
if (adv == null)
return false;
if (adv.getName() == null)
return false;
String str = adv.getName();
return str.startsWith(SftpNameTag) && str.endsWith(name);
}
private PipeAdvertisement findUserAdv(String name) {
Enumeration each;
// First look in the local storage
try {
each = discovery.getLocalAdvertisements(DiscoveryService.ADV,
PipeAdvertisement.NameTag,
SftpNameTag + ".*");
if ((each != null) && (each.hasMoreElements())) {
PipeAdvertisement adv;
while (each.hasMoreElements()) {
try {
adv = (PipeAdvertisement) each.nextElement();
if (adv.getName().endsWith(name)) {
if (checkUserAdv(adv, name)) {
return adv;
}
}
} catch (Exception e) {
continue;
}
if (checkUserAdv(adv, name)) {
return adv;
}
}
}
} catch (Exception e) {
printStackTrace("Exception locating advertisement", e);
}
// Now, search remote
discovery.getRemoteAdvertisements(null, DiscoveryService.ADV,
PipeAdvertisement.NameTag,
SftpNameTag + "." + name,
2, null);
// Wait a bit in order to get an answer.
int i = 0;
while (true) {
try {
if (i > MAXRETRIES) {
println("");
break;
}
Thread.sleep(WaitingTime);
print(".");
i++;
} catch (Exception e) {
printStackTrace("Exception locating advertisement", e);
}
// Look in the local storage again
try {
each = discovery.getLocalAdvertisements(DiscoveryService.ADV,
PipeAdvertisement.NameTag,
SftpNameTag + "." + name);
if ((each != null) && (each.hasMoreElements())) {
PipeAdvertisement adv;
while (each.hasMoreElements()) {
try {
adv = (PipeAdvertisement) each.nextElement();
} catch (Exception e) {
continue;
}
if (checkUserAdv(adv, name)) {
return adv;
}
}
}
} catch (Exception e) {
printStackTrace("Exception locating advertisement", e);
}
}
// adv not found
return null;
}
private void runDaemon(String name, PipeAdvertisement adv) {
userAdv = adv;
userName = name;
thread = new Thread(this, "Jftpd:Jftpd Deamon");
thread.start();
// Store this object
env.add(EnvName + "." + name + "@" +
(getGroup().getPeerGroupAdvertisement()).getName(),
new ShellObject<ShellApp>("Sftpd Deamon for " + name + "@" +
(getGroup().getPeerGroupAdvertisement()).getName(),
this));
}
private void stopDaemon(String name) {
String dname = EnvName + "." + name + "@" +
(getGroup().getPeerGroupAdvertisement()).getName();
ShellObject obj = env.get(dname);
if (obj == null) {
consoleMessage("Daemon(" + dname + ") for " + name + " not found");
return;
}
sftp d;
try {
d = (sftp) obj.getObject();
d.thread.interrupt();
env.remove(dname);
} catch (Exception e) {
printStackTrace("cannot stop daemon for " + name, e);
}
}
public void run() {
try {
InputPipe pipeIn;
try {
pipeIn = getGroup().getPipeService().createInputPipe(userAdv);
} catch (Exception e) {
return;
}
if (pipeIn == null) {
consoleMessage("cannot open InputPipe");
return;
}
// Listen on the pipe
Message msg;
// Message counter
int i = 0;
// Receive a file
File f;
FileOutputStream fout = null;
String fileName = null;
int fsize = 0;
int offset = 0;
long stime = 0;
while (true) {
try {
msg = pipeIn.waitForMessage();
if (msg == null) {
if (Thread.interrupted()) {
// We have been asked to stop
consoleMessage("stop listening for user " + userName);
pipeIn.close();
return;
}
println("NULL msg");
continue;
}
} catch (Exception e) {
// This exception probaly happened because the
// deamon has been interrupted. In any case, that
// shows that sftpd cannot receive messages anyway.
// clean up.
pipeIn.close();
return;
}
// Get the first tag
String senderName;
// Get the file information
// i == 0 on start or restart
if (i++ == 0) {
fout = null; // used as a flag
String fileInfo = (msg.getMessageElement(FileInfo)).toString();
if (fileInfo == null) {
// Something is broken
pipeIn.close();
consoleMessage("bad message received. Stopping daemon for user :" +
userName);
env.remove(EnvName + "." + userName + "@" +
(getGroup().getPeerGroupAdvertisement()).getName());
return;
}
// open the file for output.
// fileInfo == "filename\nfilesize"
int j = fileInfo.indexOf("\n");
fileName = fileInfo.substring(0, j);
String ofile = fdir + fileName;
try {
f = new File(ofile);
if (f.exists()) {
f.delete();
}
if (f.createNewFile()) {
fout = new FileOutputStream(f);
}
} catch (Exception e) {
printStackTrace("Could not create output file " + ofile, e);
consoleMessage("Will receive but not write the data");
fout = null;
}
// file size
String filesize = fileInfo.substring(j + 1);
fsize = new Integer(filesize);
// start at BOF
offset = 0;
stime = System.currentTimeMillis();
}
// Get sender information
senderName = (msg.getMessageElement(SenderName)).toString();
if (i == 1) {
println("\nReceive " + fileName + "[" + fsize + " bytes] from " +
senderName);
}
// write data to file
byte[] data = (msg.getMessageElement(JftpData)).getBytes(false);
if (data == null) {
println("sftpd: user " + senderName + " sent empty data");
continue;
}
if (fout != null) {
fout.write(data, 0, data.length);
}
// See if the file transfer is done
offset += data.length;
if (offset == fsize) {
println("! Done");
i = 0;
if (fout != null)
fout.close();
// elapsed time and bytes per sec.
long elapsedTime = System.currentTimeMillis() - stime;
// div by 0 check
if (elapsedTime == 0)
elapsedTime = 1;
long kbpsec = ((long) fsize) / elapsedTime;
float secs = (float) (((float) elapsedTime) / 1000.0);
println("Received: " + fsize + " bytes in " + secs + " secs[" +
kbpsec + "Kbytes/sec]");
} else {
print("!");
}
}
} catch (Throwable all) {
printStackTrace("Uncaught Throwable in thread :" + Thread.currentThread().getName(), all);
}
}
@Override
public String getDescription() {
return "Send a file to another peer";
}
@Override
public void help() {
println("NAME");
println(" sftp - send a file to another peer ");
println(" ");
println("SYNOPSIS");
println(" ");
println(" sftp -register <userName>");
println(" sftp -login <userName>");
println(" sftp -logout <userName>");
println(" sftp -s <user> <userName> <fileName>");
println(" ");
println("DESCRIPTION");
println(" ");
println("The 'sftp command implements a secure file transfer");
println("where one peer can send a file to a second.");
println("to use 'sftp'. The user needs to register himself. This is done");
println("via the following steps:");
println("Step 1: Register via 'sftp -register <username>' command. This command");
println(" creates a secure sftp advertisement for that user. This has to");
println(" be done only once, the first time the user registers with");
println(" sftp. The system remembers it across reboot.");
println(" ");
println("Step 2: Login via 'sftp -login <username>' command. This command");
println(" login the user and start a listener daemon. This has to");
println(" to be done everytime the peer is restarted.");
println(" ");
println("Step 3: User can securely send a file to another user via the command");
println(" 'sftp -s <myusername> <destusername> <filename>'. This will send the");
println(" file <filename> to the dest. The file is written on the sftp");
println(" subdirectory of the directory where the shell is started.");
println(" ");
println(" JXTA>sftp -s moi mike photo.gif");
println(" sftp is connected to user mike");
println(" Sending file photo.gif, size = 55692 bytes");
println(" ");
println("To stop receiving any more files the user can stop the sftp");
println("listener daemon by entering the command 'sftp -logout <username>'");
println(" ");
println("OPTIONS");
println(" ");
println(" -register register a new user name ");
println(" -login log user and set default user");
println(" -logout logout");
println(" -s specify current user, and file names");
println(" ");
println("EXAMPLE");
println(" ");
println(" JXTA>sftp -register me");
println(" JXTA>sftp -login me");
println(" JXTA>sftp -s me you /tmp/nihow.jpg");
println(" ");
println("This example shows how a new user 'me' can register and log into sftp,");
println("and send a file to the user 'you'. User 'you' needs to be similarly");
println("registered and logged on. The above file is written as sftp/nihow.jpg");
println(" ");
println("SEE ALSO");
}
}