Package cu.ftpd

Source Code of cu.ftpd.Services

package cu.ftpd;

import cu.ftpd.commands.site.SiteCommandHandler;
import cu.ftpd.commands.site.actions.*;
import cu.ftpd.commands.transfer.TransferPostProcessor;
import cu.ftpd.commands.transfer.TransferPostProcessorAdapter;
import cu.ftpd.events.*;
import cu.ftpd.filesystem.metadata.MetadataHandler;
import cu.ftpd.filesystem.permissions.PermissionConfigurationException;
import cu.ftpd.filesystem.permissions.Permissions;
import cu.ftpd.logging.Logging;
import cu.ftpd.modules.Module;
import cu.ftpd.modules.zipscript.ZipscriptModule;
import cu.ftpd.persistence.Saver;
import cu.ftpd.user.statistics.UserStatistics;
import cu.ftpd.user.statistics.local.LocalUserStatistics;
import cu.ftpd.user.statistics.none.NoUserStatistics;
import cu.ftpd.user.statistics.remote.client.RmiUserStatisticsClient;
import cu.ftpd.user.userbases.Userbase;
import cu.ftpd.user.userbases.actions.*;
import cu.ftpd.user.userbases.anonymous.AnonymousUserbase;
import cu.ftpd.user.userbases.changetracking.AsynchronousMessageQueueChangeTracker;
import cu.ftpd.user.userbases.changetracking.Change;
import cu.ftpd.user.userbases.changetracking.ChangeApplicator;
import cu.ftpd.user.userbases.changetracking.ChangeTracker;
import cu.ftpd.user.userbases.local.LocalUserbase;
import cu.ftpd.user.userbases.remote.client.RmiRemoteUserbase;
import cu.settings.ConfigurationException;
import cu.settings.XMLSettings;

import java.io.*;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URL;
import java.net.URLClassLoader;
import java.rmi.NotBoundException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
* @author captain
* @version $Id: Services.java 296 2009-03-06 23:22:31Z jevring $
* @since 2008-okt-25 - 01:27:17
*/
public class Services {
    protected FtpdSettings settings;
    protected SiteCommandHandler siteCommandHandler;
    protected URLClassLoader customClassLoader;
    protected EventHandler eventHandler;
    protected Userbase userbase;
    protected Map<String, Module> modules = new HashMap<>();
    protected Permissions permissions;
    protected MetadataHandler metadataHandler;
    protected UserStatistics userStatistics;
    protected TransferPostProcessor transferPostProcessor = new TransferPostProcessorAdapter(); // this is a fallback in case we don't load any other module for this purpose
    protected AsynchronousMessageQueueChangeTracker changeTracker;

    protected Services(){} // For use by CubncServices

    public Services(FtpdSettings settings) throws IOException, ClassNotFoundException, InstantiationException, IllegalAccessException, NotBoundException, ConfigurationException, PermissionConfigurationException {
        this.settings = settings;
        configureMetadataHandler();
        configureCustomClassLoader();
        this.siteCommandHandler = new SiteCommandHandler(settings);
        initializeCustomSiteCommands();
        configureEventHandler();
        configureUserbase();
        siteCommandHandler.registerAction("chown", new Chown()); // note: only use this when we have disk (cuftpd) AND a cuftpd userbase. (create a different method body when we're doing a CubncServices object)
        configureUserStatistics();
        configureModules();
        if (modules.containsKey("zipscript")) {
            Module zipscript = modules.get("zipscript");
            if (zipscript instanceof ZipscriptModule) {
                ZipscriptModule zm = (ZipscriptModule)zipscript;
                setTransferPostProcessor(zm.getZipscript());
            }
        }
        initializePermissions();
        initializeLocalhostCommands();
    }

    /**
     * Commands that only make sense on localhost, since they involve things related to the disk.
     */
    private void initializeLocalhostCommands() {
        siteCommandHandler.registerAction("wipe", new Wipe());
        siteCommandHandler.registerAction("stat", new Stat()); // statline: section, credits, etc.
        siteCommandHandler.registerAction("traffic", new Traffic());

    }

    // Settings
    public FtpdSettings getSettings() {
        return settings;
    }

    // SiteCommandHandler
    public SiteCommandHandler getSiteCommandHandler() {
        return siteCommandHandler;
    }

