usr/src/lib/libslp/javalib/com/sun/slp/slpd.java
author Mark J. Nelson <Mark.J.Nelson@Sun.COM>
Wed, 06 Aug 2008 16:29:39 -0600
changeset 7298 b69e27387f74
parent 0 68f95e015346
permissions -rw-r--r--
6733918 Teamware has retired, please welcome your new manager, Mercurial 4758439 some files use "current date" sccs keywords 6560843 asm sources should not rely on .file "%M%" for naming STT_FILE symbols 6560958 Solaris:: perl modules should not use SCCS keywords in version information 6729074 webrev doesn't deal well with remote ssh hg parents

/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */
/*
 * Copyright 2004 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 *
 */

//  sldp.java : The service location daemon.
//  Author:           Erik Guttman
//

package com.sun.slp;

import java.io.*;
import java.util.*;
import java.net.*;
import java.lang.reflect.*;
import java.awt.*;

/**
 * The slpd class is the main class for the slpd directory agent/
 * service agent server of SLP. Slpd can run in two possible modes,
 * depending on the configuration of the classes with which it was shipped
 * and information read from the optional configuration file argument:
 *
 *   <ol>
 *     <li> <b> Service Agent server </b>
 *          In this mode, slpd functions as a service agent server only.
 *	    Directory agent functionality is disabled. Service agent
 *	    clients on the local machine register and deregister services
 *	    with slpd, and slpd responds to multicast requests for
 *	    services. It passively and actively listens for directory agent
 *	    advertisements, caches them, and forwards them to both
 *	    user agent and service agent clients. A file of serialized
 *	    proxy registrations can be read at startup.
 *	    This mode is normally default.
 *
 *     <li> <b> Directory Agent/Service Agent server </b>
 *          In this mode, slpd functions  as a directory agent
 *	    for port 427 on the local machine. The directory agent
 *	    caches service advertisements, makes directory agent
 *	    advertisements of its services, and responds to requests
 *          for TCP connections with service agents and user agents.
 *	    In addition, slpd functions in the first mode, as a
 *	    service agent server, for SAs and UAs on the local host.
 *
 *   </ol>
 *
 * The slpd is invoked as follows:<br>
 *<blockquote>
 *
 *	java com.sun.slpd [monitor] [stop] [-f <config file name>]
 *
 *</blockquote>
 *
 * The optional monitor argument specifies that a GUI monitor should be
 * brought up. The optional stop argument indicates that slpd should
 * signal a running slpd to stop. The optional <config file name> argument
 * specifies that the named file should be used as the configuration file.
 * <p>
 * See <a href="slpd.conf.html">slpd.conf</a> for more information on
 * configuration file syntax and <a href="slpd.reg.html">slpd.reg</a>
 * for more information on proxy registration file syntax.
 *
 * @author Erik Guttman, James Kempf
 */

// Note that the inheritance is *only* so slpd can initialize the
// internals of SLP config.

public class slpd extends SLPConfig {

    private static final String SERVER_BUNDLE_NAME = "com/sun/slp/Server";

    // Server bundle. Set the parent.

    static class ServerBundle extends ResourceBundle {

	private ResourceBundle bundle = null;

	static ResourceBundle
	    getBundle(ResourceBundle parent, Locale locale)
	    throws MissingResourceException {

	    return new ServerBundle(parent, locale);

	}

	private ServerBundle(ResourceBundle parent, Locale locale)
	    throws MissingResourceException {

	    if (parent != null) {
		this.parent = parent;

	    }

	    try {
		URL[] urls = null;
		urls = new URL[] {new URL("file:/usr/share/lib/locale/")};
		URLClassLoader ld = new URLClassLoader(urls);

		bundle =
		    ResourceBundle.getBundle(SERVER_BUNDLE_NAME, locale, ld);

	    } catch (MalformedURLException e) {
		// fallthru to default location
	    }	// No locales in slpd.jar, so propagate the
		// MissingResourceException

	    bundle = bundle != null ?
		bundle :
		ResourceBundle.getBundle(SERVER_BUNDLE_NAME, locale);

	}

	protected Object handleGetObject(String key)
	    throws MissingResourceException {
	    Object ret = null;

	    try {

		ret = bundle.getObject(key);

	    } catch (MissingResourceException ex) {
		ret = parent.getObject(key);

	    }

	    return ret;

	}

	public Enumeration getKeys() {
	    return bundle.getKeys();

	}

    }

    /**
     * Log object for SLP to write to GUI. This class follows the
     * semantics of the other SLP logging classes:
     *
     * This class does not actually write anything until the flush() method
     * in invoked; this will write the concatenation of all messages
     * passed to the write() method since the last invocation of flush().
     *
     * The actual logging class used can be controlled via the
     * sun.net.slp.loggerClass property.
     *
     * See also the StderrLog and Syslog classes.
     */

