1255570Strasz/*- 2330449Seadler * SPDX-License-Identifier: BSD-2-Clause-FreeBSD 3330449Seadler * 4255570Strasz * Copyright (c) 2012 The FreeBSD Foundation 5255570Strasz * All rights reserved. 6255570Strasz * 7255570Strasz * This software was developed by Edward Tomasz Napierala under sponsorship 8255570Strasz * from the FreeBSD Foundation. 9255570Strasz * 10255570Strasz * Redistribution and use in source and binary forms, with or without 11255570Strasz * modification, are permitted provided that the following conditions 12255570Strasz * are met: 13255570Strasz * 1. Redistributions of source code must retain the above copyright 14255570Strasz * notice, this list of conditions and the following disclaimer. 15255570Strasz * 2. Redistributions in binary form must reproduce the above copyright 16255570Strasz * notice, this list of conditions and the following disclaimer in the 17255570Strasz * documentation and/or other materials provided with the distribution. 18255570Strasz * 19255570Strasz * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 20255570Strasz * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21255570Strasz * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 22255570Strasz * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 23255570Strasz * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 24255570Strasz * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 25255570Strasz * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 26255570Strasz * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 27255570Strasz * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 28255570Strasz * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 29255570Strasz * SUCH DAMAGE. 30255570Strasz * 31255570Strasz */ 32255570Strasz 33270279Strasz#include <sys/cdefs.h> 34270279Strasz__FBSDID("$FreeBSD: stable/11/usr.sbin/ctld/discovery.c 330449 2018-03-05 07:26:05Z eadler $"); 35270279Strasz 36255570Strasz#include <assert.h> 37255570Strasz#include <stdio.h> 38255570Strasz#include <stdlib.h> 39255570Strasz#include <string.h> 40255570Strasz#include <netinet/in.h> 41267606Smav#include <netdb.h> 42267606Smav#include <sys/socket.h> 43255570Strasz 44255570Strasz#include "ctld.h" 45255570Strasz#include "iscsi_proto.h" 46255570Strasz 47255570Straszstatic struct pdu * 48255570Strasztext_receive(struct connection *conn) 49255570Strasz{ 50255570Strasz struct pdu *request; 51255570Strasz struct iscsi_bhs_text_request *bhstr; 52255570Strasz 53255570Strasz request = pdu_new(conn); 54255570Strasz pdu_receive(request); 55255570Strasz if ((request->pdu_bhs->bhs_opcode & ~ISCSI_BHS_OPCODE_IMMEDIATE) != 56255570Strasz ISCSI_BHS_OPCODE_TEXT_REQUEST) 57255570Strasz log_errx(1, "protocol error: received invalid opcode 0x%x", 58255570Strasz request->pdu_bhs->bhs_opcode); 59255570Strasz bhstr = (struct iscsi_bhs_text_request *)request->pdu_bhs; 60255570Strasz#if 0 61255570Strasz if ((bhstr->bhstr_flags & ISCSI_BHSTR_FLAGS_FINAL) == 0) 62255570Strasz log_errx(1, "received Text PDU without the \"F\" flag"); 63255570Strasz#endif 64255570Strasz /* 65255570Strasz * XXX: Implement the C flag some day. 66255570Strasz */ 67255570Strasz if ((bhstr->bhstr_flags & BHSTR_FLAGS_CONTINUE) != 0) 68255570Strasz log_errx(1, "received Text PDU with unsupported \"C\" flag"); 69275864Smav if (ISCSI_SNLT(ntohl(bhstr->bhstr_cmdsn), conn->conn_cmdsn)) { 70255570Strasz log_errx(1, "received Text PDU with decreasing CmdSN: " 71275864Smav "was %u, is %u", conn->conn_cmdsn, ntohl(bhstr->bhstr_cmdsn)); 72255570Strasz } 73255570Strasz if (ntohl(bhstr->bhstr_expstatsn) != conn->conn_statsn) { 74300210Strasz log_errx(1, "received Text PDU with wrong ExpStatSN: " 75275864Smav "is %u, should be %u", ntohl(bhstr->bhstr_expstatsn), 76255570Strasz conn->conn_statsn); 77255570Strasz } 78255570Strasz conn->conn_cmdsn = ntohl(bhstr->bhstr_cmdsn); 79279589Smav if ((bhstr->bhstr_opcode & ISCSI_BHS_OPCODE_IMMEDIATE) == 0) 80279589Smav conn->conn_cmdsn++; 81255570Strasz 82255570Strasz return (request); 83255570Strasz} 84255570Strasz 85255570Straszstatic struct pdu * 86255570Strasztext_new_response(struct pdu *request) 87255570Strasz{ 88255570Strasz struct pdu *response; 89255570Strasz struct connection *conn; 90255570Strasz struct iscsi_bhs_text_request *bhstr; 91255570Strasz struct iscsi_bhs_text_response *bhstr2; 92255570Strasz 93255570Strasz bhstr = (struct iscsi_bhs_text_request *)request->pdu_bhs; 94255570Strasz conn = request->pdu_connection; 95255570Strasz 96255570Strasz response = pdu_new_response(request); 97255570Strasz bhstr2 = (struct iscsi_bhs_text_response *)response->pdu_bhs; 98255570Strasz bhstr2->bhstr_opcode = ISCSI_BHS_OPCODE_TEXT_RESPONSE; 99255570Strasz bhstr2->bhstr_flags = BHSTR_FLAGS_FINAL; 100255570Strasz bhstr2->bhstr_lun = bhstr->bhstr_lun; 101255570Strasz bhstr2->bhstr_initiator_task_tag = bhstr->bhstr_initiator_task_tag; 102255570Strasz bhstr2->bhstr_target_transfer_tag = bhstr->bhstr_target_transfer_tag; 103255570Strasz bhstr2->bhstr_statsn = htonl(conn->conn_statsn++); 104255570Strasz bhstr2->bhstr_expcmdsn = htonl(conn->conn_cmdsn); 105255570Strasz bhstr2->bhstr_maxcmdsn = htonl(conn->conn_cmdsn); 106255570Strasz 107255570Strasz return (response); 108255570Strasz} 109255570Strasz 110255570Straszstatic struct pdu * 111255570Straszlogout_receive(struct connection *conn) 112255570Strasz{ 113255570Strasz struct pdu *request; 114255570Strasz struct iscsi_bhs_logout_request *bhslr; 115255570Strasz 116255570Strasz request = pdu_new(conn); 117255570Strasz pdu_receive(request); 118255570Strasz if ((request->pdu_bhs->bhs_opcode & ~ISCSI_BHS_OPCODE_IMMEDIATE) != 119255570Strasz ISCSI_BHS_OPCODE_LOGOUT_REQUEST) 120255570Strasz log_errx(1, "protocol error: received invalid opcode 0x%x", 121255570Strasz request->pdu_bhs->bhs_opcode); 122255570Strasz bhslr = (struct iscsi_bhs_logout_request *)request->pdu_bhs; 123255570Strasz if ((bhslr->bhslr_reason & 0x7f) != BHSLR_REASON_CLOSE_SESSION) 124255570Strasz log_debugx("received Logout PDU with invalid reason 0x%x; " 125255570Strasz "continuing anyway", bhslr->bhslr_reason & 0x7f); 126275864Smav if (ISCSI_SNLT(ntohl(bhslr->bhslr_cmdsn), conn->conn_cmdsn)) { 127255570Strasz log_errx(1, "received Logout PDU with decreasing CmdSN: " 128275864Smav "was %u, is %u", conn->conn_cmdsn, 129255570Strasz ntohl(bhslr->bhslr_cmdsn)); 130255570Strasz } 131255570Strasz if (ntohl(bhslr->bhslr_expstatsn) != conn->conn_statsn) { 132300210Strasz log_errx(1, "received Logout PDU with wrong ExpStatSN: " 133275864Smav "is %u, should be %u", ntohl(bhslr->bhslr_expstatsn), 134255570Strasz conn->conn_statsn); 135255570Strasz } 136255570Strasz conn->conn_cmdsn = ntohl(bhslr->bhslr_cmdsn); 137279589Smav if ((bhslr->bhslr_opcode & ISCSI_BHS_OPCODE_IMMEDIATE) == 0) 138279589Smav conn->conn_cmdsn++; 139255570Strasz 140255570Strasz return (request); 141255570Strasz} 142255570Strasz 143255570Straszstatic struct pdu * 144255570Straszlogout_new_response(struct pdu *request) 145255570Strasz{ 146255570Strasz struct pdu *response; 147255570Strasz struct connection *conn; 148255570Strasz struct iscsi_bhs_logout_request *bhslr; 149255570Strasz struct iscsi_bhs_logout_response *bhslr2; 150255570Strasz 151255570Strasz bhslr = (struct iscsi_bhs_logout_request *)request->pdu_bhs; 152255570Strasz conn = request->pdu_connection; 153255570Strasz 154255570Strasz response = pdu_new_response(request); 155255570Strasz bhslr2 = (struct iscsi_bhs_logout_response *)response->pdu_bhs; 156255570Strasz bhslr2->bhslr_opcode = ISCSI_BHS_OPCODE_LOGOUT_RESPONSE; 157255570Strasz bhslr2->bhslr_flags = 0x80; 158255570Strasz bhslr2->bhslr_response = BHSLR_RESPONSE_CLOSED_SUCCESSFULLY; 159255570Strasz bhslr2->bhslr_initiator_task_tag = bhslr->bhslr_initiator_task_tag; 160255570Strasz bhslr2->bhslr_statsn = htonl(conn->conn_statsn++); 161255570Strasz bhslr2->bhslr_expcmdsn = htonl(conn->conn_cmdsn); 162255570Strasz bhslr2->bhslr_maxcmdsn = htonl(conn->conn_cmdsn); 163255570Strasz 164255570Strasz return (response); 165255570Strasz} 166255570Strasz 167267606Smavstatic void 168273543Straszdiscovery_add_target(struct keys *response_keys, const struct target *targ) 169267606Smav{ 170278322Smav struct port *port; 171267606Smav struct portal *portal; 172267606Smav char *buf; 173267606Smav char hbuf[NI_MAXHOST], sbuf[NI_MAXSERV]; 174267606Smav struct addrinfo *ai; 175267606Smav int ret; 176267606Smav 177267606Smav keys_add(response_keys, "TargetName", targ->t_name); 178278322Smav TAILQ_FOREACH(port, &targ->t_ports, p_ts) { 179278322Smav if (port->p_portal_group == NULL) 180278322Smav continue; 181278322Smav TAILQ_FOREACH(portal, &port->p_portal_group->pg_portals, p_next) { 182267606Smav ai = portal->p_ai; 183267606Smav ret = getnameinfo(ai->ai_addr, ai->ai_addrlen, 184267606Smav hbuf, sizeof(hbuf), sbuf, sizeof(sbuf), 185267606Smav NI_NUMERICHOST | NI_NUMERICSERV); 186267606Smav if (ret != 0) { 187267606Smav log_warnx("getnameinfo: %s", gai_strerror(ret)); 188267606Smav continue; 189267606Smav } 190267606Smav switch (ai->ai_addr->sa_family) { 191267606Smav case AF_INET: 192267606Smav if (strcmp(hbuf, "0.0.0.0") == 0) 193267606Smav continue; 194267606Smav ret = asprintf(&buf, "%s:%s,%d", hbuf, sbuf, 195278322Smav port->p_portal_group->pg_tag); 196267606Smav break; 197267606Smav case AF_INET6: 198267606Smav if (strcmp(hbuf, "::") == 0) 199267606Smav continue; 200267606Smav ret = asprintf(&buf, "[%s]:%s,%d", hbuf, sbuf, 201278322Smav port->p_portal_group->pg_tag); 202267606Smav break; 203267606Smav default: 204267606Smav continue; 205267606Smav } 206267606Smav if (ret <= 0) 207267606Smav log_err(1, "asprintf"); 208267606Smav keys_add(response_keys, "TargetAddress", buf); 209267606Smav free(buf); 210278322Smav } 211267606Smav } 212267606Smav} 213267606Smav 214273813Straszstatic bool 215273813Straszdiscovery_target_filtered_out(const struct connection *conn, 216278322Smav const struct port *port) 217273813Strasz{ 218273813Strasz const struct auth_group *ag; 219273813Strasz const struct portal_group *pg; 220278322Smav const struct target *targ; 221273813Strasz const struct auth *auth; 222273813Strasz int error; 223273813Strasz 224278322Smav targ = port->p_target; 225278322Smav ag = port->p_auth_group; 226278322Smav if (ag == NULL) 227278322Smav ag = targ->t_auth_group; 228273813Strasz pg = conn->conn_portal->p_portal_group; 229273813Strasz 230273813Strasz assert(pg->pg_discovery_auth_group != PG_FILTER_UNKNOWN); 231273813Strasz 232273813Strasz if (pg->pg_discovery_filter >= PG_FILTER_PORTAL && 233273813Strasz auth_portal_check(ag, &conn->conn_initiator_sa) != 0) { 234273813Strasz log_debugx("initiator does not match initiator portals " 235273813Strasz "allowed for target \"%s\"; skipping", targ->t_name); 236273813Strasz return (true); 237273813Strasz } 238273813Strasz 239273813Strasz if (pg->pg_discovery_filter >= PG_FILTER_PORTAL_NAME && 240273813Strasz auth_name_check(ag, conn->conn_initiator_name) != 0) { 241273813Strasz log_debugx("initiator does not match initiator names " 242273813Strasz "allowed for target \"%s\"; skipping", targ->t_name); 243273813Strasz return (true); 244273813Strasz } 245273813Strasz 246273813Strasz if (pg->pg_discovery_filter >= PG_FILTER_PORTAL_NAME_AUTH && 247273813Strasz ag->ag_type != AG_TYPE_NO_AUTHENTICATION) { 248273813Strasz if (conn->conn_chap == NULL) { 249273813Strasz assert(pg->pg_discovery_auth_group->ag_type == 250273813Strasz AG_TYPE_NO_AUTHENTICATION); 251273813Strasz 252273813Strasz log_debugx("initiator didn't authenticate, but target " 253273813Strasz "\"%s\" requires CHAP; skipping", targ->t_name); 254273813Strasz return (true); 255273813Strasz } 256273813Strasz 257273813Strasz assert(conn->conn_user != NULL); 258273813Strasz auth = auth_find(ag, conn->conn_user); 259273813Strasz if (auth == NULL) { 260273813Strasz log_debugx("CHAP user \"%s\" doesn't match target " 261273813Strasz "\"%s\"; skipping", conn->conn_user, targ->t_name); 262273813Strasz return (true); 263273813Strasz } 264273813Strasz 265273813Strasz error = chap_authenticate(conn->conn_chap, auth->a_secret); 266273813Strasz if (error != 0) { 267273813Strasz log_debugx("password for CHAP user \"%s\" doesn't " 268273813Strasz "match target \"%s\"; skipping", 269273813Strasz conn->conn_user, targ->t_name); 270273813Strasz return (true); 271273813Strasz } 272273813Strasz } 273273813Strasz 274273813Strasz return (false); 275273813Strasz} 276273813Strasz 277255570Straszvoid 278255570Straszdiscovery(struct connection *conn) 279255570Strasz{ 280255570Strasz struct pdu *request, *response; 281255570Strasz struct keys *request_keys, *response_keys; 282278322Smav const struct port *port; 283273543Strasz const struct portal_group *pg; 284255570Strasz const char *send_targets; 285255570Strasz 286273543Strasz pg = conn->conn_portal->p_portal_group; 287273543Strasz 288255570Strasz log_debugx("beginning discovery session; waiting for Text PDU"); 289255570Strasz request = text_receive(conn); 290255570Strasz request_keys = keys_new(); 291255570Strasz keys_load(request_keys, request); 292255570Strasz 293255570Strasz send_targets = keys_find(request_keys, "SendTargets"); 294255570Strasz if (send_targets == NULL) 295255570Strasz log_errx(1, "received Text PDU without SendTargets"); 296255570Strasz 297255570Strasz response = text_new_response(request); 298255570Strasz response_keys = keys_new(); 299255570Strasz 300255570Strasz if (strcmp(send_targets, "All") == 0) { 301278322Smav TAILQ_FOREACH(port, &pg->pg_ports, p_pgs) { 302278322Smav if (discovery_target_filtered_out(conn, port)) { 303273813Strasz /* Ignore this target. */ 304273813Strasz continue; 305273813Strasz } 306278322Smav discovery_add_target(response_keys, port->p_target); 307255570Strasz } 308255570Strasz } else { 309278322Smav port = port_find_in_pg(pg, send_targets); 310278322Smav if (port == NULL) { 311255570Strasz log_debugx("initiator requested information on unknown " 312255570Strasz "target \"%s\"; returning nothing", send_targets); 313273813Strasz } else { 314278322Smav if (discovery_target_filtered_out(conn, port)) { 315273813Strasz /* Ignore this target. */ 316273813Strasz } else { 317278322Smav discovery_add_target(response_keys, port->p_target); 318273813Strasz } 319273813Strasz } 320255570Strasz } 321255570Strasz keys_save(response_keys, response); 322255570Strasz 323255570Strasz pdu_send(response); 324255570Strasz pdu_delete(response); 325255570Strasz keys_delete(response_keys); 326255570Strasz pdu_delete(request); 327255570Strasz keys_delete(request_keys); 328255570Strasz 329255570Strasz log_debugx("done sending targets; waiting for Logout PDU"); 330255570Strasz request = logout_receive(conn); 331255570Strasz response = logout_new_response(request); 332255570Strasz 333255570Strasz pdu_send(response); 334255570Strasz pdu_delete(response); 335255570Strasz pdu_delete(request); 336255570Strasz 337255570Strasz log_debugx("discovery session done"); 338255570Strasz} 339