    public void initializeCustomSiteCommands() throws ClassNotFoundException, IllegalAccessException, InstantiationException {
        // reads the commands from cuftpd.xml and adds them as available commands.
        // NOTE: commands with the same name as built in commands will be overwritten
        String path;
        String name;
        String type;
        boolean addResponseCode;
        int i = 1;
        Map<String, Action> instances = new HashMap<>();
        while(true) {
            // loop over the sections in the file
            name = settings.get("/commands/command[" + i + "]/name");
            if (name == null || "".equals(name)) {
                break;
            }
            type = settings.get("/commands/command[" + i + "]/type");
          if (type == null || "".equals(type)) {
            type = "shell";
          }
            path = settings.get("/commands/command[" + i + "]/path");
            addResponseCode = settings.getBoolean("/commands/command[" + i + "]/add_response_code");
            //System.out.println(System.getProperty("user.dir"));
            i++;
            if ("java".equals(type)) {
                Action action = instances.get(path);
                if (action == null) {
                    // use a custom class loader to load the file, in case we are using classes from another jar.
                    Class c = customClassLoader.loadClass(path);
                    //action = (Action)Class.forName(path).newInstance();
                    action = (Action)c.newInstance();
                    instances.put(path, action);
                }
                siteCommandHandler.registerAction(name, action);
            } else if ("shell".equals(type)){
                File f = new File(path);
                siteCommandHandler.registerAction(name, new ShellExecute(name, f.getAbsolutePath(), addResponseCode));
            } else {
              siteCommandHandler.registerCustomAction(name, path, type, addResponseCode);
            }

            // todo: we should log that this has been added
        }
    }


    // Transfer post processor
    /**
     * Sets the TransferPostProcessor to be used by the system.
     * NOTE: This does NOT change the transfer post processors already in use by connections
     * but only the one used for new connections.
     *
     * @param tpp the post processor to be used. <code>null</code> if a default adapter that does nothing is to be used.
     */
    public void setTransferPostProcessor(TransferPostProcessor tpp) {
        if (tpp == null) {
            this.transferPostProcessor = new TransferPostProcessorAdapter();
        } else {
            this.transferPostProcessor = tpp;
        }
    }

    public TransferPostProcessor getTransferPostProcessor() {
        return transferPostProcessor;
    }

    // Userbase
    protected void configureUserbase() throws ConfigurationException, NotBoundException, IOException {
        switch(settings.getInt("/user/authentication/type")) {
            case Userbase.SQL:
            case Userbase.RMI:
                System.out.println("Initializing REMOTE userbase");
                if (settings.get("/user/authentication/remote/host") == null || settings.get("/user/authentication/remote/port") == null || settings.get("/user/authentication/remote/retry_interval") == null) {
                    throw new ConfigurationException("must specify {host, port, retry_interval} when using remote statistics");
                }
                userbase = new RmiRemoteUserbase(settings.get("/user/authentication/remote/host"), settings.getInt("/user/authentication/remote/port"), settings.getInt("/user/authentication/remote/retry_interval"));
                initializeCuftpdUserbaseActions();
                break;
            case Userbase.ANONYMOUS:
                System.out.println("Initializing ANONYMOUS userbase");
                userbase = new AnonymousUserbase();
                break;
            case Userbase.ASYNCHRONOUS:
                System.out.println("Initializing ASYNCHRONOUS userbase");
                try {
                    String name = settings.get("/user/authentication/asynchronous/name");
                    String uri = settings.get("/user/authentication/asynchronous/uri");

                    AsynchronousMessageQueueChangeTracker changeTracker = new AsynchronousMessageQueueChangeTracker(URI.create(uri), name);
                    this.changeTracker = changeTracker;
                    userbase = new LocalUserbase(settings.getDataDirectory(), true, changeTracker);
                    ChangeApplicator changeApplicator = new ChangeApplicator((LocalUserbase)userbase);

                    int i = 1;
                    String peerName;
                    String peerUri;
                    while(true) {
                        // loop over the sections in the file
                        peerName = settings.get("/user/authentication/asynchronous/peers/peer[" + i + "]/name");
                        if (peerName == null || "".equals(peerName)) {
                            break;
                        }
                        peerUri = settings.get("/user/authentication/asynchronous/peers/peer[" + i + "]/uri");
                        i++;
                        if (!peerUri.startsWith("failover:")) {
                            peerUri = "failover:" + peerUri;
                        }
                        changeTracker.addPeer(peerName, URI.create(peerUri), changeApplicator);
                        // todo: log each peer we connect to

                    }
                    initializeCuftpdUserbaseActions();
                    break;
                } catch (Exception e) {
                    shutdown();
                    throw new ConfigurationException("Asynchronous userbase failure", e);
                }
            case Userbase.DEFAULT:
            default:
                System.out.println("Initializing LOCAL userbase");
                userbase = new LocalUserbase(settings.getDataDirectory(), true, new ChangeTracker() {
                    @Override
                    public void addChange(Change change) {
                        // do nothing...
                    }
                });
                initializeCuftpdUserbaseActions();
                break;
        }
    }

