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