/** * Copyright (C) 2010-2014 Leon Blakey <lord.quackstar at gmail.com> * * This file is part of PircBotX. * * PircBotX is free software: you can redistribute it and/or modify it under the * terms of the GNU General Public License as published by the Free Software * Foundation, either version 3 of the License, or (at your option) any later * version. * * PircBotX 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 General Public License for more details. * * You should have received a copy of the GNU General Public License along with * PircBotX. If not, see <http://www.gnu.org/licenses/>. */ package org.pircbotx.dcc; import java.io.Closeable; import java.io.File; import java.io.IOException; import java.math.BigInteger; import java.net.InetAddress; import java.net.ServerSocket; import java.net.Socket; import java.net.UnknownHostException; import java.security.SecureRandom; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Random; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import lombok.Data; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.pircbotx.Configuration; import org.pircbotx.PircBotX; import org.pircbotx.User; import org.pircbotx.Utils; import org.pircbotx.exception.DccException; import org.pircbotx.hooks.events.IncomingChatRequestEvent; import org.pircbotx.hooks.events.IncomingFileTransferEvent; import static com.google.common.base.Preconditions.*; import com.google.common.collect.ImmutableList; import java.net.Inet4Address; import java.net.Inet6Address; import lombok.NonNull; import org.pircbotx.UserHostmask; /** * Handler of all DCC requests * <p> * @author Leon Blakey */ @RequiredArgsConstructor @Slf4j public class DccHandler implements Closeable { protected static final Random TOKEN_RANDOM = new SecureRandom(); protected static final int TOKEN_RANDOM_MAX = 20000; @NonNull protected final PircBotX bot; protected final Map<PendingRecieveFileTransfer, CountDownLatch> pendingReceiveTransfers = new HashMap<PendingRecieveFileTransfer, CountDownLatch>(); protected final List<PendingSendFileTransfer> pendingSendTransfers = new ArrayList<PendingSendFileTransfer>(); protected final Map<PendingSendFileTransferPassive, CountDownLatch> pendingSendPassiveTransfers = new HashMap<PendingSendFileTransferPassive, CountDownLatch>(); protected final Map<PendingSendChatPassive, CountDownLatch> pendingSendPassiveChat = new HashMap<PendingSendChatPassive, CountDownLatch>(); protected boolean shuttingDown = false; public boolean processDcc(UserHostmask userHostmask, final User user, String request) throws IOException { List<String> requestParts = tokenizeDccRequest(request); String type = requestParts.get(1); if (type.equals("SEND")) { //Someone is trying to send a file to us //Example: DCC SEND <filename> <ip> <port> <file size> <transferToken> (note File size is optional) String rawFilename = requestParts.get(2); final String safeFilename = (rawFilename.startsWith("\"") && rawFilename.endsWith("\"")) ? rawFilename.substring(1, rawFilename.length() - 1) : rawFilename; InetAddress address = parseRawAddress(requestParts.get(3)); int port = Integer.parseInt(requestParts.get(4)); long size = Long.parseLong(Utils.tryGetIndex(requestParts, 5, "-1")); String transferToken = Utils.tryGetIndex(requestParts, 6, null); if (transferToken != null) //Check if this is an acknowledgement of a passive dcc file request synchronized (pendingSendPassiveTransfers) { Iterator<Map.Entry<PendingSendFileTransferPassive, CountDownLatch>> pendingItr = pendingSendPassiveTransfers.entrySet().iterator(); while (pendingItr.hasNext()) { Map.Entry<PendingSendFileTransferPassive, CountDownLatch> curEntry = pendingItr.next(); PendingSendFileTransferPassive transfer = curEntry.getKey(); if (transfer.getUser() == user && transfer.getFilename().equals(rawFilename) && transfer.getTransferToken().equals(transferToken)) { transfer.setReceiverAddress(address); transfer.setReceiverPort(port); log.debug("Passive send file transfer of file {} to user {} accepted at address {} and port {}", transfer.getFilename(), transfer.getUser().getNick(), address, port); curEntry.getValue().countDown(); pendingItr.remove(); return true; } } } //Nope, this is a new transfer if (port == 0 || transferToken != null) //User is trying to use reverse DCC bot.getConfiguration().getListenerManager().onEvent(new IncomingFileTransferEvent(bot, userHostmask, user, rawFilename, safeFilename, address, port, size, transferToken, true)); else bot.getConfiguration().getListenerManager().onEvent(new IncomingFileTransferEvent(bot, userHostmask, user, rawFilename, safeFilename, address, port, size, transferToken, false)); } else if (type.equals("RESUME")) { //Someone is trying to resume sending a file to us //Example: DCC RESUME <filename> 0 <position> <token> //Reply with: DCC ACCEPT <filename> 0 <position> <token> String filename = requestParts.get(2); int port = Integer.parseInt(requestParts.get(3)); long position = Long.parseLong(requestParts.get(4)); if (port == 0) { //Passive transfer String transferToken = requestParts.get(5); synchronized (pendingSendPassiveTransfers) { Iterator<Map.Entry<PendingSendFileTransferPassive, CountDownLatch>> pendingItr = pendingSendPassiveTransfers.entrySet().iterator(); while (pendingItr.hasNext()) { Map.Entry<PendingSendFileTransferPassive, CountDownLatch> curEntry = pendingItr.next(); PendingSendFileTransferPassive transfer = curEntry.getKey(); if (transfer.getUser() == user && transfer.getFilename().equals(filename) && transfer.getTransferToken().equals(transferToken)) { transfer.setStartPosition(position); log.debug("Passive send file transfer of file {} to user {} set to position {}", transfer.getFilename(), transfer.getUser().getNick(), position); return true; } } } } else synchronized (pendingSendTransfers) { Iterator<PendingSendFileTransfer> pendingItr = pendingSendTransfers.iterator(); while (pendingItr.hasNext()) { PendingSendFileTransfer transfer = pendingItr.next(); if (transfer.getUser() == user && transfer.getFilename().equals(filename) && transfer.getPort() == port) { transfer.setPosition(position); log.debug("Send file transfer of file {} to user {} set to position {}", transfer.getFilename(), transfer.getUser().getNick(), position); return true; } } } //Haven't returned yet, received an unknown transfer throw new DccException(DccException.Reason.UnknownFileTransferResume, user, "Transfer line: " + request); } else if (type.equals("ACCEPT")) { //Someone is acknowledging a transfer resume //Example (normal): DCC ACCEPT <filename> <port> <position> //Example (passive): DCC ACCEPT <filename> 0 <position> <token> //TODO how well does this handle non passive? String filename = requestParts.get(2); int port; long position = Long.parseLong(requestParts.get(4)); String transferToken; if (requestParts.size() == 5) { //Standard request port = Integer.parseInt(requestParts.get(3)); transferToken = null; } else { //Passive request port = 0; transferToken = requestParts.get(5); } synchronized (pendingReceiveTransfers) { Iterator<Map.Entry<PendingRecieveFileTransfer, CountDownLatch>> pendingItr = pendingReceiveTransfers.entrySet().iterator(); while (pendingItr.hasNext()) { Map.Entry<PendingRecieveFileTransfer, CountDownLatch> curEntry = pendingItr.next(); IncomingFileTransferEvent transferEvent = curEntry.getKey().getEvent(); if (transferEvent.getUser() == user && transferEvent.getRawFilename().equals(filename) && transferEvent.getPort() == port && transferEvent.getToken().equals(transferToken)) { curEntry.getKey().setPosition(position); log.debug("Receive file transfer of file {} to user {} set to position {}", transferEvent.getRawFilename(), transferEvent.getUser().getNick(), position); curEntry.getValue().countDown(); pendingItr.remove(); return true; } } } } else if (type.equals("CHAT")) { //Someone is trying to chat with us //Example: DCC CHAT <protocol> <ip> <port> (protocol should be chat) InetAddress address = parseRawAddress(requestParts.get(3)); int port = Integer.parseInt(requestParts.get(4)); String chatToken = Utils.tryGetIndex(requestParts, 5, null); //Check if this is an acknowledgement of a passive chat request if (chatToken != null) synchronized (pendingSendPassiveChat) { Iterator<Map.Entry<PendingSendChatPassive, CountDownLatch>> pendingItr = pendingSendPassiveChat.entrySet().iterator(); while (pendingItr.hasNext()) { Map.Entry<PendingSendChatPassive, CountDownLatch> curEntry = pendingItr.next(); PendingSendChatPassive pendingChat = curEntry.getKey(); log.trace("Current pending chat: {}", pendingChat); if (pendingChat.getUser() == user && pendingChat.getChatToken().equals(chatToken)) { log.debug("Passive chat request to user {} accepted", user); pendingChat.setReceiverAddress(address); pendingChat.setReceiverPort(port); curEntry.getValue().countDown(); pendingItr.remove(); return true; } } } //Nope, this is a new chat if (port == 0 && chatToken != null) bot.getConfiguration().getListenerManager().onEvent(new IncomingChatRequestEvent(bot, userHostmask, user, address, port, chatToken, true)); else bot.getConfiguration().getListenerManager().onEvent(new IncomingChatRequestEvent(bot, userHostmask, user, address, port, chatToken, false)); } else return false; return true; } /** * Accept chat request, blocking until the connection is active * <p> * @param event The chat request event * @return An active {@link ReceiveChat} * @throws IOException If an error occurred during connection */ public ReceiveChat acceptChatRequest(IncomingChatRequestEvent event) throws IOException { checkNotNull(event, "Event cannot be null"); if (event.isPassive()) { ServerSocket serverSocket = createServerSocket(event.getUser()); InetAddress publicAddress = getRealDccPublicAddress(serverSocket); bot.sendDCC().chatPassiveAccept(event.getUser().getNick(), publicAddress, serverSocket.getLocalPort(), event.getToken()); log.debug("Sent DCC recieve chat accept to user {} ({}ms timeout) to passive connect on public address {}, local address {}", event.getUser().getNick(), bot.getConfiguration().getDccAcceptTimeout(), publicAddress, serverSocket.getLocalSocketAddress()); Socket userSocket = serverSocket.accept(); //User is connected, begin transfer serverSocket.close(); return bot.getConfiguration().getBotFactory().createReceiveChat(bot, event.getUser(), userSocket); } else { InetAddress localAddress = getRealDccLocalAddress(event.getAddress()); log.debug("Accepting DCC recieve chat from user {} at address {} port {} from local address {}", event.getUser().getNick(), event.getAddress(), event.getPort(), localAddress); return bot.getConfiguration().getBotFactory().createReceiveChat(bot, event.getUser(), new Socket(event.getAddress(), event.getPort(), localAddress, 0)); } } /** * Accept file transfer at position 0, blocking until the connection is * active * <p> * @param event The file request event * @param destination The destination file * @return An active {@link ReceiveFileTransfer} * @throws IOException If an error occurred during connection */ public ReceiveFileTransfer acceptFileTransfer(IncomingFileTransferEvent event, File destination) throws IOException { checkNotNull(event, "Event cannot be null"); checkNotNull(destination, "Destination file cannot be null"); return acceptFileTransfer(event, destination, 0); } /** * Accept file transfer resuming at specified position, blocking until the * connection is active * <p> * @param event The file request event * @param destination The destination file * @param startPosition The position to start the transfer at * @return An active {@link ReceiveFileTransfer} * @throws IOException If an error occurred during connection * @throws InterruptedException If this is interrupted while waiting for a * connection * @throws DccException If a timeout is reached or the bot is shutting down */ public ReceiveFileTransfer acceptFileTransferResume(IncomingFileTransferEvent event, File destination, long startPosition) throws IOException, InterruptedException, DccException { checkNotNull(event, "Event cannot be null"); checkNotNull(destination, "Destination file cannot be null"); checkArgument(startPosition >= 0, "Start position %s must be positive", startPosition); //Add to pending map so we can be notified when the user has accepted CountDownLatch countdown = new CountDownLatch(1); PendingRecieveFileTransfer pendingTransfer = new PendingRecieveFileTransfer(event); synchronized (pendingReceiveTransfers) { pendingReceiveTransfers.put(pendingTransfer, countdown); } //Tell user were going to resume transfering if (event.isPassive()) bot.sendDCC().filePassiveResumeRequest(event.getUser().getNick(), event.getRawFilename(), startPosition, event.getToken()); else bot.sendDCC().fileResumeRequest(event.getUser().getNick(), event.getRawFilename(), event.getPort(), startPosition); //Wait for response if (!countdown.await(bot.getConfiguration().getDccResumeAcceptTimeout(), TimeUnit.MILLISECONDS)) throw new DccException(DccException.Reason.FileTransferResumeTimeout, event.getUser(), "Event: " + event); if (shuttingDown) throw new DccException(DccException.Reason.FileTransferResumeCancelled, event.getUser(), "Transfer " + event + " canceled due to bot shutting down"); //User has accepted resume, begin transfer if (pendingTransfer.getPosition() != startPosition) log.warn("User is resuming transfer at position {} instead of requested position {} for transfer {}. Defaulting to users position", pendingTransfer.getPosition(), startPosition, event); return acceptFileTransfer(event, destination, pendingTransfer.getPosition()); } protected ReceiveFileTransfer acceptFileTransfer(IncomingFileTransferEvent event, File destination, long startPosition) throws IOException { checkNotNull(event, "Event cannot be null"); checkNotNull(destination, "Destination file cannot be null"); checkArgument(startPosition >= 0, "Start position %s must be positive", startPosition); if (event.isPassive()) { ServerSocket serverSocket = createServerSocket(event.getUser()); bot.sendDCC().filePassiveAccept(event.getUser().getNick(), event.getRawFilename(), getRealDccPublicAddress(serverSocket), serverSocket.getLocalPort(), event.getFilesize(), event.getToken()); Socket userSocket = serverSocket.accept(); //User is connected, begin transfer serverSocket.close(); return bot.getConfiguration().getBotFactory().createReceiveFileTransfer(bot, userSocket, event.getUser(), destination, startPosition, event.getFilesize()); } else { Socket userSocket = new Socket(event.getAddress(), event.getPort(), getRealDccLocalAddress(event.getAddress()), 0); return bot.getConfiguration().getBotFactory().createReceiveFileTransfer(bot, userSocket, event.getUser(), destination, startPosition, event.getFilesize()); } } /** * Send a chat request using {@link Configuration#isDccPassiveRequest()} * <p> * @param receiver The user to chat with * @return An active {@link SendChat} * @throws IOException If an error occurred during connection * @throws InterruptedException If passive connection was interrupted * @throws DccException If a timeout is reached or the bot is shutting down */ public SendChat sendChat(User receiver) throws IOException, InterruptedException { return sendChat(receiver, bot.getConfiguration().isDccPassiveRequest()); } /** * Send a chat request using passive parameter * <p> * @param receiver The user to chat with * @param passive Whether to connect passively * @return An active {@link SendChat} * @throws IOException If an error occurred during connection * @throws InterruptedException If passive connection was interrupted * @throws DccException If a timeout is reached or the bot is shutting down */ public SendChat sendChat(User receiver, boolean passive) throws IOException, InterruptedException { checkNotNull(receiver, "Receiver user cannot be null"); int dccAcceptTimeout = bot.getConfiguration().getDccAcceptTimeout(); if (passive) { String chatToken = Integer.toString(TOKEN_RANDOM.nextInt(TOKEN_RANDOM_MAX)); PendingSendChatPassive pendingChat = new PendingSendChatPassive(receiver, chatToken); CountDownLatch countdown = new CountDownLatch(1); synchronized (pendingSendPassiveChat) { pendingSendPassiveChat.put(pendingChat, countdown); } InetAddress publicAddress = getRealDccPublicAddress(); bot.sendDCC().chatPassiveRequest(receiver.getNick(), publicAddress, chatToken); //Wait for the user to acknowledge log.debug("Sent DCC send chat request to user {} ({}ms timeout) for passive connect info using public address {}", receiver.getNick(), bot.getConfiguration().getDccAcceptTimeout(), publicAddress); if (!countdown.await(dccAcceptTimeout, TimeUnit.MILLISECONDS)) throw new DccException(DccException.Reason.ChatTimeout, receiver, ""); if (shuttingDown) throw new DccException(DccException.Reason.ChatCancelled, receiver, ""); Socket chatSocket = new Socket(pendingChat.getReceiverAddress(), pendingChat.getReceiverPort()); return bot.getConfiguration().getBotFactory().createSendChat(bot, receiver, chatSocket); } else { //Get the user to connect to us ServerSocket serverSocket = createServerSocket(receiver); InetAddress publicAddress = getRealDccPublicAddress(serverSocket); bot.sendDCC().chatRequest(receiver.getNick(), publicAddress, serverSocket.getLocalPort()); //Wait for user to connect log.debug("Sent DCC send chat request to user {} ({}ms timeout) to connect on public address {}:{}, local address {}", receiver.getNick(), bot.getConfiguration().getDccAcceptTimeout(), publicAddress, serverSocket.getLocalPort(), serverSocket.getLocalSocketAddress()); Socket userSocket = serverSocket.accept(); log.debug("Recieved connection"); serverSocket.close(); return bot.getConfiguration().getBotFactory().createSendChat(bot, receiver, userSocket); } } /** * Send file using {@link Configuration#isDccPassiveRequest() } * <p> * @param file The file to send * @param receiver The user to send the file to * @return An active {@link SendFileTransfer} * @throws IOException If an error occurred during connecting * @throws DccException If a timeout is reached or the bot is shutting down * @throws InterruptedException If passive connection was interrupted */ public SendFileTransfer sendFile(File file, User receiver) throws IOException, DccException, InterruptedException { return sendFile(file, receiver, bot.getConfiguration().isDccPassiveRequest()); } /** * Send file using {@link Configuration#isDccPassiveRequest() } * <p> * @param file The file to send * @param receiver The user to send the file to * @param passive Whether to connect passively * @return An active {@link SendFileTransfer} * @throws IOException If an error occurred during connecting * @throws DccException If a timeout is reached or the bot is shutting down * @throws InterruptedException If passive connection was interrupted */ public SendFileTransfer sendFile(File file, User receiver, boolean passive) throws IOException, DccException, InterruptedException { checkNotNull(file, "Source file cannot be null"); checkNotNull(receiver, "Receiver cannot be null"); checkArgument(file.exists(), "File must exist"); //Make the filename safe to send String safeFilename = file.getName(); if (safeFilename.contains(" ")) if (bot.getConfiguration().isDccFilenameQuotes()) safeFilename = "\"" + safeFilename + "\""; else safeFilename = safeFilename.replace(" ", "_"); if (passive) { String transferToken = Integer.toString(TOKEN_RANDOM.nextInt(TOKEN_RANDOM_MAX)); CountDownLatch countdown = new CountDownLatch(1); PendingSendFileTransferPassive pendingPassiveTransfer = new PendingSendFileTransferPassive(receiver, safeFilename, transferToken); synchronized (pendingSendTransfers) { pendingSendPassiveTransfers.put(pendingPassiveTransfer, countdown); } InetAddress publicAddress = getRealDccPublicAddress(); bot.sendDCC().filePassiveRequest(receiver.getNick(), safeFilename, publicAddress, file.length(), transferToken); //Wait for user to acknowledge log.debug("Sent DCC send file request to user {} ({}ms timeout) for passive connect info using public address {} for file {}", receiver.getNick(), bot.getConfiguration().getDccAcceptTimeout(), publicAddress, file.getAbsolutePath()); if (!countdown.await(bot.getConfiguration().getDccAcceptTimeout(), TimeUnit.MILLISECONDS)) throw new DccException(DccException.Reason.FileTransferTimeout, receiver, "File: " + file.getAbsolutePath()); if (shuttingDown) throw new DccException(DccException.Reason.FileTransferCancelled, receiver, "Transfer of file " + file.getAbsolutePath() + " canceled due to bot shutdown"); Socket transferSocket = new Socket(pendingPassiveTransfer.getReceiverAddress(), pendingPassiveTransfer.getReceiverPort()); return bot.getConfiguration().getBotFactory().createSendFileTransfer(bot, transferSocket, receiver, file, pendingPassiveTransfer.getStartPosition()); } else { //Try to get the user to connect to us final ServerSocket serverSocket = createServerSocket(receiver); PendingSendFileTransfer pendingSendFileTransfer = new PendingSendFileTransfer(receiver, safeFilename, serverSocket.getLocalPort()); synchronized (pendingSendTransfers) { pendingSendTransfers.add(pendingSendFileTransfer); } InetAddress publicAddress = getRealDccPublicAddress(serverSocket); bot.sendDCC().fileRequest(receiver.getNick(), safeFilename, publicAddress, serverSocket.getLocalPort(), file.length()); //Wait for the user to connect log.debug("Sent DCC send file request to user {} ({}ms timeout) to connect on public address {}, local address {}, port {} for file {}", receiver.getNick(), bot.getConfiguration().getDccAcceptTimeout(), publicAddress, serverSocket.getLocalSocketAddress(), serverSocket.getLocalPort(), file.getAbsolutePath()); Socket userSocket = serverSocket.accept(); serverSocket.close(); return bot.getConfiguration().getBotFactory().createSendFileTransfer(bot, userSocket, receiver, file, pendingSendFileTransfer.getPosition()); } } /** * Try to get a real InetAddress in this order: * <ol> * <li>{@link Configuration#getDccLocalAddress()}</li> * <li>{@link Configuration#getLocalAddress()}</li> * <li>{@link PircBotX#getLocalAddress()}</li> * </ol> */ public InetAddress getRealDccLocalAddress(InetAddress destAddress) { //Issue #268: Workaround to give IPv6 users an IPv6 address, or return null to let the OS figure it out InetAddress address = bot.getConfiguration().getDccLocalAddress(); address = (address != null && destAddress.getClass().equals(address.getClass())) ? address : bot.getConfiguration().getLocalAddress(); address = (address != null && destAddress.getClass().equals(address.getClass())) ? address : bot.getLocalAddress(); address = (address != null && destAddress.getClass().equals(address.getClass())) ? address : null; return address; } public InetAddress getRealDccLocalAddress() { InetAddress address = bot.getConfiguration().getDccLocalAddress(); address = (address != null) ? address : bot.getConfiguration().getLocalAddress(); address = (address != null) ? address : bot.getLocalAddress(); return address; } /** * Try to get a real InetAddress in this order: * <ol> * <li>{@link Configuration#getDccPublicAddress()}</li> * <li>{@link #getRealDccLocalAddress() }</li> * </ol> */ public InetAddress getRealDccPublicAddress() { InetAddress address = bot.getConfiguration().getDccPublicAddress(); return (address != null) ? address : getRealDccLocalAddress(); } /** * Try to get a real InetAddress in this order: * <ol> * <li>{@link Configuration#getDccPublicAddress()}</li> * <li>The given ServerSocket's address</li> * </ol> */ public InetAddress getRealDccPublicAddress(ServerSocket ss) { InetAddress address = bot.getConfiguration().getDccPublicAddress(); return (address != null) ? address : ss.getInetAddress(); } protected ServerSocket createServerSocket(User user) throws IOException, DccException { InetAddress address = getRealDccLocalAddress(); ImmutableList<Integer> dccPorts = bot.getConfiguration().getDccPorts(); ServerSocket ss = null; if (dccPorts.isEmpty()) // Use any free port. ss = new ServerSocket(0, 1, address); else { for (int currentPort : dccPorts) try { ss = new ServerSocket(currentPort, 1, address); // Found a port number we could use. break; } catch (Exception e) { // Do nothing; go round and try another port. log.debug("Failed to create server socket on port " + currentPort + ", trying next one", e); } if (ss == null) // No ports could be used. throw new DccException(DccException.Reason.DccPortsInUse, user, "Ports " + dccPorts + " are in use."); } ss.setSoTimeout(bot.getConfiguration().getDccAcceptTimeout()); return ss; } protected static List<String> tokenizeDccRequest(String request) { int quotesIndexBegin = request.indexOf('"'); if (quotesIndexBegin == -1) //Just use tokenizeLine return Utils.tokenizeLine(request); //This is a slightly modified version of Utils.tokenizeLine to parse //potential quotes in filenames int quotesIndexEnd = request.lastIndexOf('"'); List<String> stringParts = new ArrayList<String>(); int pos = 0, end; while ((end = request.indexOf(' ', pos)) >= 0) { if (pos >= quotesIndexBegin && end < quotesIndexEnd) { //We've entered the filename. Add and skip stringParts.add(request.substring(quotesIndexBegin, quotesIndexEnd + 1)); pos = quotesIndexEnd + 2; continue; } stringParts.add(request.substring(pos, end)); pos = end + 1; if (request.charAt(pos) == ':') { stringParts.add(request.substring(pos + 1)); return stringParts; } } //No more spaces, add last part of line stringParts.add(request.substring(pos)); return stringParts; } /** * Shutdown any pending dcc transfers */ public void close() { //Shutdown open reverse dcc servers shuttingDown = true; int pendingCount = pendingReceiveTransfers.values().size() + pendingSendPassiveTransfers.values().size(); if (pendingCount > 0) { log.info("Terminating {} DCC transfers waiting to be accepted", pendingCount); for (CountDownLatch curCountdown : pendingReceiveTransfers.values()) curCountdown.countDown(); for (CountDownLatch curCountdown : pendingSendPassiveTransfers.values()) curCountdown.countDown(); } } public static String addressToInteger(InetAddress address) { if (address instanceof Inet6Address) return address.getHostAddress(); return new BigInteger(1, address.getAddress()).toString(); } public static InetAddress parseRawAddress(String rawAddress) throws UnknownHostException { //Some IPv6 clients are sending the full IPv6 address instead of a bigint if (rawAddress.contains(":")) return Inet6Address.getByName(rawAddress); //Convert the rawInteger into something usable BigInteger bigIp = new BigInteger(rawAddress); byte[] addressBytes = bigIp.toByteArray(); //If there aren't enough bytes, pad with 0 byte if (addressBytes.length == 5) //Has signum, strip it addressBytes = Arrays.copyOfRange(addressBytes, 1, 5); else if (addressBytes.length < 4) { byte[] newAddressBytes = new byte[4]; newAddressBytes[3] = addressBytes[0]; newAddressBytes[2] = (addressBytes.length > 1) ? addressBytes[1] : (byte) 0; newAddressBytes[1] = (addressBytes.length > 2) ? addressBytes[2] : (byte) 0; newAddressBytes[0] = (addressBytes.length > 3) ? addressBytes[3] : (byte) 0; addressBytes = newAddressBytes; } else if (addressBytes.length == 17) //Has signum, strip it addressBytes = Arrays.copyOfRange(addressBytes, 1, 17); try { return InetAddress.getByAddress(addressBytes); } catch (UnknownHostException ex) { throw new RuntimeException("Can't get InetAdrress version of int IP address " + rawAddress + " (bytes: " + Arrays.toString(addressBytes) + ")", ex); } } @Data protected static class PendingRecieveFileTransfer { protected final IncomingFileTransferEvent event; protected long position; } @Data protected static class PendingSendFileTransfer { protected final User user; protected final String filename; protected final int port; protected long position = 0; } @Data protected static class PendingSendFileTransferPassive { protected final User user; protected final String filename; protected final String transferToken; protected long startPosition = 0; protected InetAddress receiverAddress; protected int receiverPort; } @Data protected static class PendingSendChatPassive { protected final User user; protected final String chatToken; protected InetAddress receiverAddress; protected int receiverPort; } public static void main(String[] args) throws UnknownHostException { log.debug("IP: {}", parseRawAddress("134744072")); } }