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: releng/10.3/usr.sbin/ctld/discovery.c 288704 2015-10-05 07:42:05Z mav $"); 33270888Strasz 34255570Strasz#include <assert.h> 35255570Strasz#include <stdio.h> 36255570Strasz#include <stdlib.h> 37255570Strasz#include <string.h> 38255570Strasz#include <netinet/in.h> 39268140Smav#include <netdb.h> 40268140Smav#include <sys/socket.h> 41255570Strasz 42255570Strasz#include "ctld.h" 43255570Strasz#include "iscsi_proto.h" 44255570Strasz 45255570Straszstatic struct pdu * 46255570Strasztext_receive(struct connection *conn) 47255570Strasz{ 48255570Strasz struct pdu *request; 49255570Strasz struct iscsi_bhs_text_request *bhstr; 50255570Strasz 51255570Strasz request = pdu_new(conn); 52255570Strasz pdu_receive(request); 53255570Strasz if ((request->pdu_bhs->bhs_opcode & ~ISCSI_BHS_OPCODE_IMMEDIATE) != 54255570Strasz ISCSI_BHS_OPCODE_TEXT_REQUEST) 55255570Strasz log_errx(1, "protocol error: received invalid opcode 0x%x", 56255570Strasz request->pdu_bhs->bhs_opcode); 57255570Strasz bhstr = (struct iscsi_bhs_text_request *)request->pdu_bhs; 58255570Strasz#if 0 59255570Strasz if ((bhstr->bhstr_flags & ISCSI_BHSTR_FLAGS_FINAL) == 0) 60255570Strasz log_errx(1, "received Text PDU without the \"F\" flag"); 61255570Strasz#endif 62255570Strasz /* 63255570Strasz * XXX: Implement the C flag some day. 64255570Strasz */ 65255570Strasz if ((bhstr->bhstr_flags & BHSTR_FLAGS_CONTINUE) != 0) 66255570Strasz log_errx(1, "received Text PDU with unsupported \"C\" flag"); 67276613Smav if (ISCSI_SNLT(ntohl(bhstr->bhstr_cmdsn), conn->conn_cmdsn)) { 68255570Strasz log_errx(1, "received Text PDU with decreasing CmdSN: " 69276613Smav "was %u, is %u", conn->conn_cmdsn, ntohl(bhstr->bhstr_cmdsn)); 70255570Strasz } 71255570Strasz if (ntohl(bhstr->bhstr_expstatsn) != conn->conn_statsn) { 72255570Strasz log_errx(1, "received Text PDU with wrong StatSN: " 73276613Smav "is %u, should be %u", ntohl(bhstr->bhstr_expstatsn), 74255570Strasz conn->conn_statsn); 75255570Strasz } 76255570Strasz conn->conn_cmdsn = ntohl(bhstr->bhstr_cmdsn); 77279879Smav if ((bhstr->bhstr_opcode & ISCSI_BHS_OPCODE_IMMEDIATE) == 0) 78279879Smav conn->conn_cmdsn++; 79255570Strasz 80255570Strasz return (request); 81255570Strasz} 82255570Strasz 83255570Straszstatic struct pdu * 84255570Strasztext_new_response(struct pdu *request) 85255570Strasz{ 86255570Strasz struct pdu *response; 87255570Strasz struct connection *conn; 88255570Strasz struct iscsi_bhs_text_request *bhstr; 89255570Strasz struct iscsi_bhs_text_response *bhstr2; 90255570Strasz 91255570Strasz bhstr = (struct iscsi_bhs_text_request *)request->pdu_bhs; 92255570Strasz conn = request->pdu_connection; 93255570Strasz 94255570Strasz response = pdu_new_response(request); 95255570Strasz bhstr2 = (struct iscsi_bhs_text_response *)response->pdu_bhs; 96255570Strasz bhstr2->bhstr_opcode = ISCSI_BHS_OPCODE_TEXT_RESPONSE; 97255570Strasz bhstr2->bhstr_flags = BHSTR_FLAGS_FINAL; 98255570Strasz bhstr2->bhstr_lun = bhstr->bhstr_lun; 99255570Strasz bhstr2->bhstr_initiator_task_tag = bhstr->bhstr_initiator_task_tag; 100255570Strasz bhstr2->bhstr_target_transfer_tag = bhstr->bhstr_target_transfer_tag; 101255570Strasz bhstr2->bhstr_statsn = htonl(conn->conn_statsn++); 102255570Strasz bhstr2->bhstr_expcmdsn = htonl(conn->conn_cmdsn); 103255570Strasz bhstr2->bhstr_maxcmdsn = htonl(conn->conn_cmdsn); 104255570Strasz 105255570Strasz return (response); 106255570Strasz} 107255570Strasz 108255570Straszstatic struct pdu * 109255570Straszlogout_receive(struct connection *conn) 110255570Strasz{ 111255570Strasz struct pdu *request; 112255570Strasz struct iscsi_bhs_logout_request *bhslr; 113255570Strasz 114255570Strasz request = pdu_new(conn); 115255570Strasz pdu_receive(request); 116255570Strasz if ((request->pdu_bhs->bhs_opcode & ~ISCSI_BHS_OPCODE_IMMEDIATE) != 117255570Strasz ISCSI_BHS_OPCODE_LOGOUT_REQUEST) 118255570Strasz log_errx(1, "protocol error: received invalid opcode 0x%x", 119255570Strasz request->pdu_bhs->bhs_opcode); 120255570Strasz bhslr = (struct iscsi_bhs_logout_request *)request->pdu_bhs; 121255570Strasz if ((bhslr->bhslr_reason & 0x7f) != BHSLR_REASON_CLOSE_SESSION) 122255570Strasz log_debugx("received Logout PDU with invalid reason 0x%x; " 123255570Strasz "continuing anyway", bhslr->bhslr_reason & 0x7f); 124276613Smav if (ISCSI_SNLT(ntohl(bhslr->bhslr_cmdsn), conn->conn_cmdsn)) { 125255570Strasz log_errx(1, "received Logout PDU with decreasing CmdSN: " 126276613Smav "was %u, is %u", conn->conn_cmdsn, 127255570Strasz ntohl(bhslr->bhslr_cmdsn)); 128255570Strasz } 129255570Strasz if (ntohl(bhslr->bhslr_expstatsn) != conn->conn_statsn) { 130255570Strasz log_errx(1, "received Logout PDU with wrong StatSN: " 131276613Smav "is %u, should be %u", ntohl(bhslr->bhslr_expstatsn), 132255570Strasz conn->conn_statsn); 133255570Strasz } 134255570Strasz conn->conn_cmdsn = ntohl(bhslr->bhslr_cmdsn); 135279879Smav if ((bhslr->bhslr_opcode & ISCSI_BHS_OPCODE_IMMEDIATE) == 0) 136279879Smav conn->conn_cmdsn++; 137255570Strasz 138255570Strasz return (request); 139255570Strasz} 140255570Strasz 141255570Straszstatic struct pdu * 142255570Straszlogout_new_response(struct pdu *request) 143255570Strasz{ 144255570Strasz struct pdu *response; 145255570Strasz struct connection *conn; 146255570Strasz struct iscsi_bhs_logout_request *bhslr; 147255570Strasz struct iscsi_bhs_logout_response *bhslr2; 148255570Strasz 149255570Strasz bhslr = (struct iscsi_bhs_logout_request *)request->pdu_bhs; 150255570Strasz conn = request->pdu_connection; 151255570Strasz 152255570Strasz response = pdu_new_response(request); 153255570Strasz bhslr2 = (struct iscsi_bhs_logout_response *)response->pdu_bhs; 154255570Strasz bhslr2->bhslr_opcode = ISCSI_BHS_OPCODE_LOGOUT_RESPONSE; 155255570Strasz bhslr2->bhslr_flags = 0x80; 156255570Strasz bhslr2->bhslr_response = BHSLR_RESPONSE_CLOSED_SUCCESSFULLY; 157255570Strasz bhslr2->bhslr_initiator_task_tag = bhslr->bhslr_initiator_task_tag; 158255570Strasz bhslr2->bhslr_statsn = htonl(conn->conn_statsn++); 159255570Strasz bhslr2->bhslr_expcmdsn = htonl(conn->conn_cmdsn); 160255570Strasz bhslr2->bhslr_maxcmdsn = htonl(conn->conn_cmdsn); 161255570Strasz 162255570Strasz return (response); 163255570Strasz} 164255570Strasz 165268140Smavstatic void 166274875Straszdiscovery_add_target(struct keys *response_keys, const struct target *targ) 167268140Smav{ 168279006Smav struct port *port; 169268140Smav struct portal *portal; 170268140Smav char *buf; 171268140Smav char hbuf[NI_MAXHOST], sbuf[NI_MAXSERV]; 172268140Smav struct addrinfo *ai; 173268140Smav int ret; 174268140Smav 175268140Smav keys_add(response_keys, "TargetName", targ->t_name); 176279006Smav TAILQ_FOREACH(port, &targ->t_ports, p_ts) { 177279006Smav if (port->p_portal_group == NULL) 178279006Smav continue; 179279006Smav TAILQ_FOREACH(portal, &port->p_portal_group->pg_portals, p_next) { 180268140Smav ai = portal->p_ai; 181268140Smav ret = getnameinfo(ai->ai_addr, ai->ai_addrlen, 182268140Smav hbuf, sizeof(hbuf), sbuf, sizeof(sbuf), 183268140Smav NI_NUMERICHOST | NI_NUMERICSERV); 184268140Smav if (ret != 0) { 185268140Smav log_warnx("getnameinfo: %s", gai_strerror(ret)); 186268140Smav continue; 187268140Smav } 188268140Smav switch (ai->ai_addr->sa_family) { 189268140Smav case AF_INET: 190268140Smav if (strcmp(hbuf, "0.0.0.0") == 0) 191268140Smav continue; 192268140Smav ret = asprintf(&buf, "%s:%s,%d", hbuf, sbuf, 193279006Smav port->p_portal_group->pg_tag); 194268140Smav break; 195268140Smav case AF_INET6: 196268140Smav if (strcmp(hbuf, "::") == 0) 197268140Smav continue; 198268140Smav ret = asprintf(&buf, "[%s]:%s,%d", hbuf, sbuf, 199279006Smav port->p_portal_group->pg_tag); 200268140Smav break; 201268140Smav default: 202268140Smav continue; 203268140Smav } 204268140Smav if (ret <= 0) 205268140Smav log_err(1, "asprintf"); 206268140Smav keys_add(response_keys, "TargetAddress", buf); 207268140Smav free(buf); 208279006Smav } 209268140Smav } 210268140Smav} 211268140Smav 212275244Straszstatic bool 213275244Straszdiscovery_target_filtered_out(const struct connection *conn, 214279006Smav const struct port *port) 215275244Strasz{ 216275244Strasz const struct auth_group *ag; 217275244Strasz const struct portal_group *pg; 218279006Smav const struct target *targ; 219275244Strasz const struct auth *auth; 220275244Strasz int error; 221275244Strasz 222279006Smav targ = port->p_target; 223279006Smav ag = port->p_auth_group; 224279006Smav if (ag == NULL) 225279006Smav ag = targ->t_auth_group; 226275244Strasz pg = conn->conn_portal->p_portal_group; 227275244Strasz 228275244Strasz assert(pg->pg_discovery_auth_group != PG_FILTER_UNKNOWN); 229275244Strasz 230275244Strasz if (pg->pg_discovery_filter >= PG_FILTER_PORTAL && 231275244Strasz auth_portal_check(ag, &conn->conn_initiator_sa) != 0) { 232275244Strasz log_debugx("initiator does not match initiator portals " 233275244Strasz "allowed for target \"%s\"; skipping", targ->t_name); 234275244Strasz return (true); 235275244Strasz } 236275244Strasz 237275244Strasz if (pg->pg_discovery_filter >= PG_FILTER_PORTAL_NAME && 238275244Strasz auth_name_check(ag, conn->conn_initiator_name) != 0) { 239275244Strasz log_debugx("initiator does not match initiator names " 240275244Strasz "allowed for target \"%s\"; skipping", targ->t_name); 241275244Strasz return (true); 242275244Strasz } 243275244Strasz 244275244Strasz if (pg->pg_discovery_filter >= PG_FILTER_PORTAL_NAME_AUTH && 245275244Strasz ag->ag_type != AG_TYPE_NO_AUTHENTICATION) { 246275244Strasz if (conn->conn_chap == NULL) { 247275244Strasz assert(pg->pg_discovery_auth_group->ag_type == 248275244Strasz AG_TYPE_NO_AUTHENTICATION); 249275244Strasz 250275244Strasz log_debugx("initiator didn't authenticate, but target " 251275244Strasz "\"%s\" requires CHAP; skipping", targ->t_name); 252275244Strasz return (true); 253275244Strasz } 254275244Strasz 255275244Strasz assert(conn->conn_user != NULL); 256275244Strasz auth = auth_find(ag, conn->conn_user); 257275244Strasz if (auth == NULL) { 258275244Strasz log_debugx("CHAP user \"%s\" doesn't match target " 259275244Strasz "\"%s\"; skipping", conn->conn_user, targ->t_name); 260275244Strasz return (true); 261275244Strasz } 262275244Strasz 263275244Strasz error = chap_authenticate(conn->conn_chap, auth->a_secret); 264275244Strasz if (error != 0) { 265275244Strasz log_debugx("password for CHAP user \"%s\" doesn't " 266275244Strasz "match target \"%s\"; skipping", 267275244Strasz conn->conn_user, targ->t_name); 268275244Strasz return (true); 269275244Strasz } 270275244Strasz } 271275244Strasz 272275244Strasz return (false); 273275244Strasz} 274275244Strasz 275255570Straszvoid 276255570Straszdiscovery(struct connection *conn) 277255570Strasz{ 278255570Strasz struct pdu *request, *response; 279255570Strasz struct keys *request_keys, *response_keys; 280279006Smav const struct port *port; 281274875Strasz const struct portal_group *pg; 282255570Strasz const char *send_targets; 283255570Strasz 284274875Strasz pg = conn->conn_portal->p_portal_group; 285274875Strasz 286255570Strasz log_debugx("beginning discovery session; waiting for Text PDU"); 287255570Strasz request = text_receive(conn); 288255570Strasz request_keys = keys_new(); 289255570Strasz keys_load(request_keys, request); 290255570Strasz 291255570Strasz send_targets = keys_find(request_keys, "SendTargets"); 292255570Strasz if (send_targets == NULL) 293255570Strasz log_errx(1, "received Text PDU without SendTargets"); 294255570Strasz 295255570Strasz response = text_new_response(request); 296255570Strasz response_keys = keys_new(); 297255570Strasz 298255570Strasz if (strcmp(send_targets, "All") == 0) { 299279006Smav TAILQ_FOREACH(port, &pg->pg_ports, p_pgs) { 300279006Smav if (discovery_target_filtered_out(conn, port)) { 301275244Strasz /* Ignore this target. */ 302275244Strasz continue; 303275244Strasz } 304279006Smav discovery_add_target(response_keys, port->p_target); 305255570Strasz } 306255570Strasz } else { 307279006Smav port = port_find_in_pg(pg, send_targets); 308279006Smav if (port == NULL) { 309255570Strasz log_debugx("initiator requested information on unknown " 310255570Strasz "target \"%s\"; returning nothing", send_targets); 311275244Strasz } else { 312279006Smav if (discovery_target_filtered_out(conn, port)) { 313275244Strasz /* Ignore this target. */ 314275244Strasz } else { 315279006Smav discovery_add_target(response_keys, port->p_target); 316275244Strasz } 317275244Strasz } 318255570Strasz } 319255570Strasz keys_save(response_keys, response); 320255570Strasz 321255570Strasz pdu_send(response); 322255570Strasz pdu_delete(response); 323255570Strasz keys_delete(response_keys); 324255570Strasz pdu_delete(request); 325255570Strasz keys_delete(request_keys); 326255570Strasz 327255570Strasz log_debugx("done sending targets; waiting for Logout PDU"); 328255570Strasz request = logout_receive(conn); 329255570Strasz response = logout_new_response(request); 330255570Strasz 331255570Strasz pdu_send(response); 332255570Strasz pdu_delete(response); 333255570Strasz pdu_delete(request); 334255570Strasz 335255570Strasz log_debugx("discovery session done"); 336255570Strasz} 337