1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License (the "License").
6  * You may not use this file except in compliance with the License.
7  *
8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9  * or http://www.opensolaris.org/os/licensing.
10  * See the License for the specific language governing permissions
11  * and limitations under the License.
12  *
13  * When distributing Covered Code, include this CDDL HEADER in each
14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15  * If applicable, add the following below this CDDL HEADER, with the
16  * fields enclosed by brackets "[]" replaced with your own identifying
17  * information: Portions Copyright [yyyy] [name of copyright owner]
18  *
19  * CDDL HEADER END
20  */
21 /*
22  * Copyright 2004 Sun Microsystems, Inc.  All rights reserved.
23  * Use is subject to license terms.
24  *
25  */
26 
27 //  sldp.java : The service location daemon.
28 //  Author:           Erik Guttman
29 //
30 
31 package com.sun.slp;
32 
33 import java.io.*;
34 import java.util.*;
35 import java.net.*;
36 import java.lang.reflect.*;
37 import java.awt.*;
38 
39 /**
40  * The slpd class is the main class for the slpd directory agent/
41  * service agent server of SLP. Slpd can run in two possible modes,
42  * depending on the configuration of the classes with which it was shipped
43  * and information read from the optional configuration file argument:
44  *
45  *   <ol>
46  *     <li> <b> Service Agent server </b>
47  *          In this mode, slpd functions as a service agent server only.
48  *	    Directory agent functionality is disabled. Service agent
49  *	    clients on the local machine register and deregister services
50  *	    with slpd, and slpd responds to multicast requests for
51  *	    services. It passively and actively listens for directory agent
52  *	    advertisements, caches them, and forwards them to both
53  *	    user agent and service agent clients. A file of serialized
54  *	    proxy registrations can be read at startup.
55  *	    This mode is normally default.
56  *
57  *     <li> <b> Directory Agent/Service Agent server </b>
58  *          In this mode, slpd functions  as a directory agent
59  *	    for port 427 on the local machine. The directory agent
60  *	    caches service advertisements, makes directory agent
61  *	    advertisements of its services, and responds to requests
62  *          for TCP connections with service agents and user agents.
63  *	    In addition, slpd functions in the first mode, as a
64  *	    service agent server, for SAs and UAs on the local host.
65  *
66  *   </ol>
67  *
68  * The slpd is invoked as follows:<br>
69  *<blockquote>
70  *
71  *	java com.sun.slpd [monitor] [stop] [-f <config file name>]
72  *
73  *</blockquote>
74  *
75  * The optional monitor argument specifies that a GUI monitor should be
76  * brought up. The optional stop argument indicates that slpd should
77  * signal a running slpd to stop. The optional <config file name> argument
78  * specifies that the named file should be used as the configuration file.
79  * <p>
80  * See <a href="slpd.conf.html">slpd.conf</a> for more information on
81  * configuration file syntax and <a href="slpd.reg.html">slpd.reg</a>
82  * for more information on proxy registration file syntax.
83  *
84  * @author Erik Guttman, James Kempf
85  */
86 
87 // Note that the inheritance is *only* so slpd can initialize the
88 // internals of SLP config.
89 
90 public class slpd extends SLPConfig {
91 
92     private static final String SERVER_BUNDLE_NAME = "com/sun/slp/Server";
93 
94     // Server bundle. Set the parent.
95 
96     static class ServerBundle extends ResourceBundle {
97 
98 	private ResourceBundle bundle = null;
99 
100 	static ResourceBundle
getBundle(ResourceBundle parent, Locale locale)101 	    getBundle(ResourceBundle parent, Locale locale)
102 	    throws MissingResourceException {
103 
104 	    return new ServerBundle(parent, locale);
105 
106 	}
107 
ServerBundle(ResourceBundle parent, Locale locale)108 	private ServerBundle(ResourceBundle parent, Locale locale)
109 	    throws MissingResourceException {
110 
111 	    if (parent != null) {
112 		this.parent = parent;
113 
114 	    }
115 
116 	    try {
117 		URL[] urls = null;
118 		urls = new URL[] {new URL("file:/usr/share/lib/locale/")};
119 		URLClassLoader ld = new URLClassLoader(urls);
120 
121 		bundle =
122 		    ResourceBundle.getBundle(SERVER_BUNDLE_NAME, locale, ld);
123 
124 	    } catch (MalformedURLException e) {
125 		// fallthru to default location
126 	    }	// No locales in slpd.jar, so propagate the
127 		// MissingResourceException
128 
129 	    bundle = bundle != null ?
130 		bundle :
131 		ResourceBundle.getBundle(SERVER_BUNDLE_NAME, locale);
132 
133 	}
134 
handleGetObject(String key)135 	protected Object handleGetObject(String key)
136 	    throws MissingResourceException {
137 	    Object ret = null;
138 
139 	    try {
140 
141 		ret = bundle.getObject(key);
142 
143 	    } catch (MissingResourceException ex) {
144 		ret = parent.getObject(key);
145 
146 	    }
147 
148 	    return ret;
149 
150 	}
151 
getKeys()152 	public Enumeration getKeys() {
153 	    return bundle.getKeys();
154 
155 	}
156 
157     }
158 
159     /**
160      * Log object for SLP to write to GUI. This class follows the
161      * semantics of the other SLP logging classes:
162      *
163      * This class does not actually write anything until the flush() method
164      * in invoked; this will write the concatenation of all messages
165      * passed to the write() method since the last invocation of flush().
166      *
167      * The actual logging class used can be controlled via the
168      * sun.net.slp.loggerClass property.
169      *
170      * See also the StderrLog and Syslog classes.
171      */
172 
173     static class SLPLog extends Writer {
174 
175 	private TextArea taLog = null;
176 	private StringBuffer buf;
177 
SLPLog(TextArea nta)178 	SLPLog(TextArea nta) {
179 	    taLog = nta;
180 	    buf = new StringBuffer();
181 
182 	}
183 
184 	// Write to the StringBuffer
185 
write(char[] cbuf, int off, int len)186 	public void write(char[] cbuf, int off, int len)
187 	    throws IOException {
188 	    buf.append(cbuf, off, len);
189 
190 	}
191 
192 	// Write to the Frame.
193 
flush()194 	public void flush() throws IOException {
195 	    String date = SLPConfig.getDateString();
196 
197 	    taLog.append(
198 			 "********" +
199 			 date + "\n" +
200 			 buf.toString() + "\n" +
201 			 "********\n");
202 	    buf = new StringBuffer();
203 
204 	}
205 
206 	// These is a no-op
207 
close()208 	public void close() throws IOException {}
209 
210     }
211 
212     //
213     // slpd definition.
214     //
215 
216     private static String  configFile;		// name of configuration file
217     private static SLPDgui slpdgui;		// GUI monitor, if desired
218     private static SLPConfig config;		// handles system properties
219     private static ServerDATable daTable;	// handle recording of DAs
220 
221     // Called by the slpd and subclasses only.
222 
slpd()223     protected slpd() {
224 	super();
225     }
226 
usage()227     private static void usage() {
228 	ResourceBundle bundle =
229 	    getMessageBundleInternal(Locale.getDefault(), null);
230 	System.err.println(formatMessageInternal("slpd_usage",
231 						 new Object[0],
232 						 bundle));
233 	System.exit(1);
234     }
235 
236     /**
237      * Usage: slpd [monitor] [stop] [-f config-file name]<br>
238      * <br>
239      * String arguments are:
240      * <br>
241      * <b> monitor </b> Puts up a rudimentary GUI for the slpd.<br>
242      * <b>stop <b> Bring down a running slpd and exit.
243      * <b> config-file name </b> Reads the specified configuration file.<br>
244      *
245      * The default running mode is to have no GUI and to use SLP
246      * defined configuration.
247      */
248 
main(String args[])249     public static void main(String args[]) {
250 	boolean bMon  = false;
251 	boolean bStop = false;
252 	configFile    = null;
253 
254 	Thread.currentThread().setName("slpd");
255 
256 	// Process args.
257 
258 	if (args.length > 3) {
259 	    usage();
260 
261 	}
262 
263 	int i, n = args.length;
264 
265 	for (i = 0; i < n; i++) {
266 
267 	    // Argument is a config file.
268 
269 	    if (args[i].equals("-f")) {
270 
271 		if (configFile != null) {
272 		    usage();
273 
274 		}
275 
276 		// Make sure we can open it.
277 
278 		try {
279 		    File f = new File(args[++i]);
280 		    configFile = args[i];
281 
282 		} catch (Exception ex) {
283 		    usage();
284 
285 		}
286 	    } else if (args[i].equals("monitor")) {
287 		bMon = true;
288 
289 	    } else if (args[i].equals("stop")) {
290 		bStop = true;
291 
292 	    } else {
293 		usage();
294 
295 	    }
296 	}
297 
298 	// Read message bundle file, load config file into properties.
299 
300 	ResourceBundle bundle =
301 	    getMessageBundleInternal(Locale.getDefault(), null);
302 
303 	try {
304 	    if (configFile != null) {
305 		Properties props = System.getProperties();
306 		props.setProperty("sun.net.slp.configURL",
307 				  "file:" + configFile);
308 
309 	    }
310 
311 	    // Create a new SLP Config object from the config file.
312 	    config = initializeSLPConfig();
313 
314 	    // Create a GUI if the user asked for one.
315 
316 	    if (bMon) {
317 
318 		try {
319 		    slpdgui = new SLPDgui(configFile);
320 		    SLPLog log = new SLPLog(slpdgui.getTALog());
321 
322 		    synchronized (config) {
323 			config.log = log;
324 		    }
325 
326 		    slpdgui.setVisible(true);
327 
328 		} catch (Exception ex) {
329 		    System.err.println(formatMessageInternal("slpd_no_gui",
330 							     new Object[0],
331 							     bundle));
332 		}
333 	    }
334 
335 	    // Either start or stop the server, depending on what was
336 	    //  requested.
337 
338 	    if (!bStop) {
339 		start();
340 
341 	    } else {
342 		stop();
343 
344 	    }
345 	} catch (ServiceLocationException ex) {
346 
347 	    errorExit(bundle, ex);
348 
349 	}
350 
351     }
352 
353     /**
354      * Start the slpd.
355      *
356      * @param bMon	True if initializing with GUI monitor.
357      * @exception ServiceLocationException Internal error or network
358      *			initialization error or
359      *			internal networking error.
360      *
361      */
362 
start()363     static void start() throws ServiceLocationException {
364 
365 	// Initialize the service table.
366 
367 	ServiceTable table = ServiceTable.getServiceTable();
368 
369 	// Initialize the class name for the DA table to the Sun-specific
370 	//  DA table.
371 
372 	Properties props = System.getProperties();
373 	props.put(DATable.DA_TABLE_CLASS_PROP, "com.sun.slp.SunServerDATable");
374 
375 	// If there is a request on stdin, process it now
376 	try {
377 	    if (System.in.available() > 0) {
378 		RequestHandler rh =
379 		    new RequestHandler(System.in, System.out, config);
380 		rh.start();
381 	    }
382 	} catch (IOException e) {}
383 
384 	// Start a StreamListener on loopback to start accepting locals regs
385 
386 	StreamListener.initializeStreamListenerOnInterface(
387 							config.getLoopback());
388 
389 	// Create a ServerDATable from the class. This will initialize
390 	//  active discovery. Note that we need to record our own presence
391 	//  in the DA table because we are not yet listening for requests.
392 	//  We do this after deserialization so that if we discover any
393 	//  DAs, we can perform registrations of the serialized advertisements.
394 
395 	daTable = ServerDATable.getServerDATable();
396 
397 	// Deserialize any serialized advertisements and do them now.
398 	//  Waiting until here allows any error messages to appear in
399 	//  the GUI log, if any, and at this point the DA table is ready.
400 
401 	table.deserializeTable();
402 
403 	// Need to create datagram and stream listeners, and a
404 	//  DAAdvertiser on all network interfaces.
405 
406 	Vector interfaces = config.getInterfaces();
407 	int i, n = interfaces.size();
408 
409 	for (i = 0; i < n; i++) {
410 	    InetAddress interfac = (InetAddress)interfaces.elementAt(i);
411 
412 	    // Initialize the complex of listener/sender objects on the
413 	    // interface. This includes a datagram listener, a DAAdvertiser
414 	    // (which shares the same socket as the datagram listener), and
415 	    // a stream listener.
416 
417 	    Listener.initializeInterfaceManagers(interfac);
418 
419 	}
420 
421 	// If we've been configured as a DA, then create a DA advertiser to
422 	//  periodically advertise our presence on this interface. This
423 	//  is only done on the default interface.
424 
425 	if (config.isDA()) {
426 	    DAAdvertiser.initializeDAAdvertiserOnInterface(
427 							config.getLocalHost());
428 
429 	}
430 
431 	// Report scopes and whether DA or SA.
432 
433 	Vector discoveredScopes = daTable.findScopes();
434 	Vector serverScopes = config.getSAConfiguredScopes();
435 	Vector daAttributes = config.getDAAttributes();
436 	Vector saAttributes = config.getSAAttributes();
437 
438 	// Report that we are running if tracing is on
439 
440 	if (config.regTest() ||
441 	    config.traceMsg() ||
442 	    config.traceDrop() ||
443 	    config.traceDATraffic()) {
444 
445 	    config.writeLog((config.isDA() ? "hello_da":"hello"),
446 			    new Object[] {interfaces,
447 					      serverScopes,
448 					      discoveredScopes,
449 					      (config.isDA() ?
450 					       daAttributes:saAttributes)});
451 	}
452 
453 	// If V1 is supported, crank up V1 support as well.
454 
455 	if (config.isV1Supported()) {
456 	    SLPV1Manager.start(config, daTable, table);
457 
458 	}
459     }
460 
461     // Stop a running server by sending a DAAdvert or SAAdvert.
462 
stop()463     static void stop() throws ServiceLocationException {
464 
465 	if (daemonIsDA()) {
466 	    stopDA();
467 
468 	} else {
469 	    stopSA();
470 
471 	}
472     }
473 
474     // Determine whether the daemon running on this machine is a DA
475     //  or not.
476 
daemonIsDA()477     static boolean daemonIsDA() throws ServiceLocationException {
478 
479 	// Get a DA table with available DAs.
480 
481 	DATable table =
482 	    DATable.getDATable();
483 
484 	// Get DAs.
485 
486 	Hashtable das =
487 	    table.findDAScopes(config.getSAConfiguredScopes());
488 	Vector daRecs = (Vector)das.get(DATable.UNICAST_KEY);
489 	Vector interfaces = config.getInterfaces();
490 
491 	// If no DAs, then simply return.
492 
493 	if (daRecs == null) {
494 	    return false;
495 
496 	}
497 
498 	// Find our address in the list, if it exists.
499 
500 	int i, n = daRecs.size();
501 
502 	for (i = 0; i < n; i++) {
503 	    DATable.DARecord rec =
504 		(DATable.DARecord)daRecs.elementAt(i);
505 	    Vector daAddresses = rec.daAddresses;
506 
507 	    int j, m = interfaces.size();
508 
509 	    for (j = 0; j < m; j++) {
510 		if (daAddresses.contains(interfaces.elementAt(i))) {
511 		    return true;
512 
513 		}
514 	    }
515 	}
516 
517 	return false;
518     }
519 
520     // Stop a DA by multicasting the DAAdvert with boot timestamp 0.
521 
stopDA()522     private static void stopDA() throws ServiceLocationException {
523 
524 	// Make the DA URL and the DAAdvert. Note that we only need signal
525 	//  on the default local host interface because that is the only
526 	//  one on which the server is listening.
527 
528 	ServiceURL url =
529 	    new ServiceURL(Defaults.DA_SERVICE_TYPE +
530 			   "://" +
531 			   config.getLocalHost().getHostAddress(),
532 			   ServiceURL.LIFETIME_DEFAULT);
533 
534 	SDAAdvert advert =
535 	    new SDAAdvert(new SLPServerHeaderV2(),
536 			  (short)0x0,  // sez we're unsolicited...
537 			  0L,    // sez we're going down...
538 			  url,
539 			  config.getSAConfiguredScopes(),
540 			  new Vector()); // no attributes needed to go down...
541 
542 	// Make the DAAdvertiser.
543 
544 	DAAdvertiser daadv = new DAAdvertiser(config.getLocalHost(),
545 					      advert.getHeader());
546 
547 	// Send out unsolicted "going down" message.
548 
549 	daadv.sendAdvert();
550 
551 	// That's it! No need for any messages here.
552 
553 	System.exit(0);
554 
555     }
556 
557     // Stop an SA server by unicasting an SA advert with xid 0.
558 
stopSA()559     private static void stopSA() throws ServiceLocationException {
560 
561 	// We signal for stop on the local host, which is guaranteed
562 	//  to have an SA listener.
563 
564 	ServiceURL url =
565 	    new ServiceURL(Defaults.SA_SERVICE_TYPE + "://" +
566 			   config.getLocalHost().getHostAddress(),
567 			   ServiceURL.LIFETIME_DEFAULT);
568 
569 	SSAAdvert advert = new SSAAdvert(Defaults.version,
570 					 (short)0x0, // sez we're going down...
571 					 config.getLocale(),
572 					 url,
573 					 config.getSAConfiguredScopes(),
574 					 new Vector());
575 						// don't care about attrs..,
576 
577 	// Send it TCP. We ignore NETWORK_ERROR because it only means
578 	//  that the daemon didn't send us a reply, which is expected.
579 
580 	try {
581 
582 	    SrvLocMsg msg =
583 		Transact.transactTCPMsg(config.getLoopback(), advert, false);
584 
585 	    if (msg.getErrorCode() != ServiceLocationException.OK) {
586 		config.writeLog("slpd_sa_stop_failure",
587 				new Object[] {
588 		    new Integer(msg.getErrorCode())});
589 
590 	    }
591 
592 	} catch (ServiceLocationException ex) {
593 
594 	    if (ex.getErrorCode() != ServiceLocationException.NETWORK_ERROR) {
595 		config.writeLog("slpd_sa_stop_failure",
596 				new Object[] {new Integer(ex.getErrorCode())});
597 
598 	    }
599 
600 	}
601 
602 	// That's it!
603 
604 	System.exit(0);
605     }
606 
607     // Print error message, exit.
608 
errorExit(ResourceBundle bundle, ServiceLocationException ex)609     static void errorExit(ResourceBundle bundle, ServiceLocationException ex) {
610 
611 	switch (ex.getErrorCode()) {
612 
613 	case ServiceLocationException.INTERNAL_SYSTEM_ERROR:
614 	    System.err.println(formatMessageInternal("slpd_int_err",
615 						     new Object[] {
616 		ex.getMessage()},
617 		bundle));
618 	    break;
619 
620 	case ServiceLocationException.NETWORK_INIT_FAILED:
621 	    System.err.println(formatMessageInternal("slpd_intnet_err",
622 						     new Object[] {
623 		ex.getMessage()},
624 		bundle));
625 	    break;
626 
627 	case ServiceLocationException.NETWORK_ERROR:
628 	    System.err.println(formatMessageInternal("slpd_net_err",
629 						     new Object[] {
630 		ex.getMessage()},
631 		bundle));
632 	    break;
633 
634 	default:
635 	    System.err.println(formatMessageInternal("slpd_err",
636 						     new Object[] {
637 		new Integer(ex.getErrorCode()),
638 		    ex.getMessage()},
639 		bundle));
640 
641 	}
642 
643 	ex.printStackTrace();
644 	System.err.println(formatMessageInternal("exiting_msg",
645 						 new Object[0],
646 						 bundle));
647 
648 	System.exit(1);
649 
650     }
651 
652     // Make a new SLPConfig object of the right class type.
653 
initializeSLPConfig()654     private static SLPConfig initializeSLPConfig() {
655 
656 	// The server *always* runs as an SA. It may also run as a DA.
657 
658 	config.isSA = true;
659 
660 	// set default logging class for slpd to syslog
661 
662 	if (System.getProperty("sun.net.slp.loggerClass") == null) {
663 	    Properties props = System.getProperties();
664 	    props.setProperty("sun.net.slp.loggerClass", "com.sun.slp.Syslog");
665 	    System.setProperties(props);
666 
667 	}
668 
669 	// slpd is the server side config.
670 
671 	theSLPConfig = new slpd();
672 
673 	return theSLPConfig;
674 
675     }
676 
677     //
678     // Extensions to sldp for server side only.
679     //
680 
isDA()681     boolean isDA() {
682 	return Boolean.getBoolean("net.slp.isDA");
683     }
684 
685     // Determine whether V1 is supported. Default is no.
686 
isV1Supported()687     boolean isV1Supported() {
688 
689 	if (!isDA() || super.getSLPv1NotSupported()) {
690 	    return false;
691 
692 	}
693 
694 	boolean v1Supported = false;
695 
696 	try {
697 
698 	    Class.forName("com.sun.slp.SLPV1Manager");
699 	    v1Supported = true;
700 
701 	} catch (ClassNotFoundException ex) {
702 
703 	    // Not there.
704 
705 	}
706 
707 	return v1Supported;
708 
709     }
710 
711     // Load server message bundle.
712 
713     private static final String serverMsgBundle = "Server";
714 
getMessageBundle(Locale locale)715     ResourceBundle getMessageBundle(Locale locale) {
716 
717 	// Get the parent bundle first.
718 
719 	ResourceBundle parentBundle = super.getMessageBundle(locale);
720 
721 	return getMessageBundleInternal(locale, parentBundle);
722 
723     }
724 
725     // We need this in case we get an error before the config object is
726     //  created.
727 
getMessageBundleInternal( Locale locale, ResourceBundle parentBundle)728     static private ResourceBundle getMessageBundleInternal(
729 						Locale locale,
730 						ResourceBundle parentBundle) {
731 
732 	// Now create a server subclass.
733 
734 	ResourceBundle msgBundle = null;
735 
736 	try {
737 	    msgBundle = ServerBundle.getBundle(parentBundle, locale);
738 
739 	} catch (MissingResourceException ex) {  // can't localize this one!
740 
741 	    // We can't print out to the log, because we may be in the
742 	    //  process of trying to.
743 
744 	    System.out.println("Missing resource bundle ``"+
745 			       SERVER_BUNDLE_NAME+
746 			       "'' for locale ``"+
747 			       locale+
748 			       "''");
749 	    // Hosed if the default locale is missing.
750 
751 	    if (locale.equals(Defaults.locale)) {
752 
753 		System.out.println("Exiting...");
754 		System.exit(1);
755 	    }
756 
757 	    // Otherwise, return the default locale.
758 
759 	    System.out.println("Using SLP default locale ``" +
760 			       Defaults.locale+"''");
761 
762 	    msgBundle =
763 		getMessageBundleInternal(Defaults.locale, parentBundle);
764 
765 	}
766 
767 	return msgBundle;
768     }
769 
770 }
771