All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.oneandone.sshconfig.Main Maven / Gradle / Ivy

There is a newer version: 1.1.11
Show newest version
/*
 * Copyright 2018 1&1 Internet SE.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.oneandone.sshconfig;

import com.oneandone.sshconfig.file.SSHConfig;
import com.oneandone.sshconfig.file.Database;
import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.UUID;
import lombok.extern.slf4j.Slf4j;
import com.oneandone.sshconfig.bind.Host;
import org.slf4j.MDC;
import static java.util.stream.Collectors.toList;

import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Stream;

/**
 * The main program.
 * @author Stephan Fuhrmann
 */
@Slf4j
public final class Main implements AutoCloseable {
    /** The default TCP port to use. */
    public static final int SSH_PORT = 22;

    /** The status line to output the current progress with. */
    private final StatusLine statusLine;

    /** The command line parameters. */
    private final Params params;

    /** Constructs an instance.
     * @param inParams the command line parameters to use.
     * */
    public Main(final Params inParams) {
        this.params = Objects.requireNonNull(inParams);
        this.statusLine = new StatusLine(System.err);
    }

    /** Discover a list of hosts by their DNS name. Will only
     * return the discovered hosts. The others will be silently
     * dropped.
     * @param discover the list of FQDNs / IPs to discover.
     * @return the list of discovered hosts.
     */
    private List discover(final List discover) {
        log.debug("Discovering started for {} args", discover.size());

        List hosts = discover
                .stream()
                .parallel()
                .map(discoverMe -> ignorantDiscover(discoverMe))
                .filter(h -> h.isPresent())
                .map(h -> h.get())
                .collect(toList());

        return hosts;
    }

    /** Discover a single host using DNS. Silently ignores DNS / IO errors.
     * @param in the FQDN / IP to try to discover.
     * @return the Host generated or {@linkplain Optional#empty() empty}
     * result if an error occured.
     */
    private Optional ignorantDiscover(final String in) {
        try {
            MDC.put("in", in);
            if (in.isEmpty()) {
                return Optional.empty();
            }

            return Optional.of(discover(in));
        } catch (UnknownHostException ex) {
            log.warn(in, ex);
            return Optional.empty();
        } finally {
            MDC.remove("in");
        }
    }

    /** Discover a single host by DNS.
     * @param in a dns resolvable name.
     * @return the discovered host entry.
     * @throws UnknownHostException if the host could not be resolved
     * by domain name service.
     */
    private Host discover(final String in) throws UnknownHostException {
        InetAddress address = InetAddress.getByName(in);
        Host result = new Host();
        result.setId(UUID.randomUUID());
        result.setFqdn(address.getCanonicalHostName());
        InetAddress[] all = InetAddress.getAllByName(in);
        List allIps = Stream.of(all)
                .map(i -> i.getHostAddress())
                .collect(toList());
        result.setIps(allIps.toArray(new String[all.length]));
        statusLine.printf("%s -> %s", in, allIps.toString());
        int idx = in.indexOf(".");
        if (idx != -1) {
            result.setName(in.substring(0, idx));
        } else {
            result.setName(in);
        }
        result.setCreatedAt(new Date());
        result.setUpdatedAt(result.getCreatedAt());
        result.setUser(params.getUser());
        return result;
    }

    /** Discover a single host by DNS.
     * @param hosts the list of hosts to update.
     * @throws UnknownHostException if the host could not be resolved
     * by domain name service.
     */
    private void update(final List hosts) throws UnknownHostException {
        AtomicInteger atomicInteger = new AtomicInteger();
        hosts.stream().parallel().forEach(h -> {
            try {
                updateFqdn(h);
                updateServerAndReachability(h);
                int val = atomicInteger.addAndGet(1);
                statusLine.printf("%d/%d. %s -> %s",
                        val,
                        hosts.size(),
                        h.getName(),
                        h.getSshServerVersion());
            } finally {
                h.setUpdatedAt(new Date());
            }
        });
    }

    /** Updates the fully-qualified domain name of the host.
     * @param h the host to update the FQDN of.
     */
    private void updateFqdn(final Host h) {
        try {
            InetAddress[] all = InetAddress.getAllByName(h.getFqdn());
            List allIps = Stream.of(all)
                    .map(i -> i.getHostAddress())
                    .collect(toList());
            h.setIps(allIps.toArray(new String[all.length]));
        } catch (UnknownHostException ex) {
            try {
                InetAddress inetAddress = InetAddress.getByName(h.getIps()[0]);
                h.setFqdn(inetAddress.getCanonicalHostName());
            } catch (UnknownHostException e2) {
                // if FQDN not resolvable, use the IP as last fallback
                log.warn("Host " + h.getName() + " not found", e2);
                h.setFqdn(h.getIps()[0]); // this sucks
            }
        }
    }

    /** Updates the SSH server version and the reachability information.
     * Unreachable hosts will be {@link Host#enabled disabled}.
     * @param h the host to update the data form.
     */
    private void updateServerAndReachability(final Host h) {
        try {
            SSHHostData sshHostData = SSHHostData.from(
                    new InetSocketAddress(h.getFqdn(), SSH_PORT));
            h.setSshServerVersion(sshHostData.getServerId());
        } catch (IOException ex) {
            log.warn("Host " + h.getName() + " is not reachable. Disabling.",
                    ex);
            h.setEnabled(false);
        }
    }

    /** Entry point for the program.
     * @param args the command line arguments for parsing with {@link Params}.
     */
    public static void main(final String[] args) {
        Params params = Params.parse(args);
        if (params == null) {
            return;
        }

        try (Main main = new Main(params)) {
            Database database = Database.fromPath(params.getDb());
            if (params.isDiscover()) {
                List hosts = main.discover(params.getArguments());
                database.update(hosts);
            }
            if (params.isUpdate()) {
                List hosts = new ArrayList<>();
                hosts.addAll(database.getList());
                main.update(hosts);
                database.update(hosts);
            }

            database.save();

            if (params.getSshConfig() != null) {
                SSHConfig sshc = SSHConfig.fromPath(params.getSshConfig());
                sshc.pushOwn(database.getList());
                sshc.save();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Override
    public void close() throws Exception {
        statusLine.close();
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy