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 *
selectFrom(char * str,token_t * list)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 *
getkeyval(char * key,pdu_t * pp)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
handleTgtResp(isess_t * sess,pdu_t * pp)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
processParams(isess_t * sess,pdu_t * pp)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
handleLoginResp(isess_t * sess,pdu_t * pp)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
handleChap(isess_t * sess,pdu_t * pp)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
authenticate(isess_t * sess)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
loginPhase(isess_t * sess)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 }
438