1 /*
   2  * Copyright (c) 2009, 2010, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package sun.net.sdp;
  27 
  28 import sun.net.NetHooks;
  29 import java.net.InetAddress;
  30 import java.net.Inet4Address;
  31 import java.net.UnknownHostException;
  32 import java.util.*;
  33 import java.io.File;
  34 import java.io.FileDescriptor;
  35 import java.io.IOException;
  36 import java.io.PrintStream;
  37 import java.security.AccessController;
  38 
  39 import sun.net.sdp.SdpSupport;
  40 import sun.security.action.GetPropertyAction;
  41 
  42 /**
  43  * A NetHooks provider that converts sockets from the TCP to SDP protocol prior
  44  * to binding or connecting.
  45  */
  46 
  47 public class SdpProvider extends NetHooks.Provider {
  48     // maximum port
  49     private static final int MAX_PORT = 65535;
  50 
  51     // indicates if SDP is enabled and the rules for when the protocol is used
  52     private final boolean enabled;
  53     private final List<Rule> rules;
  54 
  55     // logging for debug purposes
  56     private PrintStream log;
  57 
  58     public SdpProvider() {
  59         // if this property is not defined then there is nothing to do.
  60         String file = AccessController.doPrivileged(
  61             new GetPropertyAction("com.sun.sdp.conf"));
  62         if (file == null) {
  63             this.enabled = false;
  64             this.rules = null;
  65             return;
  66         }
  67 
  68         // load configuration file
  69         List<Rule> list = null;
  70         if (file != null) {
  71             try {
  72                 list = loadRulesFromFile(file);
  73             } catch (IOException e) {
  74                 fail("Error reading %s: %s", file, e.getMessage());
  75             }
  76         }
  77 
  78         // check if debugging is enabled
  79         PrintStream out = null;
  80         String logfile = AccessController.doPrivileged(
  81             new GetPropertyAction("com.sun.sdp.debug"));
  82         if (logfile != null) {
  83             out = System.out;
  84             if (logfile.length() > 0) {
  85                 try {
  86                     out = new PrintStream(logfile);
  87                 } catch (IOException ignore) { }
  88             }
  89         }
  90 
  91         this.enabled = !list.isEmpty();
  92         this.rules = list;
  93         this.log = out;
  94     }
  95 
  96     // supported actions
  97     private static enum Action {
  98         BIND,
  99         CONNECT;
 100     }
 101 
 102     // a rule for matching a bind or connect request
 103     private static interface Rule {
 104         boolean match(Action action, InetAddress address, int port);
 105     }
 106 
 107     // rule to match port[-end]
 108     private static class PortRangeRule implements Rule {
 109         private final Action action;
 110         private final int portStart;
 111         private final int portEnd;
 112         PortRangeRule(Action action, int portStart, int portEnd) {
 113             this.action = action;
 114             this.portStart = portStart;
 115             this.portEnd = portEnd;
 116         }
 117         Action action() {
 118             return action;
 119         }
 120         @Override
 121         public boolean match(Action action, InetAddress address, int port) {
 122             return (action == this.action &&
 123                     port >= this.portStart &&
 124                     port <= this.portEnd);
 125         }
 126     }
 127 
 128     // rule to match address[/prefix] port[-end]
 129     private static class AddressPortRangeRule extends PortRangeRule {
 130         private final byte[] addressAsBytes;
 131         private final int prefixByteCount;
 132         private final byte mask;
 133         AddressPortRangeRule(Action action, InetAddress address,
 134                              int prefix, int port, int end)
 135         {
 136             super(action, port, end);
 137             this.addressAsBytes = address.getAddress();
 138             this.prefixByteCount = prefix >> 3;
 139             this.mask = (byte)(0xff << (8 - (prefix % 8)));
 140         }
 141         @Override
 142         public boolean match(Action action, InetAddress address, int port) {
 143             if (action != action())
 144                 return false;
 145             byte[] candidate = address.getAddress();
 146             // same address type?
 147             if (candidate.length != addressAsBytes.length)
 148                 return false;
 149             // check bytes
 150             for (int i=0; i<prefixByteCount; i++) {
 151                 if (candidate[i] != addressAsBytes[i])
 152                     return false;
 153             }
 154             // check remaining bits
 155             if ((prefixByteCount < addressAsBytes.length) &&
 156                 ((candidate[prefixByteCount] & mask) !=
 157                  (addressAsBytes[prefixByteCount] & mask)))
 158                     return false;
 159             return super.match(action, address, port);
 160         }
 161     }
 162 
 163     // parses port:[-end]
 164     private static int[] parsePortRange(String s) {
 165         int pos = s.indexOf('-');
 166         try {
 167             int[] result = new int[2];
 168             if (pos < 0) {
 169                 boolean all = s.equals("*");
 170                 result[0] = all ? 0 : Integer.parseInt(s);
 171                 result[1] = all ? MAX_PORT : result[0];
 172             } else {
 173                 String low = s.substring(0, pos);
 174                 if (low.length() == 0) low = "*";
 175                 String high = s.substring(pos+1);
 176                 if (high.length() == 0) high = "*";
 177                 result[0] = low.equals("*") ? 0 : Integer.parseInt(low);
 178                 result[1] = high.equals("*") ? MAX_PORT : Integer.parseInt(high);
 179             }
 180             return result;
 181         } catch (NumberFormatException e) {
 182             return new int[0];
 183         }
 184     }
 185 
 186     private static void fail(String msg, Object... args) {
 187         Formatter f = new Formatter();
 188         f.format(msg, args);
 189         throw new RuntimeException(f.out().toString());
 190     }
 191 
 192     // loads rules from the given file
 193     // Each non-blank/non-comment line must have the format:
 194     // ("bind" | "connect") 1*LWSP-char (hostname | ipaddress["/" prefix])
 195     //     1*LWSP-char ("*" | port) [ "-" ("*" | port) ]
 196     private static List<Rule> loadRulesFromFile(String file)
 197         throws IOException
 198     {
 199         Scanner scanner = new Scanner(new File(file));
 200         try {
 201             List<Rule> result = new ArrayList<>();
 202             while (scanner.hasNextLine()) {
 203                 String line = scanner.nextLine().trim();
 204 
 205                 // skip blank lines and comments
 206                 if (line.length() == 0 || line.charAt(0) == '#')
 207                     continue;
 208 
 209                 // must have 3 fields
 210                 String[] s = line.split("\\s+");
 211                 if (s.length != 3) {
 212                     fail("Malformed line '%s'", line);
 213                     continue;
 214                 }
 215 
 216                 // first field is the action ("bind" or "connect")
 217                 Action action = null;
 218                 for (Action a: Action.values()) {
 219                     if (s[0].equalsIgnoreCase(a.name())) {
 220                         action = a;
 221                         break;
 222                     }
 223                 }
 224                 if (action == null) {
 225                     fail("Action '%s' not recognized", s[0]);
 226                     continue;
 227                 }
 228 
 229                 // * port[-end]
 230                 int[] ports = parsePortRange(s[2]);
 231                 if (ports.length == 0) {
 232                     fail("Malformed port range '%s'", s[2]);
 233                     continue;
 234                 }
 235 
 236                 // match all addresses
 237                 if (s[1].equals("*")) {
 238                     result.add(new PortRangeRule(action, ports[0], ports[1]));
 239                     continue;
 240                 }
 241 
 242                 // hostname | ipaddress[/prefix]
 243                 int pos = s[1].indexOf('/');
 244                 try {
 245                     if (pos < 0) {
 246                         // hostname or ipaddress (no prefix)
 247                         InetAddress[] addresses = InetAddress.getAllByName(s[1]);
 248                         for (InetAddress address: addresses) {
 249                             int prefix =
 250                                 (address instanceof Inet4Address) ? 32 : 128;
 251                             result.add(new AddressPortRangeRule(action, address,
 252                                 prefix, ports[0], ports[1]));
 253                         }
 254                     } else {
 255                         // ipaddress/prefix
 256                         InetAddress address = InetAddress
 257                             .getByName(s[1].substring(0, pos));
 258                         int prefix = -1;
 259                         try {
 260                             prefix = Integer.parseInt(s[1], pos + 1,
 261                                 s[1].length(), 10);
 262                             if (address instanceof Inet4Address) {
 263                                 // must be 1-31
 264                                 if (prefix < 0 || prefix > 32) prefix = -1;
 265                             } else {
 266                                 // must be 1-128
 267                                 if (prefix < 0 || prefix > 128) prefix = -1;
 268                             }
 269                         } catch (NumberFormatException e) {
 270                         }
 271 
 272                         if (prefix > 0) {
 273                             result.add(new AddressPortRangeRule(action,
 274                                         address, prefix, ports[0], ports[1]));
 275                         } else {
 276                             fail("Malformed prefix '%s'", s[1]);
 277                             continue;
 278                         }
 279                     }
 280                 } catch (UnknownHostException uhe) {
 281                     fail("Unknown host or malformed IP address '%s'", s[1]);
 282                     continue;
 283                 }
 284             }
 285             return result;
 286         } finally {
 287             scanner.close();
 288         }
 289     }
 290 
 291     // converts unbound TCP socket to a SDP socket if it matches the rules
 292     private void convertTcpToSdpIfMatch(FileDescriptor fdObj,
 293                                                Action action,
 294                                                InetAddress address,
 295                                                int port)
 296         throws IOException
 297     {
 298         boolean matched = false;
 299         for (Rule rule: rules) {
 300             if (rule.match(action, address, port)) {
 301                 SdpSupport.convertSocket(fdObj);
 302                 matched = true;
 303                 break;
 304             }
 305         }
 306         if (log != null) {
 307             String addr = (address instanceof Inet4Address) ?
 308                 address.getHostAddress() : "[" + address.getHostAddress() + "]";
 309             if (matched) {
 310                 log.format("%s to %s:%d (socket converted to SDP protocol)\n", action, addr, port);
 311             } else {
 312                 log.format("%s to %s:%d (no match)\n", action, addr, port);
 313             }
 314         }
 315     }
 316 
 317     @Override
 318     public void implBeforeTcpBind(FileDescriptor fdObj,
 319                               InetAddress address,
 320                               int port)
 321         throws IOException
 322     {
 323         if (enabled)
 324             convertTcpToSdpIfMatch(fdObj, Action.BIND, address, port);
 325     }
 326 
 327     @Override
 328     public void implBeforeTcpConnect(FileDescriptor fdObj,
 329                                 InetAddress address,
 330                                 int port)
 331         throws IOException
 332     {
 333         if (enabled)
 334             convertTcpToSdpIfMatch(fdObj, Action.CONNECT, address, port);
 335     }
 336 }