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