View Javadoc
1   /*
2    * junixsocket
3    *
4    * Copyright 2009-2024 Christian Kohlschütter
5    *
6    * Licensed under the Apache License, Version 2.0 (the "License");
7    * you may not use this file except in compliance with the License.
8    * 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, software
13   * distributed under the License is distributed on an "AS IS" BASIS,
14   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15   * See the License for the specific language governing permissions and
16   * limitations under the License.
17   */
18  package org.newsclub.net.unix.rmi;
19  
20  import java.io.IOException;
21  import java.io.ObjectOutput;
22  import java.lang.reflect.Proxy;
23  import java.rmi.Remote;
24  import java.rmi.server.RMISocketFactory;
25  import java.rmi.server.RemoteObject;
26  import java.rmi.server.RemoteObjectInvocationHandler;
27  
28  import org.newsclub.net.unix.AFUNIXSocketCredentials;
29  
30  import com.kohlschutter.annotations.compiletime.SuppressFBWarnings;
31  
32  /**
33   * Information about the remote connection.
34   *
35   * @author Christian Kohlschütter
36   */
37  public final class RemotePeerInfo {
38    private RMISocketFactory socketFactory;
39    String host;
40    int port;
41    private AFUNIXSocketCredentials peerCredentials;
42  
43    RemotePeerInfo() {
44    }
45  
46    /**
47     * The socket factory used to establish connections.
48     *
49     * @return The socket factory.
50     */
51    public RMISocketFactory getSocketFactory() {
52      return socketFactory;
53    }
54  
55    /**
56     * The hostname.
57     *
58     * @return The hostname
59     */
60    public String getHost() {
61      return host;
62    }
63  
64    /**
65     * The port.
66     *
67     * @return The port
68     */
69    public int getPort() {
70      return port;
71    }
72  
73    /**
74     * The remote socket credentials, or {@code null} if they could not be retrieved.
75     *
76     * @return The peer credentials, or {@code null}.
77     */
78    @SuppressFBWarnings("EI_EXPOSE_REP")
79    public AFUNIXSocketCredentials getPeerCredentials() {
80      return peerCredentials;
81    }
82  
83    /**
84     * Returns the {@link AFUNIXSocketCredentials} for the currently active remote session
85     * (RemoteServer session during a remote method invocation), or {@code null} if it was not
86     * possible to retrieve these credentials.
87     *
88     * @return The credentials, or {@code null} if unable to retrieve.
89     */
90    public static AFUNIXSocketCredentials remotePeerCredentials() {
91      return AFUNIXSocketCredentials.remotePeerCredentials();
92    }
93  
94    /**
95     * Returns the {@link AFUNIXSocketCredentials} for the peer (server) of the given {@link Remote}
96     * instance, or {@code null} if it was not possible to retrieve these credentials.
97     *
98     * @param obj The remote object.
99     * @return The credentials, or {@code null} if unable to retrieve.
100    * @throws IOException if an exception occurs.
101    */
102   public static AFUNIXSocketCredentials remotePeerCredentials(Remote obj) throws IOException {
103     return getConnectionInfo(obj).getPeerCredentials();
104   }
105 
106   /**
107    * Returns the connection information ({@link RMISocketFactory}, hostname and port) used for the
108    * given {@link Remote} object, or {@code null} if no custom {@link RMISocketFactory} was
109    * specified.
110    *
111    * An {@link IOException} may be thrown if we couldn't determine the socket factory.
112    *
113    * @param obj The remote object.
114    * @return The factory, or {@code null}
115    * @throws IOException if the operation fails.
116    */
117   public static RemotePeerInfo getConnectionInfo(Remote obj) throws IOException {
118     try (RemotePeerInfo.ExtractingObjectOutput eoo = new RemotePeerInfo.ExtractingObjectOutput()) {
119       RemoteObjectInvocationHandler roih = (RemoteObjectInvocationHandler) Proxy
120           .getInvocationHandler(RemoteObject.toStub(obj));
121 
122       roih.getRef().writeExternal(eoo);
123       if (!eoo.validate()) {
124         throw new IOException("Unexpected data format for " + obj.getClass());
125       }
126 
127       RemotePeerInfo data = eoo.data;
128       if ((data.socketFactory) instanceof AFUNIXRMISocketFactory) {
129         AFUNIXRMISocketFactory sf = ((AFUNIXRMISocketFactory) (data.socketFactory));
130         data.peerCredentials = sf.peerCredentialsFor(data);
131 
132         if (data.peerCredentials == null) {
133           if (sf.isLocalServer(data.port)) {
134             data.peerCredentials = AFUNIXSocketCredentials.SAME_PROCESS;
135           }
136         }
137       } else {
138         data.peerCredentials = null;
139       }
140 
141       return data;
142     }
143   }
144 
145   /**
146    * Mimics a Serializer for RemoteRef.writeExternal, so we can extract the information we need
147    * about the remote reference without having to break-open internal Java classes.
148    *
149    * NOTE: The format for the data we extract is assumed to be stable across JVM implementations,
150    * otherwise RMI would probably not work.
151    *
152    * @author Christian Kohlschütter
153    */
154   static final class ExtractingObjectOutput implements ObjectOutput {
155     private int callId = 0;
156     private boolean done = false;
157     private boolean invalid = false;
158     private int format = -1;
159 
160     final RemotePeerInfo data = new RemotePeerInfo();
161 
162     public ExtractingObjectOutput() {
163     }
164 
165     private void setInvalid() {
166       invalid = done = true;
167     }
168 
169     private void call(int id, Object v) {
170       switch (id) {
171         case 1:
172           if (v instanceof Integer) {
173             // see sun.rmi.transport.tcp.TCPEndpoint
174             switch ((int) v) {
175               case 0:
176                 // FORMAT_HOST_PORT (= no socket factory)
177                 format = 0;
178                 return;
179               case 1:
180                 // FORMAT_HOST_PORT_FACTORY
181                 format = 1;
182                 return;
183               default:
184             }
185           }
186           break;
187         case 2:
188           if (v instanceof String) {
189             this.data.host = (String) v;
190             return;
191           }
192           break;
193         case 3:
194           if (format == 0) {
195             done = true;
196           }
197           if (v instanceof Integer) {
198             this.data.port = (int) v;
199             if (this.data.port <= 0) {
200               setInvalid();
201             }
202             return;
203           }
204           break;
205         case 4:
206           if (v instanceof RMISocketFactory && format == 1) {
207             this.data.socketFactory = (RMISocketFactory) v;
208             return;
209           }
210           break;
211         default:
212           // no need to read any further
213           done = true;
214           return;
215       }
216 
217       // otherwise:
218       setInvalid();
219     }
220 
221     @Override
222     public void writeBoolean(boolean v) {
223       if (done) {
224         return;
225       }
226       call(++callId, v);
227     }
228 
229     @Override
230     public void writeByte(int v) {
231       if (done) {
232         return;
233       }
234       call(++callId, v);
235     }
236 
237     @Override
238     public void writeShort(int v) {
239       if (done) {
240         return;
241       }
242       call(++callId, v);
243     }
244 
245     @Override
246     public void writeChar(int v) {
247       if (done) {
248         return;
249       }
250       call(++callId, v);
251     }
252 
253     @Override
254     public void writeInt(int v) {
255       if (done) {
256         return;
257       }
258       call(++callId, v);
259     }
260 
261     @Override
262     public void writeLong(long v) {
263       if (done) {
264         return;
265       }
266       call(++callId, v);
267     }
268 
269     @Override
270     public void writeFloat(float v) {
271       if (done) {
272         return;
273       }
274       call(++callId, v);
275     }
276 
277     @Override
278     public void writeDouble(double v) {
279       if (done) {
280         return;
281       }
282       call(++callId, v);
283     }
284 
285     @Override
286     public void writeBytes(String s) {
287       if (done) {
288         return;
289       }
290       call(++callId, s);
291     }
292 
293     @Override
294     public void writeChars(String s) {
295       if (done) {
296         return;
297       }
298       call(++callId, s);
299     }
300 
301     @Override
302     public void writeUTF(String s) {
303       if (done) {
304         return;
305       }
306       call(++callId, s);
307     }
308 
309     @Override
310     public void writeObject(Object obj) {
311       if (done) {
312         return;
313       }
314       call(++callId, obj);
315     }
316 
317     @Override
318     public void write(int b) {
319       if (done) {
320         return;
321       }
322       call(++callId, b);
323     }
324 
325     @Override
326     public void write(byte[] b) {
327       if (done) {
328         return;
329       }
330       call(++callId, b);
331     }
332 
333     @Override
334     public void write(byte[] b, int off, int len) {
335       if (done) {
336         return;
337       }
338       setInvalid();
339       // NOTE: not yet needed
340       // byte[] copy = Arrays.copyOfRange(b, off, off + len);
341       // call(++callId, copy);
342     }
343 
344     @Override
345     public void flush() {
346     }
347 
348     public boolean validate() {
349       this.done = true;
350       if (callId < 3) {
351         setInvalid();
352       }
353       return !invalid;
354     }
355 
356     @Override
357     public void close() {
358     }
359   }
360 
361   @Override
362   public String toString() {
363     return getClass().getName() + "[" + host + ":" + port + ";socketFactory=" + socketFactory
364         + ";peerCredentials=" + peerCredentials + "]";
365   }
366 }