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