001/* 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, 013 * software distributed under the License is distributed on an 014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 015 * KIND, either express or implied. See the License for the 016 * specific language governing permissions and limitations 017 * under the License. 018 */ 019package org.eclipse.aether.named.ipc; 020 021import java.io.IOException; 022import java.net.Inet6Address; 023import java.net.InetAddress; 024import java.net.InetSocketAddress; 025import java.net.SocketAddress; 026import java.net.StandardProtocolFamily; 027import java.net.UnixDomainSocketAddress; 028import java.net.UnknownHostException; 029import java.nio.channels.ServerSocketChannel; 030import java.nio.file.Files; 031import java.nio.file.Path; 032import java.nio.file.attribute.PosixFilePermission; 033import java.util.EnumSet; 034 035/** 036 * Socket factory. 037 * 038 * @since 2.0.1 039 */ 040public enum SocketFamily { 041 inet, 042 unix; 043 044 public ServerSocketChannel openServerSocket() throws IOException { 045 return switch (this) { 046 case inet -> ServerSocketChannel.open().bind(new InetSocketAddress(InetAddress.getLoopbackAddress(), 0), 0); 047 case unix -> { 048 ServerSocketChannel channel = 049 ServerSocketChannel.open(StandardProtocolFamily.UNIX).bind(null, 0); 050 restrictToOwner(channel); 051 yield channel; 052 } 053 default -> throw new IllegalStateException(); 054 }; 055 } 056 057 /** 058 * Restricts access to the socket file backing the given unix-domain server socket to the owning user: the IPC 059 * lock protocol carries no authentication, so the socket file permissions are what prevents other local users 060 * from connecting to the lock daemon and disrupting or stopping it. Automatically bound sockets are created 061 * in the system temporary directory, which is commonly shared between users. On filesystems without POSIX 062 * permissions (e.g. Windows) this is a no-op. 063 * 064 * @since 2.0.23 065 */ 066 private static void restrictToOwner(ServerSocketChannel channel) throws IOException { 067 SocketAddress address = channel.getLocalAddress(); 068 if (address instanceof UnixDomainSocketAddress) { 069 Path path = ((UnixDomainSocketAddress) address).getPath(); 070 try { 071 Files.setPosixFilePermissions( 072 path, 073 EnumSet.of( 074 PosixFilePermission.OWNER_READ, 075 PosixFilePermission.OWNER_WRITE, 076 PosixFilePermission.OWNER_EXECUTE)); 077 } catch (UnsupportedOperationException e) { 078 // no POSIX permissions on this filesystem: nothing to tighten here 079 } 080 } 081 } 082 083 public static SocketAddress fromString(String str) { 084 if (str.startsWith("inet:")) { 085 String s = str.substring("inet:".length()); 086 int ic = s.lastIndexOf(':'); 087 String ia = s.substring(0, ic); 088 int is = ia.indexOf('/'); 089 String h = ia.substring(0, is); 090 String a = ia.substring(is + 1); 091 String p = s.substring(ic + 1); 092 InetAddress addr; 093 if ("<unresolved>".equals(a)) { 094 return InetSocketAddress.createUnresolved(h, Integer.parseInt(p)); 095 } else { 096 if (a.indexOf('.') > 0) { 097 String[] as = a.split("\\."); 098 if (as.length != 4) { 099 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}