1 /*- 2 * SPDX-License-Identifier: BSD-2-Clause 3 * 4 * Copyright (c) 2012 The FreeBSD Foundation 5 * 6 * This software was developed by Edward Tomasz Napierala under sponsorship 7 * from the FreeBSD Foundation. 8 * 9 * Redistribution and use in source and binary forms, with or without 10 * modification, are permitted provided that the following conditions 11 * are met: 12 * 1. Redistributions of source code must retain the above copyright 13 * notice, this list of conditions and the following disclaimer. 14 * 2. Redistributions in binary form must reproduce the above copyright 15 * notice, this list of conditions and the following disclaimer in the 16 * documentation and/or other materials provided with the distribution. 17 * 18 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 19 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 21 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 22 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 24 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 25 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 26 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 27 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 28 * SUCH DAMAGE. 29 * 30 */ 31 32 #include <sys/cdefs.h> 33 #include <assert.h> 34 #include <stdio.h> 35 #include <stdlib.h> 36 #include <string.h> 37 #include <netinet/in.h> 38 #include <netdb.h> 39 #include <sys/socket.h> 40 41 #include "ctld.hh" 42 #include "iscsi.hh" 43 #include "iscsi_proto.h" 44 45 static struct pdu * 46 logout_receive(struct connection *conn) 47 { 48 struct pdu *request; 49 struct iscsi_bhs_logout_request *bhslr; 50 51 request = pdu_new(conn); 52 pdu_receive(request); 53 if ((request->pdu_bhs->bhs_opcode & ~ISCSI_BHS_OPCODE_IMMEDIATE) != 54 ISCSI_BHS_OPCODE_LOGOUT_REQUEST) 55 log_errx(1, "protocol error: received invalid opcode 0x%x", 56 request->pdu_bhs->bhs_opcode); 57 bhslr = (struct iscsi_bhs_logout_request *)request->pdu_bhs; 58 if ((bhslr->bhslr_reason & 0x7f) != BHSLR_REASON_CLOSE_SESSION) 59 log_debugx("received Logout PDU with invalid reason 0x%x; " 60 "continuing anyway", bhslr->bhslr_reason & 0x7f); 61 if (ISCSI_SNLT(ntohl(bhslr->bhslr_cmdsn), conn->conn_cmdsn)) { 62 log_errx(1, "received Logout PDU with decreasing CmdSN: " 63 "was %u, is %u", conn->conn_cmdsn, 64 ntohl(bhslr->bhslr_cmdsn)); 65 } 66 if (ntohl(bhslr->bhslr_expstatsn) != conn->conn_statsn) { 67 log_errx(1, "received Logout PDU with wrong ExpStatSN: " 68 "is %u, should be %u", ntohl(bhslr->bhslr_expstatsn), 69 conn->conn_statsn); 70 } 71 conn->conn_cmdsn = ntohl(bhslr->bhslr_cmdsn); 72 if ((bhslr->bhslr_opcode & ISCSI_BHS_OPCODE_IMMEDIATE) == 0) 73 conn->conn_cmdsn++; 74 75 return (request); 76 } 77 78 static struct pdu * 79 logout_new_response(struct pdu *request) 80 { 81 struct pdu *response; 82 struct connection *conn; 83 struct iscsi_bhs_logout_request *bhslr; 84 struct iscsi_bhs_logout_response *bhslr2; 85 86 bhslr = (struct iscsi_bhs_logout_request *)request->pdu_bhs; 87 conn = request->pdu_connection; 88 89 response = pdu_new_response(request); 90 bhslr2 = (struct iscsi_bhs_logout_response *)response->pdu_bhs; 91 bhslr2->bhslr_opcode = ISCSI_BHS_OPCODE_LOGOUT_RESPONSE; 92 bhslr2->bhslr_flags = 0x80; 93 bhslr2->bhslr_response = BHSLR_RESPONSE_CLOSED_SUCCESSFULLY; 94 bhslr2->bhslr_initiator_task_tag = bhslr->bhslr_initiator_task_tag; 95 bhslr2->bhslr_statsn = htonl(conn->conn_statsn++); 96 bhslr2->bhslr_expcmdsn = htonl(conn->conn_cmdsn); 97 bhslr2->bhslr_maxcmdsn = htonl(conn->conn_cmdsn); 98 99 return (response); 100 } 101 102 static void 103 discovery_add_target(struct keys *response_keys, const struct target *targ) 104 { 105 char *buf; 106 char hbuf[NI_MAXHOST], sbuf[NI_MAXSERV]; 107 const struct addrinfo *ai; 108 int ret; 109 110 keys_add(response_keys, "TargetName", targ->name()); 111 for (const port *port : targ->ports()) { 112 const struct portal_group *pg = port->portal_group(); 113 if (pg == nullptr) 114 continue; 115 for (const portal_up &portal : pg->portals()) { 116 if (portal->protocol() != portal_protocol::ISCSI && 117 portal->protocol() != portal_protocol::ISER) 118 continue; 119 ai = portal->ai(); 120 ret = getnameinfo(ai->ai_addr, ai->ai_addrlen, 121 hbuf, sizeof(hbuf), sbuf, sizeof(sbuf), 122 NI_NUMERICHOST | NI_NUMERICSERV); 123 if (ret != 0) { 124 log_warnx("getnameinfo: %s", gai_strerror(ret)); 125 continue; 126 } 127 switch (ai->ai_addr->sa_family) { 128 case AF_INET: 129 if (strcmp(hbuf, "0.0.0.0") == 0) 130 continue; 131 ret = asprintf(&buf, "%s:%s,%d", hbuf, sbuf, 132 pg->tag()); 133 break; 134 case AF_INET6: 135 if (strcmp(hbuf, "::") == 0) 136 continue; 137 ret = asprintf(&buf, "[%s]:%s,%d", hbuf, sbuf, 138 pg->tag()); 139 break; 140 default: 141 continue; 142 } 143 if (ret <= 0) 144 log_err(1, "asprintf"); 145 keys_add(response_keys, "TargetAddress", buf); 146 free(buf); 147 } 148 } 149 } 150 151 bool 152 iscsi_connection::discovery_target_filtered_out(const struct port *port) const 153 { 154 const struct auth_group *ag; 155 const struct portal_group *pg; 156 const struct target *targ; 157 const struct auth *auth; 158 int error; 159 160 targ = port->target(); 161 ag = port->auth_group(); 162 if (ag == nullptr) 163 ag = targ->auth_group(); 164 pg = conn_portal->portal_group(); 165 166 assert(pg->discovery_filter() != discovery_filter::UNKNOWN); 167 168 if (pg->discovery_filter() >= discovery_filter::PORTAL && 169 !ag->initiator_permitted(conn_initiator_sa)) { 170 log_debugx("initiator does not match initiator portals " 171 "allowed for target \"%s\"; skipping", targ->name()); 172 return (true); 173 } 174 175 if (pg->discovery_filter() >= discovery_filter::PORTAL_NAME && 176 !ag->initiator_permitted(conn_initiator_name)) { 177 log_debugx("initiator does not match initiator names " 178 "allowed for target \"%s\"; skipping", targ->name()); 179 return (true); 180 } 181 182 if (pg->discovery_filter() >= discovery_filter::PORTAL_NAME_AUTH && 183 ag->type() != auth_type::NO_AUTHENTICATION) { 184 if (conn_chap == nullptr) { 185 assert(pg->discovery_auth_group()->type() == 186 auth_type::NO_AUTHENTICATION); 187 188 log_debugx("initiator didn't authenticate, but target " 189 "\"%s\" requires CHAP; skipping", targ->name()); 190 return (true); 191 } 192 193 assert(!conn_user.empty()); 194 auth = ag->find_auth(conn_user); 195 if (auth == NULL) { 196 log_debugx("CHAP user \"%s\" doesn't match target " 197 "\"%s\"; skipping", conn_user.c_str(), 198 targ->name()); 199 return (true); 200 } 201 202 error = chap_authenticate(conn_chap, auth->secret()); 203 if (error != 0) { 204 log_debugx("password for CHAP user \"%s\" doesn't " 205 "match target \"%s\"; skipping", 206 conn_user.c_str(), targ->name()); 207 return (true); 208 } 209 } 210 211 return (false); 212 } 213 214 void 215 iscsi_connection::discovery() 216 { 217 struct pdu *request, *response; 218 struct keys *request_keys, *response_keys; 219 const struct port *port; 220 const struct portal_group *pg; 221 const char *send_targets; 222 223 pg = conn_portal->portal_group(); 224 225 log_debugx("beginning discovery session; waiting for TextRequest PDU"); 226 request_keys = text_read_request(&conn, &request); 227 228 send_targets = keys_find(request_keys, "SendTargets"); 229 if (send_targets == NULL) 230 log_errx(1, "received TextRequest PDU without SendTargets"); 231 232 response_keys = keys_new(); 233 234 if (strcmp(send_targets, "All") == 0) { 235 for (const auto &kv : pg->ports()) { 236 port = kv.second; 237 if (discovery_target_filtered_out(port)) { 238 /* Ignore this target. */ 239 continue; 240 } 241 discovery_add_target(response_keys, port->target()); 242 } 243 } else { 244 port = pg->find_port(send_targets); 245 if (port == NULL) { 246 log_debugx("initiator requested information on unknown " 247 "target \"%s\"; returning nothing", send_targets); 248 } else { 249 if (discovery_target_filtered_out(port)) { 250 /* Ignore this target. */ 251 } else { 252 discovery_add_target(response_keys, 253 port->target()); 254 } 255 } 256 } 257 258 text_send_response(request, response_keys); 259 keys_delete(response_keys); 260 pdu_delete(request); 261 keys_delete(request_keys); 262 263 log_debugx("done sending targets; waiting for Logout PDU"); 264 request = logout_receive(&conn); 265 response = logout_new_response(request); 266 267 pdu_send(response); 268 pdu_delete(response); 269 pdu_delete(request); 270 271 log_debugx("discovery session done"); 272 } 273