    static class SLPLog extends Writer {

	private TextArea taLog = null;
	private StringBuffer buf;

	SLPLog(TextArea nta) {
	    taLog = nta;
	    buf = new StringBuffer();

	}

	// Write to the StringBuffer

	public void write(char[] cbuf, int off, int len)
	    throws IOException {
	    buf.append(cbuf, off, len);

	}

	// Write to the Frame.

	public void flush() throws IOException {
	    String date = SLPConfig.getDateString();

	    taLog.append(
			 "********" +
			 date + "\n" +
			 buf.toString() + "\n" +
			 "********\n");
	    buf = new StringBuffer();

	}

	// These is a no-op

	public void close() throws IOException {}

    }

    //
    // slpd definition.
    //

    private static String  configFile;		// name of configuration file
    private static SLPDgui slpdgui;		// GUI monitor, if desired
    private static SLPConfig config;		// handles system properties
    private static ServerDATable daTable;	// handle recording of DAs

    // Called by the slpd and subclasses only.

    protected slpd() {
	super();
    }

    private static void usage() {
	ResourceBundle bundle =
	    getMessageBundleInternal(Locale.getDefault(), null);
	System.err.println(formatMessageInternal("slpd_usage",
						 new Object[0],
						 bundle));
	System.exit(1);
    }

    /**
     * Usage: slpd [monitor] [stop] [-f config-file name]<br>
     * <br>
     * String arguments are:
     * <br>
     * <b> monitor </b> Puts up a rudimentary GUI for the slpd.<br>
     * <b>stop <b> Bring down a running slpd and exit.
     * <b> config-file name </b> Reads the specified configuration file.<br>
     *
     * The default running mode is to have no GUI and to use SLP
     * defined configuration.
     */

    public static void main(String args[]) {
	boolean bMon  = false;
	boolean bStop = false;
	configFile    = null;

	Thread.currentThread().setName("slpd");

	// Process args.

	if (args.length > 3) {
	    usage();

	}

	int i, n = args.length;

	for (i = 0; i < n; i++) {

	    // Argument is a config file.

	    if (args[i].equals("-f")) {

		if (configFile != null) {
		    usage();

		}

		// Make sure we can open it.

		try {
		    File f = new File(args[++i]);
		    configFile = args[i];

		} catch (Exception ex) {
		    usage();

		}
	    } else if (args[i].equals("monitor")) {
		bMon = true;

	    } else if (args[i].equals("stop")) {
		bStop = true;

	    } else {
		usage();

	    }
	}

	// Read message bundle file, load config file into properties.

	ResourceBundle bundle =
	    getMessageBundleInternal(Locale.getDefault(), null);

	try {
	    if (configFile != null) {
		Properties props = System.getProperties();
		props.setProperty("sun.net.slp.configURL",
				  "file:" + configFile);

	    }

	    // Create a new SLP Config object from the config file.
	    config = initializeSLPConfig();

	    // Create a GUI if the user asked for one.

	    if (bMon) {

		try {
		    slpdgui = new SLPDgui(configFile);
		    SLPLog log = new SLPLog(slpdgui.getTALog());

		    synchronized (config) {
			config.log = log;
		    }

		    slpdgui.setVisible(true);

		} catch (Exception ex) {
		    System.err.println(formatMessageInternal("slpd_no_gui",
							     new Object[0],
							     bundle));
		}
	    }

	    // Either start or stop the server, depending on what was
	    //  requested.

	    if (!bStop) {
		start();

	    } else {
		stop();

	    }
	} catch (ServiceLocationException ex) {

	    errorExit(bundle, ex);

	}

    }

    /**
     * Start the slpd.
     *
     * @param bMon	True if initializing with GUI monitor.
     * @exception ServiceLocationException Internal error or network
     *			initialization error or
     *			internal networking error.
     *
     */

