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