View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.eclipse.aether.named.ipc;
20  
21  import java.io.IOException;
22  import java.net.Inet6Address;
23  import java.net.InetAddress;
24  import java.net.InetSocketAddress;
25  import java.net.SocketAddress;
26  import java.net.StandardProtocolFamily;
27  import java.net.UnixDomainSocketAddress;
28  import java.net.UnknownHostException;
29  import java.nio.channels.ServerSocketChannel;
30  import java.nio.file.Files;
31  import java.nio.file.Path;
32  import java.nio.file.attribute.PosixFilePermission;
33  import java.util.EnumSet;
34  
35  /**
36   * Socket factory.
37   *
38   * @since 2.0.1
39   */
40  public enum SocketFamily {
41      inet,
42      unix;
43  
44      public ServerSocketChannel openServerSocket() throws IOException {
45          return switch (this) {
46              case inet -> ServerSocketChannel.open().bind(new InetSocketAddress(InetAddress.getLoopbackAddress(), 0), 0);
47              case unix -> {
48                  ServerSocketChannel channel =
49                          ServerSocketChannel.open(StandardProtocolFamily.UNIX).bind(null, 0);
50                  restrictToOwner(channel);
51                  yield channel;
52              }
53              default -> throw new IllegalStateException();
54          };
55      }
56  
57      /**
58       * Restricts access to the socket file backing the given unix-domain server socket to the owning user: the IPC
59       * lock protocol carries no authentication, so the socket file permissions are what prevents other local users
60       * from connecting to the lock daemon and disrupting or stopping it. Automatically bound sockets are created
61       * in the system temporary directory, which is commonly shared between users. On filesystems without POSIX
62       * permissions (e.g. Windows) this is a no-op.
63       *
64       * @since 2.0.23
65       */
66      private static void restrictToOwner(ServerSocketChannel channel) throws IOException {
67          SocketAddress address = channel.getLocalAddress();
68          if (address instanceof UnixDomainSocketAddress) {
69              Path path = ((UnixDomainSocketAddress) address).getPath();
70              try {
71                  Files.setPosixFilePermissions(
72                          path,
73                          EnumSet.of(
74                                  PosixFilePermission.OWNER_READ,
75                                  PosixFilePermission.OWNER_WRITE,
76                                  PosixFilePermission.OWNER_EXECUTE));
77              } catch (UnsupportedOperationException e) {
78                  // no POSIX permissions on this filesystem: nothing to tighten here
79              }
80          }
81      }
82  
83      public static SocketAddress fromString(String str) {
84          if (str.startsWith("inet:")) {
85              String s = str.substring("inet:".length());
86              int ic = s.lastIndexOf(':');
87              String ia = s.substring(0, ic);
88              int is = ia.indexOf('/');
89              String h = ia.substring(0, is);
90              String a = ia.substring(is + 1);
91              String p = s.substring(ic + 1);
92              InetAddress addr;
93              if ("<unresolved>".equals(a)) {
94                  return InetSocketAddress.createUnresolved(h, Integer.parseInt(p));
95              } else {
96                  if (a.indexOf('.') > 0) {
97                      String[] as = a.split("\\.");
98                      if (as.length != 4) {
99                          throw new IllegalArgumentException("Unsupported socket address: '" + str + "'");
100                     }
101                     byte[] ab = new byte[4];
102                     for (int i = 0; i < 4; i++) {
103                         ab[i] = (byte) Integer.parseInt(as[i]);
104                     }
105                     try {
106                         addr = InetAddress.getByAddress(h.isEmpty() ? null : h, ab);
107                     } catch (UnknownHostException e) {
108                         throw new IllegalArgumentException("Unsupported address: " + str, e);
109                     }
110                 } else {
111                     throw new IllegalArgumentException("Unsupported address: " + str);
112                 }
113                 return new InetSocketAddress(addr, Integer.parseInt(p));
114             }
115         } else if (str.startsWith("unix:")) {
116             return UnixDomainSocketAddress.of(str.substring("unix:".length()));
117         } else {
118             throw new IllegalArgumentException("Unsupported socket address: '" + str + "'");
119         }
120     }
121 
122     public static String toString(SocketAddress address) {
123         switch (familyOf(address)) {
124             case inet:
125                 InetSocketAddress isa = (InetSocketAddress) address;
126                 String host = isa.getHostString();
127                 InetAddress addr = isa.getAddress();
128                 int port = isa.getPort();
129                 String formatted;
130                 if (addr == null) {
131                     formatted = host + "/<unresolved>";
132                 } else {
133                     formatted = addr.toString();
134                     if (addr instanceof Inet6Address) {
135                         int i = formatted.lastIndexOf("/");
136                         formatted = formatted.substring(0, i + 1) + "[" + formatted.substring(i + 1) + "]";
137                     }
138                 }
139                 return "inet:" + formatted + ":" + port;
140             case unix:
141                 // to keep address string unchanged across all OSes
142                 return "unix:" + address.toString().replace('\\', '/');
143             default:
144                 throw new IllegalArgumentException("Unsupported socket address: '" + address + "'");
145         }
146     }
147 
148     public static SocketFamily familyOf(SocketAddress address) {
149         if (address instanceof InetSocketAddress) {
150             return SocketFamily.inet;
151         } else if ("java.net.UnixDomainSocketAddress".equals(address.getClass().getName())) {
152             return SocketFamily.unix;
153         } else {
154             throw new IllegalArgumentException("Unsupported socket address '" + address + "'");
155         }
156     }
157 }