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      */
  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      */
  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      */
 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      */
 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      */
 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      */
 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      */
 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      */
 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      */
 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      */
 220     public String getFlagString() {
 221         return getFlagString(false);
 222     }
 223     
 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      */
 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      */
 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      */
 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      */
 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      */
 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      */
 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      */
 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      */
 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      */
 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      */
 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      */
 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      */
 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      */
 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      */
 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      */
 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      */
 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      */
 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      */
 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      */
 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      */
 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      */
 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      */
 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      */
 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      */
 551     public String getMacro() {
 552         return macro;
 553     }
 554     
 555     /**
 556      * Set the name of the macro used to explicitly configure this address
 557      */
 558     public void setMacro(String macro) {
 559         this.macro = macro;
 560     }
 561     
 562     /**
 563      * @return The descriptive comment for this record
 564      */
 565     public String getComment() {
 566         return comment;
 567     }
 568     
 569     /**
 570      * Set a descriptive comment for this record
 571      * @param comment The comment
 572      */
 573     public void setComment(String comment) {
 574         this.comment = comment;
 575     }
 576     
 577     /**
 578      * @return The signature for this record
 579      */
 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      */
 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      */
 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      */
 613     public long getBinaryAddress() {
 614         return (clientIP.getBinaryAddress());
 615     }
 616     
 617     /**
 618      * @return The client's hostname
 619      */
 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      */
 630     public void setClientName(String name) {
 631         clientName = name;
 632     }
 633     
 634     /**
 635      * @return The server's hostname
 636      */
 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      */
 647     public void setServerName(String name) {
 648         serverName = name;
 649     }
 650     
 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 }