1/*-
2 * Copyright (c) 2005-2010 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$");
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#include <sys/ioctl.h>
43#include <stdio.h>
44#include <stdlib.h>
45#include <string.h>
46
47#include <dev/iscsi_initiator/iscsi.h>
48#include "iscontrol.h"
49
50static 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
66static char *status_class3[] = {
67     "Target error",
68     "Service unavailable",
69     "Out of resources"
70};
71#define CLASS3_ERRS ((sizeof status_class3) / sizeof(char *))
72
73static char *
74selectFrom(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
98static char *
99getkeyval(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_addr;
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
119static int
120handleTgtResp(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
153static void
154processParams(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_addr;
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, (char **)NULL, 0);
199	       } else  if(strncmp(ptr, "TargetPortalGroupTag", klen) == 0) {
200		    op->targetPortalGroupTag = strtol(eq+1, (char **)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, (char **)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
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