]> CyberLeo.Net >> Repos - FreeBSD/releng/8.1.git/blob - sbin/iscontrol/login.c
Copy stable/8 to releng/8.1 in preparation for 8.1-RC1.
[FreeBSD/releng/8.1.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 __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 #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
53 static 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
69 static char *status_class3[] = {
70      "Target error",
71      "Service unavailable",
72      "Out of resources"
73 };
74 #define CLASS3_ERRS ((sizeof status_class3) / sizeof(char *))
75
76 static char *
77 selectFrom(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
101 static char *
102 getkeyval(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
122 static int
123 handleTgtResp(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
156 static void
157 processParams(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
225 static int
226 handleLoginResp(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
282 static int
283 handleChap(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
325 static int
326 authenticate(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
368 int
369 loginPhase(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 }