discovery.c revision 276613
1238104Sdes/*- 2238104Sdes * Copyright (c) 2012 The FreeBSD Foundation 3238104Sdes * All rights reserved. 4238104Sdes * 5238104Sdes * This software was developed by Edward Tomasz Napierala under sponsorship 6238104Sdes * from the FreeBSD Foundation. 7238104Sdes * 8238104Sdes * Redistribution and use in source and binary forms, with or without 9238104Sdes * modification, are permitted provided that the following conditions 10246827Sdes * are met: 11238104Sdes * 1. Redistributions of source code must retain the above copyright 12238104Sdes * notice, this list of conditions and the following disclaimer. 13238104Sdes * 2. Redistributions in binary form must reproduce the above copyright 14238104Sdes * notice, this list of conditions and the following disclaimer in the 15238104Sdes * documentation and/or other materials provided with the distribution. 16238104Sdes * 17238104Sdes * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 18238104Sdes * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 19238104Sdes * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 20238104Sdes * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 21238104Sdes * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 22238104Sdes * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 23238104Sdes * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 24238104Sdes * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 25238104Sdes * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 26238104Sdes * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 27238104Sdes * SUCH DAMAGE. 28238104Sdes * 29238104Sdes */ 30238104Sdes 31238104Sdes#include <sys/cdefs.h> 32238104Sdes__FBSDID("$FreeBSD: stable/10/usr.sbin/ctld/discovery.c 276613 2015-01-03 13:08:08Z mav $"); 33238104Sdes 34238104Sdes#include <assert.h> 35238104Sdes#include <stdint.h> 36238104Sdes#include <stdio.h> 37238104Sdes#include <stdlib.h> 38238104Sdes#include <string.h> 39238104Sdes#include <netinet/in.h> 40238104Sdes#include <netdb.h> 41238104Sdes#include <sys/socket.h> 42238104Sdes 43238104Sdes#include "ctld.h" 44238104Sdes#include "iscsi_proto.h" 45238104Sdes 46238104Sdesstatic struct pdu * 47238104Sdestext_receive(struct connection *conn) 48238104Sdes{ 49238104Sdes struct pdu *request; 50238104Sdes struct iscsi_bhs_text_request *bhstr; 51238104Sdes 52238104Sdes request = pdu_new(conn); 53238104Sdes pdu_receive(request); 54238104Sdes if ((request->pdu_bhs->bhs_opcode & ~ISCSI_BHS_OPCODE_IMMEDIATE) != 55238104Sdes ISCSI_BHS_OPCODE_TEXT_REQUEST) 56238104Sdes log_errx(1, "protocol error: received invalid opcode 0x%x", 57269257Sdes request->pdu_bhs->bhs_opcode); 58269257Sdes bhstr = (struct iscsi_bhs_text_request *)request->pdu_bhs; 59238104Sdes#if 0 60238104Sdes if ((bhstr->bhstr_flags & ISCSI_BHSTR_FLAGS_FINAL) == 0) 61238104Sdes log_errx(1, "received Text PDU without the \"F\" flag"); 62238104Sdes#endif 63238104Sdes /* 64238104Sdes * XXX: Implement the C flag some day. 65238104Sdes */ 66238104Sdes if ((bhstr->bhstr_flags & BHSTR_FLAGS_CONTINUE) != 0) 67238104Sdes log_errx(1, "received Text PDU with unsupported \"C\" flag"); 68238104Sdes if (ISCSI_SNLT(ntohl(bhstr->bhstr_cmdsn), conn->conn_cmdsn)) { 69238104Sdes log_errx(1, "received Text PDU with decreasing CmdSN: " 70238104Sdes "was %u, is %u", conn->conn_cmdsn, ntohl(bhstr->bhstr_cmdsn)); 71238104Sdes } 72238104Sdes if (ntohl(bhstr->bhstr_expstatsn) != conn->conn_statsn) { 73238104Sdes log_errx(1, "received Text PDU with wrong StatSN: " 74238104Sdes "is %u, should be %u", ntohl(bhstr->bhstr_expstatsn), 75269257Sdes conn->conn_statsn); 76238104Sdes } 77238104Sdes conn->conn_cmdsn = ntohl(bhstr->bhstr_cmdsn); 78238104Sdes 79238104Sdes return (request); 80238104Sdes} 81238104Sdes 82238104Sdesstatic struct pdu * 83238104Sdestext_new_response(struct pdu *request) 84238104Sdes{ 85238104Sdes struct pdu *response; 86238104Sdes struct connection *conn; 87238104Sdes struct iscsi_bhs_text_request *bhstr; 88238104Sdes struct iscsi_bhs_text_response *bhstr2; 89238104Sdes 90238104Sdes bhstr = (struct iscsi_bhs_text_request *)request->pdu_bhs; 91238104Sdes conn = request->pdu_connection; 92238104Sdes 93238104Sdes response = pdu_new_response(request); 94238104Sdes bhstr2 = (struct iscsi_bhs_text_response *)response->pdu_bhs; 95238104Sdes bhstr2->bhstr_opcode = ISCSI_BHS_OPCODE_TEXT_RESPONSE; 96238104Sdes bhstr2->bhstr_flags = BHSTR_FLAGS_FINAL; 97238104Sdes bhstr2->bhstr_lun = bhstr->bhstr_lun; 98238104Sdes bhstr2->bhstr_initiator_task_tag = bhstr->bhstr_initiator_task_tag; 99238104Sdes bhstr2->bhstr_target_transfer_tag = bhstr->bhstr_target_transfer_tag; 100238104Sdes bhstr2->bhstr_statsn = htonl(conn->conn_statsn++); 101238104Sdes bhstr2->bhstr_expcmdsn = htonl(conn->conn_cmdsn); 102238104Sdes bhstr2->bhstr_maxcmdsn = htonl(conn->conn_cmdsn); 103238104Sdes 104246827Sdes return (response); 105238104Sdes} 106238104Sdes 107238104Sdesstatic struct pdu * 108238104Sdeslogout_receive(struct connection *conn) 109238104Sdes{ 110238104Sdes struct pdu *request; 111238104Sdes struct iscsi_bhs_logout_request *bhslr; 112238104Sdes 113238104Sdes request = pdu_new(conn); 114238104Sdes pdu_receive(request); 115238104Sdes if ((request->pdu_bhs->bhs_opcode & ~ISCSI_BHS_OPCODE_IMMEDIATE) != 116238104Sdes ISCSI_BHS_OPCODE_LOGOUT_REQUEST) 117238104Sdes log_errx(1, "protocol error: received invalid opcode 0x%x", 118238104Sdes request->pdu_bhs->bhs_opcode); 119238104Sdes bhslr = (struct iscsi_bhs_logout_request *)request->pdu_bhs; 120238104Sdes if ((bhslr->bhslr_reason & 0x7f) != BHSLR_REASON_CLOSE_SESSION) 121238104Sdes log_debugx("received Logout PDU with invalid reason 0x%x; " 122238104Sdes "continuing anyway", bhslr->bhslr_reason & 0x7f); 123238104Sdes if (ISCSI_SNLT(ntohl(bhslr->bhslr_cmdsn), conn->conn_cmdsn)) { 124238104Sdes log_errx(1, "received Logout PDU with decreasing CmdSN: " 125238104Sdes "was %u, is %u", conn->conn_cmdsn, 126238104Sdes ntohl(bhslr->bhslr_cmdsn)); 127238104Sdes } 128238104Sdes if (ntohl(bhslr->bhslr_expstatsn) != conn->conn_statsn) { 129238104Sdes log_errx(1, "received Logout PDU with wrong StatSN: " 130238104Sdes "is %u, should be %u", ntohl(bhslr->bhslr_expstatsn), 131238104Sdes conn->conn_statsn); 132238104Sdes } 133238104Sdes conn->conn_cmdsn = ntohl(bhslr->bhslr_cmdsn); 134238104Sdes 135238104Sdes return (request); 136238104Sdes} 137238104Sdes 138238104Sdesstatic struct pdu * 139238104Sdeslogout_new_response(struct pdu *request) 140238104Sdes{ 141238104Sdes struct pdu *response; 142238104Sdes struct connection *conn; 143238104Sdes struct iscsi_bhs_logout_request *bhslr; 144238104Sdes struct iscsi_bhs_logout_response *bhslr2; 145238104Sdes 146238104Sdes bhslr = (struct iscsi_bhs_logout_request *)request->pdu_bhs; 147238104Sdes conn = request->pdu_connection; 148238104Sdes 149238104Sdes response = pdu_new_response(request); 150238104Sdes bhslr2 = (struct iscsi_bhs_logout_response *)response->pdu_bhs; 151238104Sdes bhslr2->bhslr_opcode = ISCSI_BHS_OPCODE_LOGOUT_RESPONSE; 152238104Sdes bhslr2->bhslr_flags = 0x80; 153238104Sdes bhslr2->bhslr_response = BHSLR_RESPONSE_CLOSED_SUCCESSFULLY; 154238104Sdes bhslr2->bhslr_initiator_task_tag = bhslr->bhslr_initiator_task_tag; 155238104Sdes bhslr2->bhslr_statsn = htonl(conn->conn_statsn++); 156238104Sdes bhslr2->bhslr_expcmdsn = htonl(conn->conn_cmdsn); 157238104Sdes bhslr2->bhslr_maxcmdsn = htonl(conn->conn_cmdsn); 158238104Sdes 159238104Sdes return (response); 160238104Sdes} 161238104Sdes 162238104Sdesstatic void 163238104Sdesdiscovery_add_target(struct keys *response_keys, const struct target *targ) 164238104Sdes{ 165238104Sdes struct portal *portal; 166246827Sdes char *buf; 167238104Sdes char hbuf[NI_MAXHOST], sbuf[NI_MAXSERV]; 168238104Sdes struct addrinfo *ai; 169238104Sdes int ret; 170238104Sdes 171238104Sdes keys_add(response_keys, "TargetName", targ->t_name); 172238104Sdes TAILQ_FOREACH(portal, &targ->t_portal_group->pg_portals, p_next) { 173238104Sdes ai = portal->p_ai; 174238104Sdes ret = getnameinfo(ai->ai_addr, ai->ai_addrlen, 175238104Sdes hbuf, sizeof(hbuf), sbuf, sizeof(sbuf), 176238104Sdes NI_NUMERICHOST | NI_NUMERICSERV); 177238104Sdes if (ret != 0) { 178238104Sdes log_warnx("getnameinfo: %s", gai_strerror(ret)); 179238104Sdes continue; 180238104Sdes } 181238104Sdes switch (ai->ai_addr->sa_family) { 182238104Sdes case AF_INET: 183238104Sdes if (strcmp(hbuf, "0.0.0.0") == 0) 184238104Sdes continue; 185238104Sdes ret = asprintf(&buf, "%s:%s,%d", hbuf, sbuf, 186238104Sdes targ->t_portal_group->pg_tag); 187238104Sdes break; 188238104Sdes case AF_INET6: 189238104Sdes if (strcmp(hbuf, "::") == 0) 190238104Sdes continue; 191238104Sdes ret = asprintf(&buf, "[%s]:%s,%d", hbuf, sbuf, 192238104Sdes targ->t_portal_group->pg_tag); 193238104Sdes break; 194238104Sdes default: 195238104Sdes continue; 196238104Sdes } 197238104Sdes if (ret <= 0) 198238104Sdes log_err(1, "asprintf"); 199238104Sdes keys_add(response_keys, "TargetAddress", buf); 200238104Sdes free(buf); 201238104Sdes } 202238104Sdes} 203238104Sdes 204238104Sdesstatic bool 205238104Sdesdiscovery_target_filtered_out(const struct connection *conn, 206238104Sdes const struct target *targ) 207238104Sdes{ 208238104Sdes const struct auth_group *ag; 209238104Sdes const struct portal_group *pg; 210238104Sdes const struct auth *auth; 211238104Sdes int error; 212238104Sdes 213238104Sdes ag = targ->t_auth_group; 214238104Sdes pg = conn->conn_portal->p_portal_group; 215238104Sdes 216238104Sdes assert(pg->pg_discovery_auth_group != PG_FILTER_UNKNOWN); 217238104Sdes 218238104Sdes if (pg->pg_discovery_filter >= PG_FILTER_PORTAL && 219238104Sdes auth_portal_check(ag, &conn->conn_initiator_sa) != 0) { 220238104Sdes log_debugx("initiator does not match initiator portals " 221238104Sdes "allowed for target \"%s\"; skipping", targ->t_name); 222238104Sdes return (true); 223238104Sdes } 224238104Sdes 225238104Sdes if (pg->pg_discovery_filter >= PG_FILTER_PORTAL_NAME && 226238104Sdes auth_name_check(ag, conn->conn_initiator_name) != 0) { 227238104Sdes log_debugx("initiator does not match initiator names " 228238104Sdes "allowed for target \"%s\"; skipping", targ->t_name); 229238104Sdes return (true); 230238104Sdes } 231238104Sdes 232238104Sdes if (pg->pg_discovery_filter >= PG_FILTER_PORTAL_NAME_AUTH && 233238104Sdes ag->ag_type != AG_TYPE_NO_AUTHENTICATION) { 234238104Sdes if (conn->conn_chap == NULL) { 235238104Sdes assert(pg->pg_discovery_auth_group->ag_type == 236238104Sdes AG_TYPE_NO_AUTHENTICATION); 237238104Sdes 238238104Sdes log_debugx("initiator didn't authenticate, but target " 239238104Sdes "\"%s\" requires CHAP; skipping", targ->t_name); 240238104Sdes return (true); 241238104Sdes } 242238104Sdes 243238104Sdes assert(conn->conn_user != NULL); 244238104Sdes auth = auth_find(ag, conn->conn_user); 245238104Sdes if (auth == NULL) { 246238104Sdes log_debugx("CHAP user \"%s\" doesn't match target " 247238104Sdes "\"%s\"; skipping", conn->conn_user, targ->t_name); 248238104Sdes return (true); 249238104Sdes } 250238104Sdes 251238104Sdes error = chap_authenticate(conn->conn_chap, auth->a_secret); 252238104Sdes if (error != 0) { 253238104Sdes log_debugx("password for CHAP user \"%s\" doesn't " 254238104Sdes "match target \"%s\"; skipping", 255238104Sdes conn->conn_user, targ->t_name); 256238104Sdes return (true); 257238104Sdes } 258238104Sdes } 259238104Sdes 260238104Sdes return (false); 261238104Sdes} 262238104Sdes 263238104Sdesvoid 264238104Sdesdiscovery(struct connection *conn) 265238104Sdes{ 266238104Sdes struct pdu *request, *response; 267238104Sdes struct keys *request_keys, *response_keys; 268238104Sdes const struct portal_group *pg; 269238104Sdes const struct target *targ; 270238104Sdes const char *send_targets; 271238104Sdes 272246827Sdes pg = conn->conn_portal->p_portal_group; 273238104Sdes 274238104Sdes log_debugx("beginning discovery session; waiting for Text PDU"); 275238104Sdes request = text_receive(conn); 276238104Sdes request_keys = keys_new(); 277238104Sdes keys_load(request_keys, request); 278238104Sdes 279238104Sdes send_targets = keys_find(request_keys, "SendTargets"); 280238104Sdes if (send_targets == NULL) 281238104Sdes log_errx(1, "received Text PDU without SendTargets"); 282238104Sdes 283238104Sdes response = text_new_response(request); 284238104Sdes response_keys = keys_new(); 285238104Sdes 286238104Sdes if (strcmp(send_targets, "All") == 0) { 287238104Sdes TAILQ_FOREACH(targ, &pg->pg_conf->conf_targets, t_next) { 288238104Sdes if (targ->t_portal_group != pg) { 289238104Sdes log_debugx("not returning target \"%s\"; " 290238104Sdes "belongs to a different portal group", 291238104Sdes targ->t_name); 292238104Sdes continue; 293238104Sdes } 294238104Sdes if (discovery_target_filtered_out(conn, targ)) { 295238104Sdes /* Ignore this target. */ 296238104Sdes continue; 297238104Sdes } 298238104Sdes discovery_add_target(response_keys, targ); 299238104Sdes } 300238104Sdes } else { 301238104Sdes targ = target_find(pg->pg_conf, send_targets); 302238104Sdes if (targ == NULL) { 303238104Sdes log_debugx("initiator requested information on unknown " 304238104Sdes "target \"%s\"; returning nothing", send_targets); 305238104Sdes } else { 306238104Sdes if (discovery_target_filtered_out(conn, targ)) { 307238104Sdes /* Ignore this target. */ 308238104Sdes } else { 309238104Sdes discovery_add_target(response_keys, targ); 310238104Sdes } 311238104Sdes } 312238104Sdes } 313238104Sdes keys_save(response_keys, response); 314238104Sdes 315238104Sdes pdu_send(response); 316238104Sdes pdu_delete(response); 317246827Sdes keys_delete(response_keys); 318238104Sdes pdu_delete(request); 319238104Sdes keys_delete(request_keys); 320238104Sdes 321238104Sdes log_debugx("done sending targets; waiting for Logout PDU"); 322238104Sdes request = logout_receive(conn); 323238104Sdes response = logout_new_response(request); 324238104Sdes 325238104Sdes pdu_send(response); 326238104Sdes pdu_delete(response); 327238104Sdes pdu_delete(request); 328238104Sdes 329238104Sdes log_debugx("discovery session done"); 330238104Sdes} 331238104Sdes