View Javadoc

1   package org.jscsi.target.connection.stage.login;
2   
3   
4   import java.io.IOException;
5   import java.security.DigestException;
6   import java.util.List;
7   import java.util.Vector;
8   
9   import org.jscsi.exception.InternetSCSIException;
10  import org.jscsi.parser.BasicHeaderSegment;
11  import org.jscsi.parser.ProtocolDataUnit;
12  import org.jscsi.parser.login.LoginStage;
13  import org.jscsi.parser.login.LoginStatus;
14  import org.jscsi.target.connection.phase.TargetLoginPhase;
15  import org.jscsi.target.settings.SettingsException;
16  import org.jscsi.target.settings.TextKeyword;
17  import org.jscsi.target.settings.TextParameter;
18  import org.slf4j.Logger;
19  import org.slf4j.LoggerFactory;
20  
21  
22  /**
23   * A {@link TargetLoginStage} sub-class representing Security Negotiation Stages.
24   * 
25   * @author Andreas Ergenzinger
26   */
27  public final class SecurityNegotiationStage extends TargetLoginStage {
28  
29      private static final Logger LOGGER = LoggerFactory.getLogger(SecurityNegotiationStage.class);
30  
31      /**
32       * The constructor.
33       * 
34       * @param targetLoginPhase the login phase this stage is a part of
35       */
36      public SecurityNegotiationStage (TargetLoginPhase targetLoginPhase) {
37          super(targetLoginPhase, LoginStage.SECURITY_NEGOTIATION);
38      }
39  
40      @Override
41      public void execute (ProtocolDataUnit initialPdu) throws IOException , InterruptedException , InternetSCSIException , DigestException , SettingsException {
42  
43          // "receive" initial PDU
44          BasicHeaderSegment bhs = initialPdu.getBasicHeaderSegment();
45          initiatorTaskTag = bhs.getInitiatorTaskTag();
46  
47          boolean authenticated = false;
48  
49          do {// while initiator is not willing and not authorized to transit to
50              // next stage
51  
52              // build text parameter string from current login PDU sequence
53              final String requestTextParameters = receivePduSequence(initialPdu);
54  
55              // split key-value pairs
56              final List<String> requestKeyValuePairs = TextParameter.tokenizeKeyValuePairs(requestTextParameters);
57  
58              // Vector for AuthMethod keys
59              final List<String> authMethodKeyValuePairs = new Vector<String>();
60  
61              // log initiator's key-value pairs
62              if (LOGGER.isDebugEnabled()) {
63                  final StringBuilder sb = new StringBuilder();
64                  sb.append("request key value pairs:\n");
65                  for (String s : requestKeyValuePairs)
66                      sb.append("   " + s + "\n");
67                  LOGGER.debug(sb.toString());
68              }
69  
70              // extract available AuthMethod key-value pair, so that settings can
71              // finish
72              // processing the other parameters before authorization begins
73              String authMethodValues = null;
74              if (!authenticated) {// authentication part one
75                  for (int i = 0; i < requestKeyValuePairs.size(); ++i) {
76                      final String[] split = TextParameter.splitKeyValuePair(requestKeyValuePairs.get(i));
77                      if (split == null) {
78                          sendRejectPdu(LoginStatus.INITIATOR_ERROR);
79                          throw new InternetSCSIException("key=value format error: " + requestKeyValuePairs.get(i));
80                      }
81                      if (TextKeyword.AUTH_METHOD.equals(split[0])) {
82                          authMethodValues = split[1];
83                          // remove key-value pair from Vector
84                          requestKeyValuePairs.remove(i--);// correct for shifted
85                                                           // indices
86                          // no break here to catch all authMethodKeyValuePairs in
87                          // else block
88                      } else if (isAuthenticationKey(split[0])) {
89                          // move key-value pair to authMethodKeyValuePairs
90                          authMethodKeyValuePairs.add(requestKeyValuePairs.remove(i--));// correct for shifted
91                                                                                        // indices
92                      }
93                  }
94                  if (authMethodValues == null) {// missing AuthMethod key
95                      sendRejectPdu(LoginStatus.MISSING_PARAMETER);// require
96                                                                   // AuthMethod
97                                                                   // to be
98                                                                   // specified in
99                                                                   // first PDU
100                                                                  // sequence
101                     // close connection
102                     throw new InternetSCSIException("Missing AuthMethod key-value pair");
103                 }
104             }
105 
106             // negotiate remaining parameters
107             final Vector<String> responseKeyValuePairs = new Vector<String>();// these
108                                                                               // will
109                                                                               // be
110                                                                               // sent
111                                                                               // back
112             if (!negotiator.negotiate(session.getTargetServer(), stageNumber, connection.isLeadingConnection(), ((TargetLoginPhase) targetPhase).getFirstPduAndSetToFalse(), requestKeyValuePairs, responseKeyValuePairs)) {
113                 // negotiation error
114                 sendRejectPdu(LoginStatus.INITIATOR_ERROR);
115                 throw new InternetSCSIException("negotiation failure");
116             }
117 
118             // ** authentication ** (part two)
119             if (!authenticated) {
120                 if (authMethodValues.contains(TextKeyword.NONE)) {
121 
122                     authenticated = true;
123                     responseKeyValuePairs.add(TextParameter.toKeyValuePair(TextKeyword.AUTH_METHOD,// key
124                             TextKeyword.NONE));// value
125 
126                     // concatenate key value pairs to single string
127                     final String responseString = TextParameter.concatenateKeyValuePairs(responseKeyValuePairs);
128 
129                     if (LOGGER.isDebugEnabled()) LOGGER.debug("response: " + responseString);
130 
131                     // send reply (sequence), set transit bit of last PDU
132                     sendPduSequence(responseString, requestedNextStageNumber);
133 
134                     // leave this (and proceed to next) stage
135                     if (requestedNextStageNumber == LoginStage.LOGIN_OPERATIONAL_NEGOTIATION || requestedNextStageNumber == LoginStage.FULL_FEATURE_PHASE) {
136                         nextStageNumber = requestedNextStageNumber;
137                         return;
138                     }
139                 } else {
140                     // TODO support CHAP (and use String
141                     // authMethodKeyValuePairs)
142                     LOGGER.error("initiator attempted CHAP authentication");
143                     // nextStageNumber = null;//no change
144                     return;
145                 }
146 
147             }
148 
149         } while (!bhs.isFinalFlag() && !authenticated);
150     }
151 
152     /**
153      * Checks if a the parameter is the key of an AuthMethod Key, which means one of the following (where &#60;key&#62;
154      * depends on the AuthMethod prefix):
155      * <ul>
156      * <li>CHAP_&#60;key&#62;/il>
157      * <li>KRB_&#60;key&#62;/il>
158      * <li>SPKM_&#60;key&#62;/il>
159      * <li>SRP_&#60;key&#62;/il>
160      * </ul>
161      * 
162      * @param <i>key</i> part of a <i>key-value</i> pair
163      * @return <code>true</code> if the String is an AuthMethod key, <code>false</code> if it is not.
164      */
165     private final boolean isAuthenticationKey (final String key) {
166         if (key == null || key.length() < 5) return false;
167         final String fourChars = key.substring(0, 4);
168         final String fiveChars = key.substring(0, 5);
169         if ("CHAP_".matches(fiveChars) || "KRB_".matches(fourChars) || "SPKM_".matches(fiveChars) || "SRP_".matches(fourChars) || (key.length() >= 10 && "TargetAuth".matches(key.substring(0, 10)))) return true;
170         return false;
171     }
172 }