CrushFTP provides a plugin interface where you can develop your own third party plugins.

.


There are a few hooks into CrushFTP that the plugins get.  Plugins are run synchronously, so if you are going to do something that will take a while, multi thread it off.  While I won't likely change how these hooks are called, I may be willing to add additional hooks if your situation demonstrates it would be beneficial.

Hook#1
When the user logs in there is a hook to make up a user from the plugin before CrushFTP tries to verify the user in its internal system.  This is how the SQL & LDAP plugins work.  The action is "login".

Hook#2
Once the login has been verified, there is an action of "afterLogin" called for any manipulations that need to be done to the user before anything else happens.  This is where the HomeDirectory plugin modifies the directory access and generates the appropriate directory.  This hook includes the special item "uVFSObject" which is the CrushFTP class crushftp.server.VFS.  See the HomeDirectory for how to use this.

Hook#3
When any action is done, an access check is performed.  There is a hook provided here where the plugin will be called with an action of "access".

Hook#4
When any listing is produced, an action of "list" will be called passing in the listing object. (FTP,HTTP,WebDAV)

Hook#5
For FTP only: when a command is issued, an action of "command" is called.  This can be used to filter commands out, or to do specific actions.  If you respond to the command, be sure to clear out the command so CrushFTP doesn't also try and respond and put the FTP client out of sync.

To access a download, disconnect, upload type situation, use the event interface of a user and specify your plugin to be called in those types of events.

What can I do?
Virtually all info about a user is passed to the plugin when its called.  If you edit a string item directly in the object that is called, nothing will happen.  However, pulling out any of the "Properties" objects and changing values there will cause immediate results as that is how everything internally is referenced.

To learn the structure of things, I recommend printing it out to system out or logging it in some way.  Then look through it to find what you are interested in.

.

When a plugin is packaged up to be deployed into CrushFTP, there must be a minimum of two classes.  Start, and GUI.  These two classes must be in the base package that is the same name and case as the plugin.  For example, CrushSQL is named CrushSQL.jar.  Start and GUI are at: /CrushSQL/Start.java  and /CrushSQL/GUI.java.  You are free to have any other classes you want as well, just those two must exist and must have the required methods in them.

