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
35package com.sun.slp;
36
37import java.util.*;
38import java.io.*;
39import java.security.*;
40import 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
50class 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