xref: /illumos-gate/usr/src/lib/libslp/javalib/com/sun/slp/ServiceTable.java (revision fd6d41c5025e9fb45a115fc82d86e9983d1e9fd6)
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 (c) 1999 by Sun Microsystems, Inc.
23  * All rights reserved.
24  *
25  */
26 
27 //  ServiceTable.java: Storage of all services.
28 //  Author:           James Kempf
29 //  Created On:       Fri Oct 10 14:23:25 1997
30 //  Last Modified By: James Kempf
31 //  Last Modified On: Thu Apr  1 10:33:46 1999
32 //  Update Count:     461
33 //
34 
35 package com.sun.slp;
36 
37 import java.util.*;
38 import java.io.*;
39 import java.security.*;
40 import java.net.*;
41 
42 /**
43  * The ServiceTable object records all service registrations. Note
44  * that any exceptions internal to the service table are processed
45  * and either returned as SrvRply objects or are reported.
46  *
47  * @author James Kempf
48  */
49 
50 class ServiceTable extends Object {
51 
52     // Key for SDAAdvert class.
53 
54     static final String SDAADVERT = "com.sun.slp.SDAAdvert";
55 
56     private static final String locationMsg = "Service table";
57 
58     //
59     // Instance variables.
60     //
61 
62     // The service store.
63 
64     protected ServiceStore store = null;
65 
66     //
67     // Class variables.
68 
69     // System properties.
70 
71     static protected SLPConfig conf = null;
72 
73     // Singleton objects for the service tables.
74 
75     static protected ServiceTable table = null;
76 
77     // The ager thread.
78 
79     static protected AgerThread thrAger = null;
80 
81     // Time to sleep. Adjusted depending on incoming URLs.
82 
83     private static long sleepyTime = Defaults.lMaxSleepTime;
84 
85     //
86     // Creation of singleton.
87     //
88 
89     // Protected constructor.
90 
91     protected ServiceTable() {
92 
93 	if (thrAger != null) {
94 	    return;
95 
96 	}
97 
98 	// Create the ager thread.
99 
100 	thrAger = new AgerThread();
101 
102 	// Set the priority low, so other things (like active discovery)
103 	//  take priority.
104 
105 	thrAger.setPriority(Thread.MIN_PRIORITY);
106 
107 	thrAger.start();
108 
109     }
110 
111     /**
112      * Return an SA service store.
113      *
114      * @return The distinguished table object.
115      */
116 
117     static ServiceTable getServiceTable()
118 	throws ServiceLocationException {
119 
120 	if (conf == null) {
121 	    conf = SLPConfig.getSLPConfig();
122 
123 	}
124 
125 	if (table == null) {
126 
127 	    table = createServiceTable();
128 
129 	}
130 
131 	return table;
132     }
133 
134     /**
135      * Return a service table object.
136      *
137      * @return The service table object.
138      */
139 
140     private static ServiceTable createServiceTable()
141 	throws ServiceLocationException {
142 
143 	ServiceTable table = new ServiceTable();
144 
145 	table.store = ServiceStoreFactory.createServiceStore();
146 
147 	return table;
148     }
149 
150     //
151     // Support for serializated registrations.
152     //
153 
154     /**
155      * If any serialized registrations are pending, then unserialize
156      * and register.
157      */
158 
159     public void deserializeTable() {
160 
161 	// If there are any serialized registrations, then get
162 	//  them and perform registrations.
163 
164 	String serializedURL = conf.getSerializedRegURL();
165 
166 	if (serializedURL != null) {
167 
168 	    ServiceStore serStore = getStoreFromURL(serializedURL);
169 
170 	    if (serStore != null) {
171 		registerStore(serStore);
172 	    }
173 	}
174     }
175 
176     /**
177      * Serialize the table to the URL.
178      *
179      * @param URL String giving the URL to which the store should be
180      * serialized.
181      */
182 
183     void serializeServiceStore(String URL) {
184 
185 	// Open an object output stream for the URL, serialize through
186 	//  the factory.
187 
188 	try {
189 
190 	    URL url = new URL(URL);
191 	    URLConnection urlConn = url.openConnection();
192 	    OutputStream os = urlConn.getOutputStream();
193 	    BufferedWriter di =
194 		new BufferedWriter(new OutputStreamWriter(os));
195 
196 	    // Serialize the store.
197 
198 	    ServiceStoreFactory.serialize(di, store);
199 
200 	} catch (MalformedURLException ex) {
201 
202 	    conf.writeLog("st_serialized_malform",
203 			  new Object[] {URL});
204 
205 	} catch (UnsupportedEncodingException ex) {
206 
207 	    conf.writeLog("st_unsupported_encoding",
208 			  new Object[] {URL});
209 
210 	} catch (IOException ex) {
211 
212 	    conf.writeLog("st_serialized_ioexception",
213 			  new Object[] {URL, ex});
214 
215 	} catch (ServiceLocationException ex) {
216 
217 	    conf.writeLog("st_serialized_sle",
218 			  new Object[] {URL, ex.getMessage()});
219 
220 	}
221 
222     }
223 
224     // Read proxy registrations from the URL.
225 
226     private ServiceStore getStoreFromURL(String serializedURL) {
227 
228 	ServiceStore serStore = null;
229 
230 	// Open an object input stream for the URL, deserialize through
231 	//  the factory.
232 
233 	try {
234 
235 	    URL url = new URL(serializedURL);
236 	    InputStream is = url.openStream();
237 	    BufferedReader di = new BufferedReader(new InputStreamReader(is));
238 
239 	    // Deserialize the objects.
240 
241 	    serStore =
242 		ServiceStoreFactory.deserializeServiceStore(di);
243 
244 	} catch (MalformedURLException ex) {
245 
246 	    conf.writeLog("st_serialized_malform",
247 			  new Object[] {serializedURL});
248 
249 	} catch (UnsupportedEncodingException ex) {
250 
251 	    conf.writeLog("st_unsupported_encoding",
252 			  new Object[] {serializedURL});
253 
254 	} catch (IOException ex) {
255 
256 	    conf.writeLog("st_serialized_ioexception",
257 			  new Object[] {
258 		serializedURL,
259 		    ex.getMessage()});
260 
261 	} catch (ServiceLocationException ex) {
262 
263 	    conf.writeLog("st_serialized_sle",
264 			  new Object[] {
265 		serializedURL,
266 		    ex.getMessage()});
267 
268 	}
269 
270 	return serStore;
271     }
272 
273     // Walk the table, performing actual registrations on all records.
274 
275     private void registerStore(ServiceStore serStore) {
276 
277 	// Walk the table.
278 
279 	Enumeration en = serStore.getServiceRecordsByScope(null);
280 	boolean hasURLSig = conf.getHasSecurity();
281 	boolean hasAttrSig = conf.getHasSecurity();
282 	PermSARegTable pregTable = 	SARequester.getPermSARegTable();
283 
284 	while (en.hasMoreElements()) {
285 	    ServiceStore.ServiceRecord rec =
286 		(ServiceStore.ServiceRecord)en.nextElement();
287 	    ServiceURL surl = rec.getServiceURL();
288 	    Vector scopes = rec.getScopes();
289 	    Vector attrs = rec.getAttrList();
290 	    Locale locale = rec.getLocale();
291 	    Hashtable urlSig = null;
292 	    Hashtable attrSig = null;
293 
294 	    // Note that we can't use the Advertiser to register here,
295 	    //  because we may not be listening yet for registrations.
296 	    //  We need to do this all by hand.
297 
298 	    try {
299 
300 		// Create a registration message for refreshing.
301 
302 		CSrvReg creg = new CSrvReg(false,
303 					   locale,
304 					   surl,
305 					   scopes,
306 					   attrs,
307 					   null,
308 					   null);
309 
310 		// We externalize to a server side message if authentication
311 		//  is needed. This creates the auth blocks for the scopes.
312 		//  Doing this in any other way is alot more complicated,
313 		//  although doing it this way seems kludgy.
314 
315 		if (hasURLSig || hasAttrSig) {
316 		    ByteArrayOutputStream baos = new ByteArrayOutputStream();
317 
318 		    creg.getHeader().externalize(baos, false, true);
319 
320 		    ByteArrayInputStream bais =
321 			new ByteArrayInputStream(baos.toByteArray());
322 		    bais.read();	// pop off version and function code...
323 		    bais.read();
324 		    DataInputStream dis = new DataInputStream(bais);
325 		    SLPHeaderV2 hdr = new SLPHeaderV2();
326 		    hdr.parseHeader(SrvLocHeader.SrvReg, dis);
327 		    SSrvReg sreg = new SSrvReg(hdr, dis);
328 
329 		    // Now we've got it, after much effort. Get the auths.
330 
331 		    urlSig = sreg.URLSignature;
332 		    attrSig = sreg.attrSignature;
333 
334 		}
335 
336 		store.register(surl, attrs, scopes, locale, urlSig, attrSig);
337 
338 		// Now we've got to put the registration into the
339 		//  PermSARegTable. Again, we do everything by hand
340 		//  because we can't use Advertiser.
341 
342 		if (surl.getIsPermanent()) {
343 		    pregTable.reg(surl, creg);
344 
345 		}
346 
347 		// Report registration.
348 
349 		if (conf.regTest()) {
350 		    conf.writeLog("st_reg_add",
351 				  new Object[] {
352 			locationMsg,
353 			    locale,
354 			    surl.getServiceType(),
355 			    surl,
356 			    attrs,
357 			    scopes});
358 
359 		}
360 	    } catch (ServiceLocationException ex) {
361 
362 		String msg = ex.getMessage();
363 
364 		conf.writeLog("st_serialized_seex",
365 			      new Object[] {
366 		    new Integer(ex.getErrorCode()),
367 			surl,
368 			(msg == null ? "<no message>":msg)});
369 
370 	    } catch (Exception ex) {
371 
372 		String msg = ex.getMessage();
373 
374 		conf.writeLog("st_serialized_seex",
375 			      new Object[] {
376 		    surl,
377 			(msg == null ? "<no message>":msg)});
378 	    }
379 	}
380     }
381 
382     //
383     // Record aging.
384     //
385 
386     //
387     // Run the thread that ages out records.
388     //
389 
390     private class AgerThread extends Thread {
391 
392 	public void run() {
393 
394 	    setName("SLP Service Table Age-out");
395 	    long alarmTime = sleepyTime;  // when to wake up next
396 	    long wentToSleep = 0;	    // what time we went to bed
397 
398 	    while (true) {
399 
400 		try {
401 
402 		    // Record when we went to sleep.
403 
404 		    wentToSleep = System.currentTimeMillis();
405 
406 		    // Sleep for the minimum amount of time needed before we
407 		    //  must wake up and check.
408 
409 		    sleep(alarmTime);
410 
411 		} catch (InterruptedException ie) {
412 
413 		    // A new registration came in. Calculate how much time
414 		    //  remains until we would have woken up. If this is
415 		    //  less than the new sleepyTime, then we set the alarm
416 		    //  for this time. If it is more, then we set the alarm
417 		    //  for the new sleepyTime.
418 
419 		    long remainingSleepTime =
420 			(wentToSleep + alarmTime) - System.currentTimeMillis();
421 
422 		    remainingSleepTime =		// just in case...
423 			((remainingSleepTime <= 0) ? 0 : remainingSleepTime);
424 
425 		    alarmTime = sleepyTime;
426 
427 		    if (remainingSleepTime < alarmTime) {
428 			alarmTime = remainingSleepTime;
429 
430 		    }
431 
432 		    continue;  // we don't have to walk yet...
433 
434 		}
435 
436 		// Walk the table, get the new alarm and sleepy times.
437 
438 		if (table != null) {
439 		    table.ageStore();
440 
441 		    alarmTime = sleepyTime;
442 
443 		}
444 	    }
445 	}
446 
447     }
448 
449     /**
450      * Age the service store.
451      */
452 
453     // this method cannot be private... due to compiler weakness
454     void ageStore() {
455 
456 	try {
457 
458 	    // We synchronize in case somebody registers and tries to
459 	    //  change sleepy time.
460 
461 	    synchronized (store) {
462 		Vector deleted = new Vector();
463 
464 		sleepyTime = store.ageOut(deleted);
465 
466 		// Track unregistered services.
467 
468 		int i, n = deleted.size();
469 
470 		for (i = 0; i < n; i++) {
471 		    ServiceStore.ServiceRecord rec =
472 			(ServiceStore.ServiceRecord)deleted.elementAt(i);
473 		    ServiceURL surl = rec.getServiceURL();
474 
475 		    trackRegisteredServiceTypes(); // it's deleted...
476 
477 		}
478 
479 	    }
480 
481 	} catch (RuntimeException ex) {
482 
483 	    reportNonfatalException(ex, new Vector(), store);
484 
485 	} catch (ServiceLocationException ex) {
486 
487 	    reportNonfatalException(ex, new Vector(), store);
488 
489 	}
490 
491     }
492 
493     //
494     // SLP Service Table operations (register, deregister, etc.)
495     //
496 
497     /**
498      * Process the registration and record if no errors found.
499      *
500      * @param req Service registration request message.
501      * @return SrvLocMsg A service registration acknowledgement.
502      */
503 
504     SrvLocMsg register(SSrvReg req) {
505 
506 	SrvLocHeader hdr = req.getHeader();
507 	Locale locale = hdr.locale;
508 	boolean fresh = hdr.fresh;
509 	Vector scopes = hdr.scopes;
510 	ServiceURL surl = req.URL;
511 	String serviceType = req.serviceType;
512 	Vector attrList = req.attrList;
513 	Hashtable urlSig = req.URLSignature;
514 	Hashtable attrSig = req.attrSignature;
515 	short errorCode =
516 	    (fresh ? ServiceLocationException.INVALID_REGISTRATION :
517 	    ServiceLocationException.INVALID_UPDATE);
518 
519 	try {
520 
521 	    // If a sig block came in, verify it.
522 
523 	    if (urlSig != null) {
524 
525 		AuthBlock.verifyAll(urlSig);
526 	    }
527 
528 	    if (attrSig != null) {
529 
530 		AuthBlock.verifyAll(attrSig);
531 
532 	    }
533 
534 	    // Check whether the URL has a zero lifetime. If so, it
535 	    // isn't cached.
536 
537 	    if (surl.getLifetime() <= 0) {
538 		throw
539 		    new ServiceLocationException(errorCode,
540 						 "st_zero",
541 						 new Object[0]);
542 
543 	    }
544 
545 	    // Check if the service type is restricted. If so, nobody outside
546 	    //  this process is allowed to register it.
547 
548 	    checkForRestrictedType(surl.getServiceType());
549 
550 	    // Check that attribute signature bit on implies URL signature
551 	    //  bit on.
552 
553 	    if (attrSig != null && urlSig == null) {
554 		throw
555 		    new ServiceLocationException(errorCode,
556 						 "st_attr_sig",
557 						 new Object[0]);
558 
559 	    }
560 
561 	    // If a signature and the fresh bit was not set, error since signed
562 	    //  registrations don't allow updating.
563 
564 	    if (urlSig != null && !fresh) {
565 		throw
566 		    new ServiceLocationException(
567 				ServiceLocationException.INVALID_UPDATE,
568 				"st_prot_update",
569 				new Object[0]);
570 	    }
571 
572 	    // Check if scopes are supported.
573 
574 	    if (!areSupportedScopes(scopes)) {
575 		throw
576 		    new ServiceLocationException(
577 				ServiceLocationException.SCOPE_NOT_SUPPORTED,
578 				"st_scope_unsup",
579 				new Object[0]);
580 	    }
581 
582 	    // Check if the reg is signed and auth is off or vice versa.
583 	    //  Check is really simple. If security is on, then all regs
584 	    //  to this DA/SA server must be signed, so toss out any regs
585 	    //  that aren't, and vice versa.
586 
587 	    if (conf.getHasSecurity() && (urlSig == null || attrSig == null)) {
588 		throw
589 		    new ServiceLocationException(
590 				ServiceLocationException.AUTHENTICATION_FAILED,
591 				"st_unprot_non_reg",
592 				new Object[0]);
593 
594 	    } else if (!conf.getHasSecurity() &&
595 		       (urlSig != null || attrSig != null)) {
596 		throw
597 		    new ServiceLocationException(
598 				ServiceLocationException.INVALID_REGISTRATION,
599 				"st_prot_non_reg",
600 				new Object[0]);
601 
602 	    }
603 
604 	    // Merge any duplicates.
605 
606 	    Vector attrs = new Vector();
607 	    Hashtable attrHash = new Hashtable();
608 	    int i, n = attrList.size();
609 
610 	    for (i = 0; i < n; i++) {
611 		ServiceLocationAttribute attr =
612 		    (ServiceLocationAttribute)attrList.elementAt(i);
613 
614 		ServiceLocationAttribute.mergeDuplicateAttributes(
615 								  attr,
616 								  attrHash,
617 								  attrs,
618 								  false);
619 	    }
620 
621 	    // Store register or update.
622 
623 	    boolean existing = false;
624 
625 	    if (fresh) {
626 		existing = store.register(surl,
627 					  attrs,
628 					  scopes,
629 					  locale,
630 					  urlSig,
631 					  attrSig);
632 
633 		// Track registred service types in case we get a
634 		// SAAdvert solicatation.
635 
636 		trackRegisteredServiceTypes();
637 
638 	    } else {
639 		store.updateRegistration(surl, attrs, scopes, locale);
640 
641 	    }
642 
643 	    // Create the reply.
644 
645 	    SrvLocMsg ack = req.makeReply(existing);
646 
647 	    if (conf.regTest()) {
648 		conf.writeLog((fresh ? "st_reg_add":"st_reg_update"),
649 			      new Object[] {
650 		    locationMsg,
651 			locale,
652 			serviceType,
653 			surl,
654 			attrs,
655 			scopes});
656 
657 	    }
658 
659 	    if (conf.traceAll()) {
660 		conf.writeLog("st_dump", new Object[] {locationMsg});
661 		store.dumpServiceStore();
662 
663 	    }
664 
665 	    // Calculate time increment until next update. This is used
666 	    //  to adjust the sleep interval in the ager thread.
667 
668 	    long sTime = getSleepIncrement(surl);
669 
670 	    // We synchronize in case the ager thread is in the middle
671 	    //  of trying to set the time.
672 
673 	    synchronized (store) {
674 
675 		// If we need to wake up sooner, adjust the sleep time.
676 
677 		if (sTime < sleepyTime) {
678 
679 		    sleepyTime = sTime;
680 
681 		    // Interrupt the thread so we go back to
682 		    //  sleep for the right amount of time.
683 
684 		    thrAger.interrupt();
685 		}
686 	    }
687 
688 	    return ack;
689 
690 	} catch (ServiceLocationException ex) {
691 
692 	    if (conf.traceDrop()) {
693 		conf.writeLog("st_reg_drop",
694 			      new Object[] {
695 		    locationMsg,
696 			ex.getMessage()+"("+ex.getErrorCode()+")",
697 			locale,
698 			serviceType,
699 			surl,
700 			attrList,
701 			scopes});
702 	    }
703 
704 	    return hdr.makeErrorReply(ex);
705 
706 	} catch (RuntimeException ex) {
707 
708 	    // These exceptions are not declared in throws but can occur
709 	    //  anywhere.
710 
711 	    Vector args = new Vector();
712 
713 	    args.addElement(req);
714 
715 	    reportNonfatalException(ex, args, store);
716 
717 	    return hdr.makeErrorReply(ex);
718 
719 	}
720     }
721 
722     /**
723      * Process the deregistration and return the result in a reply.
724      *
725      * @param req Service deregistration request message.
726      * @return SrvLocMsg A service registration acknowledgement.
727      */
728 
729     SrvLocMsg deregister(SSrvDereg req) {
730 
731 	// We need to determine whether this is an attribute deregistration
732 	//  or a deregistration of the entire URL.
733 
734 	SrvLocHeader hdr = req.getHeader();
735 	Locale locale = hdr.locale;
736 	Vector scopes = hdr.scopes;
737 	ServiceURL surl = req.URL;
738 	Hashtable urlSig = req.URLSignature;
739 	Vector tags = req.tags;
740 	short errorCode = ServiceLocationException.OK;
741 
742 	try {
743 
744 	    // Verify if signature is nonnull.
745 
746 	    if (urlSig != null) {
747 		AuthBlock.verifyAll(urlSig);
748 
749 	    }
750 
751 	    // Check if the service type is restricted. If so, nobody outside
752 	    //  this process is allowed to register it.
753 
754 	    checkForRestrictedType(surl.getServiceType());
755 
756 	    // Error if there's a signature and attempt at deleting attributes.
757 
758 	    if ((urlSig != null) && (tags != null)) {
759 		throw
760 		    new ServiceLocationException(
761 				ServiceLocationException.AUTHENTICATION_FAILED,
762 				"st_prot_attr_dereg",
763 				new Object[0]);
764 	    }
765 
766 	    // Check if scope is protected and auth is off or vice versa.
767 	    //  Check is really simple. If security is on, then all scopes
768 	    //  in this DA/SA server are protected, so toss out any regs
769 	    //  that aren't, and vice versa.
770 
771 	    if (conf.getHasSecurity() && urlSig == null) {
772 		throw
773 		    new ServiceLocationException(
774 				ServiceLocationException.AUTHENTICATION_FAILED,
775 				"st_unprot_non_dereg",
776 				new Object[0]);
777 
778 	    } else if (!conf.getHasSecurity() && urlSig != null) {
779 		throw
780 		    new ServiceLocationException(
781 				ServiceLocationException.INVALID_REGISTRATION,
782 				"st_prot_non_dereg",
783 				new Object[0]);
784 
785 	    }
786 
787 	    // If it's a service URL, then deregister the URL.
788 
789 	    if (tags == null) {
790 		store.deregister(surl, scopes, urlSig);
791 
792 		// Track registred service types in case we get a
793 		// SAAdvert solicatation.
794 
795 		trackRegisteredServiceTypes();
796 
797 	    } else {
798 
799 		// Just delete the attributes.
800 
801 		store.deleteAttributes(surl, scopes, tags, locale);
802 
803 	    }
804 
805 	    // Create the reply.
806 
807 	    SrvLocMsg ack = req.makeReply();
808 
809 	    if (conf.regTest()) {
810 		conf.writeLog((tags == null ? "st_dereg":"st_delattr"),
811 			      new Object[] {
812 		    locationMsg,
813 	                locale,
814 			surl.getServiceType(),
815 			surl,
816 			tags});
817 
818 	    }
819 
820 	    if (conf.traceAll()) {
821 		conf.writeLog("st_dump",
822 			      new Object[] {locationMsg});
823 		store.dumpServiceStore();
824 
825 	    }
826 
827 	    return ack;
828 
829 	} catch (ServiceLocationException ex) {
830 
831 	    if (conf.traceDrop()) {
832 		conf.writeLog((tags == null ?
833 			       "st_dereg_drop" : "st_dereg_attr_drop"),
834 			      new Object[] {
835 		    locationMsg,
836 			ex.getMessage()+"("+ex.getErrorCode()+")",
837 			locale,
838 			surl.getServiceType(),
839 			surl,
840 			tags});
841 	    }
842 
843 	    return hdr.makeErrorReply(ex);
844 
845 	} catch (RuntimeException ex) {
846 
847 	    // These exceptions are not declared in throws but can occur
848 	    //  anywhere.
849 
850 	    Vector args = new Vector();
851 
852 	    args.addElement(req);
853 
854 	    reportNonfatalException(ex, args, store);
855 
856 	    return hdr.makeErrorReply(ex);
857 
858 	}
859     }
860 
861     /**
862      * Process the service type request and return the result in a reply.
863      *
864      * @param req Service type request message.
865      * @return SrvTypeRply A service type reply.
866      */
867 
868     SrvLocMsg findServiceTypes(SSrvTypeMsg req) {
869 
870 	SrvLocHeader hdr = req.getHeader();
871 	Vector scopes = hdr.scopes;
872 	String namingAuthority = req.namingAuthority;
873 	short errorCode = ServiceLocationException.OK;
874 
875 	try {
876 
877 	    // Check whether the scope is supported.
878 
879 	    if (!areSupportedScopes(scopes)) {
880 		throw
881 		    new ServiceLocationException(
882 				ServiceLocationException.SCOPE_NOT_SUPPORTED,
883 				"st_scope_unsup",
884 				new Object[0]);
885 
886 	    }
887 
888 	    // Get the vector of service types in the store, independent
889 	    //  of language.
890 
891 	    Vector types = store.findServiceTypes(namingAuthority, scopes);
892 
893 	    // Create the reply.
894 
895 	    SrvLocMsg ack = req.makeReply(types);
896 
897 	    if (conf.traceAll()) {
898 		conf.writeLog("st_stypes",
899 			      new Object[] {
900 		    locationMsg,
901 			namingAuthority,
902 			scopes,
903 			types});
904 	    }
905 
906 	    return ack;
907 
908 	} catch (ServiceLocationException ex) {
909 
910 	    if (conf.traceDrop()) {
911 		conf.writeLog("st_stypes_drop",
912 			      new Object[] {
913 		    locationMsg,
914 			ex.getMessage()+"("+ex.getErrorCode()+")",
915 			namingAuthority,
916 			scopes,
917 			hdr.locale});
918 	    }
919 
920 	    return hdr.makeErrorReply(ex);
921 
922 	} catch (RuntimeException ex) {
923 
924 	    // These exceptions are not declared in throws but can occur
925 	    //  anywhere.
926 
927 	    Vector args = new Vector();
928 
929 	    args.addElement(req);
930 
931 	    reportNonfatalException(ex, args, store);
932 
933 	    return hdr.makeErrorReply(ex);
934 
935 	}
936     }
937 
938     /**
939      * Process the service request and return the result in a reply.
940      *
941      * @param req Service request message.
942      * @return SrvRply A service reply.
943      */
944 
945     SrvLocMsg findServices(SSrvMsg req) {
946 
947 	SrvLocHeader hdr = req.getHeader();
948 	Locale locale = hdr.locale;
949 	Vector scopes = hdr.scopes;
950 	String serviceType = req.serviceType;
951 	String query = req.query;
952 	short errorCode = ServiceLocationException.OK;
953 
954 	try {
955 
956 	    // Check whether the scope is supported.
957 
958 	    if (!areSupportedScopes(scopes)) {
959 		throw
960 		    new ServiceLocationException(
961 				ServiceLocationException.SCOPE_NOT_SUPPORTED,
962 				"st_scope_unsup",
963 				new Object[0]);
964 	    }
965 
966 	    // Get the hashtable of returns.
967 
968 	    Hashtable returns =
969 		store.findServices(serviceType,
970 				   scopes,
971 				   query,
972 				   locale);
973 
974 	    // Get the hashtable of services v.s. scopes, and signatures, if
975 	    //  any.
976 
977 	    Hashtable services =
978 		(Hashtable)returns.get(ServiceStore.FS_SERVICES);
979 	    Hashtable signatures =
980 		(Hashtable)returns.get(ServiceStore.FS_SIGTABLE);
981 	    boolean hasSignatures = (signatures != null);
982 
983 	    // for each candidate URL, make sure it has the requested SPI
984 	    // (if any)
985 	    if (hasSignatures && !req.spi.equals("")) {
986 		Enumeration allSurls = services.keys();
987 		while (allSurls.hasMoreElements()) {
988 		    Object aSurl = allSurls.nextElement();
989 		    Hashtable auths = (Hashtable) signatures.get(aSurl);
990 		    AuthBlock auth =
991 			AuthBlock.getEquivalentAuth(req.spi, auths);
992 		    if (auth == null) {
993 			// doesn't have the requested SPI
994 			services.remove(aSurl);
995 		    }
996 		}
997 	    }
998 
999 	    // Create return message.
1000 
1001 	    SrvLocMsg ack = req.makeReply(services, signatures);
1002 
1003 	    if (conf.traceAll()) {
1004 		conf.writeLog("st_sreq",
1005 			      new Object[] {
1006 		    locationMsg,
1007 			serviceType,
1008 			scopes,
1009 			query,
1010 			locale,
1011 			services,
1012 			signatures});
1013 	    }
1014 
1015 	    return ack;
1016 
1017 	} catch (ServiceLocationException ex) {
1018 
1019 	    if (conf.traceDrop()) {
1020 		conf.writeLog("st_sreq_drop",
1021 			      new Object[] {
1022 		    locationMsg,
1023 			ex.getMessage()+"("+ex.getErrorCode()+")",
1024 			serviceType,
1025 			scopes,
1026 			query,
1027 			locale});
1028 	    }
1029 
1030 	    return hdr.makeErrorReply(ex);
1031 
1032 	} catch (RuntimeException ex) {
1033 
1034 	    // These exceptions are not declared in throws but can occur
1035 	    //  anywhere.
1036 
1037 	    Vector args = new Vector();
1038 
1039 	    args.addElement(req);
1040 
1041 	    reportNonfatalException(ex, args, store);
1042 
1043 	    return hdr.makeErrorReply(ex);
1044 
1045 	}
1046     }
1047 
1048     /**
1049      * Process the attribute request and return the result in a reply.
1050      *
1051      * @param req Attribute request message.
1052      * @return AttrRply An attribute reply.
1053      */
1054 
1055     SrvLocMsg findAttributes(SAttrMsg req) {
1056 
1057 	// We need to determine whether this is a request for attributes
1058 	//  on a specific URL or for an entire service type.
1059 
1060 	SrvLocHeader hdr = req.getHeader();
1061 	Vector scopes = hdr.scopes;
1062 	Locale locale = hdr.locale;
1063 	ServiceURL surl = req.URL;
1064 	String serviceType = req.serviceType;
1065 	Vector tags = req.tags;
1066 	short errorCode = ServiceLocationException.OK;
1067 
1068 	try {
1069 
1070 	    // Check whether the scope is supported.
1071 
1072 	    if (!areSupportedScopes(scopes)) {
1073 	throw
1074 	    new ServiceLocationException(
1075 				ServiceLocationException.SCOPE_NOT_SUPPORTED,
1076 				"st_scope_unsup",
1077 				new Object[0]);
1078 	    }
1079 
1080 	    Vector attributes = null;
1081 	    Hashtable sig = null;
1082 
1083 	    // If it's a service URL, then get the attributes just for
1084 	    // that URL.
1085 
1086 	    if (serviceType == null) {
1087 
1088 		// If the attrs are signed, then error if any tags, since
1089 		//  we must ask for *all* attributes in for a signed reg
1090 
1091 		if (!req.spi.equals("") && tags.size() > 0) {
1092 		    throw
1093 			new ServiceLocationException(
1094 				ServiceLocationException.AUTHENTICATION_FAILED,
1095 				"st_par_attr",
1096 				new Object[0]);
1097 
1098 		}
1099 
1100 		Hashtable ht =
1101 		    store.findAttributes(surl, scopes, tags, locale);
1102 
1103 		// Get the attributes and signatures.
1104 
1105 		attributes = (Vector)ht.get(ServiceStore.FA_ATTRIBUTES);
1106 
1107 		sig = (Hashtable)ht.get(ServiceStore.FA_SIG);
1108 
1109 		// make sure the attr has the requested SPI (if any)
1110 		if (sig != null && !req.spi.equals("")) {
1111 		    AuthBlock auth = AuthBlock.getEquivalentAuth(req.spi, sig);
1112 		    if (auth == null) {
1113 			// return empty
1114 			attributes = new Vector();
1115 		    }
1116 		}
1117 
1118 	    } else {
1119 
1120 		if (!req.spi.equals("")) {
1121 		    throw
1122 			new ServiceLocationException(
1123 				ServiceLocationException.AUTHENTICATION_FAILED,
1124 				"st_par_attr",
1125 				new Object[0]);
1126 		}
1127 
1128 		// Otherwise find the attributes for all service types.
1129 
1130 		attributes =
1131 		    store.findAttributes(serviceType, scopes, tags, locale);
1132 
1133 	    }
1134 
1135 	    ServiceType type =
1136 		(serviceType == null ? surl.getServiceType():
1137 		 new ServiceType(serviceType));
1138 
1139 
1140 	    // Create the reply.
1141 
1142 	    SrvLocMsg ack = req.makeReply(attributes, sig);
1143 
1144 	    if (conf.traceAll()) {
1145 		conf.writeLog((serviceType != null ?
1146 			       "st_st_attr" : "st_url_attr"),
1147 			      new Object[] {
1148 		    locationMsg,
1149 			(serviceType != null ? serviceType.toString() :
1150 			 surl.toString()),
1151 		      	scopes,
1152 			tags,
1153 			locale,
1154 			attributes});
1155 	    }
1156 
1157 	    return ack;
1158 
1159 	} catch (ServiceLocationException ex) {
1160 
1161 	    if (conf.traceDrop()) {
1162 		conf.writeLog((serviceType != null ? "st_st_attr_drop":
1163 			       "st_url_attr_drop"),
1164 			      new Object[] {
1165 		    locationMsg,
1166 			ex.getMessage()+"("+ex.getErrorCode()+")",
1167 		        (serviceType != null ? serviceType.toString() :
1168 			 surl.toString()),
1169 		        scopes,
1170 			tags,
1171 			locale});
1172 	    }
1173 
1174 	    return hdr.makeErrorReply(ex);
1175 
1176 	} catch (RuntimeException ex) {
1177 
1178 	    // These exceptions are not declared in throws but can occur
1179 	    //  anywhere.
1180 
1181 	    Vector args = new Vector();
1182 
1183 	    args.addElement(req);
1184 
1185 	    reportNonfatalException(ex, args, store);
1186 
1187 	    return hdr.makeErrorReply(ex);
1188 
1189 	}
1190     }
1191 
1192     // Return the service record corresponding to the URL.
1193 
1194     ServiceStore.ServiceRecord getServiceRecord(ServiceURL URL,
1195 						Locale locale) {
1196 	return store.getServiceRecord(URL, locale);
1197 
1198     }
1199 
1200     //
1201     // Utility methods.
1202     //
1203 
1204     //
1205     //  Protected/private methods.
1206     //
1207 
1208     // Check whether the type is restricted, through an exception if so.
1209 
1210     private void checkForRestrictedType(ServiceType type)
1211 	throws ServiceLocationException {
1212 
1213 	if (Defaults.restrictedTypes.contains(type)) {
1214 	    throw
1215 		new ServiceLocationException(
1216 				ServiceLocationException.INVALID_REGISTRATION,
1217 				"st_restricted_type",
1218 				new Object[] {type});
1219 	}
1220     }
1221 
1222     // Insert a record for type "service-agent" with attributes having
1223     //  the types currently supported, if the new URL is not on the
1224     //  list of supported types. This allows us to perform queries
1225     //  for supported service types.
1226 
1227     private void trackRegisteredServiceTypes()
1228 	throws ServiceLocationException {
1229 
1230 	// First find the types.
1231 
1232 	Vector types = store.findServiceTypes(Defaults.ALL_AUTHORITIES,
1233 					      conf.getSAConfiguredScopes());
1234 
1235 	// Get preconfigured attributes.
1236 
1237 	Vector attrs = conf.getSAAttributes();
1238 
1239 	// Make an attribute with the service types.
1240 
1241 	ServiceLocationAttribute attr =
1242 	    new ServiceLocationAttribute(Defaults.SERVICE_TYPE_ATTR_ID,
1243 					 types);
1244 
1245 	attrs.addElement(attr);
1246 
1247 	// Construct URL to use on all interfaces.
1248 
1249 	Vector interfaces = conf.getInterfaces();
1250 	int i, n = interfaces.size();
1251 
1252 	for (i = 0; i < n; i++) {
1253 	    InetAddress addr = (InetAddress)interfaces.elementAt(i);
1254 	    ServiceURL url =
1255 		new ServiceURL(Defaults.SUN_SA_SERVICE_TYPE + "://" +
1256 			       addr.getHostAddress(),
1257 			       ServiceURL.LIFETIME_MAXIMUM);
1258 
1259 	    Vector scopes = conf.getSAOnlyScopes();
1260 
1261 	    Locale locale = Defaults.locale;
1262 
1263 	    // Make a new registration for this SA.
1264 
1265 	    store.register(url,
1266 			   attrs,
1267 			   scopes,
1268 			   locale,
1269 			   null,
1270 			   null);  // we could sign, but we do that later...
1271 	}
1272 
1273 	// Note that we don't need a refresh on the URLs because they
1274 	//  will get refreshed when the service URLs that they track
1275 	//  are refreshed. If the tracked URLs aren't refreshed, then
1276 	//  these will get updated when the tracked URLs age out.
1277     }
1278 
1279     // Return true if the scopes in the vector are supported by the DA
1280     //  or SA server.
1281 
1282     final private boolean areSupportedScopes(Vector scopes) {
1283 
1284 	Vector configuredScopes = conf.getSAConfiguredScopes();
1285 	Vector saOnlyScopes = conf.getSAOnlyScopes();
1286 	int i = 0;
1287 
1288 	while (i < scopes.size()) {
1289 	    Object o = scopes.elementAt(i);
1290 
1291 	    // Remove it if we don't support it.
1292 
1293 	    if (!configuredScopes.contains(o) && !saOnlyScopes.contains(o)) {
1294 		// This will shift the Vector's elements down one, so
1295 		// don't increment i
1296 		scopes.removeElementAt(i);
1297 	    } else {
1298 		i++;
1299 	    }
1300 	}
1301 
1302 	if (scopes.size() <= 0) {
1303 	    return false;
1304 
1305 	}
1306 
1307 	return true;
1308     }
1309 
1310     /**
1311      * Return the sleep increment from the URL lifetime. Used by the
1312      * ServiceStore to calculate the new sleep interval in addition
1313      * to this class, when a new URL comes in. The algorithm
1314      * subtracts x% of the lifetime from the lifetime and schedules the
1315      * timeout at that time.
1316      *
1317      * @param url The URL to use for calculation.
1318      * @return The sleep interval.
1319      */
1320 
1321     private long getSleepIncrement(ServiceURL url) {
1322 	long urlLifetime = (long)(url.getLifetime() * 1000);
1323 	long increment =
1324 	    (long)((float)urlLifetime * Defaults.fRefreshGranularity);
1325 	long sTime = urlLifetime - increment;
1326 
1327 	// If URL lives only one second, update every half second.
1328 
1329 	if (sTime <= 0) {
1330 	    sTime = 500;
1331 
1332 	}
1333 
1334 	return sTime;
1335     }
1336 
1337     // Make a DAADvert for the DA service request. This only applies
1338     //  to DAs, not to SA servers.
1339 
1340     SrvLocMsg
1341 	makeDAAdvert(SSrvMsg rqst,
1342 		     InetAddress daAddr,
1343 		     SLPConfig conf) {
1344 
1345 	SrvLocHeader hdr = rqst.getHeader();
1346 	Vector scopes = hdr.scopes;
1347 	short xid = hdr.xid;
1348 	String query = rqst.query;
1349 
1350 	try {
1351 
1352 	    // If security is on, proceed only if we can sign as rqst.spi
1353 	    if (conf.getHasSecurity() && !AuthBlock.canSignAs(rqst.spi)) {
1354 		throw new ServiceLocationException(
1355 			ServiceLocationException.AUTHENTICATION_UNKNOWN,
1356 			"st_cant_sign_as",
1357 			new Object[] {rqst.spi});
1358 	    }
1359 
1360 	    // Get the hashtable of service URLs v.s. scopes.
1361 
1362 	    Hashtable services =
1363 		ServerDATable.getServerDATable().returnMatchingDAs(query);
1364 
1365 	    // Go through the table checking whether the IP address came back.
1366 
1367 	    Enumeration urls = services.keys();
1368 	    boolean foundIt = false;
1369 	    String strDAAddr = daAddr.getHostAddress();
1370 
1371 	    while (urls.hasMoreElements()) {
1372 		ServiceURL url = (ServiceURL)urls.nextElement();
1373 
1374 		if (url.getHost().equals(strDAAddr)) {
1375 		    foundIt = true;
1376 		    break;
1377 
1378 		}
1379 	    }
1380 
1381 	    // If we didn't find anything, make a null service reply.
1382 
1383 	    if (!foundIt) {
1384 		return rqst.makeReply(new Hashtable(), new Hashtable());
1385 
1386 	    }
1387 
1388 	    return makeDAAdvert(hdr, daAddr, xid, scopes, conf);
1389 
1390 
1391 	} catch (ServiceLocationException ex) {
1392 
1393 	    return hdr.makeErrorReply(ex);
1394 
1395 	}
1396 
1397     }
1398 
1399     // Make a DAAdvert from the input arguments.
1400     SrvLocMsg
1401 	makeDAAdvert(SrvLocHeader hdr,
1402 		     InetAddress daAddr,
1403 		     short xid,
1404 		     Vector scopes,
1405 		     SLPConfig config)
1406 	throws ServiceLocationException {
1407 
1408 	// If this is a request for a V1 Advert, truncate the scopes vector
1409 	//  since DA solicitations in V1 are always unscoped
1410 
1411 	if (hdr.version == 1) {
1412 	    scopes = new Vector();
1413 
1414 	}
1415 
1416 	// Check if we support scopes first. If not, return an
1417 	//  error reply unless the scope vector is zero. Upper layers
1418 	//  must sort out whether this is a unicast or multicast.
1419 
1420 	if (scopes.size() > 0 && !areSupportedScopes(scopes)) {
1421 	    throw
1422 		new ServiceLocationException(
1423 				ServiceLocationException.SCOPE_NOT_SUPPORTED,
1424 				"st_scope_unsup",
1425 				new Object[0]);
1426 
1427 	}
1428 
1429 	// Get the service store's timestamp. This must be the
1430 	//  time since last stateless reboot for a stateful store,
1431 	//  or the current time.
1432 
1433 	long timestamp = store.getStateTimestamp();
1434 
1435 	ServiceURL url =
1436 	    new ServiceURL(Defaults.DA_SERVICE_TYPE + "://" +
1437 			   daAddr.getHostAddress(),
1438 			   ServiceURL.LIFETIME_DEFAULT);
1439 
1440 	SDAAdvert advert =
1441 	    hdr.getDAAdvert(xid,
1442 			    timestamp,
1443 			    url,
1444 			    scopes,
1445 			    conf.getDAAttributes());
1446 
1447 	return advert;
1448     }
1449 
1450     // Make a SAADvert for the SA service request. This only applies
1451     //  to SA servers, not DA's. Note that we only advertise the "public"
1452     //  scopes, not the private ones.
1453 
1454     SSAAdvert
1455 	makeSAAdvert(SSrvMsg rqst,
1456 		     InetAddress interfac,
1457 		     SLPConfig conf)
1458 	throws ServiceLocationException {
1459 	SrvLocHeader hdr = rqst.getHeader();
1460 	int version = hdr.version;
1461 	short xid = hdr.xid;
1462 	Locale locale = hdr.locale;
1463 	Vector scopes = hdr.scopes;
1464 	String query = rqst.query;
1465 	String serviceType = rqst.serviceType;
1466 	Vector saOnlyScopes = conf.getSAOnlyScopes();
1467 
1468 	// If security is on, proceed only if we can sign as rqst.spi
1469 	if (conf.getHasSecurity() && !AuthBlock.canSignAs(rqst.spi)) {
1470 	    throw new ServiceLocationException(
1471 			ServiceLocationException.AUTHENTICATION_UNKNOWN,
1472 			"st_cant_sign_as",
1473 			new Object[] {rqst.spi});
1474 	}
1475 
1476 
1477 	// Check if we support scopes first. Note that this may allow
1478 	//  someone to get at the SA only scopes off machine, but that's
1479 	//  OK. Since the SAAdvert is only ever multicast, this is OK.
1480 
1481 	if (!areSupportedScopes(scopes) && !(scopes.size() <= 0)) {
1482 	    return null;
1483 
1484 	}
1485 
1486 	// If the scopes vector is null, then use all configured scopes.
1487 
1488 	if (scopes.size() <= 0) {
1489 	    scopes = (Vector)conf.getSAConfiguredScopes().clone();
1490 
1491 	}
1492 
1493 	// Check to be sure the query matches.
1494 	//  If it doesn't, we don't need to return anything.
1495 
1496 	Hashtable returns =
1497 	    store.findServices(Defaults.SUN_SA_SERVICE_TYPE.toString(),
1498 			       saOnlyScopes,
1499 			       query,
1500 			       Defaults.locale);
1501 	Hashtable services =
1502 	    (Hashtable)returns.get(ServiceStore.FS_SERVICES);
1503 	Enumeration en = services.keys();
1504 
1505 	// Indicates we don't support the service type.
1506 
1507 	if (!en.hasMoreElements()) {
1508 	    return null;
1509 
1510 	}
1511 
1512 	// Find the URL to use. The interface on which the message came in
1513 	//  needs to match one of the registered URLs.
1514 
1515 	ServiceURL url = null;
1516 	ServiceURL surl = null;
1517 	String addr = interfac.getHostAddress();
1518 
1519 	while (en.hasMoreElements()) {
1520 	    surl = (ServiceURL)en.nextElement();
1521 
1522 	    if (addr.equals(surl.getHost())) {
1523 		url = new ServiceURL(Defaults.SA_SERVICE_TYPE + "://" +
1524 				     addr,
1525 				     ServiceURL.LIFETIME_DEFAULT);
1526 		break;
1527 	    }
1528 	}
1529 
1530 	// If none of the URLs matched this interface, then return null.
1531 
1532 	if (url == null) {
1533 	    return null;
1534 
1535 	}
1536 
1537 	// Find the SA's attributes.
1538 
1539 	Hashtable ht =
1540 	    store.findAttributes(surl,
1541 				 saOnlyScopes,
1542 				 new Vector(),
1543 				 Defaults.locale);
1544 
1545 	Vector attrs = (Vector)ht.get(ServiceStore.FA_ATTRIBUTES);
1546 
1547 	// Construct return.
1548 
1549 	return
1550 	    new SSAAdvert(version,
1551 			  xid,
1552 			  locale,
1553 			  url,
1554 			  conf.getSAConfiguredScopes(), // report all scopes...
1555 			  attrs);
1556     }
1557 
1558     /**
1559      * Report a fatal exception to the log.
1560      *
1561      * @param ex The exception to report.
1562      */
1563 
1564     protected static void reportFatalException(Exception ex) {
1565 
1566 	reportException(true, ex, new Vector());
1567 
1568 	if (table != null) {
1569 	    table.store.dumpServiceStore();
1570 	}
1571 
1572 	conf.writeLog("exiting_msg", new Object[0]);
1573 
1574 	System.exit(1);
1575 
1576     }
1577 
1578     /**
1579      * Report a nonfatal exception to the log.
1580      *
1581      * @param ex The exception to report.
1582      * @param args The method arguments.
1583      * @param store The service store being processed.
1584      */
1585 
1586     protected static void reportNonfatalException(Exception ex,
1587 						  Vector args,
1588 						  ServiceStore store) {
1589 
1590 	reportException(false, ex, args);
1591 
1592 	if (conf.traceAll()) {
1593 	    store.dumpServiceStore();
1594 	}
1595 
1596     }
1597 
1598     /**
1599      * Report an exception to the log.
1600      *
1601      * @param isFatal Indicates whether the exception is fatal or not.
1602      * @param ex The exception to report.
1603      * @param args A potentially null vector of arguments to the
1604      * 			method where the exception was caught.
1605      */
1606 
1607     private static void
1608 	reportException(boolean isFatal, Exception ex, Vector args) {
1609 
1610 	StringWriter sw = new StringWriter();
1611 	PrintWriter writer = new PrintWriter(sw);
1612 
1613 	// Get the backtrace.
1614 
1615 	ex.printStackTrace(writer);
1616 
1617 	String severity = (isFatal ? "fatal_error":"nonfatal_error");
1618 	String msg = ex.getMessage();
1619 
1620 	if (msg == null) {
1621 	    msg = conf.formatMessage("no_message", new Object[0]);
1622 
1623 	} else if (ex instanceof ServiceLocationException) {
1624 	    msg = msg +
1625 		"(" + ((ServiceLocationException)ex).getErrorCode() + ")";
1626 
1627 	}
1628 
1629 	StringBuffer argMsg = new StringBuffer();
1630 
1631 	int i, n = args.size();
1632 
1633 	for (i = 0; i < n; i++) {
1634 	    argMsg.append("\n        (" + Integer.toString(i) + "):" +
1635 			  args.elementAt(i).toString());
1636 	}
1637 
1638 	conf.writeLog(severity,
1639 		      new Object[] {
1640 	    ex.getClass().getName(),
1641 		msg,
1642 		argMsg,
1643 		sw.toString()});
1644 
1645     }
1646 }
1647