001: package CustomDNS;
002:
003: import java.lang.Exception;
004: import java.io.*;
005: import java.net.*;
006: import java.util.StringTokenizer;
007:
008: import net.espeak.infra.cci.exception.*;
009: import net.espeak.jesi.*;
010:
011: import ZoneRegistrarIntf;
012: import ZoneAuthenticatorIntf;
013:
014: import CustomDNS.UpdateProtocol;
015: import CustomDNS.UpdateProtocol.ServerResponse;
016:
017: /*************************************************************************
018: * TCP/IP server for our custom update protocol.
019: *************************************************************************
020: * We listen on a TCP/IP port. Clients connect to us to update their
021: * address records.
022: */
023:
024: public class UpdateServer {
025:
026: // Timeout values, in milliseconds. These are pretty arbitrary. We're
027: // just trying to shut down unused connections promptly.
028: static private final int TCP_TIMEOUT = 60000;
029:
030: // Instance variables.
031: private String zone;
032: private ZoneRegistrarIntf registrar;
033: private ZoneAuthenticatorIntf authenticator;
034: private short port;
035:
036: /*********************************************************************
037: * Start a new update server.
038: *********************************************************************
039: * @param args Our command-line arguments. Pass an espeak connection
040: * properties file and customdns.prop.
041: * @see CustomDNS.Configuration
042: */
043:
044: public UpdateServer(String zone, short port,
045: ZoneRegistrarIntf registrar,
046: ZoneAuthenticatorIntf authenticator) throws IOException {
047: this .zone = zone;
048: this .registrar = registrar;
049: this .authenticator = authenticator;
050: this .port = port;
051: startServer();
052: }
053:
054: // Connect the server to a TCP/IP port and listen for requests.
055: // TODO - We should spawn a listener thread.
056: private void startServer() throws IOException {
057: ServerSocket listener = new ServerSocket(this .port);
058: while (true) {
059: final Socket socket = listener.accept();
060: socket.setSoTimeout(TCP_TIMEOUT);
061: Thread handler = new Thread(new Runnable() {
062: public void run() {
063: handleConnection(socket);
064: }
065: });
066: handler.start();
067: }
068: }
069:
070: // Handle a single client connection.
071: private void handleConnection(Socket socket) {
072: try {
073: Reader rawin = new InputStreamReader(socket
074: .getInputStream());
075: BufferedReader in = new BufferedReader(rawin);
076: Writer out = new OutputStreamWriter(socket
077: .getOutputStream());
078: InetAddress remoteAddress = socket.getInetAddress();
079: runCommandLoop(in, out, remoteAddress);
080: } catch (Exception e) {
081: System.err.println(e.toString());
082: } finally {
083: try {
084: socket.close();
085: } catch (IOException e) {
086: }
087: }
088: }
089:
090: // End the current command and send a response code.
091: // We throw an exception to end the current command
092: // and print a response across the network. For example:
093: // 500 Unknown error
094: // Yes, this a pretty odd way of doing things. But it saves a lot
095: // of ugly state flags in runCommandLoop.
096: private void sendResponse(int code, String message)
097: throws ServerResponse {
098: throw new ServerResponse(code, message);
099: }
100:
101: // Run our command loop.
102: private void runCommandLoop(BufferedReader in, Writer out,
103: InetAddress remoteAddress) throws IOException {
104: String username = null;
105: String password = null;
106:
107: UpdateProtocol.writeLine(out,
108: "201 1.0 CustomDNS Update Server Ready");
109: while (true) {
110: try {
111: // Get our command.
112: String commandLine = in.readLine();
113: if (commandLine == null)
114: return;
115: StringTokenizer tokens = new StringTokenizer(
116: commandLine);
117: int tokenCount = tokens.countTokens();
118: if (tokenCount == 0)
119: sendResponse(UpdateProtocol.STATUS_SYNTAX_ERROR,
120: "No command specified");
121: String command = tokens.nextToken().toUpperCase();
122:
123: // Handle our command.
124: if (command.equals("QUIT")) {
125:
126: // Bail out of our command loop.
127: break;
128:
129: } else if (command.equals("AUTH")) {
130:
131: // Parse our arguments.
132: if (tokenCount != 5)
133: sendResponse(
134: UpdateProtocol.STATUS_SYNTAX_ERROR,
135: "Wrong number of arguments");
136: String authZone = tokens.nextToken();
137: String authType = tokens.nextToken().toUpperCase();
138: username = tokens.nextToken();
139: password = tokens.nextToken();
140:
141: // Do some error checking.
142: if (!this .zone.equals(authZone))
143: sendResponse(
144: UpdateProtocol.STATUS_UNKNOWN_ZONE,
145: "Unknown zone");
146: if (!authType.equals("PASS"))
147: sendResponse(
148: UpdateProtocol.STATUS_UNKNOWN_AUTH_TYPE,
149: "Unknown authentication type");
150:
151: // Send back an optimistic response.
152: sendResponse(UpdateProtocol.STATUS_AUTH_ACCEPTED,
153: "Authentication accepted");
154:
155: } else if (command.equals("ADDRESS")) {
156:
157: // Parse our arguments.
158: if (tokenCount != 3)
159: sendResponse(
160: UpdateProtocol.STATUS_SYNTAX_ERROR,
161: "Wrong number of arguments");
162: String hostname = tokens.nextToken().toLowerCase();
163: String address = tokens.nextToken();
164:
165: // Check the user's privileges.
166: approveUpdate(username, password, hostname);
167:
168: if (address.toUpperCase().equals("AUTOMATIC")) {
169: // Use the address of the remote end of our
170: // socket.
171: address = remoteAddress.getHostAddress();
172: } else {
173: // Sanity-check the address field.
174: address = cleanUpAddress(address);
175: }
176:
177: // Attempt to perform the update.
178: try {
179: registrar.registerAddressForHost(hostname,
180: address);
181: } catch (ESInvocationException e) {
182: sendResponse(UpdateProtocol.STATUS_NO_UPDATE,
183: "Unable to update address");
184: }
185: sendResponse(UpdateProtocol.STATUS_OK,
186: "Address updated");
187:
188: } else {
189: sendResponse(UpdateProtocol.STATUS_UNKNOWN_COMMAND,
190: "Unknown command");
191: }
192: } catch (ServerResponse e) {
193: UpdateProtocol.writeLine(out, e.getMessage());
194: }
195: }
196: UpdateProtocol.writeLine(out, "200 Goodbye");
197: }
198:
199: // Authenticate the user.
200: private void approveUpdate(String user, String password, String host)
201: throws ServerResponse {
202: if (user == null || password == null)
203: sendResponse(UpdateProtocol.STATUS_WRONG_STATE,
204: "Must use AUTH command before ADDRESS");
205: try {
206: if (!authenticator.authenticateUser(user, password, host))
207: sendResponse(UpdateProtocol.STATUS_NOT_ALLOWED,
208: "Update not allowed");
209: } catch (ESInvocationException e) {
210: sendResponse(UpdateProtocol.STATUS_COULD_NOT_AUTH,
211: "Couldn't peform authentication");
212: }
213: }
214:
215: // Try to bash our address into something safe.
216: // (We don't want to put garbage into the database.)
217: // This has the unfortunate side effect of allowing users to supply
218: // hostnames as addresses, and cause us to do a real lookup.
219: private String cleanUpAddress(String address) throws ServerResponse {
220: String result = null;
221: try {
222: InetAddress inetaddr = InetAddress.getByName(address);
223: result = inetaddr.getHostAddress();
224: } catch (UnknownHostException e) {
225: sendResponse(UpdateProtocol.STATUS_MALFORMED_ADDRESS,
226: "Malformed address");
227: }
228: return result;
229: }
230:
231: /*********************************************************************
232: * Start a new update server.
233: *********************************************************************
234: * @param args Our command-line arguments. Pass an espeak connection
235: * properties file and customdns.prop.
236: * @see CustomDNS.Configuration
237: */
238:
239: public static void main(String[] args) {
240: String appname = "CustomDNS.UpdateServer";
241: try {
242: // Parse our command line arguments, and get the configuration
243: // information we'll be needing.
244: Configuration config = Configuration.parseArguments(
245: appname, args);
246: String zone = config
247: .getProperty("customdns.updateserver.zone");
248: String defaultPort = Short
249: .toString(UpdateProtocol.DEFAULT_PORT);
250: String portstr = config.getProperty(
251: "customdns.updateserver.port", defaultPort);
252: short port = Short.parseShort(portstr);
253: String connfile = config.getConnectionFile();
254:
255: // Connect to e-speak, and find our zone registarar and
256: // zone authentator.
257: ESConnection core = new ESConnection(connfile);
258: ESQuery query = new ESQuery("Name == '" + zone + "'");
259: String intfName = "ZoneRegistrarIntf";
260: ESServiceFinder finder = new ESServiceFinder(core, intfName);
261: ZoneRegistrarIntf reg = (ZoneRegistrarIntf) finder
262: .find(query);
263: intfName = "ZoneAuthenticatorIntf";
264: finder = new ESServiceFinder(core, intfName);
265: ZoneAuthenticatorIntf auth = (ZoneAuthenticatorIntf) finder
266: .find(query);
267:
268: // Start our server.
269: System.err.println("CustomDNS.UpdateServer: Starting.");
270: new UpdateServer(zone, port, reg, auth);
271: } catch (Exception e) {
272: System.err.println("CustomDNS.UpdateServer: "
273: + e.toString());
274: System.exit(1);
275: }
276: }
277: }
|