FilterCommand Example Source Code: (essentially the bare minimum in the classes.) [Start.java|http://www.crushftp.com/crush5wiki/attach/Third%20Party/filtercommand_Start.java]
[GUI.java|http://www.crushftp.com/crush5wiki/attach/Third%20Party/filtercommand_GUI.java]

CrushSQL Example Source Code:
[Start.java|http://www.crushftp.com/crush5wiki/attach/Third%20Party/Start.java]
[GUI.java|http://www.crushftp.com/crush5wiki/attach/Third%20Party/GUI.java]

----
Here is the source code to the HomeDirectory plugin.  Someone may find this useful in developing their own plugins.  Same rules as the above on how to make the package structure.
----
HomeDirectory Start.java

%%prettify 
{{{
package HomeDirectory;

import java.util.*;
import java.io.*;
import java.text.*;
import javax.swing.*;
import crushftp.server.*;
import crushftp.handlers.*;

public class Start implements Serializable
{
	Properties settings = new Properties();
	String version = "4.3.5";
	GUI g = null;
	Common common_code = new Common();

	public Properties getDefaults()
	{
		Properties p = new Properties();
		p.put("enabled","false");
		p.put("debug","false");
		p.put("path","");
		p.put("always_generate","false");
		p.put("uniqueFolder","false");
		p.put("uniqueFolderFormat","_MM.dd.yyyy");
		p.put("templateUser","");
		p.put("permissions","(read)(write)(view)(delete)(resume)(rename)(makedir)(deletedir)");
		p.put("username_filter","*");
		p.put("server_item","All");
		p.put("version",version);
		return p;
	}

	public void setSettings(Properties p) throws Exception
	{
		settings = p;

		if (g != null) g.setSettings(settings);
	}

	public Properties getSettings()
	{
		if (g != null) settings = g.getSettings();
		settings.put("version",version);
		return settings;
	}

	public javax.swing.JPanel getPrefsPanel()
	{
		if (g == null) g = new GUI();
		g.setParent(this);
		g.setSettings(settings);
		return g;
	}

	public Object run(Properties info)
	{
		if (!settings.getProperty("enabled").equalsIgnoreCase("true")) return null;

		Properties server_item = (Properties)info.get("server_item");
		if (info.getProperty("action","").equals("afterLogin") && (get("server_item").trim().equalsIgnoreCase("All") || get("server_item").trim().length() == 0 || (server_item.getProperty("ip","")+"_"+server_item.getProperty("port","")).equals(get("server_item").trim())))
		{
			generateHomeDir(info);
		}
		return null;
	}

	public void generateHomeDir(Properties info)
	{
		boolean found_user = false;
		Properties user = (Properties)info.get("user");
		String username = info.getProperty("username");
		if (username.equalsIgnoreCase("") || username.equalsIgnoreCase("anonymous") || username.equalsIgnoreCase("webstatistics") || username.equalsIgnoreCase("crush4admincontroller")) return;
		if (!Common.do_search(get("username_filter"),username,false,0)) return;
		try
		{
			VFS uVFS = (VFS)info.get("uVFSObject");
			Vector listing = new Vector();
			uVFS.getListing(listing,"/");
			String path = settings.getProperty("path","");
			if (!path.endsWith("/")) path += "/";
			String uniqueAddon = "";
			try{if (settings.getProperty("uniqueFolder","").equals("true")) uniqueAddon = new SimpleDateFormat(settings.getProperty("uniqueFolderFormat")).format(new java.util.Date());}catch(Exception e){msg(e);}

			msg("listing : " + listing.toString());
			if (get("always_generate").equals("true") || listing.size() == 0 || (!uniqueAddon.equals("") && new File(path+username+uniqueAddon).exists()))//empty dir, or this user was one we created previously
			{
				new File(path+username+uniqueAddon).mkdirs();

				Vector VFSItems = new Vector();
				Properties permissions = new Properties();
				//we also generate a cache of what the permissions are for the file system instead of loading from a file
				String uVFS_home = uVFS.getHome();
				String localPath = path+username+uniqueAddon;
				if (new File(uVFS_home+"/../VFS.XML").exists() && get("always_generate").equals("true"))
				{
					permissions = (Properties)Common.readXMLObject(uVFS_home+"/../VFS.XML");
				}

				if (permissions == null) permissions = new Properties();

				String buildPrivs = settings.getProperty("permissions","");
				permissions.put("/"+new File(localPath).getName().toUpperCase()+"/",buildPrivs);
				Properties dir_item = new Properties();
				dir_item.put("url",new File(localPath).toURI().toURL().toExternalForm());
				dir_item.put("type","dir");
				Vector v = new Vector();
				v.addElement(dir_item);
				Properties p = new Properties();
				p.put("dir","/");
				p.put("name",new File(localPath).getName());
				p.put("data",v);
				VFSItems.addElement(p);

				if (!get("always_generate").equals("true"))
				{
					Common.recurseDelete(uVFS_home+"/VFS/",false);
				}
				for (int x=0; x<VFSItems.size(); x++)
				{
					Properties pp = (Properties)VFSItems.elementAt(x);
					new File(uVFS_home+pp.getProperty("dir")).mkdirs();
					common_code.writeXMLObject(uVFS_home+pp.getProperty("dir")+pp.getProperty("name"),pp.get("data"),"VFS");
				}
				common_code.writeXMLObject(uVFS_home+"/../VFS.XML",permissions,"VFS");
				msg("setting home directory and permissions : " + buildPrivs);
				uVFS.reset();
				uVFS.setHome(uVFS_home);
				found_user = true;
			}
		}
		catch(Exception e)
		{
			msg(e);
		}


		if (found_user)
		{
			info.put("action","success");
		}
		else
		{
			info.put("action","failed");
		}
	}

	public void testSettings()
	{
		String error = "OK";
		try
		{
		}
		catch(Exception e)
		{
			error = e.toString();
		}
		JOptionPane.showMessageDialog(null, error, "Alert",JOptionPane.ERROR_MESSAGE);
	}

	public String get(String key)
	{
		return settings.getProperty(key,"");
	}

	public void msg(String s)
	{
		if (settings.getProperty("debug").equals("true")) crushftp.handlers.Common.debug(0,"HomeDirectory:"+s);
	}

	public void msg(Exception e)
	{
		if (settings.getProperty("debug").equals("true")) crushftp.handlers.Common.debug(0,e);
	}

}

}}}
/%


HomeDirectory GUI.java

%%prettify 
{{{
package HomeDirectory;

import java.awt.*;

import javax.swing.*;

import java.awt.event.*;
import java.util.*;
import java.lang.reflect.*;
import java.io.*;

public class GUI extends JPanel
{
	private JLabel jLabel1 = new JLabel();
	private JTextField path_field = new JTextField();

	public GUI()
	{
		try
		{
			jbInit();
		}
		catch(Exception ex)
		{
			ex.printStackTrace();
		}
	}

	Object parent = null;
	private JPanel jPanel1 = new JPanel();
	private JCheckBox enabled_cb = new JCheckBox();
	private JCheckBox debug_cb = new JCheckBox();
    private JLabel jLabel10 = new JLabel();
	private JComboBox server_item_combo = new JComboBox();
    private JButton browse_button = new JButton();
    private JScrollPane jScrollPane1 = new JScrollPane();
    private JTextField permissions_field = new JTextField();
    private JLabel jLabel7 = new JLabel();
    private JCheckBox uniqueFolder_cb = new JCheckBox();
    private JTextField uniqueFolderFormat_field = new JTextField();
    private JCheckBox always_generate_cb = new JCheckBox();
    private JLabel jLabel8 = new JLabel();
    private JTextField username_filter = new JTextField();
    private JLabel jLabel2 = new JLabel();
	public void setParent(Object parent)
	{
		this.parent = parent;
	}

	void jbInit() throws Exception
	{
		jLabel1.setFont(new java.awt.Font("Arial", 0, 12));
		jLabel1.setText("Path:");
		jLabel1.setBounds(new Rectangle(15, 16, 37, 29));
		this.setLayout(null);
		path_field.setBounds(new Rectangle(76, 18, 382, 22));
		jPanel1.setBorder(BorderFactory.createEtchedBorder());
		jPanel1.setBounds(new Rectangle(3, 38, 583, 334));
		jPanel1.setLayout(null);
		enabled_cb.setFont(new java.awt.Font("Arial", 0, 12));
        enabled_cb.setText("Enabled");
		enabled_cb.setBounds(new Rectangle(6, 6, 100, 22));
		debug_cb.setBounds(new Rectangle(122, 6, 100, 22));
	debug_cb.setFont(new java.awt.Font("Arial", 0, 12));
        debug_cb.setText("Debug");
        jLabel10.setFont(new java.awt.Font("Arial", 0, 12));
        jLabel10.setText("<html> <b>HomeDirectory Plugin Help</b><br> Simple plugin that just " +
    "generates the users home directory when they first login.<br> This " +
    "way you could use say the SQL plugin and setup a user.  When they " +
    "actually<br> login, a new folder could be created for them to upload " +
    "into.<br> <br> <b>How to use:</b><br> Set a location where users home " +
    "folders will be created at.  Use the browse button to pick<br> the " +
    "location.<br> <br> Specify the permission they will have in that " +
    "folder.  In general you will give them full<br> permissions here " +
    "as this folder is probably empty.  This way they can then login, " +
    "and upload<br> files to it.  If the folder doesn\'t exist, it will " +
    "be created for them.<br> <br> To test, create a new user via the " +
    "user manager.  Don\'t setup any directory<br> items in the \"user\'s " +
    "stuff (VFS)\" pane.  Then login with that user and look for<br> the " +
    "new folder that just got created.<br>"+
	"For the time/date format on a unique folder, search google for 'SimpleDateFormat'.<br>" +
	"<br> Questions?  Email me: " +
    "ben@crushftp.com<br> <br> --Ben Spink<br> </html> ");
        browse_button.setBounds(new Rectangle(462, 10, 107, 31));
        browse_button.setText("Browse...");
        browse_button.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(ActionEvent e) {
                browse_button_actionPerformed(e);
            }
        });
        jScrollPane1.setBounds(new Rectangle(15, 173, 557, 142));
		server_item_combo.setToolTipText("Allows you to specify the individual server that can use this plugin " +
	    "instance.");
	        server_item_combo.setBounds(new Rectangle(390, 133, 183, 27));
        permissions_field.setText("(read)(write)(view)(delete)(resume)(rename)(makedir)(deletedir)");
        permissions_field.setBounds(new Rectangle(147, 55, 412, 22));
        jLabel7.setFont(new java.awt.Font("Arial", 0, 12));
        jLabel7.setText("Directory permissions:");
        jLabel7.setBounds(new Rectangle(16, 51, 131, 29));
		uniqueFolder_cb.setText("Use Unique Time Stamped Folder? (ex. johndoe_01.01.2007)");
        uniqueFolder_cb.setFont(new java.awt.Font("Arial", 0, 12));
        uniqueFolder_cb.setOpaque(false);
        uniqueFolder_cb.setBounds(new Rectangle(17, 84, 379, 22));
        uniqueFolderFormat_field.setBounds(new Rectangle(402, 84, 140, 22));
        uniqueFolderFormat_field.setToolTipText("Java SimpleDateFormat - search google for the format.");
        uniqueFolderFormat_field.setText("MM.dd.yyyy");
        always_generate_cb.setBounds(new Rectangle(17, 135, 213, 22));
        always_generate_cb.setFont(new java.awt.Font("Arial", 0, 12));
        always_generate_cb.setText("Always Generate Home Folder");
        jLabel8.setBounds(new Rectangle(325, 132, 67, 29));
        jLabel8.setText("Server:");
        jLabel8.setFont(new java.awt.Font("Arial", 0, 12));
        username_filter.setBounds(new Rectangle(157, 109, 120, 22));
        jLabel2.setBounds(new Rectangle(17, 106, 142, 29));
        jLabel2.setText("Username matching :");
        jLabel2.setFont(new java.awt.Font("Arial", 0, 12));
        this.add(jPanel1, null);
        jPanel1.add(jLabel1, null);
        jPanel1.add(path_field, null);
        jPanel1.add(browse_button, null);
		this.add(enabled_cb, null);
		this.add(debug_cb, null);
        jPanel1.add(jScrollPane1, null);
        jPanel1.add(permissions_field, null);
        jPanel1.add(jLabel7, null);
        jPanel1.add(uniqueFolder_cb, null);
        jPanel1.add(always_generate_cb, null);
        jPanel1.add(server_item_combo, null);
        jPanel1.add(jLabel8, null);
        jPanel1.add(uniqueFolderFormat_field, null);
        jPanel1.add(jLabel2, null);
        jPanel1.add(username_filter, null);
        jScrollPane1.getViewport().add(jLabel10, null);
		buildServerList();
	}

	public Properties getSettings()
	{
		Properties p = new Properties();
		p.put("enabled",enabled_cb.isSelected()+"");
		p.put("debug",debug_cb.isSelected()+"");
		p.put("path",path_field.getText());
		p.put("permissions",permissions_field.getText());
		p.put("username_filter",username_filter.getText());
		p.put("always_generate",always_generate_cb.isSelected()+"");
		p.put("uniqueFolder",uniqueFolder_cb.isSelected()+"");
		p.put("uniqueFolderFormat",uniqueFolderFormat_field.getText());
		p.put("server_item",server_item_combo.getSelectedItem());
		return p;
	}

	public void setSettings(Properties p)
	{
		  enabled_cb.setSelected(p.getProperty("enabled").equalsIgnoreCase("true"));
		  debug_cb.setSelected(p.getProperty("debug").equalsIgnoreCase("true"));
		  path_field.setText(p.getProperty("path"));
		  permissions_field.setText(p.getProperty("permissions"));
		  username_filter.setText(p.getProperty("username_filter"));
		  always_generate_cb.setSelected(p.getProperty("always_generate").equalsIgnoreCase("true"));
		  uniqueFolder_cb.setSelected(p.getProperty("uniqueFolder").equalsIgnoreCase("true"));
		  uniqueFolderFormat_field.setText(p.getProperty("uniqueFolderFormat"));
		  server_item_combo.setSelectedItem(p.getProperty("server_item"));
	}

	public void buildServerList()
	{
		server_item_combo.removeAllItems();
		server_item_combo.addItem("All");
		try
		{
			Vector the_server_list = (Vector)crushftp.server.ServerStatus.server_settings.get("server_list");
			for (int x=0; x<the_server_list.size(); x++)
			{
				Properties server_item = (Properties)((Properties)the_server_list.elementAt(x)).clone();
				server_item_combo.addItem(server_item.getProperty("ip","lookup")+"_"+server_item.getProperty("port","21"));
			}
		}
		catch(Exception e)
		{
			e.printStackTrace();
		}
	}

	void test_btn_actionPerformed(ActionEvent e)
	{
		Properties p = getSettings();
		try
		{
			Method setSettings = parent.getClass().getMethod("setSettings",new Class[] { new Properties().getClass() });
			setSettings.invoke(parent,new Object[]{p});
			Method testSettings = parent.getClass().getMethod("testSettings",null);
			testSettings.invoke(parent,null);
		}
		catch(Exception ee)
		{
		}
	}

    void browse_button_actionPerformed(ActionEvent e)
	{
		JFileChooser d = new JFileChooser();
		d.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
		d.setCurrentDirectory(new File("/"));
		d.setApproveButtonText("Pick");
		d.setFileFilter(null);
		int res = d.showOpenDialog(this);
		String the_dir = "";
		if(res == JFileChooser.APPROVE_OPTION)
		{
			File f = d.getSelectedFile();
			the_dir = f.toString().replace('\\','/')+"/";
		}
		else return;
		path_field.setText(the_dir);
    }

}
}}}
/%