    static void start() throws ServiceLocationException {

	// Initialize the service table.

	ServiceTable table = ServiceTable.getServiceTable();

	// Initialize the class name for the DA table to the Sun-specific
	//  DA table.

	Properties props = System.getProperties();
	props.put(DATable.DA_TABLE_CLASS_PROP, "com.sun.slp.SunServerDATable");

	// If there is a request on stdin, process it now
	try {
	    if (System.in.available() > 0) {
		RequestHandler rh =
		    new RequestHandler(System.in, System.out, config);
		rh.start();
	    }
	} catch (IOException e) {}

	// Start a StreamListener on loopback to start accepting locals regs

	StreamListener.initializeStreamListenerOnInterface(
							config.getLoopback());

	// Create a ServerDATable from the class. This will initialize
	//  active discovery. Note that we need to record our own presence
	//  in the DA table because we are not yet listening for requests.
	//  We do this after deserialization so that if we discover any
	//  DAs, we can perform registrations of the serialized advertisements.

	daTable = ServerDATable.getServerDATable();

	// Deserialize any serialized advertisements and do them now.
	//  Waiting until here allows any error messages to appear in
	//  the GUI log, if any, and at this point the DA table is ready.

	table.deserializeTable();

	// Need to create datagram and stream listeners, and a
	//  DAAdvertiser on all network interfaces.

	Vector interfaces = config.getInterfaces();
	int i, n = interfaces.size();

	for (i = 0; i < n; i++) {
	    InetAddress interfac = (InetAddress)interfaces.elementAt(i);

	    // Initialize the complex of listener/sender objects on the
	    // interface. This includes a datagram listener, a DAAdvertiser
	    // (which shares the same socket as the datagram listener), and
	    // a stream listener.

	    Listener.initializeInterfaceManagers(interfac);

	}

	// If we've been configured as a DA, then create a DA advertiser to
	//  periodically advertise our presence on this interface. This
	//  is only done on the default interface.

	if (config.isDA()) {
	    DAAdvertiser.initializeDAAdvertiserOnInterface(
							config.getLocalHost());

	}

	// Report scopes and whether DA or SA.

	Vector discoveredScopes = daTable.findScopes();
	Vector serverScopes = config.getSAConfiguredScopes();
	Vector daAttributes = config.getDAAttributes();
	Vector saAttributes = config.getSAAttributes();

	// Report that we are running if tracing is on

	if (config.regTest() ||
	    config.traceMsg() ||
	    config.traceDrop() ||
	    config.traceDATraffic()) {

	    config.writeLog((config.isDA() ? "hello_da":"hello"),
			    new Object[] {interfaces,
					      serverScopes,
					      discoveredScopes,
					      (config.isDA() ?
					       daAttributes:saAttributes)});
	}

	// If V1 is supported, crank up V1 support as well.

	if (config.isV1Supported()) {
	    SLPV1Manager.start(config, daTable, table);

	}
    }

    // Stop a running server by sending a DAAdvert or SAAdvert.

    static void stop() throws ServiceLocationException {

	if (daemonIsDA()) {
	    stopDA();

	} else {
	    stopSA();

	}
    }

    // Determine whether the daemon running on this machine is a DA
    //  or not.

    static boolean daemonIsDA() throws ServiceLocationException {

	// Get a DA table with available DAs.

	DATable table =
	    DATable.getDATable();

	// Get DAs.

	Hashtable das =
	    table.findDAScopes(config.getSAConfiguredScopes());
	Vector daRecs = (Vector)das.get(DATable.UNICAST_KEY);
	Vector interfaces = config.getInterfaces();

	// If no DAs, then simply return.

	if (daRecs == null) {
	    return false;

	}

	// Find our address in the list, if it exists.

	int i, n = daRecs.size();

	for (i = 0; i < n; i++) {
	    DATable.DARecord rec =
		(DATable.DARecord)daRecs.elementAt(i);
	    Vector daAddresses = rec.daAddresses;

	    int j, m = interfaces.size();

	    for (j = 0; j < m; j++) {
		if (daAddresses.contains(interfaces.elementAt(i))) {
		    return true;

		}
	    }
	}

	return false;
    }

    // Stop a DA by multicasting the DAAdvert with boot timestamp 0.

    private static void stopDA() throws ServiceLocationException {

	// Make the DA URL and the DAAdvert. Note that we only need signal
	//  on the default local host interface because that is the only
	//  one on which the server is listening.

	ServiceURL url =
	    new ServiceURL(Defaults.DA_SERVICE_TYPE +
			   "://" +
			   config.getLocalHost().getHostAddress(),
			   ServiceURL.LIFETIME_DEFAULT);

	SDAAdvert advert =
	    new SDAAdvert(new SLPServerHeaderV2(),
			  (short)0x0,  // sez we're unsolicited...
			  0L,    // sez we're going down...
			  url,
			  config.getSAConfiguredScopes(),
			  new Vector()); // no attributes needed to go down...

	// Make the DAAdvertiser.

	DAAdvertiser daadv = new DAAdvertiser(config.getLocalHost(),
					      advert.getHeader());

	// Send out unsolicted "going down" message.

	daadv.sendAdvert();

	// That's it! No need for any messages here.

	System.exit(0);

    }

    // Stop an SA server by unicasting an SA advert with xid 0.

