/* Submit : A Course Project Submission Program * * Copyright (C) 1998 Alexander V. Konstantinou (akonstan@acm.org) * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, * MA 02111-1307, USA. * * $Id: SocketIdentification.java,v 1.2 2001/11/08 22:57:55 akonstan Exp $ */ package org.acm.akonstan.net; import java.io.*; import java.net.*; import java.util.Date; import java.text.DateFormat; /** * Implements the Identification Protocol proposed in the IETF RFC 1413. *

* The Identification Protocol (a.k.a., ``ident'', a.k.a., ``the Ident * Protocol'') provides a means to determine the identity of a user of a * particular TCP connection. Given a TCP port number pair, it returns * a character string which identifies the owner of that connection on * the server's system. *

* Instances of SocketIdentification are created using the * static factory method identify() *

* Note - only handles US-ASCII encoded replies, * throws ParseError exception for other character encodings. * * @see IETF RFC 1413 * * @version $Revision: 1.2 $ ; $Date: 2001/11/08 22:57:55 $ * @author Alexander V. Konstantinou (akonstan@acm.org) */ public class SocketIdentification { /** Well known TCP port of the Ident Protocol */ public static final int IDENT_PORT = 113; /** Default identification response timeout (to handle firewalls) */ public static final int IDENT_CLIENT_TIMEOUT_MS = 30000; private int portOnServer = -1; private int portOnClient = -1; private String charSet = null; private String userID = null; private String opSys = null; private String errorType; /** * Instances created using the static factory method identify() */ protected SocketIdentification() { } /** * Returns an instance of SocketIdentification containing the * userID and opSys values returned, or an errorType. * * @param sock - the socket whose user needs to be identified * * @exception java.io.IOException indicating a communication error * @exception java.io.ParseException with an appropriate message * when unable to parse the server's response */ public static SocketIdentification identify(Socket sock) throws java.io.IOException, java.text.ParseException { return(identify(sock, IDENT_CLIENT_TIMEOUT_MS)); } /** * Returns an instance of SocketIdentification containing the * userID and opSys values returned, or an errorType. * * @param sock - the socket whose user needs to be identified * @param timeoutMillis - the identification request timeout * * @exception java.io.IOException indicating a communication error * @exception java.io.ParseException with an appropriate message * when unable to parse the server's response */ public static SocketIdentification identify(Socket sock, int timeoutMillis) throws java.io.IOException, java.text.ParseException { if (sock == null) throw new NullPointerException("null socket argument"); if (timeoutMillis < 0) throw new IllegalArgumentException("negative response timeout argument"); SocketIdentification ident = new SocketIdentification(); ident.portOnServer = sock.getPort(); ident.portOnClient = sock.getLocalPort(); Socket idsocket = null; String response = null; try { idsocket = new Socket(sock.getInetAddress(), IDENT_PORT); idsocket.setSoTimeout(timeoutMillis); // // create QUERY string // // , OutputStream os = idsocket.getOutputStream(); String message = ident.portOnServer + ", " + ident.portOnClient + "\n"; os.write(message.getBytes("8859_1")); // // receive RESPONSE // InputStream is = idsocket.getInputStream(); byte[] replyOctet = new byte[4096]; int n = is.read(replyOctet); response = new String(replyOctet, "8859_1"); } catch (ConnectException e) { throw new ConnectException(e.getMessage() + ": the ident service is not available"); } finally { if (idsocket != null) idsocket.close(); } /* parse RESPONSE : * * ::= * ::= "015 012" ; CR-LF End of Line Indicator * ::= ":" "ERROR" ":" * ::= ":" "USERID" ":" * ":" * ::= "INVALID-PORT" | "NO-USER" | "UNKNOWN-ERROR" * | "HIDDEN-USER" | * ::= [ "," ] * ::= "OTHER" | "UNIX" | ...etc. * ::= "US-ASCII" | ...etc. * ::= * ::= 1*64 ; 1-64 characters * ::= "X"1*63 */ int current, next; // port-pair is ignored since we already know it (I guess we could // verify it if we were paranoid) current = response.indexOf(':', 0); if (current == -1) { throw new java.text.ParseException ("Error parsing Ident response (response does not contain ':'", 0); } current++; // extract response type next = response.indexOf(':', current); if (next == -1) { throw new java.text.ParseException ("Error parsing Ident response (response type not followed by ':'", current); } String responseType = response.substring(current, next).trim(); current = next+1; // parse response type if (responseType.equalsIgnoreCase("ERROR")) { // parse error response next = response.indexOf('\015', current); if (next == -1) { // don't complain about non-compliant servers ! ident.errorType = response.substring(current).trim(); } else { ident.errorType = response.substring(current, next).trim(); } } else if (responseType.equalsIgnoreCase("USERID")) { // parse opsys field in response next = response.indexOf(':', current); if (next == -1) { throw new java.text.ParseException ("Error parsing Ident response (userid response not followed " + "by ':'", current); } ident.opSys = response.substring(current, next).trim(); ident.charSet = "US-ASCII"; // default current = next + 1; // check for optional character set specification int commaPos = ident.opSys.lastIndexOf(','); if (commaPos != -1) { // ident.charSet = ident.opSys.substring(commaPos + 1).trim(); ident.opSys = ident.opSys.substring(0, commaPos).trim(); if (ident.charSet.equals("US-ASCII")) { // no need to modify anything, already parsed as ISO-8859-1 } else { // at this point, we would need to convert the raw response // as the new character-set. Not implemented - throw exception throw new java.text.ParseException ("Unsupported used ID character encoding " + ident.charSet, current); } } // parse user-id field next = response.indexOf('\015', current); if (next == -1) { // don't complain about non-compliant servers ! ident.userID = response.substring(current).trim(); } else { ident.userID = response.substring(current, next).trim(); } } else { throw new java.text.ParseException("Unknown Ident response type " + responseType, current); } return ident; } // identify /** * @return the originating port at the server running the Ident daemon */ public int getPortOnServer() { return portOnServer; } /** * @return the destination port number at the local host */ public int getPortOnClient() { return portOnClient; } /** * @return the user ID as reported by Ident, or null if the identification * process was aborted. */ public String getUserID() { return userID; } /** * @return the operating system name as reported by Ident, or null if * the identification process was aborted. */ public String getOpSys() { return opSys; } /** * @return a string description of the error type, or null if no error * has occured. */ public String getErrorType() { return errorType; } /** * Used for debuging and testing the identification information passed * by a host. */ public static void main(String args[]) { ServerSocket sock = null; int serverPort = 6789; if (args.length == 1) { try { serverPort = Integer.parseInt(args[0]); } catch (NumberFormatException e) { System.err.println("Invalid socket number " + args[0]); System.err.println("Usage: SocketIdentification { }"); System.exit(1); } } try { sock = new ServerSocket(serverPort); // test port System.out.println("SocketIdentification tester listening on port " + sock.getLocalPort() + "\n"); System.out.println("You may test your identification by telneting " + "from this, or another host\nto this server port.\n"); } catch (Exception e) { e.printStackTrace(); System.exit(1); } while(true) { Socket sockIn = null; try { sockIn = sock.accept(); Date now = new Date(); System.out.println("Connect from " + sockIn.getInetAddress() + " to port " + sockIn.getLocalPort() + " on " + DateFormat.getDateInstance().format(now) + " " + DateFormat.getTimeInstance().format(now)); try { SocketIdentification ident = SocketIdentification.identify(sockIn); System.out.println("User ID=" + ident.getUserID()); System.out.println("Operating System=" + ident.getOpSys()); } catch(Throwable e) { System.err.println(e.getClass().getName() + ": " + e.getMessage()); } } catch (Throwable e) { System.err.println(e.getClass().getName() + ": " + e.getMessage()); } finally { if (sockIn != null) { try { sockIn.close(); } catch (Throwable e2) { } } } } } // main } // class SocketIdentification