    protected void initializeCuftpdUserbaseActions() {
        // todo: "site help" should depend on which commands are actually added (same for "feat", actually, but that'll be harder, since we don't load commands like classes
        Action seen = new Seen();
        siteCommandHandler.registerAction("seen", seen);
        siteCommandHandler.registerAction("laston", seen);
        siteCommandHandler.registerAction("lastlog", seen);
        siteCommandHandler.registerAction("users", new Users());
        siteCommandHandler.registerAction("give", new Credits(true));
        siteCommandHandler.registerAction("take", new Credits(false));
        siteCommandHandler.registerAction("addgadmin", new Gadmin(true));
        siteCommandHandler.registerAction("delgadmin", new Gadmin(false));
        siteCommandHandler.registerAction("addgroup", new AddGroup());
        siteCommandHandler.registerAction("delgroup", new DelGroup());
        siteCommandHandler.registerAction("autg", new AddUserToGroup());
        siteCommandHandler.registerAction("rufg", new RemoveUserFromGroup());
        siteCommandHandler.registerAction("user", new ViewUser(new File(settings.getDataDirectory(), "/templates/user_template.txt")));
        siteCommandHandler.registerAction("addip", new AddIP());
        siteCommandHandler.registerAction("delip", new DelIP());
        siteCommandHandler.registerAction("group", new ViewGroup(new File(settings.getDataDirectory(), "/templates/group_template.txt")));
        siteCommandHandler.registerAction("groups", new Groups());
        siteCommandHandler.registerAction("groupchange", new GroupChange());
        siteCommandHandler.registerAction("gadduser", new Gadduser());
        siteCommandHandler.registerAction("adduser", new AddUser());
        siteCommandHandler.registerAction("deluser", new DelUser());
        siteCommandHandler.registerAction("change", new UserChange());
        siteCommandHandler.registerAction("passwd", new Passwd());
        siteCommandHandler.registerAction("tagline", new Tagline());
        siteCommandHandler.registerAction("allotments", new Allotments());
        siteCommandHandler.registerAction("leechers", new Leechers());
        siteCommandHandler.registerAction("primarygroup", new PrimaryGroup());
        siteCommandHandler.registerAction("permissions", new cu.ftpd.user.userbases.actions.Permissions());
        siteCommandHandler.registerAction("addpermissions", new ModifyPermissions(true));
        siteCommandHandler.registerAction("delpermissions", new ModifyPermissions(false));
    }

    public Userbase getUserbase() {
        return userbase;
    }

    // User Statistics
    protected void configureUserStatistics() throws ConfigurationException, IOException {
      boolean registerStatisticsCommands = true;
        switch (settings.getInt("/user/statistics/type")) {
            case 0: // none
              registerStatisticsCommands = false;
                userStatistics = new NoUserStatistics();
                break;
            case 2:
                if (settings.get("/user/statistics/remote/host") == null || settings.get("/user/statistics/remote/port") == null || settings.get("/user/statistics/remote/retry_interval") == null) {
                    throw new ConfigurationException("must specify {host, port, retry_interval} when using remote statistics");
                }
                userStatistics = new RmiUserStatisticsClient(settings.get("/user/statistics/remote/host"), settings.getInt("/user/statistics/remote/port"), settings.getInt("/user/statistics/remote/retry_interval"));
                break;
            case 1:
            default:
                userStatistics = new LocalUserStatistics(new File(settings.getDataDirectory(), "/logs/userstatistics"));
                break;
        }
      if (registerStatisticsCommands) {
        // without any statistics, we don't want these commands here.
        // otherwise, things like cubnc using cuftpd will have commands blocking
        // the slave commands.
            initializeUserStatisticsActions();
      }
    }

