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 
  38 import sun.net.sdp.SdpSupport;
  39 import sun.security.action.GetPropertyAction;
  40 
  41 /**
  42  * A NetHooks provider that converts sockets from the TCP to SDP protocol prior
  43  * to binding or connecting.
  44  */
  45 
  46 public class SdpProvider extends NetHooks.Provider {
  47     // maximum port
  48     private static final int MAX_PORT = 65535;
  49 
  50     // indicates if SDP is enabled and the rules for when the protocol is used
  51     private final boolean enabled;
  52     private final List<Rule> rules;
  53 
  54     // logging for debug purposes
  55     private PrintStream log;
  56 
  57     public SdpProvider() {
  58         Properties props = GetPropertyAction.privilegedGetProperties();
  59         // if this property is not defined then there is nothing to do.
  60         String file = props.getProperty("com.sun.sdp.conf");
  61         if (file == null) {
  62             this.enabled = false;
  63             this.rules = null;
  64             return;
  65         }
  66 
  67         // load configuration file
  68         List<Rule> list = null;
  69         try {
  70             list = loadRulesFromFile(file);
  71         } catch (IOException e) {
  72             fail("Error reading %s: %s", file, e.getMessage());
  73         }
  74 
  75         // check if debugging is enabled
  76         PrintStream out = null;
  77         String logfile = props.getProperty("com.sun.sdp.debug");
  78         if (logfile != null) {
  79             out = System.out;
  80             if (!logfile.isEmpty()) {
  81                 try {
  82                     out = new PrintStream(logfile);
  83                 } catch (IOException ignore) { }
  84             }
  85         }
  86 
  87         this.enabled = !list.isEmpty();
  88         this.rules = list;
  89         this.log = out;
  90     }
  91 
  92     // supported actions
  93     private static enum Action {
  94         BIND,
  95         CONNECT;
  96     }
  97 
  98     // a rule for matching a bind or connect request
  99     private static interface Rule {
 100         boolean match(Action action, InetAddress address, int port);
 101     }
 102 
 103     // rule to match port[-end]
 104     private static class PortRangeRule implements Rule {
 105         private final Action action;
 106         private final int portStart;
 107         private final int portEnd;
 108         PortRangeRule(Action action, int portStart, int portEnd) {
 109             this.action = action;
 110             this.portStart = portStart;
 111             this.portEnd = portEnd;
 112         }
 113         Action action() {
 114             return action;
 115         }
 116         @Override
 117         public boolean match(Action action, InetAddress address, int port) {
 118             return (action == this.action &&
 119                     port >= this.portStart &&
 120                     port <= this.portEnd);
 121         }
 122     }
 123 
 124     // rule to match address[/prefix] port[-end]
 125     private static class AddressPortRangeRule extends PortRangeRule {
 126         private final byte[] addressAsBytes;
 127         private final int prefixByteCount;
 128         private final byte mask;
 129         AddressPortRangeRule(Action action, InetAddress address,
 130                              int prefix, int port, int end)
 131         {
 132             super(action, port, end);
 133             this.addressAsBytes = address.getAddress();
 134             this.prefixByteCount = prefix >> 3;
 135             this.mask = (byte)(0xff << (8 - (prefix % 8)));
 136         }
 137         @Override
 138         public boolean match(Action action, InetAddress address, int port) {
 139             if (action != action())
 140                 return false;
 141             byte[] candidate = address.getAddress();
 142             // same address type?
 143             if (candidate.length != addressAsBytes.length)
 144                 return false;
 145             // check bytes
 146             for (int i=0; i<prefixByteCount; i++) {
 147                 if (candidate[i] != addressAsBytes[i])
 148                     return false;
 149             }
 150             // check remaining bits
 151             if ((prefixByteCount < addressAsBytes.length) &&
 152                 ((candidate[prefixByteCount] & mask) !=
 153                  (addressAsBytes[prefixByteCount] & mask)))
 154                     return false;
 155             return super.match(action, address, port);
 156         }
 157     }
 158 
 159     // parses port:[-end]
 160     private static int[] parsePortRange(String s) {
 161         int pos = s.indexOf('-');
 162         try {
 163             int[] result = new int[2];
 164             if (pos < 0) {
 165                 boolean all = s.equals("*");
 166                 result[0] = all ? 0 : Integer.parseInt(s);
 167                 result[1] = all ? MAX_PORT : result[0];
 168             } else {
 169                 String low = s.substring(0, pos);
 170                 if (low.isEmpty()) low = "*";
 171                 String high = s.substring(pos+1);
 172                 if (high.isEmpty()) high = "*";
 173                 result[0] = low.equals("*") ? 0 : Integer.parseInt(low);
 174                 result[1] = high.equals("*") ? MAX_PORT : Integer.parseInt(high);
 175             }
 176             return result;
 177         } catch (NumberFormatException e) {
 178             return new int[0];
 179         }
 180     }
 181 
 182     private static void fail(String msg, Object... args) {
 183         Formatter f = new Formatter();
 184         f.format(msg, args);
 185         throw new RuntimeException(f.out().toString());
 186     }
 187 
 188     // loads rules from the given file
 189     // Each non-blank/non-comment line must have the format:
 190     // ("bind" | "connect") 1*LWSP-char (hostname | ipaddress["/" prefix])
 191     //     1*LWSP-char ("*" | port) [ "-" ("*" | port) ]
 192     private static List<Rule> loadRulesFromFile(String file)
 193         throws IOException
 194     {
 195         Scanner scanner = new Scanner(new File(file));
 196         try {
 197             List<Rule> result = new ArrayList<>();
 198             while (scanner.hasNextLine()) {
 199                 String line = scanner.nextLine().trim();
 200 
 201                 // skip blank lines and comments
 202                 if (line.isEmpty() || line.charAt(0) == '#')
 203                     continue;
 204 
 205                 // must have 3 fields
 206                 String[] s = line.split("\\s+");
 207                 if (s.length != 3) {
 208                     fail("Malformed line '%s'", line);
 209                     continue;
 210                 }
 211 
 212                 // first field is the action ("bind" or "connect")
 213                 Action action = null;
 214                 for (Action a: Action.values()) {
 215                     if (s[0].equalsIgnoreCase(a.name())) {
 216                         action = a;
 217                         break;
 218                     }
 219                 }
 220                 if (action == null) {
 221                     fail("Action '%s' not recognized", s[0]);
 222                     continue;
 223                 }
 224 
 225                 // * port[-end]
 226                 int[] ports = parsePortRange(s[2]);
 227                 if (ports.length == 0) {
 228                     fail("Malformed port range '%s'", s[2]);
 229                     continue;
 230                 }
 231 
 232                 // match all addresses
 233                 if (s[1].equals("*")) {
 234                     result.add(new PortRangeRule(action, ports[0], ports[1]));
 235                     continue;
 236                 }
 237 
 238                 // hostname | ipaddress[/prefix]
 239                 int pos = s[1].indexOf('/');
 240                 try {
 241                     if (pos < 0) {
 242                         // hostname or ipaddress (no prefix)
 243                         InetAddress[] addresses = InetAddress.getAllByName(s[1]);
 244                         for (InetAddress address: addresses) {
 245                             int prefix =
 246                                 (address instanceof Inet4Address) ? 32 : 128;
 247                             result.add(new AddressPortRangeRule(action, address,
 248                                 prefix, ports[0], ports[1]));
 249                         }
 250                     } else {
 251                         // ipaddress/prefix
 252                         InetAddress address = InetAddress
 253                             .getByName(s[1].substring(0, pos));
 254                         int prefix = -1;
 255                         try {
 256                             prefix = Integer.parseInt(s[1], pos + 1,
 257                                 s[1].length(), 10);
 258                             if (address instanceof Inet4Address) {
 259                                 // must be 1-31
 260                                 if (prefix < 0 || prefix > 32) prefix = -1;
 261                             } else {
 262                                 // must be 1-128
 263                                 if (prefix < 0 || prefix > 128) prefix = -1;
 264                             }
 265                         } catch (NumberFormatException e) {
 266                         }
 267 
 268                         if (prefix > 0) {
 269                             result.add(new AddressPortRangeRule(action,
 270                                         address, prefix, ports[0], ports[1]));
 271                         } else {
 272                             fail("Malformed prefix '%s'", s[1]);
 273                             continue;
 274                         }
 275                     }
 276                 } catch (UnknownHostException uhe) {
 277                     fail("Unknown host or malformed IP address '%s'", s[1]);
 278                     continue;
 279                 }
 280             }
 281             return result;
 282         } finally {
 283             scanner.close();
 284         }
 285     }
 286 
 287     // converts unbound TCP socket to a SDP socket if it matches the rules
 288     private void convertTcpToSdpIfMatch(FileDescriptor fdObj,
 289                                                Action action,
 290                                                InetAddress address,
 291                                                int port)
 292         throws IOException
 293     {
 294         boolean matched = false;
 295         for (Rule rule: rules) {
 296             if (rule.match(action, address, port)) {
 297                 SdpSupport.convertSocket(fdObj);
 298                 matched = true;
 299                 break;
 300             }
 301         }
 302         if (log != null) {
 303             String addr = (address instanceof Inet4Address) ?
 304                 address.getHostAddress() : "[" + address.getHostAddress() + "]";
 305             if (matched) {
 306                 log.format("%s to %s:%d (socket converted to SDP protocol)\n", action, addr, port);
 307             } else {
 308                 log.format("%s to %s:%d (no match)\n", action, addr, port);
 309             }
 310         }
 311     }
 312 
 313     @Override
 314     public void implBeforeTcpBind(FileDescriptor fdObj,
 315                               InetAddress address,
 316                               int port)
 317         throws IOException
 318     {
 319         if (enabled)
 320             convertTcpToSdpIfMatch(fdObj, Action.BIND, address, port);
 321     }
 322 
 323     @Override
 324     public void implBeforeTcpConnect(FileDescriptor fdObj,
 325                                 InetAddress address,
 326                                 int port)
 327         throws IOException
 328     {
 329         if (enabled)
 330             convertTcpToSdpIfMatch(fdObj, Action.CONNECT, address, port);
 331     }
 332 }