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