    protected void initializeUserStatisticsActions() {
        siteCommandHandler.registerAction("allup", new Statistics(UserStatistics.ALLUP_BYTES, false, "allup"));
        siteCommandHandler.registerAction("alldn", new Statistics(UserStatistics.ALLDN_BYTES, false, "alldn"));
        siteCommandHandler.registerAction("mnup", new Statistics(UserStatistics.MNUP_BYTES, false, "mnup"));
        siteCommandHandler.registerAction("mndn", new Statistics(UserStatistics.MNDN_BYTES, false, "mndn"));
        siteCommandHandler.registerAction("wkup", new Statistics(UserStatistics.WKUP_BYTES, false, "wkup"));
        siteCommandHandler.registerAction("wkdn", new Statistics(UserStatistics.WKDN_BYTES, false, "wkdn"));
        siteCommandHandler.registerAction("dayup", new Statistics(UserStatistics.DAYUP_BYTES, false, "dayup"));
        siteCommandHandler.registerAction("daydn", new Statistics(UserStatistics.DAYDN_BYTES, false, "daydn"));

        siteCommandHandler.registerAction("gpallup", new Statistics(UserStatistics.ALLUP_BYTES, true, "gpallup"));
        siteCommandHandler.registerAction("gpalldn", new Statistics(UserStatistics.ALLDN_BYTES, true, "gpalldn"));
        siteCommandHandler.registerAction("gpmnup", new Statistics(UserStatistics.MNUP_BYTES, true, "gpmnup"));
        siteCommandHandler.registerAction("gpmndn", new Statistics(UserStatistics.MNDN_BYTES, true, "gpmndn"));
        siteCommandHandler.registerAction("gpwkup", new Statistics(UserStatistics.WKUP_BYTES, true, "gpwkup"));
        siteCommandHandler.registerAction("gpwkdn", new Statistics(UserStatistics.WKDN_BYTES, true, "gpwkdn"));
        siteCommandHandler.registerAction("gpdayup", new Statistics(UserStatistics.DAYUP_BYTES, true, "gpdayup"));
        siteCommandHandler.registerAction("gpdaydn", new Statistics(UserStatistics.DAYDN_BYTES, true, "gpdaydn"));
    }

    public UserStatistics getUserStatistics() {
        return userStatistics;
    }

    // Permissions
    public synchronized void initializePermissions() throws PermissionConfigurationException, IOException {
        // check the time of last load
        // if the modification time of the file is later than that, load the new file.
        // else just skip
        File permissionFile = new File(settings.getDataDirectory(), "permissions.acl");
        if (permissions == null || permissionFile.lastModified() > permissions.getLoadTime()) {
            permissions = new Permissions(permissionFile);
        }
    }

    public Permissions getPermissions() {
        return permissions;
    }

    // MetadataHandler
    protected void configureMetadataHandler() {
        metadataHandler = new MetadataHandler();
        Server.getInstance().getTimer().schedule(new Saver(metadataHandler), 15000, 15000); // this doesn't actually save anything, it just clears the cache of dead things
    }

    public MetadataHandler getMetadataHandler() {
        return metadataHandler;
    }

    // Modules
    protected void configureModules() throws ConfigurationException {
        Module module;

        // _todo: use this style of configuration whereever we have enumerations, like the site-commands and the event-handlers
        // actually, NO, we're not going to use the .getNode() and then iterate over them approach, because we can't get named access to any nodes, which is utterly useless.
        // Node modulesNode = settings.getNode("/modules");
        // The only thing we get that kind of access from is an attribute list

        String moduleName;
        try {
            int i = 1;
            String clazz;
            boolean active;
            while(true) {
                // loop over the sections in the file
                moduleName = settings.get("/modules/module[" + i + "]/name");
                if (moduleName == null || "".equals(moduleName)) {
                    break;
                }
                clazz = settings.get("/modules/module[" + i + "]/class");
                active = settings.getBoolean("/modules/module[" + i + "]/active");
                if (active) {
                    // use a custom class loader to load the file, in case we are using classes from another jar.
                    Class c = customClassLoader.loadClass(clazz);
                    if (Module.class.isAssignableFrom(c)) {
                        module = (Module)c.newInstance();
                        module.initialize(new XMLSettings(settings.getNode("/modules/module[" + i + "]/settings")));
                        module.registerActions(siteCommandHandler);
                        module.registerEventHandlers(eventHandler);
                        modules.put(moduleName, module);
                    } else {
                        throw new ConfigurationException("Class " + clazz + " does not implement interface " + Module.class.toString());
                    }
                }
                i++;
            }
        } catch (ClassNotFoundException | IllegalAccessException | InstantiationException e) {
            throw new ConfigurationException("Could not load class: " + e.getMessage(), e);
        }
    }

    protected void shutdownModules() {
        for (Module m : modules.values()) {
            m.stop();
        }
    }

