xref: /dragonfly/sbin/iscontrol/login.c (revision e0ecab34)
1 /*-
2  * Copyright (c) 2005-2008 Daniel Braniss <danny@cs.huji.ac.il>
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  *    notice, this list of conditions and the following disclaimer in the
12  *    documentation and/or other materials provided with the distribution.
13  *
14  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24  * SUCH DAMAGE.
25  *
26  */
27 /*
28  | $Id: login.c,v 1.4 2007/04/27 07:40:40 danny Exp danny $
29  */
30 
31 #include <sys/cdefs.h>
32 
33 #include <sys/param.h>
34 #include <sys/types.h>
35 #include <sys/socket.h>
36 #include <sys/sysctl.h>
37 
38 #include <netinet/in.h>
39 #include <netinet/tcp.h>
40 #include <arpa/inet.h>
41 #if __FreeBSD_version < 500000
42 #include <sys/time.h>
43 #endif
44 #include <sys/ioctl.h>
45 #include <stdio.h>
46 #include <stdlib.h>
47 #include <string.h>
48 
49 #include "iscsi.h"
50 #include "iscontrol.h"
51 
52 static char *status_class1[] = {
53      "Initiator error",
54      "Authentication failure",
55      "Authorization failure",
56      "Not found",
57      "Target removed",
58      "Unsupported version",
59      "Too many connections",
60      "Missing parameter",
61      "Can't include in session",
62      "Session type not suported",
63      "Session does not exist",
64      "Invalid during login",
65 };
66 #define CLASS1_ERRS ((sizeof status_class1) / sizeof(char *))
67 
68 static char *status_class3[] = {
69      "Target error",
70      "Service unavailable",
71      "Out of resources"
72 };
73 #define CLASS3_ERRS ((sizeof status_class3) / sizeof(char *))
74 
75 static char *
76 selectFrom(char *str, token_t *list)
77 {
78      char	*sep, *sp;
79      token_t	*lp;
80      int	n;
81 
82      sp = str;
83      do {
84 	  sep = strchr(sp, ',');
85 	  if(sep != NULL)
86 	       n = sep - sp;
87 	  else
88 	       n = strlen(sp);
89 
90 	  for(lp = list; lp->name != NULL; lp++) {
91 	       if(strncasecmp(lp->name, sp, n) == 0)
92 		    return strdup(lp->name);
93 	  }
94 	  sp = sep + 1;
95      } while(sep != NULL);
96 
97      return NULL;
98 }
99 
100 static char *
101 getkeyval(char *key, pdu_t *pp)
102 {
103     char	*ptr;
104     int	klen, len, n;
105 
106     debug_called(3);
107 
108     len = pp->ds_len;
109     ptr = (char *)pp->ds;
110     klen = strlen(key);
111     while(len > klen) {
112 	 if(strncmp(key, ptr, klen) == 0)
113 	      return ptr+klen;
114 	 n = strlen(ptr) + 1;
115 	 len -= n;
116 	 ptr += n;
117     }
118     return 0;
119 }
120 
121 static int
122 handleTgtResp(isess_t *sess, pdu_t *pp)
123 {
124      isc_opt_t	*op = sess->op;
125      char	*np, *rp, *d1, *d2;
126      int	res, l1, l2;
127 
128      res = -1;
129      if(((np = getkeyval("CHAP_N=", pp)) == NULL) ||
130 	((rp = getkeyval("CHAP_R=", pp)) == NULL))
131 	  goto out;
132      if(strcmp(np, op->tgtChapName? op->tgtChapName: op->initiatorName) != 0) {
133 	  fprintf(stderr, "%s does not match\n", np);
134 	  goto out;
135      }
136      l1 = str2bin(op->tgtChapDigest, &d1);
137      l2 = str2bin(rp, &d2);
138 
139      debug(3, "l1=%d '%s' l2=%d '%s'", l1, op->tgtChapDigest, l2, rp);
140      if(l1 == l2 && memcmp(d1, d2, l1) == 0)
141 	res = 0;
142      if(l1)
143 	  free(d1);
144      if(l2)
145 	  free(d2);
146  out:
147      free(op->tgtChapDigest);
148      op->tgtChapDigest = NULL;
149 
150      debug(3, "res=%d", res);
151 
152      return res;
153 }
154 
155 static void
156 processParams(isess_t *sess, pdu_t *pp)
157 {
158      isc_opt_t		*op = sess->op;
159      int		len, klen, n;
160      char		*eq, *ptr;
161 
162      debug_called(3);
163 
164      len = pp->ds_len;
165      ptr = (char *)pp->ds;
166      while(len > 0) {
167 	  if(vflag > 1)
168 	       printf("got: len=%d %s\n", len, ptr);
169 	  klen = 0;
170 	  if((eq = strchr(ptr, '=')) != NULL)
171 	       klen = eq - ptr;
172 	  if(klen > 0) {
173 	       if(strncmp(ptr, "TargetAddress", klen) == 0) {
174 		    char	*p, *q, *ta = NULL;
175 
176 		    // TargetAddress=domainname[:port][,portal-group-tag]
177 		    // XXX: if(op->targetAddress) free(op->targetAddress);
178 		    q = op->targetAddress = strdup(eq+1);
179 		    if(*q == '[') {
180 			 // bracketed IPv6
181 			 if((q = strchr(q, ']')) != NULL) {
182 			      *q++ = '\0';
183 			      ta = op->targetAddress;
184 			      op->targetAddress = strdup(ta+1);
185 			 } else
186 			      q = op->targetAddress;
187 		    }
188 		    if((p = strchr(q, ',')) != NULL) {
189 			 *p++ = 0;
190 			 op->targetPortalGroupTag = atoi(p);
191 		    }
192 		    if((p = strchr(q, ':')) != NULL) {
193 			 *p++ = 0;
194 			 op->port = atoi(p);
195 		    }
196 		    if(ta)
197 			 free(ta);
198 	       } else if(strncmp(ptr, "MaxRecvDataSegmentLength", klen) == 0) {
199 		    // danny's RFC
200 		    op->maxXmitDataSegmentLength = strtol(eq+1, (char **)NULL, 0);
201 	       } else  if(strncmp(ptr, "TargetPortalGroupTag", klen) == 0) {
202 		    op->targetPortalGroupTag = strtol(eq+1, (char **)NULL, 0);
203 	       } else if(strncmp(ptr, "HeaderDigest", klen) == 0) {
204 		    op->headerDigest = selectFrom(eq+1, DigestMethods);
205 	       } else if(strncmp(ptr, "DataDigest", klen) == 0) {
206 		    op->dataDigest = selectFrom(eq+1, DigestMethods);
207 	       } else if(strncmp(ptr, "MaxOutstandingR2T", klen) == 0)
208 		    op->maxOutstandingR2T = strtol(eq+1, (char **)NULL, 0);
209 #if 0
210 	       else
211 	       for(kp = keyMap; kp->name; kp++) {
212 		    if(strncmp(ptr, kp->name, kp->len) == 0 && ptr[kp->len] == '=')
213 			 mp->func(sess, ptr+kp->len+1, GET);
214 	       }
215 #endif
216 	  }
217 	  n = strlen(ptr) + 1;
218 	  len -= n;
219 	  ptr += n;
220      }
221 
222 }
223 
224 static int
225 handleLoginResp(isess_t *sess, pdu_t *pp)
226 {
227      login_rsp_t *lp = (login_rsp_t *)pp;
228      uint	st_class, status = ntohs(lp->status);
229 
230      debug_called(3);
231      debug(4, "Tbit=%d csg=%d nsg=%d status=%x", lp->T, lp->CSG, lp->NSG, status);
232 
233      st_class  = status >> 8;
234      if(status) {
235 	  unsigned int st_detail = status & 0xff;
236 
237 	  switch(st_class) {
238 	  case 1: // Redirect
239 	       switch(st_detail) {
240 		    // the ITN (iSCSI target Name) requests a:
241 	       case 1: // temporary address change
242 	       case 2: // permanent address change
243 		    status = 0;
244 	       }
245 	       break;
246 
247 	  case 2: // Initiator Error
248 	       if(st_detail < CLASS1_ERRS)
249 		    printf("0x%04x: %s\n", status, status_class1[st_detail]);
250 	       break;
251 
252 	  case 3:
253 	       if(st_detail < CLASS3_ERRS)
254 		    printf("0x%04x: %s\n", status, status_class3[st_detail]);
255 	       break;
256 	  }
257      }
258 
259      if(status == 0) {
260 	  processParams(sess, pp);
261 	  setOptions(sess, 0); // XXX: just in case ...
262 
263 	  if(lp->T) {
264 	       isc_opt_t	*op = sess->op;
265 
266 	       if(sess->csg == SN_PHASE && (op->tgtChapDigest != NULL))
267 		    if(handleTgtResp(sess, pp) != 0)
268 			 return 1; // XXX: Authentication failure ...
269 	       sess->csg = lp->NSG;
270 	       if(sess->csg == FF_PHASE) {
271 		    // XXX: will need this when implementing reconnect.
272 		    sess->tsih = lp->tsih;
273 		    debug(2, "TSIH=%x", sess->tsih);
274 	       }
275 	  }
276      }
277 
278      return st_class;
279 }
280 
281 static int
282 handleChap(isess_t *sess, pdu_t *pp)
283 {
284      pdu_t		spp;
285      login_req_t	*lp;
286      isc_opt_t		*op = sess->op;
287      char		*ap, *ip, *cp, *digest; // MD5 is 128bits, SHA1 160bits
288 
289      debug_called(3);
290 
291      bzero(&spp, sizeof(pdu_t));
292      lp = (login_req_t *)&spp.ipdu.bhs;
293      lp->cmd = ISCSI_LOGIN_CMD | 0x40; // login request + Inmediate
294      memcpy(lp->isid, sess->isid, 6);
295      lp->tsih = sess->tsih;    // MUST be zero the first time!
296      lp->CID = htons(1);
297      lp->CSG = SN_PHASE;       // Security Negotiation
298      lp->NSG = LON_PHASE;
299      lp->T = 1;
300 
301      if(((ap = getkeyval("CHAP_A=", pp)) == NULL) ||
302 	((ip = getkeyval("CHAP_I=", pp)) == NULL) ||
303 	((cp = getkeyval("CHAP_C=", pp)) == NULL))
304 	  return -1;
305 
306      if((digest = chapDigest(ap, (char)strtol(ip, (char **)NULL, 0), cp, op->chapSecret)) == NULL)
307 	  return -1;
308 
309      addText(&spp, "CHAP_N=%s", op->chapIName? op->chapIName: op->initiatorName);
310      addText(&spp, "CHAP_R=%s", digest);
311      free(digest);
312 
313      if(op->tgtChapSecret != NULL) {
314 	  op->tgtChapID = (random() >> 24) % 255; // should be random enough ...
315 	  addText(&spp, "CHAP_I=%d", op->tgtChapID);
316 	  cp = genChapChallenge(cp, op->tgtChallengeLen? op->tgtChallengeLen: 8);
317 	  addText(&spp, "CHAP_C=%s", cp);
318 	  op->tgtChapDigest = chapDigest(ap, op->tgtChapID, cp, op->tgtChapSecret);
319      }
320 
321      return sendPDU(sess, &spp, handleLoginResp);
322 }
323 
324 static int
325 authenticate(isess_t *sess)
326 {
327      pdu_t		spp;
328      login_req_t	*lp;
329      isc_opt_t	*op = sess->op;
330 
331      bzero(&spp, sizeof(pdu_t));
332      lp = (login_req_t *)&spp.ipdu.bhs;
333      lp->cmd = ISCSI_LOGIN_CMD | 0x40; // login request + Inmediate
334      memcpy(lp->isid, sess->isid, 6);
335      lp->tsih = sess->tsih;	// MUST be zero the first time!
336      lp->CID = htons(1);
337      lp->CSG = SN_PHASE;	// Security Negotiation
338      lp->NSG = SN_PHASE;
339      lp->T = 0;
340 
341      switch((authm_t)lookup(AuthMethods, op->authMethod)) {
342      case NONE:
343 	  return 0;
344 
345      case KRB5:
346      case SPKM1:
347      case SPKM2:
348      case SRP:
349 	  return 2;
350 
351      case CHAP:
352 	  if(op->chapDigest == 0)
353 	       addText(&spp, "CHAP_A=5");
354 	  else
355 	  if(strcmp(op->chapDigest, "MD5") == 0)
356 	       addText(&spp, "CHAP_A=5");
357 	  else
358 	  if(strcmp(op->chapDigest, "SHA1") == 0)
359 	       addText(&spp, "CHAP_A=7");
360 	  else
361 	       addText(&spp, "CHAP_A=5,7");
362 	  return sendPDU(sess, &spp, handleChap);
363      }
364      return 1;
365 }
366 
367 int
368 loginPhase(isess_t *sess)
369 {
370      pdu_t		spp, *sp = &spp;
371      isc_opt_t  	*op = sess->op;
372      login_req_t	*lp;
373      int		status = 1;
374 
375      debug_called(3);
376 
377      bzero(sp, sizeof(pdu_t));
378      lp = (login_req_t *)&spp.ipdu.bhs;
379      lp->cmd = ISCSI_LOGIN_CMD | 0x40; // login request + Inmediate
380      memcpy(lp->isid, sess->isid, 6);
381      lp->tsih = sess->tsih;	// MUST be zero the first time!
382      lp->CID = htons(1);	// sess->cid?
383 
384      if((lp->CSG = sess->csg) == LON_PHASE)
385 	  lp->NSG = FF_PHASE;	// lets try and go full feature ...
386      else
387 	  lp->NSG = LON_PHASE;
388      lp->T = 1;			// transit to next login stage
389 
390      if(sess->flags & SESS_INITIALLOGIN1) {
391 	  sess->flags &= ~SESS_INITIALLOGIN1;
392 
393 	  addText(sp, "SessionType=%s", op->sessionType);
394 	  addText(sp, "InitiatorName=%s", op->initiatorName);
395 	  if(strcmp(op->sessionType, "Discovery") != 0) {
396 	       addText(sp, "TargetName=%s", op->targetName);
397 	  }
398      }
399      switch(sess->csg) {
400      case SN_PHASE:	// Security Negotiation
401 	  addText(sp, "AuthMethod=%s", op->authMethod);
402 	  break;
403 
404      case LON_PHASE:	// Login Operational Negotiation
405 	  if((sess->flags & SESS_NEGODONE) == 0) {
406 	       sess->flags |= SESS_NEGODONE;
407 	       addText(sp, "MaxBurstLength=%d", op->maxBurstLength);
408 	       addText(sp, "HeaderDigest=%s", op->headerDigest);
409 	       addText(sp, "DataDigest=%s", op->dataDigest);
410 	       addText(sp, "MaxRecvDataSegmentLength=%d", op->maxRecvDataSegmentLength);
411 	       addText(sp, "ErrorRecoveryLevel=%d", op->errorRecoveryLevel);
412 	       addText(sp, "DefaultTime2Wait=%d", op->defaultTime2Wait);
413 	       addText(sp, "DefaultTime2Retain=%d", op->defaultTime2Retain);
414 	       addText(sp, "DataPDUInOrder=%s", op->dataPDUInOrder? "Yes": "No");
415 	       addText(sp, "DataSequenceInOrder=%s", op->dataSequenceInOrder? "Yes": "No");
416 	       addText(sp, "MaxOutstandingR2T=%d", op->maxOutstandingR2T);
417 
418 	       if(strcmp(op->sessionType, "Discovery") != 0) {
419 		    addText(sp, "MaxConnections=%d", op->maxConnections);
420 		    addText(sp, "FirstBurstLength=%d", op->firstBurstLength);
421 		    addText(sp, "InitialR2T=%s", op->initialR2T? "Yes": "No");
422 		    addText(sp, "ImmediateData=%s", op->immediateData? "Yes": "No");
423 	       }
424 	  }
425 
426 	  break;
427      }
428 
429      status = sendPDU(sess, &spp, handleLoginResp);
430 
431      switch(status) {
432      case 0: // all is ok ...
433 	  if(sess->csg == SN_PHASE)
434 	       /*
435 		| if we are still here, then we need
436 		| to exchange some secrets ...
437 	        */
438 	       status = authenticate(sess);
439      }
440 
441      return status;
442 }
443