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 //  ServerDATable.java: Abstract class for DA Table in the DA/SA server.
28 //  Author:           James Kempf
29 //  Created On:       Wed May 20 08:30:46 1998
30 //  Last Modified By: James Kempf
31 //  Last Modified On: Tue Mar  9 12:36:37 1999
32 //  Update Count:     124
33 //
34 
35 package com.sun.slp;
36 
37 import java.util.*;
38 import java.net.*;
39 
40 /**
41  * ServerDATable is an abstract class that provides the interface for DA
42  * and scope discovery, storage of DA information from incoming DAAdverts,
43  * and forwarding of registrations and deregistration to DAs having
44  * the same scopes. A variety of implementations are possible.
45  * The getServerDATable() method creates the right one from a subclass.
46  * We keep separate track of the superclass DA table and the server
47  * DA table so that these two classes can co-exist in the same JVM.
48  * Note that the code for forwarding registrations must keep track of
49  * only those registrations that were done on this host. It does this
50  * by saving the registrations as they come in. The forwarding code
51  * is optimized so that forwarding of a new message is fast, while
52  * forwarding of a message due to discovery of a new DA is somewhat
53  * slower. This helps assure that SA clients get good service.
54  *
55  * The ServerDATable also does active discovery for the SA server/DA,
56  * in a separate thread.
57  *
58  * @author James Kempf
59  */
60 
61 abstract class ServerDATable extends DATable {
62 
63     // The active discovery object.
64 
65     protected static ActiveDiscoverer activeDiscoverer = null;
66 
67     // The table of regs to forward. Keys are the reg URL and locale, values
68     //  are the SSrvReg objects.
69 
70     protected Hashtable forwardRegs = new Hashtable();
71 
72     // This acts as a guard protecting an non-initialized DA table:
73     //  If the DA Table hasn't been populated by active discovery yet,
74     //  other threads accessing the DA table will block on readyLock.
75     private static Object readyLock = new Object();
76 
77     // Keeps track of which DAs support which SPIs. The actual mapping
78     //  is DA InetAddress to LinkedList of SPI Strings. recordNewDA
79     //  populates this.
80     protected Hashtable daSPIsHash = new Hashtable();
81 
82     /**
83      * Get the right server DA table from the subclass.
84      *
85      * @return Table for handling DAs in the server.
86      */
87 
getServerDATable()88     static ServerDATable getServerDATable()
89 	throws ServiceLocationException {
90 
91 	ServerDATable table = null;
92 
93 	synchronized (readyLock) {
94 
95 	    // Note that we are expecting this subclass. We will get a
96 	    //  cast exception if somebody instantiated with a
97 	    //  DATable subclass.
98 
99 	    if (daTable != null) {
100 		return (ServerDATable)daTable;
101 
102 	    }
103 
104 	    conf = SLPConfig.getSLPConfig();
105 
106 	    // Call the superclass method to link it.
107 
108 	    daTable = linkAndInstantiateFromProp();
109 
110 	    table = (ServerDATable)daTable;
111 
112 	    // Advertise for *all* scopes. This is because we need to
113 	    //  be able to support clients that do user scoping.
114 
115 	    Vector useScopes = new Vector();
116 
117 	    activeDiscoverer =
118 		new ActiveDiscoverer(Defaults.version,
119 				     table,
120 				     useScopes,
121 				     conf.getMulticastAddress());
122 
123 	    activeDiscoverer.start();
124 
125 	}	// readyLock
126 
127 	return table;
128 
129     }
130 
131     /**
132      * Record a new DA.
133      *
134      * @param URL The DAAdvert URL.
135      * @param scopes The scopes.
136      * @param version DA version number.
137      * @param attrs Attributes of DA.
138      * @param spis SPIs supported by DA
139      * @return True if recorded, false if not.
140      */
141 
142     abstract long
recordNewDA(ServiceURL url, Vector scopes, long timestamp, int version, Vector attrs, String spis)143 	recordNewDA(ServiceURL url,
144 		    Vector scopes,
145 		    long timestamp,
146 		    int version,
147 		    Vector attrs,
148 		    String spis);
149 
150     /**
151      * Return a hashtable in ServiceTable.findServices() format (e.g.
152      * URL's as keys, scopes as values) for DAs matching the query.
153      *
154      * @param query Query for DA attributes.
155      */
156 
returnMatchingDAs(String query)157     abstract Hashtable returnMatchingDAs(String query)
158 	throws ServiceLocationException;
159 
160     /**
161      * Forward a registration or deregistration to all DAs that have matching
162      * scopes.
163      *
164      * @param msg Registration or deregistration message, server side.
165      * @param source The address of the source.
166      */
167 
forwardSAMessage(SrvLocMsg msg, InetAddress source)168     void forwardSAMessage(SrvLocMsg msg, InetAddress source)
169 	throws ServiceLocationException {
170 
171 	SrvLocHeader hdr = msg.getHeader();
172 
173 	// If the message is not from this host (on any interface)
174 	//  then don't forward it.
175 
176 	if (!conf.isLocalHostSource(source)) {
177 	    return;
178 
179 	}
180 
181 	// Record it so we can forward to a new DA.
182 
183 	if (msg instanceof SSrvReg || msg instanceof CSrvReg) {
184 	    ServiceURL url;
185 	    if (msg instanceof SSrvReg) {
186 		url = ((SSrvReg)msg).URL;
187 	    } else {
188 		url = ((CSrvReg)msg).URL;
189 	    }
190 
191 	    String key = makeKey(url, hdr.locale); // need locale also...
192 	    forwardRegs.put(key, msg);  // fresh doesn't matter.
193 
194 	} else {
195 	    SSrvDereg smsg = (SSrvDereg)msg;
196 
197 	    // Only remove if tags are null. Otherwise, the updated record
198 	    //  will be sought.
199 
200 	    if (smsg.tags == null) {
201 		String key = makeKey(smsg.URL, hdr.locale);
202 		forwardRegs.remove(key);
203 
204 	    }
205 	}
206 
207 	// We only forward registrations to v2 DAs because we are
208 	//  acting as an SA server here. There is no requirement
209 	//  for v2 SAs to communicate with v1 DAs.
210 
211 	// Get a hashtable of DAs that match the scopes in the message.
212 
213 	Hashtable daScopeRec = findDAScopes(hdr.scopes);
214 
215 	// We are only concerned with the unicast key, since it contains
216 	//  the DAs to which forwarding is required.
217 
218 	Vector daRecs = (Vector)daScopeRec.get(UNICAST_KEY);
219 
220 	// If there are no daRecs, then simply return.
221 
222 	if (daRecs == null) {
223 	    return;
224 
225 	}
226 
227 	// Otherwise, forward the registration to all DAs in the vector.
228 
229 	int i, n = daRecs.size();
230 
231 	for (i = 0; i < n; i++) {
232 	    DARecord rec = (DARecord)daRecs.elementAt(i);
233 	    Vector daAddresses = rec.daAddresses;
234 
235 	    int j, m = daAddresses.size();
236 
237 	    for (j = 0; j < m; j++) {
238 		InetAddress addr = (InetAddress)daAddresses.elementAt(j);
239 
240 		// Don't forward if it's the host from which the registration
241 		//  came. Otherwise, we're hosed.
242 
243 		if (!source.equals(addr)) {
244 		    forwardRegOrDereg(addr, msg);
245 
246 		}
247 	    }
248 	}
249     }
250 
251     // Make a key for the service agent message table.
252 
makeKey(ServiceURL url, Locale locale)253     private String makeKey(ServiceURL url, Locale locale) {
254 
255 	return url.toString() + "/" + locale.toString();
256 
257     }
258 
259 
260     /**
261      * Handle an incoming DAAdvert. Presence must be recorded in the
262      * implementation specific server DA table and any registrations need
263      * to be forwarded if the boot timestamp is different from the
264      * old boot timestamp.
265      *
266      * @param advert Incoming DAAdvert.
267      */
268 
handleAdvertIn(CDAAdvert advert)269     void handleAdvertIn(CDAAdvert advert) {
270 
271 	SrvLocHeader hdr = advert.getHeader();
272 
273 	// Remove if DA is going down.
274 
275 	if (advert.isGoingDown()) {
276 
277 	    InetAddress addr = null;
278 
279 	    try {
280 
281 		addr = InetAddress.getByName(advert.URL.getHost());
282 
283 	    } catch (UnknownHostException ex) {
284 		conf.writeLog("unknown_da_address",
285 			      new Object[] {advert.URL.getHost()});
286 
287 		return;
288 	    }
289 
290 	    if (removeDA(addr, hdr.scopes)) {
291 
292 		if (conf.traceDATraffic()) {
293 		    conf.writeLog("sdat_delete_da",
294 				  new Object[] {
295 			advert.URL,
296 			    hdr.scopes});
297 		}
298 	    }
299 
300 	} else {
301 
302 	    // verify the DAAdvert
303 	    if (advert.authBlock != null) {
304 		try {
305 		    AuthBlock.verifyAll(advert.authBlock);
306 		} catch (ServiceLocationException e) {
307 		    if (conf.traceDrop()) {
308 			conf.writeLog("sdat_daadvert_vrfy_failed",
309 				      new Object[] {advert.URL});
310 		    }
311 		    return;
312 		}
313 	    }
314 
315 	    long timestamp =
316 		recordNewDA(advert.URL,
317 			    hdr.scopes,
318 			    advert.timestamp,
319 			    hdr.version,
320 			    advert.attrs,
321 			    advert.spis);
322 
323 	    // Don't forward if the advert was rejected, or if the
324 	    //  old timestamp greater than or equal to the new timestamp.
325 	    //  If the old timestamp is greater than or equal to the new,
326 	    //  it means that we have already forwarded to this DA.
327 	    //  IF the old timestamp is less, it means that
328 	    //  the DA has crashed and come up again since we last saw
329 	    //  it, so we may have missed forwarding something to it.
330 
331 	    if (timestamp >= advert.timestamp) {
332 
333 		if (conf.traceDATraffic()) {
334 		    conf.writeLog("sdat_add_da_no_forward",
335 				  new Object[] {
336 			advert.URL,
337 			    hdr.scopes,
338 			    new Long(timestamp)});
339 		}
340 
341 		return;
342 
343 	    }
344 
345 	    if (conf.traceDATraffic()) {
346 		conf.writeLog("sdat_add_da",
347 			      new Object[] {
348 		    advert.URL,
349 			hdr.scopes,
350 			new Long(advert.timestamp)});
351 	    }
352 
353 	    // Forward existing registrations to the new advert.
354 
355 	    forwardRegistrations(advert.URL, hdr.scopes,
356 				 advert.timestamp, hdr.version);
357 	}
358     }
359 
360     //
361     // Private methods.
362     //
363 
364     private void
forwardRegistrations(ServiceURL url, Vector scopes, long timestamp, int version)365 	forwardRegistrations(ServiceURL url,
366 			     Vector scopes,
367 			     long timestamp,
368 			     int version) {
369 
370 	// Wait a random amount of time before forwarding.
371 
372 	try {
373 
374 	    Thread.currentThread().sleep(conf.getRandomWait());
375 
376 	} catch (InterruptedException ex) {
377 
378 	}
379 
380 	// Get the registrations to forward.
381 
382 	Enumeration regs = forwardRegs.elements();
383 
384 	// Get the address of the DA.
385 
386 	InetAddress addr = null;
387 	String host = url.getHost();
388 
389 	try {
390 	    addr = InetAddress.getByName(host);
391 
392 	} catch (UnknownHostException ex) {
393 	    if (conf.traceDrop() || conf.traceDATraffic()) {
394 		conf.writeLog("sdat_drop_fwd",
395 			      new Object[] {
396 		    host});
397 
398 	    }
399 
400 	    return;
401 	}
402 
403 	ServiceTable serviceTable = null;
404 
405 	try {
406 
407 	    serviceTable = ServiceTable.getServiceTable();
408 
409 	} catch (ServiceLocationException ex) {
410 
411 	    // By this time, we should have it.
412 
413 	}
414 
415 	// Forward the registrations. Keep track of any deleted elements.
416 
417 	Vector deleted = new Vector();
418 
419 	while (regs.hasMoreElements()) {
420 	    SrvLocMsg reg = (SrvLocMsg)regs.nextElement();
421 
422 	    ServiceURL regurl;
423 	    if (reg instanceof SSrvReg) {
424 		regurl = ((SSrvReg)reg).URL;
425 	    } else {
426 		regurl = ((CSrvReg)reg).URL;
427 	    }
428 
429 	    SrvLocHeader hdr = reg.getHeader();
430 
431 	    // Get the record and modify the reg to reflect the
432 	    //  record. We must do this because the SA may have
433 	    //  changed the record since it was first registred
434 	    //  and we do not keep track of the changes here.
435 
436 	    ServiceStore.ServiceRecord rec =
437 		serviceTable.getServiceRecord(regurl, hdr.locale);
438 
439 	    // If the record is null, it means that the entry was
440 	    //  aged out.
441 
442 	    if (rec == null) {
443 		deleted.addElement(reg);
444 
445 	    } else {
446 
447 		// Check that the scopes match.
448 
449 		Vector sscopes = (Vector)hdr.scopes.clone();
450 
451 		DATable.filterScopes(sscopes, scopes, false);
452 
453 		if (sscopes.size() <= 0) {
454 		    continue;
455 
456 		}
457 
458 		if (reg instanceof SSrvReg) {
459 		    SSrvReg sreg = (SSrvReg)reg;
460 
461 		    hdr.scopes = (Vector)hdr.scopes.clone();
462 		    sreg.attrList = (Vector)rec.getAttrList().clone();
463 		    sreg.URLSignature = rec.getURLSignature();
464 		    sreg.attrSignature = rec.getAttrSignature();
465 		}
466 
467 		forwardRegOrDereg(addr, reg);
468 	    }
469 
470 	}
471 
472 	// Remove any deleted elements from the hashtable.
473 	//  We do this in a separate loop because enumerations
474 	//  aren't synchronized.
475 
476 	int i, n = deleted.size();
477 
478 	for (i = 0; i < n; i++) {
479 	    SrvLocMsg reg = (SrvLocMsg)deleted.elementAt(i);
480 	    SrvLocHeader hdr = reg.getHeader();
481 	    ServiceURL regurl;
482 	    if (reg instanceof SSrvReg) {
483 		regurl = ((SSrvReg)reg).URL;
484 	    } else {
485 		regurl = ((CSrvReg)reg).URL;
486 	    }
487 
488 	    String key = makeKey(regurl, hdr.locale);
489 
490 	    forwardRegs.remove(key);
491 
492 	}
493 
494     }
495 
496 
497     // Forward the registration or deregistration to the URL.
498 
forwardRegOrDereg(InetAddress addr, SrvLocMsg rqst)499     private void forwardRegOrDereg(InetAddress addr, SrvLocMsg rqst) {
500 	SrvLocHeader hdr = rqst.getHeader();
501 
502 	// Don't forward to myself! Otherwise, nasty recursion happens.
503 
504 	if (conf.isLocalHostSource(addr)) {
505 	    return;
506 
507 	}
508 
509 	// If security is on, only forward if this DA can verify the authblocks
510 	if (conf.getHasSecurity()) {
511 	    LinkedList spis = (LinkedList)daSPIsHash.get(addr);
512 	    if (spis == null) {
513 		// internal error; skip this DA to be safe
514 		return;
515 	    }
516 
517 	    Hashtable auths = null;
518 	    if (rqst instanceof SSrvReg) {
519 		auths = ((SSrvReg)rqst).URLSignature;
520 	    } else if (rqst instanceof SSrvDereg) {
521 		auths = ((SSrvDereg)rqst).URLSignature;
522 	    } else {
523 		// shouldn't even be forwarding this!
524 		return;
525 	    }
526 
527 	    // If each authblock is equiv to at least one SPI, forward the reg
528 	    Enumeration abs = auths.elements();
529 	    while (abs.hasMoreElements()) {
530 		AuthBlock ab = (AuthBlock)abs.nextElement();
531 
532 		// check each DA SPI
533 		boolean daSPImatch = false;
534 		for (int SPIi = 0; SPIi < spis.size(); SPIi++) {
535 		    if (AuthBlock.checkEquiv((String)spis.get(SPIi), ab)) {
536 			daSPImatch = true;
537 			break;
538 		    }
539 		}
540 
541 		if (!daSPImatch) {
542 		    return;
543 		}
544 	    }
545 	}
546 
547 	if (conf.traceDATraffic()) {
548 	    conf.writeLog("sdat_forward",
549 			  new Object[] {
550 		Integer.toHexString(hdr.xid),
551 		    addr});
552 
553 	}
554 
555 
556 	// Send it via TCP. DAs should understand TCP, and it's reliable.
557 
558 	SrvLocMsg rply = null;
559 
560 	try {
561 
562 	    // Construct the client side message, for outgoing.
563 
564 	    if (rqst instanceof SSrvReg) {
565 		SSrvReg rrqst = (SSrvReg)rqst;
566 		CSrvReg msg = new CSrvReg(hdr.fresh,
567 					  hdr.locale,
568 					  rrqst.URL,
569 					  hdr.scopes,
570 					  rrqst.attrList,
571 					  rrqst.URLSignature,
572 					  rrqst.attrSignature);
573 		rply = msg;
574 
575 	    } else if (rqst instanceof SSrvDereg) {
576 		SSrvDereg drqst = (SSrvDereg)rqst;
577 		CSrvDereg msg = new CSrvDereg(hdr.locale,
578 					      drqst.URL,
579 					      hdr.scopes,
580 					      drqst.tags,
581 					      drqst.URLSignature);
582 		rply = msg;
583 
584 	    } else if (rqst instanceof CSrvReg) {
585 		rply = rqst;
586 
587 	    }
588 
589 	    rply = Transact.transactTCPMsg(addr, rply, false);
590 
591 	} catch (ServiceLocationException ex) {
592 
593 	    if (conf.traceDATraffic()) {
594 		conf.writeLog("sdat_forward_exception",
595 			      new Object[] {
596 		    Integer.toHexString(hdr.xid),
597 			addr,
598 			new Integer(ex.getErrorCode()),
599 			ex.getMessage()});
600 
601 	    }
602 	}
603 
604 	// Report any errors.
605 
606 	if (rply == null ||
607 	    rply.getErrorCode() != ServiceLocationException.OK) {
608 	    if (conf.traceDATraffic()) {
609 		conf.writeLog("sdat_forward_err",
610 			      new Object[] {
611 		    Integer.toHexString(hdr.xid),
612 			addr,
613 			(rply == null ? "<null>":
614 			 Integer.toString(rply.getErrorCode()))});
615 
616 	    }
617 	}
618     }
619 }
620