13584Ssos/*-
23584Ssos * Copyright (c) 2005-2010 Daniel Braniss <danny@cs.huji.ac.il>
3230132Suqs * All rights reserved.
411397Sswallace *
53584Ssos * Redistribution and use in source and binary forms, with or without
63584Ssos * modification, are permitted provided that the following conditions
73584Ssos * are met:
83584Ssos * 1. Redistributions of source code must retain the above copyright
93584Ssos *    notice, this list of conditions and the following disclaimer.
103584Ssos * 2. Redistributions in binary form must reproduce the above copyright
113584Ssos *    notice, this list of conditions and the following disclaimer in the
123584Ssos *    documentation and/or other materials provided with the distribution.
133584Ssos *
143584Ssos * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
153584Ssos * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
163584Ssos * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
1797748Sschweikh * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
183584Ssos * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
193584Ssos * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
203584Ssos * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
213584Ssos * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
223584Ssos * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
233584Ssos * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
243584Ssos * SUCH DAMAGE.
253584Ssos *
263584Ssos */
273584Ssos/*
283584Ssos | $Id: login.c,v 1.4 2007/04/27 07:40:40 danny Exp danny $
293584Ssos */
303584Ssos
31115684Sobrien#include <sys/cdefs.h>
32115684Sobrien__FBSDID("$FreeBSD$");
33115684Sobrien
343584Ssos#include <sys/param.h>
3511397Sswallace#include <sys/types.h>
3614885Sswallace#include <sys/socket.h>
3711397Sswallace#include <sys/sysctl.h>
38162954Sphk
3991388Srobert#include <netinet/in.h>
4011397Sswallace#include <netinet/tcp.h>
41141488Sjhb#include <arpa/inet.h>
4224205Sbde#include <sys/ioctl.h>
4311397Sswallace#include <stdio.h>
44141488Sjhb#include <stdlib.h>
4511397Sswallace#include <string.h>
46160241Sjhb
47141488Sjhb#include <dev/iscsi_initiator/iscsi.h>
4811397Sswallace#include "iscontrol.h"
493584Ssos
503584Ssosstatic char *status_class1[] = {
5111397Sswallace     "Initiator error",
5214885Sswallace     "Authentication failure",
5311397Sswallace     "Authorization failure",
5411397Sswallace     "Not found",
5511397Sswallace     "Target removed",
5611397Sswallace     "Unsupported version",
5711397Sswallace     "Too many connections",
583584Ssos     "Missing parameter",
5954655Seivind     "Can't include in session",
6011397Sswallace     "Session type not suported",
6111397Sswallace     "Session does not exist",
6211397Sswallace     "Invalid during login",
6383366Sjulian};
643584Ssos#define CLASS1_ERRS ((sizeof status_class1) / sizeof(char *))
6583366Sjulian
6611397Sswallacestatic char *status_class3[] = {
67147820Sjhb     "Target error",
68147820Sjhb     "Service unavailable",
693584Ssos     "Out of resources"
7011397Sswallace};
7111397Sswallace#define CLASS3_ERRS ((sizeof status_class3) / sizeof(char *))
7211397Sswallace
73160798Sjhbstatic char *
74147820SjhbselectFrom(char *str, token_t *list)
75160798Sjhb{
76147820Sjhb     char	*sep, *sp;
77147820Sjhb     token_t	*lp;
783584Ssos     int	n;
793584Ssos
8011397Sswallace     sp = str;
8183366Sjulian     do {
8283366Sjulian	  sep = strchr(sp, ',');
8311397Sswallace	  if(sep != NULL)
8411397Sswallace	       n = sep - sp;
85160192Sjhb	  else
863584Ssos	       n = strlen(sp);
8711397Sswallace
88160192Sjhb	  for(lp = list; lp->name != NULL; lp++) {
89160192Sjhb	       if(strncasecmp(lp->name, sp, n) == 0)
90160192Sjhb		    return strdup(lp->name);
91160192Sjhb	  }
92160192Sjhb	  sp = sep + 1;
93160192Sjhb     } while(sep != NULL);
9411397Sswallace
9511397Sswallace     return NULL;
9611397Sswallace}
9783366Sjulian
9883366Sjulianstatic char *
9911397Sswallacegetkeyval(char *key, pdu_t *pp)
1003584Ssos{
10111397Sswallace    char	*ptr;
1023584Ssos    int	klen, len, n;
10311397Sswallace
104107849Salfred    debug_called(3);
105107849Salfred
106225617Skmacy    len = pp->ds_len;
1073584Ssos    ptr = (char *)pp->ds_addr;
1083584Ssos    klen = strlen(key);
1093584Ssos    while(len > klen) {
11011397Sswallace	 if(strncmp(key, ptr, klen) == 0)
11183366Sjulian	      return ptr+klen;
11283366Sjulian	 n = strlen(ptr) + 1;
11311397Sswallace	 len -= n;
1143584Ssos	 ptr += n;
11511397Sswallace    }
11626819Ssef    return 0;
117103870Salfred}
11826819Ssef
11926819Ssefstatic int
12026819SsefhandleTgtResp(isess_t *sess, pdu_t *pp)
12126819Ssef{
12211397Sswallace     isc_opt_t	*op = sess->op;
12311397Sswallace     char	*np, *rp, *d1, *d2;
12411397Sswallace     int	res, l1, l2;
12511397Sswallace
12611397Sswallace     res = -1;
127110299Sphk     if(((np = getkeyval("CHAP_N=", pp)) == NULL) ||
128110299Sphk	((rp = getkeyval("CHAP_R=", pp)) == NULL))
12911397Sswallace	  goto out;
130107849Salfred     if(strcmp(np, op->tgtChapName? op->tgtChapName: op->initiatorName) != 0) {
13126819Ssef	  fprintf(stderr, "%s does not match\n", np);
13211397Sswallace	  goto out;
13311397Sswallace     }
13411397Sswallace     l1 = str2bin(op->tgtChapDigest, &d1);
13583366Sjulian     l2 = str2bin(rp, &d2);
13611397Sswallace
1373584Ssos     debug(3, "l1=%d '%s' l2=%d '%s'", l1, op->tgtChapDigest, l2, rp);
1383584Ssos     if(l1 == l2 && memcmp(d1, d2, l1) == 0)
139107849Salfred	res = 0;
140107849Salfred     if(l1)
1413584Ssos	  free(d1);
142167086Sjhb     if(l2)
1433584Ssos	  free(d2);
1443584Ssos out:
1453584Ssos     free(op->tgtChapDigest);
14611397Sswallace     op->tgtChapDigest = NULL;
14783366Sjulian
1483584Ssos     debug(3, "res=%d", res);
1493584Ssos
1503584Ssos     return res;
1513584Ssos}
1523584Ssos
1533584Ssosstatic void
1543584SsosprocessParams(isess_t *sess, pdu_t *pp)
1553584Ssos{
1563584Ssos     isc_opt_t		*op = sess->op;
1573584Ssos     int		len, klen, n;
1583584Ssos     char		*eq, *ptr;
1593584Ssos
1603584Ssos     debug_called(3);
1613584Ssos
1623584Ssos     len = pp->ds_len;
16311397Sswallace     ptr = (char *)pp->ds_addr;
1643584Ssos     while(len > 0) {
16541514Sarchie	  if(vflag > 1)
16641514Sarchie	       printf("got: len=%d %s\n", len, ptr);
16791393Srobert	  klen = 0;
16891388Srobert	  if((eq = strchr(ptr, '=')) != NULL)
16941514Sarchie	       klen = eq - ptr;
17041514Sarchie	  if(klen > 0) {
17141514Sarchie	       if(strncmp(ptr, "TargetAddress", klen) == 0) {
17241514Sarchie		    char	*p, *q, *ta = NULL;
17341514Sarchie
17441514Sarchie		    // TargetAddress=domainname[:port][,portal-group-tag]
17541514Sarchie		    // XXX: if(op->targetAddress) free(op->targetAddress);
17641514Sarchie		    q = op->targetAddress = strdup(eq+1);
17741514Sarchie		    if(*q == '[') {
17841514Sarchie			 // bracketed IPv6
17941514Sarchie			 if((q = strchr(q, ']')) != NULL) {
18041514Sarchie			      *q++ = '\0';
1813584Ssos			      ta = op->targetAddress;
1823584Ssos			      op->targetAddress = strdup(ta+1);
1833584Ssos			 } else
18438354Sbde			      q = op->targetAddress;
18538354Sbde		    }
1863584Ssos		    if((p = strchr(q, ',')) != NULL) {
1873584Ssos			 *p++ = 0;
1883584Ssos			 op->targetPortalGroupTag = atoi(p);
1893584Ssos		    }
19083366Sjulian		    if((p = strchr(q, ':')) != NULL) {
1913584Ssos			 *p++ = 0;
19211397Sswallace			 op->port = atoi(p);
19383366Sjulian		    }
19411397Sswallace		    if(ta)
1953584Ssos			 free(ta);
19614885Sswallace	       } else if(strncmp(ptr, "MaxRecvDataSegmentLength", klen) == 0) {
19714885Sswallace		    // danny's RFC
19883366Sjulian		    op->maxXmitDataSegmentLength = strtol(eq+1, (char **)NULL, 0);
19914885Sswallace	       } else  if(strncmp(ptr, "TargetPortalGroupTag", klen) == 0) {
200141488Sjhb		    op->targetPortalGroupTag = strtol(eq+1, (char **)NULL, 0);
201141488Sjhb	       } else if(strncmp(ptr, "HeaderDigest", klen) == 0) {
20214885Sswallace		    op->headerDigest = selectFrom(eq+1, DigestMethods);
203141488Sjhb	       } else if(strncmp(ptr, "DataDigest", klen) == 0) {
204141488Sjhb		    op->dataDigest = selectFrom(eq+1, DigestMethods);
205141488Sjhb	       } else if(strncmp(ptr, "MaxOutstandingR2T", klen) == 0)
206141488Sjhb		    op->maxOutstandingR2T = strtol(eq+1, (char **)NULL, 0);
207141488Sjhb#if 0
208141488Sjhb	       else
209141488Sjhb	       for(kp = keyMap; kp->name; kp++) {
21014885Sswallace		    if(strncmp(ptr, kp->name, kp->len) == 0 && ptr[kp->len] == '=')
211141488Sjhb			 mp->func(sess, ptr+kp->len+1, GET);
212155401Sjhb	       }
213141488Sjhb#endif
214141488Sjhb	  }
21514885Sswallace	  n = strlen(ptr) + 1;
216	  len -= n;
217	  ptr += n;
218     }
219
220}
221
222static int
223handleLoginResp(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	  uint	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
279static int
280handleChap(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, (char **)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
322static int
323authenticate(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
365int
366loginPhase(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