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 1998-2002 Sun Microsystems, Inc.  All rights reserved.
26  * Use is subject to license terms.
27  */
28 
29 package com.sun.dhcpmgr.data;
30 
31 import java.util.Date;
32 import java.text.SimpleDateFormat;
33 import java.text.DateFormat;
34 import java.util.StringTokenizer;
35 import java.io.Serializable;
36 
37 /**
38  * This class represents a record in a DHCP network table.  It can also be used
39  * to manage an associated hosts record by setting the client name; that effect
40  * is not part of this class, but rather is provided by the DhcpNetMgr.
41  */
42 public class DhcpClientRecord implements Serializable, Comparable, Cloneable {
43 
44     /**
45      * Default values for class attributes.
46      */
47     public static final String DEFAULT_CLIENT_ID	= new String("00");
48     public static final String DEFAULT_FLAGS		= new String("00");
49     public static final String DEFAULT_CLIENT_NAME	= new String();
50     public static final String DEFAULT_EXPIRATION	= new String("0");
51     public static final String DEFAULT_SIGNATURE	= new String("0");
52     public static final String DEFAULT_MACRO		= new String("UNKNOWN");
53     public static final String DEFAULT_COMMENT		= new String();
54 
55     /**
56      * Expiration special values.
57      */
58     private static final String EXPIRATION_ZERO		= new String("0");
59     private static final String EXPIRATION_FOREVER	= new String("-1");
60 
61     private String clientId;
62     private byte flags;
63     private IPAddress clientIP;
64     private IPAddress serverIP;
65     private Date expiration;
66     private String signature = DEFAULT_SIGNATURE;
67     private String macro;
68     private String comment;
69     private String clientName = null;
70     private String serverName = null;
71 
72     // Serialization id of this class
73     static final long serialVersionUID = 5007310554198923085L;
74 
75     /**
76      * Constructs a basic, empty client record.
77      */
DhcpClientRecord()78     public DhcpClientRecord() {
79 	clientId = DEFAULT_CLIENT_ID;
80 	macro = comment = null;
81 	flags = 0;
82 	clientIP = serverIP = null;
83 	expiration = null;
84 	signature = DEFAULT_SIGNATURE;
85     }
86 
87     /**
88      * Constructs a client record with a client IP.
89      * @param clientIP the client IP address for the record.
90      */
DhcpClientRecord(String clientIP)91     public DhcpClientRecord(String clientIP) throws ValidationException {
92 	setDefaults();
93 	setClientIP(new IPAddress(clientIP));
94     }
95 
96     /**
97      * Constructs a fully specified client record
98      * @param clientId Client's unique identifier
99      * @param flags Status flags for the record
100      * @param clientIP Client's IP address
101      * @param serverIP IP address of owning server
102      * @param expiration Lease expiration time in seconds since Unix epoch
103      * @param macro Configuration macro associated with this record
104      * @param comment User notes on this record
105      */
DhcpClientRecord(String clientId, String flags, String clientIP, String serverIP, String expiration, String macro, String comment)106     public DhcpClientRecord(String clientId, String flags, String clientIP,
107 			    String serverIP, String expiration, String macro,
108 			    String comment) throws ValidationException {
109 
110 	this(clientId, flags, clientIP, serverIP, expiration, macro,
111 			    comment, DEFAULT_SIGNATURE);
112 	}
113 
114     /**
115      * Constructs a fully specified client record
116      * @param clientId Client's unique identifier
117      * @param flags Status flags for the record
118      * @param clientIP Client's IP address
119      * @param serverIP IP address of owning server
120      * @param expiration Lease expiration time in seconds since Unix epoch
121      * @param macro Configuration macro associated with this record
122      * @param comment User notes on this record
123      * @param signature Opaque signature
124      */
DhcpClientRecord(String clientId, String flags, String clientIP, String serverIP, String expiration, String macro, String comment, String signature)125     public DhcpClientRecord(String clientId, String flags, String clientIP,
126 			    String serverIP, String expiration, String macro,
127 			    String comment, String signature)
128 				throws ValidationException {
129 	setClientId(clientId);
130 	this.flags = Byte.parseByte(flags);
131 	setClientIP(new IPAddress(clientIP));
132 	setServerIP(new IPAddress(serverIP));
133 	setExpiration(expiration);
134 	this.macro = macro;
135 	this.comment = comment;
136 	this.signature = signature;
137     }
138 
139     /**
140      * Make a copy of this record
141      */
clone()142     public Object clone() {
143 	DhcpClientRecord newrec = new DhcpClientRecord();
144 	newrec.clientId = clientId;
145 	newrec.flags = flags;
146 	if (clientIP != null) {
147 	    newrec.clientIP = (IPAddress)clientIP.clone();
148 	}
149 	if (serverIP != null) {
150 	    newrec.serverIP = (IPAddress)serverIP.clone();
151 	}
152 	if (expiration != null) {
153 	    newrec.expiration = (Date)expiration.clone();
154 	}
155 	newrec.macro = macro;
156 	newrec.comment = comment;
157 	newrec.clientName = clientName;
158 	newrec.serverName = serverName;
159 	newrec.signature = signature;
160 	return newrec;
161     }
162 
163     /**
164      * Fully specifies the defaults for a client record
165      */
setDefaults()166     public void setDefaults()
167 	throws ValidationException {
168 	setClientId(DEFAULT_CLIENT_ID);
169 	setFlags(DEFAULT_FLAGS);
170 	setClientName(DEFAULT_CLIENT_NAME);
171 	setExpiration(DEFAULT_EXPIRATION);
172 	setMacro(DEFAULT_MACRO);
173 	setComment(DEFAULT_COMMENT);
174     }
175 
176     /**
177      * Retrieve the client ID
178      * @return Client ID as a String
179      */
getClientId()180     public String getClientId() {
181 	return clientId;
182     }
183 
184     /**
185      * Set the client ID.  See dhcp_network(4) for the rules about client
186      * ID syntax which are implemented here.
187      * @param clientId Client's unique identifier
188      */
setClientId(String clientId)189     public void setClientId(String clientId) throws ValidationException {
190 	if (clientId.length() > 128 || clientId.length() % 2 != 0) {
191 	    // Must be even number of characters, no more than 128 characters
192 	    String msg = ResourceStrings.getString("dcr_invalid_clientid");
193 	    throw new ValidationException(msg);
194 	}
195 	char [] c = clientId.toCharArray();
196 	for (int i = 0; i < c.length; ++i) {
197 	    if ((c[i] < '0' || c[i] > '9') && (c[i] < 'A' || c[i] > 'F')) {
198 		String msg = ResourceStrings.getString("dcr_invalid_clientid");
199 		throw new ValidationException(msg);
200 	    }
201 	}
202 	this.clientId = clientId;
203 	if (this.clientId.length() == 0) {
204 	    this.clientId = DEFAULT_CLIENT_ID;
205 	}
206     }
207 
208     /**
209      * Get the flags byte
210      * @return A <code>byte</code> containing the record's status flags
211      */
getFlags()212     public byte getFlags() {
213 	return flags;
214     }
215 
216     /**
217      * Get the flags as a string
218      * @return The flag byte converted to a String
219      */
getFlagString()220     public String getFlagString() {
221 	return getFlagString(false);
222     }
223 
getFlagString(boolean verbose)224     public String getFlagString(boolean verbose) {
225 
226 	StringBuffer b = new StringBuffer();
227 	if (!verbose) {
228 	    b.append(flags);
229 	    // Make sure we always have a 2-character representation.
230 	    if (flags < 10) {
231 		b.insert(0, 0);
232 	    }
233 	}
234 	else {
235 	    if (flags == 0) {
236 		b.append(DhcpClientFlagTypes.DYNAMIC.getCharVal());
237 	    } else {
238 		if (isPermanent()) {
239 		    b.append(DhcpClientFlagTypes.PERMANENT.getCharVal());
240 		}
241 		if (isManual()) {
242 		    b.append(DhcpClientFlagTypes.MANUAL.getCharVal());
243 		}
244 		if (isUnusable()) {
245 		    b.append(DhcpClientFlagTypes.UNUSABLE.getCharVal());
246 		}
247 		if (isBootp()) {
248 		    b.append(DhcpClientFlagTypes.BOOTP.getCharVal());
249 		}
250 	    }
251 	}
252 	return b.toString();
253     }
254 
255     /**
256      * Test for setting of unusable flag
257      * @return <code>true</code> if the unusable flag is set,
258      * <code>false</code> if not.
259      */
isUnusable()260     public boolean isUnusable() {
261 	return DhcpClientFlagTypes.UNUSABLE.isSet(flags);
262     }
263 
264     /**
265      * Set/reset the unusable flag.
266      * @param state <code>true</code> if address is to be unusable
267      */
setUnusable(boolean state)268     public void setUnusable(boolean state) {
269 	if (state) {
270 	    flags |= DhcpClientFlagTypes.UNUSABLE.getNumericVal();
271 	} else {
272 	    flags &= ~DhcpClientFlagTypes.UNUSABLE.getNumericVal();
273 	}
274     }
275 
276     /**
277      * Test for setting of bootp flag
278      * @return <code>true</code> if the bootp flag is set,
279      * <code>false</code> if not.
280      */
isBootp()281     public boolean isBootp() {
282 	return DhcpClientFlagTypes.BOOTP.isSet(flags);
283     }
284 
285     /**
286      * Set/reset the bootp flag
287      * @param state <code>true</code> if address is reserved for BOOTP clients
288      */
setBootp(boolean state)289     public void setBootp(boolean state) {
290 	if (state) {
291 	    flags |= DhcpClientFlagTypes.BOOTP.getNumericVal();
292 	} else {
293 	    flags &= ~DhcpClientFlagTypes.BOOTP.getNumericVal();
294 	}
295     }
296 
297     /**
298      * Test for setting of manual assignment flag
299      * @return <code>true</code> if address is manually assigned,
300      * <code>false</code> if not.
301      */
isManual()302     public boolean isManual() {
303 	return DhcpClientFlagTypes.MANUAL.isSet(flags);
304     }
305 
306     /**
307      * Set/reset the manual assignment flag
308      * @param state <code>true</code> if the address is manually assigned
309      */
setManual(boolean state)310     public void setManual(boolean state) {
311 	if (state) {
312 	    flags |= DhcpClientFlagTypes.MANUAL.getNumericVal();
313 	} else {
314 	    flags &= ~DhcpClientFlagTypes.MANUAL.getNumericVal();
315 	}
316     }
317 
318     /**
319      * Test for setting of permanent assignment flag
320      * @return <code>true</code> if lease is permanent,
321      * <code>false</code> if dynamic
322      */
isPermanent()323     public boolean isPermanent() {
324 	return DhcpClientFlagTypes.PERMANENT.isSet(flags);
325     }
326 
327     /**
328      * Set/reset the permanent assignment flag
329      * @param state <code>true</code> if the address is permanently leased
330      */
setPermanent(boolean state)331     public void setPermanent(boolean state) {
332 	if (state) {
333 	    flags |= DhcpClientFlagTypes.PERMANENT.getNumericVal();
334 	} else {
335 	    flags &= ~DhcpClientFlagTypes.PERMANENT.getNumericVal();
336 	}
337     }
338 
339     /**
340      * Set the flags as a unit
341      * @param flags a <code>byte</code> setting for the flags
342      */
setFlags(String flags)343     public void setFlags(String flags) throws ValidationException {
344 	if (flags.charAt(0) >= '0' && flags.charAt(0) <= '9') {
345 	    this.flags = Byte.parseByte(flags);
346 	} else {
347 	    this.flags = 0;
348 	    StringTokenizer flagTokenizer = new StringTokenizer(flags, "+");
349 	    while (flagTokenizer.hasMoreTokens()) {
350 		String keyword = flagTokenizer.nextToken();
351 		if (keyword.equalsIgnoreCase(
352 		    DhcpClientFlagTypes.DYNAMIC.getKeyword())) {
353 		    // nothing to do, default is Dynamic.
354 		} else if (keyword.equalsIgnoreCase(
355 		    DhcpClientFlagTypes.PERMANENT.getKeyword())) {
356 		    this.flags |= DhcpClientFlagTypes.PERMANENT.getNumericVal();
357 		} else if (keyword.equalsIgnoreCase(
358 		    DhcpClientFlagTypes.MANUAL.getKeyword())) {
359 		    this.flags |= DhcpClientFlagTypes.MANUAL.getNumericVal();
360 		} else if (keyword.equalsIgnoreCase(
361 		    DhcpClientFlagTypes.UNUSABLE.getKeyword())) {
362 		    this.flags |= DhcpClientFlagTypes.UNUSABLE.getNumericVal();
363 		} else if (keyword.equalsIgnoreCase(
364 		    DhcpClientFlagTypes.BOOTP.getKeyword())) {
365 		    this.flags |= DhcpClientFlagTypes.BOOTP.getNumericVal();
366 		} else {
367 		    String msg = ResourceStrings.getString("dcr_invalid_flags");
368 		    throw new ValidationException(msg);
369 		}
370 	    }
371 	}
372     }
373 
374     /**
375      * Set the flags as a unit
376      * @param flags a <code>byte</code> setting for the flags
377      */
setFlags(byte flags)378     public void setFlags(byte flags) {
379 	this.flags = flags;
380     }
381 
382     /**
383      * Retrieve the client's IP address
384      * @return the client's IP address
385      */
getClientIP()386     public IPAddress getClientIP() {
387 	return clientIP;
388     }
389 
390     /**
391      * Retrieve a string version of the client's IP address
392      * @return A <code>String</code> containing the dotted decimal IP address.
393      */
getClientIPAddress()394     public String getClientIPAddress() {
395 	if (clientIP == null) {
396 	    return "";
397 	} else {
398 	    return clientIP.getHostAddress();
399 	}
400     }
401 
402     /**
403      * Set the client's IP address
404      * @param clientIP A String representation of the <code>IPAddress</code>
405      * to assign from this record.
406      */
setClientIP(String clientIP)407     public void setClientIP(String clientIP) throws ValidationException {
408 	if (clientIP == null) {
409 	    String msg = ResourceStrings.getString("dcr_invalid_null_clientip");
410 	    throw new ValidationException(msg);
411 	}
412 
413 	try {
414 	    setClientIP(new IPAddress(clientIP));
415 	} catch (Throwable e) {
416 	    String msg = ResourceStrings.getString("dcr_invalid_clientip");
417 	    throw new ValidationException(msg);
418 	}
419     }
420 
421     /**
422      * Set the client's IP address
423      * @param clientIP An <code>IPAddress</code> to assign from this record.
424      */
setClientIP(IPAddress clientIP)425     public void setClientIP(IPAddress clientIP) throws ValidationException {
426 	if (clientIP == null) {
427 	    String msg = ResourceStrings.getString("dcr_invalid_null_clientip");
428 	    throw new ValidationException(msg);
429 	}
430 	this.clientIP = clientIP;
431     }
432 
433     /**
434      * Retrieve the IP address of the owning server.
435      * @return An <code>IPAddress</code> for the server controlling this record.
436      */
getServerIP()437     public IPAddress getServerIP() {
438 	return serverIP;
439     }
440 
441     /**
442      * Retrieve a string version of the owning server's IP address
443      * @return The server's dotted decimal IP address as a <code>String</code>
444      */
getServerIPAddress()445     public String getServerIPAddress() {
446 	if (serverIP == null) {
447 	    return "";
448 	} else {
449 	    return serverIP.getHostAddress();
450 	}
451     }
452 
453     /**
454      * Set the server's IP address
455      * @param serverIP A String representation of the <code>IPAddress</code>
456      * to assign from this record.
457      */
setServerIP(String serverIP)458     public void setServerIP(String serverIP) throws ValidationException {
459 	if (serverIP == null) {
460 	    String msg = ResourceStrings.getString("dcr_invalid_null_serverip");
461 	    throw new ValidationException(msg);
462 	}
463 
464 	try {
465 	    setServerIP(new IPAddress(serverIP));
466 	} catch (Throwable e) {
467 	    String msg = ResourceStrings.getString("dcr_invalid_serverip");
468 	    throw new ValidationException(msg);
469 	}
470     }
471 
472     /**
473      * Assign this address to a server denoted by its IP address
474      * @param serverIP The <code>IPAddress</code> of the owning server.
475      */
setServerIP(IPAddress serverIP)476     public void setServerIP(IPAddress serverIP) throws ValidationException {
477 	if (serverIP == null) {
478 	    String msg = ResourceStrings.getString("dcr_invalid_null_serverip");
479 	    throw new ValidationException(msg);
480 	}
481 	this.serverIP = serverIP;
482     }
483 
484     /**
485      * @return The expiration time of this record's lease as a <code>Date</code>
486      */
getExpiration()487     public Date getExpiration() {
488 	return expiration;
489     }
490 
491     /**
492      * @return The expiration time of this record's lease in seconds
493      * since the epoch, as a <code>String</code>
494      */
getExpirationTime()495     public String getExpirationTime() {
496 	if (expiration == null) {
497 	    return null;
498 	}
499 	if (expiration.getTime() == Long.parseLong(EXPIRATION_FOREVER)) {
500 	    return EXPIRATION_FOREVER;
501 	} else {
502 	    return String.valueOf((expiration.getTime()/(long)1000));
503 	}
504     }
505 
506     /**
507      * Set the lease expiration date.
508      * @param expiration Lease expiration time in seconds since Unix epoch
509      */
setExpiration(String expiration)510     public void setExpiration(String expiration) {
511 	this.expiration = new Date((long)(Long.parseLong(expiration)*1000));
512     }
513 
514     /**
515      * Set the lease expiration date.
516      * @param expiration The <code>Date</code> when the lease expires.
517      */
setExpiration(Date expiration)518     public void setExpiration(Date expiration) {
519 	this.expiration = expiration;
520     }
521 
522     /**
523      * Set the lease expiration date by parsing a formatted string.  Also
524      * provides special handling of the "0" and "-1" values.
525      * @param dateFormat A DateFormat used to parse the expiration date
526      * @param date Lease expiration in desired format.
527      */
setExpiration(DateFormat dateFormat, String date)528     public void setExpiration(DateFormat dateFormat, String date)
529 	throws ValidationException {
530 
531 	if (date == null) {
532 	    setExpiration(date);
533 	} else if (date.equals(EXPIRATION_ZERO)) {
534 	    setExpiration(date);
535 	} else if (date.equals(EXPIRATION_FOREVER)) {
536 	    setExpiration(date);
537 	} else {
538 	    try {
539 		expiration = dateFormat.parse(date);
540 	    } catch (Exception ex) {
541 		String msg =
542 		    ResourceStrings.getString("dcr_invalid_expiration");
543 		throw new ValidationException(msg);
544 	    }
545 	}
546     }
547 
548     /**
549      * @return The name of the macro used to explicitly configure this address
550      */
getMacro()551     public String getMacro() {
552 	return macro;
553     }
554 
555     /**
556      * Set the name of the macro used to explicitly configure this address
557      */
setMacro(String macro)558     public void setMacro(String macro) {
559 	this.macro = macro;
560     }
561 
562     /**
563      * @return The descriptive comment for this record
564      */
getComment()565     public String getComment() {
566 	return comment;
567     }
568 
569     /**
570      * Set a descriptive comment for this record
571      * @param comment The comment
572      */
setComment(String comment)573     public void setComment(String comment) {
574 	this.comment = comment;
575     }
576 
577     /**
578      * @return The signature for this record
579      */
getSignature()580     public String getSignature() {
581 	return signature;
582     }
583 
584     /**
585      * Set the signature for this record
586      * @param signature The new signature value
587      */
setSignature(String signature)588     public void setSignature(String signature) {
589 	this.signature = signature;
590     }
591 
592     /**
593      * Perform comparisons to another DhcpClientRecord instance.  This is used
594      * for sorting a network table by client address.
595      * @param o A <code>DhcpClientRecord</code> to compare against.
596      * @return 0 if the objects have the same address,
597      * a negative number if this record has a lower IP address than the
598      * supplied record, a positive number if this record has a higher IP
599      * address than the supplied record.
600      */
compareTo(Object o)601     public int compareTo(Object o) {
602 	DhcpClientRecord r = (DhcpClientRecord)o;
603 	return (int)(getBinaryAddress() - r.getBinaryAddress());
604     }
605 
606     /**
607      * Retrieve the IP address as a number suitable for arithmetic operations.
608      * We use a <code>long</code> rather than an <code>int</code> in order to
609      * be able to treat it as an unsigned value, since all Java types are
610      * signed.
611      * @return The IP address as a <code>long</code>.
612      */
getBinaryAddress()613     public long getBinaryAddress() {
614 	return (clientIP.getBinaryAddress());
615     }
616 
617     /**
618      * @return The client's hostname
619      */
getClientName()620     public String getClientName() {
621 	if (clientName == null && clientIP != null) {
622 		clientName = clientIP.getHostName();
623 	}
624 	return clientName;
625     }
626 
627     /**
628      * @param name The hostname for the client.
629      */
setClientName(String name)630     public void setClientName(String name) {
631 	clientName = name;
632     }
633 
634     /**
635      * @return The server's hostname
636      */
getServerName()637     public String getServerName() {
638 	if (serverName == null && serverIP != null) {
639 		serverName = serverIP.getHostName();
640 	}
641 	return serverName;
642     }
643 
644     /**
645      * @param name The server's hostname
646      */
setServerName(String name)647     public void setServerName(String name) {
648 	serverName = name;
649     }
650 
toString()651     public String toString() {
652 
653 	String server = null;
654 	if (serverIP != null) {
655 	    server = serverIP.getHostAddress();
656 	}
657 
658 	String client = null;
659 	if (clientIP != null) {
660 	    client = clientIP.getHostAddress();
661 	}
662 
663 	String expiration = null;
664 	if (this.expiration != null) {
665 	    expiration = this.expiration.toString();
666 	}
667 
668 	String s = clientId + " " + String.valueOf(flags) + " "
669 			    + client + " " + server
670 		            + " " + expiration + " " + signature
671 			    + " " + macro + " " + comment;
672 	return s;
673     }
674 }
675