    private static void stopSA() throws ServiceLocationException {

	// We signal for stop on the local host, which is guaranteed
	//  to have an SA listener.

	ServiceURL url =
	    new ServiceURL(Defaults.SA_SERVICE_TYPE + "://" +
			   config.getLocalHost().getHostAddress(),
			   ServiceURL.LIFETIME_DEFAULT);

	SSAAdvert advert = new SSAAdvert(Defaults.version,
					 (short)0x0, // sez we're going down...
					 config.getLocale(),
					 url,
					 config.getSAConfiguredScopes(),
					 new Vector());
						// don't care about attrs..,

	// Send it TCP. We ignore NETWORK_ERROR because it only means
	//  that the daemon didn't send us a reply, which is expected.

	try {

	    SrvLocMsg msg =
		Transact.transactTCPMsg(config.getLoopback(), advert, false);

	    if (msg.getErrorCode() != ServiceLocationException.OK) {
		config.writeLog("slpd_sa_stop_failure",
				new Object[] {
		    new Integer(msg.getErrorCode())});

	    }

	} catch (ServiceLocationException ex) {

	    if (ex.getErrorCode() != ServiceLocationException.NETWORK_ERROR) {
		config.writeLog("slpd_sa_stop_failure",
				new Object[] {new Integer(ex.getErrorCode())});

	    }

	}

	// That's it!

	System.exit(0);
    }

    // Print error message, exit.

    static void errorExit(ResourceBundle bundle, ServiceLocationException ex) {

	switch (ex.getErrorCode()) {

	case ServiceLocationException.INTERNAL_SYSTEM_ERROR:
	    System.err.println(formatMessageInternal("slpd_int_err",
						     new Object[] {
		ex.getMessage()},
		bundle));
	    break;

	case ServiceLocationException.NETWORK_INIT_FAILED:
	    System.err.println(formatMessageInternal("slpd_intnet_err",
						     new Object[] {
		ex.getMessage()},
		bundle));
	    break;

	case ServiceLocationException.NETWORK_ERROR:
	    System.err.println(formatMessageInternal("slpd_net_err",
						     new Object[] {
		ex.getMessage()},
		bundle));
	    break;

	default:
	    System.err.println(formatMessageInternal("slpd_err",
						     new Object[] {
		new Integer(ex.getErrorCode()),
		    ex.getMessage()},
		bundle));

	}

	ex.printStackTrace();
	System.err.println(formatMessageInternal("exiting_msg",
						 new Object[0],
						 bundle));

	System.exit(1);

    }

    // Make a new SLPConfig object of the right class type.

    private static SLPConfig initializeSLPConfig() {

	// The server *always* runs as an SA. It may also run as a DA.

	config.isSA = true;

	// set default logging class for slpd to syslog

	if (System.getProperty("sun.net.slp.loggerClass") == null) {
	    Properties props = System.getProperties();
	    props.setProperty("sun.net.slp.loggerClass", "com.sun.slp.Syslog");
	    System.setProperties(props);

	}

	// slpd is the server side config.

	theSLPConfig = new slpd();

	return theSLPConfig;

    }

    //
    // Extensions to sldp for server side only.
    //

    boolean isDA() {
	return Boolean.getBoolean("net.slp.isDA");
    }

    // Determine whether V1 is supported. Default is no.

    boolean isV1Supported() {

	if (!isDA() || super.getSLPv1NotSupported()) {
	    return false;

	}

	boolean v1Supported = false;

	try {

	    Class.forName("com.sun.slp.SLPV1Manager");
	    v1Supported = true;

	} catch (ClassNotFoundException ex) {

	    // Not there.

	}

	return v1Supported;

    }

    // Load server message bundle.

    private static final String serverMsgBundle = "Server";

    ResourceBundle getMessageBundle(Locale locale) {

	// Get the parent bundle first.

	ResourceBundle parentBundle = super.getMessageBundle(locale);

	return getMessageBundleInternal(locale, parentBundle);

    }

    // We need this in case we get an error before the config object is
    //  created.

    static private ResourceBundle getMessageBundleInternal(
						Locale locale,
						ResourceBundle parentBundle) {

	// Now create a server subclass.

	ResourceBundle msgBundle = null;

	try {
	    msgBundle = ServerBundle.getBundle(parentBundle, locale);

	} catch (MissingResourceException ex) {  // can't localize this one!

	    // We can't print out to the log, because we may be in the
	    //  process of trying to.

	    System.out.println("Missing resource bundle ``"+
			       SERVER_BUNDLE_NAME+
			       "'' for locale ``"+
			       locale+
			       "''");
	    // Hosed if the default locale is missing.

	    if (locale.equals(Defaults.locale)) {

		System.out.println("Exiting...");
		System.exit(1);
	    }

	    // Otherwise, return the default locale.

	    System.out.println("Using SLP default locale ``" +
			       Defaults.locale+"''");

	    msgBundle =
		getMessageBundleInternal(Defaults.locale, parentBundle);

	}

	return msgBundle;
    }

}