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