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