/**
* Scanner
* Copyright 2010 by Michael Peter Christen, mc@yacy.net, Frankfurt am Main, Germany
* First released 28.10.2010 at http://yacy.net
*
* $LastChangedDate$
* $LastChangedRevision$
* $LastChangedBy$
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program in the file lgpl21.txt
* If not, see <http://www.gnu.org/licenses/>.
*/
package net.yacy.cora.protocol;
import java.io.IOException;
import java.net.InetAddress;
import java.net.MalformedURLException;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import net.yacy.cora.document.id.DigestURL;
import net.yacy.cora.document.id.MultiProtocolURL;
import net.yacy.cora.protocol.ftp.FTPClient;
import net.yacy.cora.protocol.http.HTTPClient;
/**
* a protocol scanner
* scans given ip's for existing http, https, ftp and smb services
*/
public class Scanner {
public static enum Access {unknown, empty, granted, denied;}
public static enum Protocol {http(80), https(443), ftp(21), smb(445);
public int port;
private Protocol(final int port) {this.port = port;}
}
public class Service implements Runnable {
public Protocol protocol;
public InetAddress inetAddress;
private String hostname;
private final long starttime;
public Service(final Protocol protocol, final InetAddress inetAddress) {
this.protocol = protocol;
this.inetAddress = inetAddress;
this.hostname = null;
this.starttime = System.currentTimeMillis();
}
public Service(final String protocol, final InetAddress inetAddress) {
this.protocol = protocol.equals("http") ? Protocol.http : protocol.equals("https") ? Protocol.https : protocol.equals("ftp") ? Protocol.ftp : Protocol.smb;
this.inetAddress = inetAddress;
this.hostname = null;
this.starttime = System.currentTimeMillis();
}
public Protocol getProtocol() {
return this.protocol;
}
public InetAddress getInetAddress() {
return this.inetAddress;
}
public String getHostName() {
if (this.hostname != null) {
if (this.hostname.equals(this.inetAddress.getHostAddress())) {
// if the hostname was created in case of a time-out from TimoutRequest
// then in rare cases we try to get that name again
if ( (System.currentTimeMillis() / 1000) % 10 != 1) return this.hostname;
} else {
return this.hostname;
}
}
try {
this.hostname = TimeoutRequest.getHostName(this.inetAddress, 100);
Domains.setHostName(this.inetAddress, this.hostname);
} catch (final ExecutionException e) {
this.hostname = this.inetAddress.getHostAddress();
}
//this.hostname = Domains.getHostName(this.inetAddress);
return this.hostname;
}
public DigestURL url() throws MalformedURLException {
return new DigestURL(this.protocol.name() + "://" + getHostName() + "/");
}
@Override
public String toString() {
try {
return new MultiProtocolURL(this.protocol.name() + "://" + this.inetAddress.getHostAddress() + "/").toNormalform(true);
} catch (final MalformedURLException e) {
return "";
}
}
@Override
public int hashCode() {
return (this.inetAddress.toString() + ":" + protocol.port).hashCode();
}
@Override
public boolean equals(final Object o) {
return (o instanceof Service) && ((Service) o).protocol == this.protocol && ((Service) o).inetAddress.equals(this.inetAddress);
}
@Override
public void run() {
try {
Thread.currentThread().setName("Scanner.Runner: Ping to " + this.getInetAddress().getHostAddress() + ":" + this.getProtocol().port); // good for debugging
if (TimeoutRequest.ping(this.getInetAddress().getHostAddress(), this.getProtocol().port, Scanner.this.timeout)) {
Access access = this.getProtocol() == Protocol.http || this.getProtocol() == Protocol.https ? Access.granted : Access.unknown;
Scanner.this.services.put(this, access);
if (access == Access.unknown) {
// ask the service if it lets us in
if (this.getProtocol() == Protocol.ftp) {
final FTPClient ftpClient = new FTPClient();
try {
ftpClient.open(this.getInetAddress().getHostAddress(), this.getProtocol().port);
ftpClient.login(FTPClient.ANONYMOUS, "anomic@");
final List<String> list = ftpClient.list("/", false);
ftpClient.CLOSE();
access = list == null || list.isEmpty() ? Access.empty : Access.granted;
} catch (final IOException e) {
access = Access.denied;
}
}
if (this.getProtocol() == Protocol.smb) {
try {
final MultiProtocolURL uri = new MultiProtocolURL(this.toString());
final String[] list = uri.list();
access = list == null || list.length == 0 ? Access.empty : Access.granted;
} catch (final IOException e) {
access = Access.denied;
}
}
}
if (access != Access.unknown) Scanner.this.services.put(this, access);
}
} catch (final OutOfMemoryError e) {
}
}
public long age() {
return System.currentTimeMillis() - this.starttime;
}
}
private final static Map<Service, Access> scancache = new ConcurrentHashMap<Service, Access>();
public static int scancacheSize() {
return scancache.size();
}
public static void scancacheReplace(final Scanner newScanner) {
scancache.clear();
scancache.putAll(newScanner.services());
}
public static void scancacheExtend(final Scanner newScanner) {
final Iterator<Map.Entry<Service, Access>> i = Scanner.scancache.entrySet().iterator();
Map.Entry<Service, Access> entry;
while (i.hasNext()) {
entry = i.next();
if (entry.getValue() != Access.granted) i.remove();
}
scancache.putAll(newScanner.services());
}
public static Iterator<Map.Entry<Service, Scanner.Access>> scancacheEntries() {
return scancache.entrySet().iterator();
}
/**
* check if the url can be accepted by the scanner. the scanner accepts the url if:
* - the host of the url is not supervised (it is not in the scan range), or
* - the host is supervised (it is in the scan range) and the host is in the scan cache
* @param url
* @return true if the url shall be part of a search result
*/
public static boolean acceptURL(final MultiProtocolURL url) {
// if the scan range is empty, then all urls are accepted
if (scancache == null || scancache.isEmpty()) return true;
//if (System.currentTimeMillis() > scancacheValidUntilTime) return true;
final InetAddress a = url.getInetAddress(); // try to avoid that!
if (a == null) return true;
for (Map.Entry<Service, Access> entry: scancache.entrySet()) {
Service service = entry.getKey();
if (service.inetAddress.equals(a) && service.protocol.toString().equals(url.getProtocol())) {
Access access = entry.getValue();
if (access == null) return false;
return access == Access.granted;
}
}
return true;
}
private final Map<Service, Access> services;
private final ThreadPoolExecutor threadPool;
private final int timeout;
public Scanner(final int concurrentRunner, final int timeout) {
this.services = Collections.synchronizedMap(new HashMap<Service, Access>());
this.threadPool = new ThreadPoolExecutor(concurrentRunner, concurrentRunner, 10, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>());
this.timeout = timeout;
}
public int pending() {
return this.threadPool.getQueue().size() + this.threadPool.getActiveCount();
}
public void terminate() {
this.threadPool.shutdown();
}
public void addProtocols(final List<InetAddress> addresses, boolean http, boolean https, boolean ftp, boolean smb) {
if (http) addProtocol(Protocol.http, addresses);
if (https) addProtocol(Protocol.https, addresses);
if (ftp) addProtocol(Protocol.ftp, addresses);
if (smb) addProtocol(Protocol.smb, addresses);
}
private void addProtocol(final Protocol protocol, final List<InetAddress> addresses) {
for (final InetAddress i: addresses) {
threadPool.execute(new Service(protocol, i));
}
}
/**
* generate a list of internetaddresses
* @param subnet the subnet: 24 will generate 254 addresses, 16 will generate 256 * 254; must be >= 16 and <= 24
* @return
*/
public static final List<InetAddress> genlist(Collection<InetAddress> base, final int subnet) {
final ArrayList<InetAddress> c = new ArrayList<InetAddress>(1);
for (final InetAddress i: base) {
genlist(c, i, subnet);
}
return c;
}
public static final List<InetAddress> genlist(InetAddress base, final int subnet) {
final ArrayList<InetAddress> c = new ArrayList<InetAddress>(1);
genlist(c, base, subnet);
return c;
}
private static final void genlist(ArrayList<InetAddress> c, InetAddress base, final int subnet) {
if (subnet == 31) {
try {
c.add(InetAddress.getByAddress(base.getAddress()));
} catch (final UnknownHostException e) {}
} else {
int ul = subnet >= 24 ? base.getAddress()[2] : (1 << (24 - subnet)) - 1;
for (int br = subnet >= 24 ? base.getAddress()[2] : 0; br <= ul; br++) {
for (int j = 1; j < 255; j++) {
final byte[] address = base.getAddress();
address[2] = (byte) br;
address[3] = (byte) j;
try {
c.add(InetAddress.getByAddress(address));
} catch (final UnknownHostException e) {
}
}
}
}
}
public Map<Service, Access> services() {
return this.services;
}
public static byte[] inIndex(final Map<byte[], String> commentCache, final String url) {
for (final Map.Entry<byte[], String> comment: commentCache.entrySet()) {
if (comment.getValue().contains(url)) return comment.getKey();
}
return null;
}
public static void main(final String[] args) {
//try {System.out.println("192.168.1.91: " + ping(new MultiProtocolURI("smb://192.168.1.91/"), 1000));} catch (final MalformedURLException e) {}
final Scanner scanner = new Scanner(100, 10);
List<InetAddress> addresses = genlist(Domains.myIntranetIPs(), 20);
scanner.addProtocols(addresses, true, true, true, true);
scanner.terminate();
for (final Service service: scanner.services().keySet()) {
System.out.println(service.toString());
}
try {
HTTPClient.closeConnectionManager();
} catch (final InterruptedException e) {
}
}
}