001/*
002 *  Licensed to the Apache Software Foundation (ASF) under one
003 *  or more contributor license agreements.  See the NOTICE file
004 *  distributed with this work for additional information
005 *  regarding copyright ownership.  The ASF licenses this file
006 *  to you under the Apache License, Version 2.0 (the
007 *  "License"); you may not use this file except in compliance
008 *  with the License.  You may obtain a copy of the License at
009 *  
010 *    http://www.apache.org/licenses/LICENSE-2.0
011 *  
012 *  Unless required by applicable law or agreed to in writing,
013 *  software distributed under the License is distributed on an
014 *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015 *  KIND, either express or implied.  See the License for the
016 *  specific language governing permissions and limitations
017 *  under the License. 
018 *  
019 */
020package org.apache.directory.shared.kerberos.components;
021
022
023import java.nio.BufferOverflowException;
024import java.nio.ByteBuffer;
025import java.text.ParseException;
026import java.util.ArrayList;
027import java.util.List;
028
029import javax.security.auth.kerberos.KerberosPrincipal;
030
031import org.apache.directory.api.asn1.Asn1Object;
032import org.apache.directory.api.asn1.EncoderException;
033import org.apache.directory.api.asn1.ber.tlv.BerValue;
034import org.apache.directory.api.asn1.ber.tlv.TLV;
035import org.apache.directory.api.asn1.ber.tlv.UniversalTag;
036import org.apache.directory.api.util.Strings;
037import org.apache.directory.server.i18n.I18n;
038import org.apache.directory.shared.kerberos.KerberosUtils;
039import org.apache.directory.shared.kerberos.codec.types.PrincipalNameType;
040import org.slf4j.Logger;
041import org.slf4j.LoggerFactory;
042
043
044/**
045 * A principal Name, composed of a type and N names.
046 * <pre>
047 * PrincipalName   ::= SEQUENCE {
048 *        name-type       [0] Int32,
049 *        name-string     [1] SEQUENCE OF KerberosString
050 * }
051 * </pre>
052 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
053 */
054public class PrincipalName implements Asn1Object
055{
056    /** The logger */
057    private static final Logger LOG = LoggerFactory.getLogger( PrincipalName.class );
058
059    /** Speedup for logs */
060    private static final boolean IS_DEBUG = LOG.isDebugEnabled();
061
062    /** The type for this principal */
063    private PrincipalNameType nameType;
064
065    /** The principal name - we may have more than one - */
066    private List<String> nameString = new ArrayList<>();
067
068    /** The realm part */
069    private String realm;
070
071    /** The principal name as a byte[], for encoding purpose */
072    private List<byte[]> nameBytes;
073
074    // Storage for computed lengths
075    private int principalNameSeqLength;
076    private int principalTypeTagLength;
077    private int principalTypeLength;
078    private int principalStringsTagLength;
079    private int principalStringsSeqLength;
080
081
082    /**
083     * Creates a new empty instance of PrincipalName.
084     */
085    public PrincipalName()
086    {
087    }
088
089
090    /**
091     * Creates a new instance of PrincipalName, given a KerberosPrincipal.
092     * 
093     * We assume that a principal has only one type, even if there are
094     * more than one name component.
095     *
096     * @param principal A Sun kerberosPrincipal instance
097     */
098    public PrincipalName( KerberosPrincipal principal )
099    {
100        try
101        {
102            nameString = KerberosUtils.getNames( principal );
103            realm = principal.getRealm();
104        }
105        catch ( ParseException pe )
106        {
107            nameString = KerberosUtils.EMPTY_PRINCIPAL_NAME;
108        }
109
110        this.nameType = PrincipalNameType.getTypeByValue( principal.getNameType() );
111    }
112
113
114    /**
115     * Creates a new instance of PrincipalName given a String and an 
116     * principal type.
117     * 
118     * @param nameString The name string, which can contains more than one nameComponent
119     * @param nameType The principal name
120     */
121    public PrincipalName( String nameString, PrincipalNameType nameType ) throws ParseException
122    {
123        this.nameString = KerberosUtils.getNames( nameString );
124        this.nameType = nameType;
125    }
126
127
128    /**
129     * Creates a new instance of PrincipalName given a String[] and an 
130     * principal type.
131     * 
132     * @param nameParts The name string, which can contains more than one nameComponent
133     * @param nameType The principal name type
134     */
135    public PrincipalName( String[] nameParts, int nameType )
136    {
137        if ( nameParts == null || nameParts.length == 0 )
138        {
139            throw new IllegalArgumentException( "Empty name parts" );
140        }
141
142        List<String> nameComponents = new ArrayList<>();
143        for ( String np : nameParts )
144        {
145            nameComponents.add( np );
146        }
147
148        this.nameString = nameComponents;
149        this.nameType = PrincipalNameType.getTypeByValue( nameType );
150    }
151
152
153    /**
154     * Creates a new instance of PrincipalName.
155     *
156     * @param nameString
157     * @param nameType
158     */
159    public PrincipalName( String nameString, int nameType )
160    {
161        try
162        {
163            this.nameString = KerberosUtils.getNames( nameString );
164        }
165        catch ( ParseException pe )
166        {
167            throw new IllegalArgumentException( pe );
168        }
169
170        this.nameType = PrincipalNameType.getTypeByValue( nameType );
171    }
172
173
174    /**
175     * Returns the type of the {@link PrincipalName}.
176     *
177     * @return The type of the {@link PrincipalName}.
178     */
179    public PrincipalNameType getNameType()
180    {
181        return nameType;
182    }
183
184
185    /** 
186     * Set the Principal name Type
187     * @param nameType the Principal name Type
188     */
189    public void setNameType( PrincipalNameType nameType )
190    {
191        this.nameType = nameType;
192    }
193
194
195    /** 
196     * Set the Principal name Type
197     * @param nameType the Principal name Type
198     */
199    public void setNameType( int nameType )
200    {
201        this.nameType = PrincipalNameType.getTypeByValue( nameType );
202    }
203
204
205    /**
206     * Set the realm for the principal
207     * @param realm the realm of the principal
208     */
209    public void setRealm( String realm )
210    {
211        this.realm = realm;
212    }
213
214
215    /**
216     * Get the realm for the principal
217     * @return realm the realm of the principal
218     */
219    public String getRealm()
220    {
221        return realm;
222    }
223
224
225    /**
226     * Returns the name components.
227     *
228     * @return The name components.
229     */
230    public List<String> getNames()
231    {
232        return nameString;
233    }
234
235
236    /**
237     * @return A String representing the principal names as a String 
238     */
239    public String getNameString()
240    {
241        if ( ( nameString == null ) || nameString.isEmpty() )
242        {
243            return "";
244        }
245        else
246        {
247            StringBuilder sb = new StringBuilder();
248            boolean isFirst = true;
249
250            for ( String name : nameString )
251            {
252                if ( isFirst )
253                {
254                    isFirst = false;
255                }
256                else
257                {
258                    sb.append( '/' );
259                }
260
261                sb.append( name );
262            }
263
264            return sb.toString();
265        }
266    }
267
268
269    /**
270     * Add a new name to the PrincipalName
271     * @param name The name to add
272     */
273    public void addName( String name )
274    {
275        if ( nameString == null )
276        {
277            nameString = new ArrayList<>();
278        }
279
280        nameString.add( name );
281    }
282
283
284    /**
285     * Compute the PrincipalName length
286     * <pre>
287     * PrincipalName :
288     * 
289     * 0x30 L1 PrincipalName sequence
290     *  |
291     *  +--&gt; 0xA1 L2 name-type tag
292     *  |     |
293     *  |     +--&gt; 0x02 L2-1 addressType (int)
294     *  |
295     *  +--&gt; 0xA2 L3 name-string tag
296     *        |
297     *        +--&gt; 0x30 L3-1 name-string (SEQUENCE OF KerberosString)
298     *              |
299     *              +--&gt; 0x1B L4[1] value (KerberosString)
300     *              |
301     *              +--&gt; 0x1B L4[2] value (KerberosString)
302     *              |
303     *              ...
304     *              |
305     *              +--&gt; 0x1B L4[n] value (KerberosString)
306     * </pre>
307     */
308    public int computeLength()
309    {
310        // The principalName can't be empty.
311        principalTypeLength = BerValue.getNbBytes( nameType.getValue() );
312        principalTypeTagLength = 1 + 1 + principalTypeLength;
313
314        principalNameSeqLength = 1 + TLV.getNbBytes( principalTypeTagLength ) + principalTypeTagLength;
315
316        // Compute the keyValue
317        if ( ( nameString == null ) || nameString.isEmpty() )
318        {
319            principalStringsSeqLength = 0;
320        }
321        else
322        {
323            principalStringsSeqLength = 0;
324            nameBytes = new ArrayList<>( nameString.size() );
325
326            for ( String name : nameString )
327            {
328                if ( name != null )
329                {
330                    byte[] bytes = Strings.getBytesUtf8( name );
331                    nameBytes.add( bytes );
332                    principalStringsSeqLength += 1 + TLV.getNbBytes( bytes.length ) + bytes.length;
333                }
334                else
335                {
336                    nameBytes.add( Strings.EMPTY_BYTES );
337                    principalStringsSeqLength += 1 + 1;
338                }
339            }
340        }
341
342        principalStringsTagLength = 1 + TLV.getNbBytes( principalStringsSeqLength ) + principalStringsSeqLength;
343        principalNameSeqLength += 1 + TLV.getNbBytes( principalStringsTagLength ) + principalStringsTagLength;
344
345        // Compute the whole sequence length
346        return 1 + TLV.getNbBytes( principalNameSeqLength ) + principalNameSeqLength;
347    }
348
349
350    /**
351     * Encode the PrincipalName message to a PDU. 
352     * <pre>
353     * PrincipalName :
354     * 
355     * 0x30 LL
356     *   0xA0 LL 
357     *     0x02 0x01 name-type (integer)
358     *   0xA1 LL 
359     *     0x30 LL name-string (SEQUENCE OF KerberosString)
360     *       0x1B LL name-string[1]
361     *       0x1B LL name-string[2]
362     *       ...
363     *       0x1B LL name-string[n]
364     * </pre>
365     * @param buffer The buffer where to put the PDU. It should have been allocated
366     * before, with the right size.
367     * @return The constructed PDU.
368     */
369    public ByteBuffer encode( ByteBuffer buffer ) throws EncoderException
370    {
371        if ( buffer == null )
372        {
373            throw new EncoderException( I18n.err( I18n.ERR_148 ) );
374        }
375
376        try
377        {
378            // The PrincipalName SEQ Tag
379            buffer.put( UniversalTag.SEQUENCE.getValue() );
380            buffer.put( TLV.getBytes( principalNameSeqLength ) );
381
382            // The name-type, first the tag, then the value
383            buffer.put( ( byte ) 0xA0 );
384            buffer.put( TLV.getBytes( principalTypeTagLength ) );
385            BerValue.encode( buffer, nameType.getValue() );
386
387            // The name-string tag
388            buffer.put( ( byte ) 0xA1 );
389            buffer.put( TLV.getBytes( principalStringsTagLength ) );
390
391            // The name-string sequence
392            buffer.put( UniversalTag.SEQUENCE.getValue() );
393
394            if ( ( nameString == null ) || nameString.isEmpty() )
395            {
396                buffer.put( ( byte ) 0x00 );
397            }
398            else
399            {
400                buffer.put( TLV.getBytes( principalStringsSeqLength ) );
401
402                // The kerberosStrings
403                for ( byte[] name : nameBytes )
404                {
405                    buffer.put( UniversalTag.GENERAL_STRING.getValue() );
406
407                    if ( ( name == null ) || ( name.length == 0 ) )
408                    {
409                        buffer.put( ( byte ) 0x00 );
410                    }
411                    else
412                    {
413                        buffer.put( TLV.getBytes( name.length ) );
414                        buffer.put( name );
415                    }
416                }
417            }
418        }
419        catch ( BufferOverflowException boe )
420        {
421            LOG.error( I18n.err( I18n.ERR_146, 1 + TLV.getNbBytes( principalNameSeqLength )
422                + principalNameSeqLength, buffer.capacity() ) );
423            throw new EncoderException( I18n.err( I18n.ERR_138 ), boe );
424        }
425
426        if ( IS_DEBUG )
427        {
428            LOG.debug( "PrinipalName encoding : {}", Strings.dumpBytes( buffer.array() ) );
429            LOG.debug( "PrinipalName initial value : {}", this );
430        }
431
432        return buffer;
433    }
434
435
436    /**
437     * @see Object#toString()
438     */
439    public String toString()
440    {
441        StringBuilder sb = new StringBuilder();
442
443        sb.append( "{ " );
444
445        sb.append( "name-type: " ).append( nameType.name() );
446
447        if ( ( nameString != null ) && !nameString.isEmpty() )
448        {
449            sb.append( ", name-string : <" );
450            boolean isFirst = true;
451
452            for ( String name : nameString )
453            {
454                if ( isFirst )
455                {
456                    isFirst = false;
457                }
458                else
459                {
460                    sb.append( ", " );
461                }
462
463                sb.append( '\'' ).append( name ).append( '\'' );
464            }
465
466            sb.append( ">" );
467        }
468        else
469        {
470            sb.append( " no name-string" );
471        }
472
473        if ( realm != null )
474        {
475            sb.append( "realm: " ).append( realm );
476        }
477
478        sb.append( " }" );
479
480        return sb.toString();
481    }
482
483
484    @Override
485    public int hashCode()
486    {
487        final int prime = 31;
488        int result = 1;
489        result = prime * result + ( ( nameString == null ) ? 0 : nameString.hashCode() );
490        result = prime * result + ( ( nameType == null ) ? 0 : nameType.hashCode() );
491        return result;
492    }
493
494
495    /**
496     * {@inheritDoc}
497     */
498    @Override
499    public boolean equals( Object obj )
500    {
501        if ( this == obj )
502        {
503            return true;
504        }
505
506        if ( !( obj instanceof PrincipalName ) )
507        {
508            return false;
509        }
510
511        PrincipalName other = ( PrincipalName ) obj;
512
513        if ( nameString == null )
514        {
515            if ( other.nameString != null )
516            {
517                return false;
518            }
519        }
520        else if ( !nameString.equals( other.nameString ) )
521        {
522            return false;
523        }
524
525        return nameType == other.nameType;
526    }
527
528}