    // Custom Class Loader
    protected void configureCustomClassLoader() throws IOException {
        /*
        If I load a class via a class loader, then load other classes inside
        that class using "new", do they then use the classloader that created
        the first class? (i.e. Can we create top-level objects via out special
        class loader and then use "new" in everything else?)
         */
        File classpath = new File(settings.getDataDirectory(), "classpath.txt");
        try (BufferedReader in = new BufferedReader(new InputStreamReader(new FileInputStream(classpath)))) {
            List<URL> jars = new ArrayList<>();
            String classpathEntry;
            while ((classpathEntry = in.readLine()) != null) {
                if (classpathEntry.startsWith("#")) {
                    continue;
                }
                if (!classpathEntry.startsWith("jar:file://")) {
                    classpathEntry = "jar:file://" + classpathEntry;
                }
                if (!classpathEntry.endsWith("!/")) {
                    classpathEntry += "!/";
                }
                jars.add(new URL(classpathEntry));
            }
            URL[] urls = new URL[jars.size()];
            jars.toArray(urls);
            customClassLoader = new URLClassLoader(urls);
        } catch (MalformedURLException e) {
            // while this is immediately thrown, we can't use the catch-all for this. We want it thrown all the way out.
            throw e;
        } catch (IOException e) {
            System.err.println("Could not read " + classpath.getAbsolutePath() + ". This is not a problem unless you are loading custom scripts. Creating...");
            @SuppressWarnings("unused")
            final boolean created = classpath.createNewFile();
        }
    }

    public URLClassLoader getCustomClassLoader() {
        return customClassLoader;
    }

    // Event Handler
    protected void configureEventHandler() throws ClassNotFoundException, IllegalAccessException, InstantiationException {
        // EventHandler eventHandler = new EventHandler();
        eventHandler = new EventHandler();

        String event;
        String type;
        String path;
        String time;

        Map<String, BeforeEventHandler> beforeEventHandlers = new HashMap<>();
        Map<String, AfterEventHandler> afterEventHandlers = new HashMap<>();

        int i = 1;
        while(true) {
            event = settings.get("/events/handler[" + i + "]/event");
            if (event == null || "".equals(event)) {
                break;
            }
            type = settings.get("/events/handler[" + i + "]/type");
            path = settings.get("/events/handler[" + i + "]/path");
            time = settings.get("/events/handler[" + i + "]/time");
            i++;

            if ("before".equalsIgnoreCase(time)) {
                BeforeEventHandler beh = beforeEventHandlers.get(path);
                if (beh == null) {
                    // we didn't already have an instance of this handler, lets create one
                    if ("java".equalsIgnoreCase(type)) {
                        // use a custom class loader to load the file, in case we are using classes from another jar.
                        Class c = customClassLoader.loadClass(path);
                        //Class c = Class.forName(path);
                        // if we have an instance of SiteCommand (if it's some other class, we don't want to load it)
                        if (BeforeEventHandler.class.isAssignableFrom(c)) {
                            beh = (BeforeEventHandler)c.newInstance();
                            beforeEventHandlers.put(path, beh);
                        } else {
                            throw new ClassCastException("Class " + path + " does not implement interface cu.ftpd.events.BeforeEventHandler");
                        }
                    } else if ("shell".equalsIgnoreCase(type)) {
                        File f = new File(path);
                        beh = new ShellEventHandler(f.getAbsolutePath());
                    } // can't be anything else, because we'll have the XSD check it for us
                }
                // add it in any case, since we only care about the class, not which event it catches
                eventHandler.addBeforeEventHandler(Event.resolve(event.toLowerCase()),beh);
            } else if ("after".equalsIgnoreCase(time)) {
                AfterEventHandler aeh = afterEventHandlers.get(path);
                if (aeh == null) {
                    // we didn't already have an instance of this handler, lets create one
                    if ("java".equalsIgnoreCase(type)) {
                        // use a custom class loader to load the file, in case we are using classes from another jar.
                        Class c = customClassLoader.loadClass(path);
                        //Class c = Class.forName(path);
                        if (AfterEventHandler.class.isAssignableFrom(c)) {
                            aeh = (AfterEventHandler)c.newInstance();
                            afterEventHandlers.put(path, aeh);
                        } else {
                            throw new ClassCastException("Class " + path + " does not implement interface cu.ftpd.events.AfterEventHandler");
                        }
                    } else if ("shell".equalsIgnoreCase(type)) {
                        File f = new File(path);
                        aeh = new ShellEventHandler(f.getAbsolutePath());
                    } // can't be anything else, because we'll have the XSD check it for us
                }
                eventHandler.addAfterEventHandler(Event.resolve(event.toLowerCase()), aeh);
            }
        }
    }

    public EventHandler getEventHandler() {
        return eventHandler;
    }

    public void shutdown() {
        shutdownModules();

      if (changeTracker != null) {
        try {
            changeTracker.stop();
        } catch (Exception e) {
            Logging.getErrorLog().reportException("Shutdown failure change tracker", e);
        }
      }
    }
}
TOP

Related Classes of cu.ftpd.Services

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.