1255570Strasz/*- 2255570Strasz * Copyright (c) 2012 The FreeBSD Foundation 3255570Strasz * All rights reserved. 4255570Strasz * 5255570Strasz * This software was developed by Edward Tomasz Napierala under sponsorship 6255570Strasz * from the FreeBSD Foundation. 7255570Strasz * 8255570Strasz * Redistribution and use in source and binary forms, with or without 9255570Strasz * modification, are permitted provided that the following conditions 10255570Strasz * are met: 11255570Strasz * 1. Redistributions of source code must retain the above copyright 12255570Strasz * notice, this list of conditions and the following disclaimer. 13255570Strasz * 2. Redistributions in binary form must reproduce the above copyright 14255570Strasz * notice, this list of conditions and the following disclaimer in the 15255570Strasz * documentation and/or other materials provided with the distribution. 16255570Strasz * 17255570Strasz * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 18255570Strasz * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 19255570Strasz * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 20255570Strasz * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 21255570Strasz * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 22255570Strasz * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 23255570Strasz * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 24255570Strasz * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 25255570Strasz * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 26255570Strasz * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 27255570Strasz * SUCH DAMAGE. 28255570Strasz * 29255570Strasz */ 30255570Strasz 31270888Strasz#include <sys/cdefs.h> 32270888Strasz__FBSDID("$FreeBSD$"); 33270888Strasz 34255570Strasz#include <sys/types.h> 35269065Smav#include <sys/ioctl.h> 36255570Strasz#include <assert.h> 37255570Strasz#include <stdbool.h> 38255570Strasz#include <stdio.h> 39255570Strasz#include <stdlib.h> 40255570Strasz#include <string.h> 41255570Strasz#include <netinet/in.h> 42255570Strasz#include <openssl/err.h> 43255570Strasz#include <openssl/md5.h> 44255570Strasz#include <openssl/rand.h> 45255570Strasz 46255570Strasz#include "iscsid.h" 47255570Strasz#include "iscsi_proto.h" 48255570Strasz 49255570Straszstatic int 50255570Straszlogin_nsg(const struct pdu *response) 51255570Strasz{ 52255570Strasz struct iscsi_bhs_login_response *bhslr; 53255570Strasz 54255570Strasz bhslr = (struct iscsi_bhs_login_response *)response->pdu_bhs; 55255570Strasz 56255570Strasz return (bhslr->bhslr_flags & 0x03); 57255570Strasz} 58255570Strasz 59255570Straszstatic void 60255570Straszlogin_set_nsg(struct pdu *request, int nsg) 61255570Strasz{ 62255570Strasz struct iscsi_bhs_login_request *bhslr; 63255570Strasz 64255570Strasz assert(nsg == BHSLR_STAGE_SECURITY_NEGOTIATION || 65255570Strasz nsg == BHSLR_STAGE_OPERATIONAL_NEGOTIATION || 66255570Strasz nsg == BHSLR_STAGE_FULL_FEATURE_PHASE); 67255570Strasz 68255570Strasz bhslr = (struct iscsi_bhs_login_request *)request->pdu_bhs; 69255570Strasz 70255570Strasz bhslr->bhslr_flags &= 0xFC; 71255570Strasz bhslr->bhslr_flags |= nsg; 72255570Strasz} 73255570Strasz 74255570Straszstatic void 75255570Straszlogin_set_csg(struct pdu *request, int csg) 76255570Strasz{ 77255570Strasz struct iscsi_bhs_login_request *bhslr; 78255570Strasz 79255570Strasz assert(csg == BHSLR_STAGE_SECURITY_NEGOTIATION || 80255570Strasz csg == BHSLR_STAGE_OPERATIONAL_NEGOTIATION || 81255570Strasz csg == BHSLR_STAGE_FULL_FEATURE_PHASE); 82255570Strasz 83255570Strasz bhslr = (struct iscsi_bhs_login_request *)request->pdu_bhs; 84255570Strasz 85255570Strasz bhslr->bhslr_flags &= 0xF3; 86255570Strasz bhslr->bhslr_flags |= csg << 2; 87255570Strasz} 88255570Strasz 89255570Straszstatic const char * 90255570Straszlogin_target_error_str(int class, int detail) 91255570Strasz{ 92255570Strasz static char msg[128]; 93255570Strasz 94255570Strasz /* 95255570Strasz * RFC 3270, 10.13.5. Status-Class and Status-Detail 96255570Strasz */ 97255570Strasz switch (class) { 98255570Strasz case 0x01: 99255570Strasz switch (detail) { 100255570Strasz case 0x01: 101255570Strasz return ("Target moved temporarily"); 102255570Strasz case 0x02: 103255570Strasz return ("Target moved permanently"); 104255570Strasz default: 105255570Strasz snprintf(msg, sizeof(msg), "unknown redirection; " 106255570Strasz "Status-Class 0x%x, Status-Detail 0x%x", 107255570Strasz class, detail); 108255570Strasz return (msg); 109255570Strasz } 110255570Strasz case 0x02: 111255570Strasz switch (detail) { 112255570Strasz case 0x00: 113255570Strasz return ("Initiator error"); 114255570Strasz case 0x01: 115255570Strasz return ("Authentication failure"); 116255570Strasz case 0x02: 117255570Strasz return ("Authorization failure"); 118255570Strasz case 0x03: 119255570Strasz return ("Not found"); 120255570Strasz case 0x04: 121255570Strasz return ("Target removed"); 122255570Strasz case 0x05: 123255570Strasz return ("Unsupported version"); 124255570Strasz case 0x06: 125255570Strasz return ("Too many connections"); 126255570Strasz case 0x07: 127255570Strasz return ("Missing parameter"); 128255570Strasz case 0x08: 129255570Strasz return ("Can't include in session"); 130255570Strasz case 0x09: 131255570Strasz return ("Session type not supported"); 132255570Strasz case 0x0a: 133255570Strasz return ("Session does not exist"); 134255570Strasz case 0x0b: 135255570Strasz return ("Invalid during login"); 136255570Strasz default: 137255570Strasz snprintf(msg, sizeof(msg), "unknown initiator error; " 138255570Strasz "Status-Class 0x%x, Status-Detail 0x%x", 139255570Strasz class, detail); 140255570Strasz return (msg); 141255570Strasz } 142255570Strasz case 0x03: 143255570Strasz switch (detail) { 144255570Strasz case 0x00: 145255570Strasz return ("Target error"); 146255570Strasz case 0x01: 147255570Strasz return ("Service unavailable"); 148255570Strasz case 0x02: 149255570Strasz return ("Out of resources"); 150255570Strasz default: 151255570Strasz snprintf(msg, sizeof(msg), "unknown target error; " 152255570Strasz "Status-Class 0x%x, Status-Detail 0x%x", 153255570Strasz class, detail); 154255570Strasz return (msg); 155255570Strasz } 156255570Strasz default: 157255570Strasz snprintf(msg, sizeof(msg), "unknown error; " 158255570Strasz "Status-Class 0x%x, Status-Detail 0x%x", 159255570Strasz class, detail); 160255570Strasz return (msg); 161255570Strasz } 162255570Strasz} 163255570Strasz 164269065Smavstatic void 165269065Smavkernel_modify(const struct connection *conn, const char *target_address) 166269065Smav{ 167269065Smav struct iscsi_session_modify ism; 168269065Smav int error; 169269065Smav 170269065Smav memset(&ism, 0, sizeof(ism)); 171269065Smav ism.ism_session_id = conn->conn_session_id; 172269065Smav memcpy(&ism.ism_conf, &conn->conn_conf, sizeof(ism.ism_conf)); 173269065Smav strlcpy(ism.ism_conf.isc_target_addr, target_address, 174269065Smav sizeof(ism.ism_conf.isc_target)); 175269065Smav error = ioctl(conn->conn_iscsi_fd, ISCSISMODIFY, &ism); 176269065Smav if (error != 0) { 177269065Smav log_err(1, "failed to redirect to %s: ISCSISMODIFY", 178269065Smav target_address); 179269065Smav } 180269065Smav} 181269065Smav 182269065Smav/* 183269065Smav * XXX: The way it works is suboptimal; what should happen is described 184269065Smav * in draft-gilligan-iscsi-fault-tolerance-00. That, however, would 185269065Smav * be much more complicated: we would need to keep "dependencies" 186269065Smav * for sessions, so that, in case described in draft and using draft 187269065Smav * terminology, we would have three sessions: one for discovery, 188269065Smav * one for initial target portal, and one for redirect portal. 189269065Smav * This would allow us to "backtrack" on connection failure, 190269065Smav * as described in draft. 191269065Smav */ 192269065Smavstatic void 193269065Smavlogin_handle_redirection(struct connection *conn, struct pdu *response) 194269065Smav{ 195269065Smav struct iscsi_bhs_login_response *bhslr; 196269065Smav struct keys *response_keys; 197269065Smav const char *target_address; 198269065Smav 199269065Smav bhslr = (struct iscsi_bhs_login_response *)response->pdu_bhs; 200269065Smav assert (bhslr->bhslr_status_class == 1); 201269065Smav 202269065Smav response_keys = keys_new(); 203269065Smav keys_load(response_keys, response); 204269065Smav 205269065Smav target_address = keys_find(response_keys, "TargetAddress"); 206269065Smav if (target_address == NULL) 207269065Smav log_errx(1, "received redirection without TargetAddress"); 208269065Smav if (target_address[0] == '\0') 209269065Smav log_errx(1, "received redirection with empty TargetAddress"); 210269065Smav if (strlen(target_address) >= 211269065Smav sizeof(conn->conn_conf.isc_target_addr) - 1) 212269065Smav log_errx(1, "received TargetAddress is too long"); 213269065Smav 214269065Smav log_debugx("received redirection to \"%s\"", target_address); 215269065Smav kernel_modify(conn, target_address); 216269065Smav} 217269065Smav 218255570Straszstatic struct pdu * 219269069Smavlogin_receive(struct connection *conn) 220255570Strasz{ 221255570Strasz struct pdu *response; 222255570Strasz struct iscsi_bhs_login_response *bhslr; 223255570Strasz const char *errorstr; 224269069Smav static bool initial = true; 225255570Strasz 226255570Strasz response = pdu_new(conn); 227255570Strasz pdu_receive(response); 228255570Strasz if (response->pdu_bhs->bhs_opcode != ISCSI_BHS_OPCODE_LOGIN_RESPONSE) { 229255570Strasz log_errx(1, "protocol error: received invalid opcode 0x%x", 230255570Strasz response->pdu_bhs->bhs_opcode); 231255570Strasz } 232255570Strasz bhslr = (struct iscsi_bhs_login_response *)response->pdu_bhs; 233255570Strasz /* 234255570Strasz * XXX: Implement the C flag some day. 235255570Strasz */ 236255570Strasz if ((bhslr->bhslr_flags & BHSLR_FLAGS_CONTINUE) != 0) 237255570Strasz log_errx(1, "received Login PDU with unsupported \"C\" flag"); 238255570Strasz if (bhslr->bhslr_version_max != 0x00) 239255570Strasz log_errx(1, "received Login PDU with unsupported " 240255570Strasz "Version-max 0x%x", bhslr->bhslr_version_max); 241255570Strasz if (bhslr->bhslr_version_active != 0x00) 242255570Strasz log_errx(1, "received Login PDU with unsupported " 243255570Strasz "Version-active 0x%x", bhslr->bhslr_version_active); 244269065Smav if (bhslr->bhslr_status_class == 1) { 245269065Smav login_handle_redirection(conn, response); 246269065Smav log_debugx("redirection handled; exiting"); 247269065Smav exit(0); 248269065Smav } 249255570Strasz if (bhslr->bhslr_status_class != 0) { 250255570Strasz errorstr = login_target_error_str(bhslr->bhslr_status_class, 251255570Strasz bhslr->bhslr_status_detail); 252255570Strasz fail(conn, errorstr); 253255570Strasz log_errx(1, "target returned error: %s", errorstr); 254255570Strasz } 255255570Strasz if (initial == false && 256255570Strasz ntohl(bhslr->bhslr_statsn) != conn->conn_statsn + 1) { 257255570Strasz /* 258255570Strasz * It's a warning, not an error, to work around what seems 259255570Strasz * to be bug in NetBSD iSCSI target. 260255570Strasz */ 261255570Strasz log_warnx("received Login PDU with wrong StatSN: " 262255570Strasz "is %d, should be %d", ntohl(bhslr->bhslr_statsn), 263255570Strasz conn->conn_statsn + 1); 264255570Strasz } 265268703Smav conn->conn_tsih = ntohs(bhslr->bhslr_tsih); 266255570Strasz conn->conn_statsn = ntohl(bhslr->bhslr_statsn); 267255570Strasz 268269069Smav initial = false; 269269069Smav 270255570Strasz return (response); 271255570Strasz} 272255570Strasz 273255570Straszstatic struct pdu * 274269068Smavlogin_new_request(struct connection *conn, int csg) 275255570Strasz{ 276255570Strasz struct pdu *request; 277255570Strasz struct iscsi_bhs_login_request *bhslr; 278269068Smav int nsg; 279255570Strasz 280255570Strasz request = pdu_new(conn); 281255570Strasz bhslr = (struct iscsi_bhs_login_request *)request->pdu_bhs; 282255570Strasz bhslr->bhslr_opcode = ISCSI_BHS_OPCODE_LOGIN_REQUEST | 283255570Strasz ISCSI_BHS_OPCODE_IMMEDIATE; 284269068Smav 285255570Strasz bhslr->bhslr_flags = BHSLR_FLAGS_TRANSIT; 286269068Smav switch (csg) { 287269068Smav case BHSLR_STAGE_SECURITY_NEGOTIATION: 288269068Smav nsg = BHSLR_STAGE_OPERATIONAL_NEGOTIATION; 289269068Smav break; 290269068Smav case BHSLR_STAGE_OPERATIONAL_NEGOTIATION: 291269068Smav nsg = BHSLR_STAGE_FULL_FEATURE_PHASE; 292269068Smav break; 293269068Smav default: 294269068Smav assert(!"invalid csg"); 295269068Smav log_errx(1, "invalid csg %d", csg); 296269068Smav } 297269068Smav login_set_csg(request, csg); 298269068Smav login_set_nsg(request, nsg); 299269068Smav 300255570Strasz memcpy(bhslr->bhslr_isid, &conn->conn_isid, sizeof(bhslr->bhslr_isid)); 301268703Smav bhslr->bhslr_tsih = htons(conn->conn_tsih); 302255570Strasz bhslr->bhslr_initiator_task_tag = 0; 303255570Strasz bhslr->bhslr_cmdsn = 0; 304255570Strasz bhslr->bhslr_expstatsn = htonl(conn->conn_statsn + 1); 305255570Strasz 306255570Strasz return (request); 307255570Strasz} 308255570Strasz 309255570Straszstatic int 310255570Straszlogin_list_prefers(const char *list, 311255570Strasz const char *choice1, const char *choice2) 312255570Strasz{ 313255570Strasz char *tofree, *str, *token; 314255570Strasz 315255570Strasz tofree = str = checked_strdup(list); 316255570Strasz 317255570Strasz while ((token = strsep(&str, ",")) != NULL) { 318255570Strasz if (strcmp(token, choice1) == 0) { 319255570Strasz free(tofree); 320255570Strasz return (1); 321255570Strasz } 322255570Strasz if (strcmp(token, choice2) == 0) { 323255570Strasz free(tofree); 324255570Strasz return (2); 325255570Strasz } 326255570Strasz } 327255570Strasz free(tofree); 328255570Strasz return (-1); 329255570Strasz} 330255570Strasz 331255570Straszstatic int 332255570Straszlogin_hex2int(const char hex) 333255570Strasz{ 334255570Strasz switch (hex) { 335255570Strasz case '0': 336255570Strasz return (0x00); 337255570Strasz case '1': 338255570Strasz return (0x01); 339255570Strasz case '2': 340255570Strasz return (0x02); 341255570Strasz case '3': 342255570Strasz return (0x03); 343255570Strasz case '4': 344255570Strasz return (0x04); 345255570Strasz case '5': 346255570Strasz return (0x05); 347255570Strasz case '6': 348255570Strasz return (0x06); 349255570Strasz case '7': 350255570Strasz return (0x07); 351255570Strasz case '8': 352255570Strasz return (0x08); 353255570Strasz case '9': 354255570Strasz return (0x09); 355255570Strasz case 'a': 356255570Strasz case 'A': 357255570Strasz return (0x0a); 358255570Strasz case 'b': 359255570Strasz case 'B': 360255570Strasz return (0x0b); 361255570Strasz case 'c': 362255570Strasz case 'C': 363255570Strasz return (0x0c); 364255570Strasz case 'd': 365255570Strasz case 'D': 366255570Strasz return (0x0d); 367255570Strasz case 'e': 368255570Strasz case 'E': 369255570Strasz return (0x0e); 370255570Strasz case 'f': 371255570Strasz case 'F': 372255570Strasz return (0x0f); 373255570Strasz default: 374255570Strasz return (-1); 375255570Strasz } 376255570Strasz} 377255570Strasz 378255570Strasz/* 379255570Strasz * XXX: Review this _carefully_. 380255570Strasz */ 381255570Straszstatic int 382255570Straszlogin_hex2bin(const char *hex, char **binp, size_t *bin_lenp) 383255570Strasz{ 384255570Strasz int i, hex_len, nibble; 385255570Strasz bool lo = true; /* As opposed to 'hi'. */ 386255570Strasz char *bin; 387255570Strasz size_t bin_off, bin_len; 388255570Strasz 389255570Strasz if (strncasecmp(hex, "0x", strlen("0x")) != 0) { 390255570Strasz log_warnx("malformed variable, should start with \"0x\""); 391255570Strasz return (-1); 392255570Strasz } 393255570Strasz 394255570Strasz hex += strlen("0x"); 395255570Strasz hex_len = strlen(hex); 396255570Strasz if (hex_len < 1) { 397255570Strasz log_warnx("malformed variable; doesn't contain anything " 398255570Strasz "but \"0x\""); 399255570Strasz return (-1); 400255570Strasz } 401255570Strasz 402255570Strasz bin_len = hex_len / 2 + hex_len % 2; 403255570Strasz bin = calloc(bin_len, 1); 404255570Strasz if (bin == NULL) 405255570Strasz log_err(1, "calloc"); 406255570Strasz 407255570Strasz bin_off = bin_len - 1; 408255570Strasz for (i = hex_len - 1; i >= 0; i--) { 409255570Strasz nibble = login_hex2int(hex[i]); 410255570Strasz if (nibble < 0) { 411255570Strasz log_warnx("malformed variable, invalid char \"%c\"", 412255570Strasz hex[i]); 413255570Strasz return (-1); 414255570Strasz } 415255570Strasz 416255570Strasz assert(bin_off < bin_len); 417255570Strasz if (lo) { 418255570Strasz bin[bin_off] = nibble; 419255570Strasz lo = false; 420255570Strasz } else { 421255570Strasz bin[bin_off] |= nibble << 4; 422255570Strasz bin_off--; 423255570Strasz lo = true; 424255570Strasz } 425255570Strasz } 426255570Strasz 427255570Strasz *binp = bin; 428255570Strasz *bin_lenp = bin_len; 429255570Strasz return (0); 430255570Strasz} 431255570Strasz 432255570Straszstatic char * 433255570Straszlogin_bin2hex(const char *bin, size_t bin_len) 434255570Strasz{ 435255570Strasz unsigned char *hex, *tmp, ch; 436255570Strasz size_t hex_len; 437255570Strasz size_t i; 438255570Strasz 439255570Strasz hex_len = bin_len * 2 + 3; /* +2 for "0x", +1 for '\0'. */ 440255570Strasz hex = malloc(hex_len); 441255570Strasz if (hex == NULL) 442255570Strasz log_err(1, "malloc"); 443255570Strasz 444255570Strasz tmp = hex; 445255570Strasz tmp += sprintf(tmp, "0x"); 446255570Strasz for (i = 0; i < bin_len; i++) { 447255570Strasz ch = bin[i]; 448255570Strasz tmp += sprintf(tmp, "%02x", ch); 449255570Strasz } 450255570Strasz 451255570Strasz return (hex); 452255570Strasz} 453255570Strasz 454255570Straszstatic void 455255570Straszlogin_compute_md5(const char id, const char *secret, 456255570Strasz const void *challenge, size_t challenge_len, void *response, 457255570Strasz size_t response_len) 458255570Strasz{ 459255570Strasz MD5_CTX ctx; 460255570Strasz int rv; 461255570Strasz 462255570Strasz assert(response_len == MD5_DIGEST_LENGTH); 463255570Strasz 464255570Strasz MD5_Init(&ctx); 465255570Strasz MD5_Update(&ctx, &id, sizeof(id)); 466255570Strasz MD5_Update(&ctx, secret, strlen(secret)); 467255570Strasz MD5_Update(&ctx, challenge, challenge_len); 468255570Strasz rv = MD5_Final(response, &ctx); 469255570Strasz if (rv != 1) 470255570Strasz log_errx(1, "MD5_Final"); 471255570Strasz} 472255570Strasz 473255570Straszstatic void 474255570Straszlogin_negotiate_key(struct connection *conn, const char *name, 475255570Strasz const char *value) 476255570Strasz{ 477255570Strasz int which, tmp; 478255570Strasz 479255570Strasz if (strcmp(name, "TargetAlias") == 0) { 480255570Strasz strlcpy(conn->conn_target_alias, value, 481255570Strasz sizeof(conn->conn_target_alias)); 482255570Strasz } else if (strcmp(value, "Irrelevant") == 0) { 483255570Strasz /* Ignore. */ 484255570Strasz } else if (strcmp(name, "HeaderDigest") == 0) { 485255570Strasz which = login_list_prefers(value, "CRC32C", "None"); 486255570Strasz switch (which) { 487255570Strasz case 1: 488255570Strasz log_debugx("target prefers CRC32C " 489255570Strasz "for header digest; we'll use it"); 490255570Strasz conn->conn_header_digest = CONN_DIGEST_CRC32C; 491255570Strasz break; 492255570Strasz case 2: 493255570Strasz log_debugx("target prefers not to do " 494255570Strasz "header digest; we'll comply"); 495255570Strasz break; 496255570Strasz default: 497255570Strasz log_warnx("target sent unrecognized " 498255570Strasz "HeaderDigest value \"%s\"; will use None", value); 499255570Strasz break; 500255570Strasz } 501255570Strasz } else if (strcmp(name, "DataDigest") == 0) { 502255570Strasz which = login_list_prefers(value, "CRC32C", "None"); 503255570Strasz switch (which) { 504255570Strasz case 1: 505255570Strasz log_debugx("target prefers CRC32C " 506255570Strasz "for data digest; we'll use it"); 507255570Strasz conn->conn_data_digest = CONN_DIGEST_CRC32C; 508255570Strasz break; 509255570Strasz case 2: 510255570Strasz log_debugx("target prefers not to do " 511255570Strasz "data digest; we'll comply"); 512255570Strasz break; 513255570Strasz default: 514255570Strasz log_warnx("target sent unrecognized " 515255570Strasz "DataDigest value \"%s\"; will use None", value); 516255570Strasz break; 517255570Strasz } 518255570Strasz } else if (strcmp(name, "MaxConnections") == 0) { 519255570Strasz /* Ignore. */ 520255570Strasz } else if (strcmp(name, "InitialR2T") == 0) { 521255570Strasz if (strcmp(value, "Yes") == 0) 522255570Strasz conn->conn_initial_r2t = true; 523255570Strasz else 524255570Strasz conn->conn_initial_r2t = false; 525255570Strasz } else if (strcmp(name, "ImmediateData") == 0) { 526255570Strasz if (strcmp(value, "Yes") == 0) 527255570Strasz conn->conn_immediate_data = true; 528255570Strasz else 529255570Strasz conn->conn_immediate_data = false; 530255570Strasz } else if (strcmp(name, "MaxRecvDataSegmentLength") == 0) { 531255570Strasz tmp = strtoul(value, NULL, 10); 532255570Strasz if (tmp <= 0) 533255570Strasz log_errx(1, "received invalid " 534255570Strasz "MaxRecvDataSegmentLength"); 535255570Strasz conn->conn_max_data_segment_length = tmp; 536255570Strasz } else if (strcmp(name, "MaxBurstLength") == 0) { 537255570Strasz if (conn->conn_immediate_data) { 538255570Strasz tmp = strtoul(value, NULL, 10); 539255570Strasz if (tmp <= 0) 540255570Strasz log_errx(1, "received invalid MaxBurstLength"); 541255570Strasz conn->conn_max_burst_length = tmp; 542255570Strasz } 543255570Strasz } else if (strcmp(name, "FirstBurstLength") == 0) { 544255570Strasz tmp = strtoul(value, NULL, 10); 545255570Strasz if (tmp <= 0) 546255570Strasz log_errx(1, "received invalid FirstBurstLength"); 547255570Strasz conn->conn_first_burst_length = tmp; 548255570Strasz } else if (strcmp(name, "DefaultTime2Wait") == 0) { 549255570Strasz /* Ignore */ 550255570Strasz } else if (strcmp(name, "DefaultTime2Retain") == 0) { 551255570Strasz /* Ignore */ 552255570Strasz } else if (strcmp(name, "MaxOutstandingR2T") == 0) { 553255570Strasz /* Ignore */ 554255570Strasz } else if (strcmp(name, "DataPDUInOrder") == 0) { 555255570Strasz /* Ignore */ 556255570Strasz } else if (strcmp(name, "DataSequenceInOrder") == 0) { 557255570Strasz /* Ignore */ 558255570Strasz } else if (strcmp(name, "ErrorRecoveryLevel") == 0) { 559255570Strasz /* Ignore */ 560255570Strasz } else if (strcmp(name, "OFMarker") == 0) { 561255570Strasz /* Ignore */ 562255570Strasz } else if (strcmp(name, "IFMarker") == 0) { 563255570Strasz /* Ignore */ 564255570Strasz } else if (strcmp(name, "TargetPortalGroupTag") == 0) { 565255570Strasz /* Ignore */ 566255570Strasz } else { 567255570Strasz log_debugx("unknown key \"%s\"; ignoring", name); 568255570Strasz } 569255570Strasz} 570255570Strasz 571255570Straszstatic void 572255570Straszlogin_negotiate(struct connection *conn) 573255570Strasz{ 574255570Strasz struct pdu *request, *response; 575255570Strasz struct keys *request_keys, *response_keys; 576255570Strasz struct iscsi_bhs_login_response *bhslr; 577271706Strasz int i, nrequests = 0; 578255570Strasz 579269067Smav log_debugx("beginning operational parameter negotiation"); 580269068Smav request = login_new_request(conn, BHSLR_STAGE_OPERATIONAL_NEGOTIATION); 581255570Strasz request_keys = keys_new(); 582255678Strasz 583255678Strasz /* 584255678Strasz * The following keys are irrelevant for discovery sessions. 585255678Strasz */ 586255570Strasz if (conn->conn_conf.isc_discovery == 0) { 587255570Strasz if (conn->conn_conf.isc_header_digest != 0) 588255570Strasz keys_add(request_keys, "HeaderDigest", "CRC32C"); 589255678Strasz else 590255678Strasz keys_add(request_keys, "HeaderDigest", "None"); 591255570Strasz if (conn->conn_conf.isc_data_digest != 0) 592255570Strasz keys_add(request_keys, "DataDigest", "CRC32C"); 593255678Strasz else 594255678Strasz keys_add(request_keys, "DataDigest", "None"); 595255570Strasz 596255570Strasz keys_add(request_keys, "ImmediateData", "Yes"); 597255570Strasz keys_add_int(request_keys, "MaxBurstLength", 598255570Strasz ISCSI_MAX_DATA_SEGMENT_LENGTH); 599255570Strasz keys_add_int(request_keys, "FirstBurstLength", 600255570Strasz ISCSI_MAX_DATA_SEGMENT_LENGTH); 601255678Strasz keys_add(request_keys, "InitialR2T", "Yes"); 602255678Strasz } else { 603255678Strasz keys_add(request_keys, "HeaderDigest", "None"); 604255678Strasz keys_add(request_keys, "DataDigest", "None"); 605255570Strasz } 606255678Strasz 607255570Strasz keys_add_int(request_keys, "MaxRecvDataSegmentLength", 608255570Strasz ISCSI_MAX_DATA_SEGMENT_LENGTH); 609255570Strasz keys_add(request_keys, "DefaultTime2Wait", "0"); 610255570Strasz keys_add(request_keys, "DefaultTime2Retain", "0"); 611255678Strasz keys_add(request_keys, "ErrorRecoveryLevel", "0"); 612255678Strasz keys_add(request_keys, "MaxOutstandingR2T", "1"); 613255570Strasz keys_save(request_keys, request); 614255570Strasz keys_delete(request_keys); 615255570Strasz request_keys = NULL; 616255570Strasz pdu_send(request); 617255570Strasz pdu_delete(request); 618255570Strasz request = NULL; 619255570Strasz 620269069Smav response = login_receive(conn); 621255570Strasz response_keys = keys_new(); 622255570Strasz keys_load(response_keys, response); 623255570Strasz for (i = 0; i < KEYS_MAX; i++) { 624255570Strasz if (response_keys->keys_names[i] == NULL) 625255570Strasz break; 626255570Strasz 627255570Strasz login_negotiate_key(conn, 628255570Strasz response_keys->keys_names[i], response_keys->keys_values[i]); 629255570Strasz } 630255570Strasz 631271706Strasz keys_delete(response_keys); 632271706Strasz response_keys = NULL; 633271706Strasz 634271706Strasz for (;;) { 635271706Strasz bhslr = (struct iscsi_bhs_login_response *)response->pdu_bhs; 636271706Strasz if ((bhslr->bhslr_flags & BHSLR_FLAGS_TRANSIT) != 0) 637271706Strasz break; 638271706Strasz 639271706Strasz nrequests++; 640271706Strasz if (nrequests > 5) { 641271706Strasz log_warnx("received login response " 642271706Strasz "without the \"T\" flag too many times; giving up"); 643271706Strasz break; 644271706Strasz } 645271706Strasz 646271706Strasz log_debugx("received login response " 647271706Strasz "without the \"T\" flag; sending another request"); 648271706Strasz 649271706Strasz pdu_delete(response); 650271706Strasz 651271706Strasz request = login_new_request(conn, 652271706Strasz BHSLR_STAGE_OPERATIONAL_NEGOTIATION); 653271706Strasz pdu_send(request); 654271706Strasz pdu_delete(request); 655271706Strasz 656271706Strasz response = login_receive(conn); 657271706Strasz } 658271706Strasz 659271706Strasz if (login_nsg(response) != BHSLR_STAGE_FULL_FEATURE_PHASE) 660255570Strasz log_warnx("received final login response with wrong NSG 0x%x", 661255570Strasz login_nsg(response)); 662271706Strasz pdu_delete(response); 663255570Strasz 664269067Smav log_debugx("operational parameter negotiation done; " 665255570Strasz "transitioning to Full Feature phase"); 666255570Strasz} 667255570Strasz 668255570Straszstatic void 669255570Straszlogin_send_chap_a(struct connection *conn) 670255570Strasz{ 671255570Strasz struct pdu *request; 672255570Strasz struct keys *request_keys; 673255570Strasz 674269068Smav request = login_new_request(conn, BHSLR_STAGE_SECURITY_NEGOTIATION); 675255570Strasz request_keys = keys_new(); 676255570Strasz keys_add(request_keys, "CHAP_A", "5"); 677255570Strasz keys_save(request_keys, request); 678255570Strasz keys_delete(request_keys); 679255570Strasz pdu_send(request); 680255570Strasz pdu_delete(request); 681255570Strasz} 682255570Strasz 683255570Straszstatic void 684255570Straszlogin_send_chap_r(struct pdu *response) 685255570Strasz{ 686255570Strasz struct connection *conn; 687255570Strasz struct pdu *request; 688255570Strasz struct keys *request_keys, *response_keys; 689255570Strasz const char *chap_a, *chap_c, *chap_i; 690255570Strasz char *chap_r, *challenge, response_bin[MD5_DIGEST_LENGTH]; 691255570Strasz size_t challenge_len; 692255570Strasz int error, rv; 693255570Strasz unsigned char id; 694255570Strasz char *mutual_chap_c, mutual_chap_i[4]; 695255570Strasz 696255570Strasz /* 697255570Strasz * As in the rest of the initiator, 'request' means 698255570Strasz * 'initiator -> target', and 'response' means 'target -> initiator', 699255570Strasz * 700255570Strasz * So, here the 'response' from the target is the packet that contains 701255570Strasz * CHAP challenge; our CHAP response goes into 'request'. 702255570Strasz */ 703255570Strasz 704255570Strasz conn = response->pdu_connection; 705255570Strasz 706255570Strasz response_keys = keys_new(); 707255570Strasz keys_load(response_keys, response); 708255570Strasz 709255570Strasz /* 710255570Strasz * First, compute the response. 711255570Strasz */ 712255570Strasz chap_a = keys_find(response_keys, "CHAP_A"); 713255570Strasz if (chap_a == NULL) 714255570Strasz log_errx(1, "received CHAP packet without CHAP_A"); 715255570Strasz chap_c = keys_find(response_keys, "CHAP_C"); 716255570Strasz if (chap_c == NULL) 717255570Strasz log_errx(1, "received CHAP packet without CHAP_C"); 718255570Strasz chap_i = keys_find(response_keys, "CHAP_I"); 719255570Strasz if (chap_i == NULL) 720255570Strasz log_errx(1, "received CHAP packet without CHAP_I"); 721255570Strasz 722255570Strasz if (strcmp(chap_a, "5") != 0) 723255570Strasz log_errx(1, "received CHAP packet " 724255570Strasz "with unsupported CHAP_A \"%s\"", chap_a); 725255570Strasz id = strtoul(chap_i, NULL, 10); 726255570Strasz error = login_hex2bin(chap_c, &challenge, &challenge_len); 727255570Strasz if (error != 0) 728255570Strasz log_errx(1, "received CHAP packet with malformed CHAP_C"); 729255570Strasz login_compute_md5(id, conn->conn_conf.isc_secret, 730255570Strasz challenge, challenge_len, response_bin, sizeof(response_bin)); 731255570Strasz free(challenge); 732255570Strasz chap_r = login_bin2hex(response_bin, sizeof(response_bin)); 733255570Strasz 734255570Strasz keys_delete(response_keys); 735255570Strasz 736269068Smav request = login_new_request(conn, BHSLR_STAGE_SECURITY_NEGOTIATION); 737255570Strasz request_keys = keys_new(); 738255570Strasz keys_add(request_keys, "CHAP_N", conn->conn_conf.isc_user); 739255570Strasz keys_add(request_keys, "CHAP_R", chap_r); 740255570Strasz free(chap_r); 741255570Strasz 742255570Strasz /* 743255570Strasz * If we want mutual authentication, we're expected to send 744255570Strasz * our CHAP_I/CHAP_C now. 745255570Strasz */ 746255570Strasz if (conn->conn_conf.isc_mutual_user[0] != '\0') { 747255570Strasz log_debugx("requesting mutual authentication; " 748255570Strasz "binary challenge size is %zd bytes", 749255570Strasz sizeof(conn->conn_mutual_challenge)); 750255570Strasz 751255570Strasz rv = RAND_bytes(conn->conn_mutual_challenge, 752255570Strasz sizeof(conn->conn_mutual_challenge)); 753255570Strasz if (rv != 1) { 754255570Strasz log_errx(1, "RAND_bytes failed: %s", 755255570Strasz ERR_error_string(ERR_get_error(), NULL)); 756255570Strasz } 757255570Strasz rv = RAND_bytes(&conn->conn_mutual_id, 758255570Strasz sizeof(conn->conn_mutual_id)); 759255570Strasz if (rv != 1) { 760255570Strasz log_errx(1, "RAND_bytes failed: %s", 761255570Strasz ERR_error_string(ERR_get_error(), NULL)); 762255570Strasz } 763255570Strasz mutual_chap_c = login_bin2hex(conn->conn_mutual_challenge, 764255570Strasz sizeof(conn->conn_mutual_challenge)); 765255570Strasz snprintf(mutual_chap_i, sizeof(mutual_chap_i), 766255570Strasz "%d", conn->conn_mutual_id); 767255570Strasz keys_add(request_keys, "CHAP_I", mutual_chap_i); 768255570Strasz keys_add(request_keys, "CHAP_C", mutual_chap_c); 769255570Strasz free(mutual_chap_c); 770255570Strasz } 771255570Strasz 772255570Strasz keys_save(request_keys, request); 773255570Strasz keys_delete(request_keys); 774255570Strasz pdu_send(request); 775255570Strasz pdu_delete(request); 776255570Strasz} 777255570Strasz 778255570Straszstatic void 779255570Straszlogin_verify_mutual(const struct pdu *response) 780255570Strasz{ 781255570Strasz struct connection *conn; 782255570Strasz struct keys *response_keys; 783255570Strasz const char *chap_n, *chap_r; 784255570Strasz char *response_bin, expected_response_bin[MD5_DIGEST_LENGTH]; 785255570Strasz size_t response_bin_len; 786255570Strasz int error; 787255570Strasz 788255570Strasz conn = response->pdu_connection; 789255570Strasz 790255570Strasz response_keys = keys_new(); 791255570Strasz keys_load(response_keys, response); 792255570Strasz 793255570Strasz chap_n = keys_find(response_keys, "CHAP_N"); 794255570Strasz if (chap_n == NULL) 795255570Strasz log_errx(1, "received CHAP Response PDU without CHAP_N"); 796255570Strasz chap_r = keys_find(response_keys, "CHAP_R"); 797255570Strasz if (chap_r == NULL) 798255570Strasz log_errx(1, "received CHAP Response PDU without CHAP_R"); 799255570Strasz error = login_hex2bin(chap_r, &response_bin, &response_bin_len); 800255570Strasz if (error != 0) 801255570Strasz log_errx(1, "received CHAP Response PDU with malformed CHAP_R"); 802255570Strasz 803255570Strasz if (strcmp(chap_n, conn->conn_conf.isc_mutual_user) != 0) { 804255570Strasz fail(conn, "Mutual CHAP failed"); 805255570Strasz log_errx(1, "mutual CHAP authentication failed: wrong user"); 806255570Strasz } 807255570Strasz 808255570Strasz login_compute_md5(conn->conn_mutual_id, 809255570Strasz conn->conn_conf.isc_mutual_secret, conn->conn_mutual_challenge, 810255570Strasz sizeof(conn->conn_mutual_challenge), expected_response_bin, 811255570Strasz sizeof(expected_response_bin)); 812255570Strasz 813255570Strasz if (memcmp(response_bin, expected_response_bin, 814255570Strasz sizeof(expected_response_bin)) != 0) { 815255570Strasz fail(conn, "Mutual CHAP failed"); 816255570Strasz log_errx(1, "mutual CHAP authentication failed: wrong secret"); 817255570Strasz } 818255570Strasz 819255570Strasz keys_delete(response_keys); 820255570Strasz free(response_bin); 821255570Strasz 822255570Strasz log_debugx("mutual CHAP authentication succeeded"); 823255570Strasz} 824255570Strasz 825255570Straszstatic void 826255570Straszlogin_chap(struct connection *conn) 827255570Strasz{ 828255570Strasz struct pdu *response; 829255570Strasz 830255570Strasz log_debugx("beginning CHAP authentication; sending CHAP_A"); 831255570Strasz login_send_chap_a(conn); 832255570Strasz 833255570Strasz log_debugx("waiting for CHAP_A/CHAP_C/CHAP_I"); 834269069Smav response = login_receive(conn); 835255570Strasz 836255570Strasz log_debugx("sending CHAP_N/CHAP_R"); 837255570Strasz login_send_chap_r(response); 838255570Strasz pdu_delete(response); 839255570Strasz 840255570Strasz /* 841255570Strasz * XXX: Make sure this is not susceptible to MITM. 842255570Strasz */ 843255570Strasz 844255570Strasz log_debugx("waiting for CHAP result"); 845269069Smav response = login_receive(conn); 846255570Strasz if (conn->conn_conf.isc_mutual_user[0] != '\0') 847255570Strasz login_verify_mutual(response); 848255570Strasz pdu_delete(response); 849255570Strasz 850255570Strasz log_debugx("CHAP authentication done"); 851255570Strasz} 852255570Strasz 853255570Straszvoid 854255570Straszlogin(struct connection *conn) 855255570Strasz{ 856255570Strasz struct pdu *request, *response; 857255570Strasz struct keys *request_keys, *response_keys; 858255570Strasz struct iscsi_bhs_login_response *bhslr2; 859255570Strasz const char *auth_method; 860255570Strasz int i; 861255570Strasz 862255570Strasz log_debugx("beginning Login phase; sending Login PDU"); 863269068Smav request = login_new_request(conn, BHSLR_STAGE_SECURITY_NEGOTIATION); 864255570Strasz request_keys = keys_new(); 865255678Strasz if (conn->conn_conf.isc_mutual_user[0] != '\0') { 866255678Strasz keys_add(request_keys, "AuthMethod", "CHAP"); 867255678Strasz } else if (conn->conn_conf.isc_user[0] != '\0') { 868255678Strasz /* 869255678Strasz * Give target a chance to skip authentication if it 870255678Strasz * doesn't feel like it. 871255678Strasz * 872255678Strasz * None is first, CHAP second; this is to work around 873255678Strasz * what seems to be LIO (Linux target) bug: otherwise, 874255678Strasz * if target is configured with no authentication, 875255678Strasz * and we are configured to authenticate, the target 876255678Strasz * will erroneously respond with AuthMethod=CHAP 877255678Strasz * instead of AuthMethod=None, and will subsequently 878255678Strasz * fail the connection. This usually happens with 879255678Strasz * Discovery sessions, which default to no authentication. 880255678Strasz */ 881255678Strasz keys_add(request_keys, "AuthMethod", "None,CHAP"); 882255678Strasz } else { 883255570Strasz keys_add(request_keys, "AuthMethod", "None"); 884255678Strasz } 885255570Strasz keys_add(request_keys, "InitiatorName", 886255570Strasz conn->conn_conf.isc_initiator); 887255570Strasz if (conn->conn_conf.isc_initiator_alias[0] != '\0') { 888255570Strasz keys_add(request_keys, "InitiatorAlias", 889255570Strasz conn->conn_conf.isc_initiator_alias); 890255570Strasz } 891255570Strasz if (conn->conn_conf.isc_discovery == 0) { 892255570Strasz keys_add(request_keys, "SessionType", "Normal"); 893255570Strasz keys_add(request_keys, 894255570Strasz "TargetName", conn->conn_conf.isc_target); 895255570Strasz } else { 896255570Strasz keys_add(request_keys, "SessionType", "Discovery"); 897255570Strasz } 898255570Strasz keys_save(request_keys, request); 899255570Strasz keys_delete(request_keys); 900255570Strasz pdu_send(request); 901255570Strasz pdu_delete(request); 902255570Strasz 903269069Smav response = login_receive(conn); 904255570Strasz 905255570Strasz response_keys = keys_new(); 906255570Strasz keys_load(response_keys, response); 907255570Strasz 908255570Strasz for (i = 0; i < KEYS_MAX; i++) { 909255570Strasz if (response_keys->keys_names[i] == NULL) 910255570Strasz break; 911255570Strasz 912255570Strasz /* 913255570Strasz * Not interested in AuthMethod at this point; we only need 914255570Strasz * to parse things such as TargetAlias. 915255570Strasz * 916255570Strasz * XXX: This is somewhat ugly. We should have a way to apply 917255570Strasz * all the keys to the session and use that by default 918255570Strasz * instead of discarding them. 919255570Strasz */ 920255570Strasz if (strcmp(response_keys->keys_names[i], "AuthMethod") == 0) 921255570Strasz continue; 922255570Strasz 923255570Strasz login_negotiate_key(conn, 924255570Strasz response_keys->keys_names[i], response_keys->keys_values[i]); 925255570Strasz } 926255570Strasz 927255570Strasz bhslr2 = (struct iscsi_bhs_login_response *)response->pdu_bhs; 928255570Strasz if ((bhslr2->bhslr_flags & BHSLR_FLAGS_TRANSIT) != 0 && 929255570Strasz login_nsg(response) == BHSLR_STAGE_OPERATIONAL_NEGOTIATION) { 930255678Strasz if (conn->conn_conf.isc_mutual_user[0] != '\0') { 931255678Strasz log_errx(1, "target requested transition " 932269067Smav "to operational parameter negotiation, " 933269067Smav "but we require mutual CHAP"); 934255678Strasz } 935255678Strasz 936255570Strasz log_debugx("target requested transition " 937269067Smav "to operational parameter negotiation"); 938255570Strasz keys_delete(response_keys); 939255570Strasz pdu_delete(response); 940255570Strasz login_negotiate(conn); 941255570Strasz return; 942255570Strasz } 943255570Strasz 944255570Strasz auth_method = keys_find(response_keys, "AuthMethod"); 945255570Strasz if (auth_method == NULL) 946255570Strasz log_errx(1, "received response without AuthMethod"); 947255570Strasz if (strcmp(auth_method, "None") == 0) { 948255678Strasz if (conn->conn_conf.isc_mutual_user[0] != '\0') { 949255678Strasz log_errx(1, "target does not require authantication, " 950255678Strasz "but we require mutual CHAP"); 951255678Strasz } 952255678Strasz 953255570Strasz log_debugx("target does not require authentication"); 954255570Strasz keys_delete(response_keys); 955255570Strasz pdu_delete(response); 956255570Strasz login_negotiate(conn); 957255570Strasz return; 958255570Strasz } 959255570Strasz 960255570Strasz if (strcmp(auth_method, "CHAP") != 0) { 961255570Strasz fail(conn, "Unsupported AuthMethod"); 962255570Strasz log_errx(1, "received response " 963255570Strasz "with unsupported AuthMethod \"%s\"", auth_method); 964255570Strasz } 965255570Strasz 966255570Strasz if (conn->conn_conf.isc_user[0] == '\0' || 967255570Strasz conn->conn_conf.isc_secret[0] == '\0') { 968255570Strasz fail(conn, "Authentication required"); 969255570Strasz log_errx(1, "target requests CHAP authentication, but we don't " 970255570Strasz "have user and secret"); 971255570Strasz } 972255570Strasz 973255570Strasz keys_delete(response_keys); 974255570Strasz response_keys = NULL; 975255570Strasz pdu_delete(response); 976255570Strasz response = NULL; 977255570Strasz 978255570Strasz login_chap(conn); 979255570Strasz login_negotiate(conn); 980255570Strasz} 981