/* 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