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 } 438