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