discovery.c revision 279589
1/*-
2 * Copyright (c) 2012 The FreeBSD Foundation
3 * All rights reserved.
4 *
5 * This software was developed by Edward Tomasz Napierala under sponsorship
6 * from the FreeBSD Foundation.
7 *
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions
10 * are met:
11 * 1. Redistributions of source code must retain the above copyright
12 *    notice, this list of conditions and the following disclaimer.
13 * 2. Redistributions in binary form must reproduce the above copyright
14 *    notice, this list of conditions and the following disclaimer in the
15 *    documentation and/or other materials provided with the distribution.
16 *
17 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
18 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
21 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
23 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
24 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
26 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
27 * SUCH DAMAGE.
28 *
29 */
30
31#include <sys/cdefs.h>
32__FBSDID("$FreeBSD: head/usr.sbin/ctld/discovery.c 279589 2015-03-04 12:12:46Z mav $");
33
34#include <assert.h>
35#include <stdint.h>
36#include <stdio.h>
37#include <stdlib.h>
38#include <string.h>
39#include <netinet/in.h>
40#include <netdb.h>
41#include <sys/socket.h>
42
43#include "ctld.h"
44#include "iscsi_proto.h"
45
46static struct pdu *
47text_receive(struct connection *conn)
48{
49	struct pdu *request;
50	struct iscsi_bhs_text_request *bhstr;
51
52	request = pdu_new(conn);
53	pdu_receive(request);
54	if ((request->pdu_bhs->bhs_opcode & ~ISCSI_BHS_OPCODE_IMMEDIATE) !=
55	    ISCSI_BHS_OPCODE_TEXT_REQUEST)
56		log_errx(1, "protocol error: received invalid opcode 0x%x",
57		    request->pdu_bhs->bhs_opcode);
58	bhstr = (struct iscsi_bhs_text_request *)request->pdu_bhs;
59#if 0
60	if ((bhstr->bhstr_flags & ISCSI_BHSTR_FLAGS_FINAL) == 0)
61		log_errx(1, "received Text PDU without the \"F\" flag");
62#endif
63	/*
64	 * XXX: Implement the C flag some day.
65	 */
66	if ((bhstr->bhstr_flags & BHSTR_FLAGS_CONTINUE) != 0)
67		log_errx(1, "received Text PDU with unsupported \"C\" flag");
68	if (ISCSI_SNLT(ntohl(bhstr->bhstr_cmdsn), conn->conn_cmdsn)) {
69		log_errx(1, "received Text PDU with decreasing CmdSN: "
70		    "was %u, is %u", conn->conn_cmdsn, ntohl(bhstr->bhstr_cmdsn));
71	}
72	if (ntohl(bhstr->bhstr_expstatsn) != conn->conn_statsn) {
73		log_errx(1, "received Text PDU with wrong StatSN: "
74		    "is %u, should be %u", ntohl(bhstr->bhstr_expstatsn),
75		    conn->conn_statsn);
76	}
77	conn->conn_cmdsn = ntohl(bhstr->bhstr_cmdsn);
78	if ((bhstr->bhstr_opcode & ISCSI_BHS_OPCODE_IMMEDIATE) == 0)
79		conn->conn_cmdsn++;
80
81	return (request);
82}
83
84static struct pdu *
85text_new_response(struct pdu *request)
86{
87	struct pdu *response;
88	struct connection *conn;
89	struct iscsi_bhs_text_request *bhstr;
90	struct iscsi_bhs_text_response *bhstr2;
91
92	bhstr = (struct iscsi_bhs_text_request *)request->pdu_bhs;
93	conn = request->pdu_connection;
94
95	response = pdu_new_response(request);
96	bhstr2 = (struct iscsi_bhs_text_response *)response->pdu_bhs;
97	bhstr2->bhstr_opcode = ISCSI_BHS_OPCODE_TEXT_RESPONSE;
98	bhstr2->bhstr_flags = BHSTR_FLAGS_FINAL;
99	bhstr2->bhstr_lun = bhstr->bhstr_lun;
100	bhstr2->bhstr_initiator_task_tag = bhstr->bhstr_initiator_task_tag;
101	bhstr2->bhstr_target_transfer_tag = bhstr->bhstr_target_transfer_tag;
102	bhstr2->bhstr_statsn = htonl(conn->conn_statsn++);
103	bhstr2->bhstr_expcmdsn = htonl(conn->conn_cmdsn);
104	bhstr2->bhstr_maxcmdsn = htonl(conn->conn_cmdsn);
105
106	return (response);
107}
108
109static struct pdu *
110logout_receive(struct connection *conn)
111{
112	struct pdu *request;
113	struct iscsi_bhs_logout_request *bhslr;
114
115	request = pdu_new(conn);
116	pdu_receive(request);
117	if ((request->pdu_bhs->bhs_opcode & ~ISCSI_BHS_OPCODE_IMMEDIATE) !=
118	    ISCSI_BHS_OPCODE_LOGOUT_REQUEST)
119		log_errx(1, "protocol error: received invalid opcode 0x%x",
120		    request->pdu_bhs->bhs_opcode);
121	bhslr = (struct iscsi_bhs_logout_request *)request->pdu_bhs;
122	if ((bhslr->bhslr_reason & 0x7f) != BHSLR_REASON_CLOSE_SESSION)
123		log_debugx("received Logout PDU with invalid reason 0x%x; "
124		    "continuing anyway", bhslr->bhslr_reason & 0x7f);
125	if (ISCSI_SNLT(ntohl(bhslr->bhslr_cmdsn), conn->conn_cmdsn)) {
126		log_errx(1, "received Logout PDU with decreasing CmdSN: "
127		    "was %u, is %u", conn->conn_cmdsn,
128		    ntohl(bhslr->bhslr_cmdsn));
129	}
130	if (ntohl(bhslr->bhslr_expstatsn) != conn->conn_statsn) {
131		log_errx(1, "received Logout PDU with wrong StatSN: "
132		    "is %u, should be %u", ntohl(bhslr->bhslr_expstatsn),
133		    conn->conn_statsn);
134	}
135	conn->conn_cmdsn = ntohl(bhslr->bhslr_cmdsn);
136	if ((bhslr->bhslr_opcode & ISCSI_BHS_OPCODE_IMMEDIATE) == 0)
137		conn->conn_cmdsn++;
138
139	return (request);
140}
141
142static struct pdu *
143logout_new_response(struct pdu *request)
144{
145	struct pdu *response;
146	struct connection *conn;
147	struct iscsi_bhs_logout_request *bhslr;
148	struct iscsi_bhs_logout_response *bhslr2;
149
150	bhslr = (struct iscsi_bhs_logout_request *)request->pdu_bhs;
151	conn = request->pdu_connection;
152
153	response = pdu_new_response(request);
154	bhslr2 = (struct iscsi_bhs_logout_response *)response->pdu_bhs;
155	bhslr2->bhslr_opcode = ISCSI_BHS_OPCODE_LOGOUT_RESPONSE;
156	bhslr2->bhslr_flags = 0x80;
157	bhslr2->bhslr_response = BHSLR_RESPONSE_CLOSED_SUCCESSFULLY;
158	bhslr2->bhslr_initiator_task_tag = bhslr->bhslr_initiator_task_tag;
159	bhslr2->bhslr_statsn = htonl(conn->conn_statsn++);
160	bhslr2->bhslr_expcmdsn = htonl(conn->conn_cmdsn);
161	bhslr2->bhslr_maxcmdsn = htonl(conn->conn_cmdsn);
162
163	return (response);
164}
165
166static void
167discovery_add_target(struct keys *response_keys, const struct target *targ)
168{
169	struct port *port;
170	struct portal *portal;
171	char *buf;
172	char hbuf[NI_MAXHOST], sbuf[NI_MAXSERV];
173	struct addrinfo *ai;
174	int ret;
175
176	keys_add(response_keys, "TargetName", targ->t_name);
177	TAILQ_FOREACH(port, &targ->t_ports, p_ts) {
178	    if (port->p_portal_group == NULL)
179		continue;
180	    TAILQ_FOREACH(portal, &port->p_portal_group->pg_portals, p_next) {
181		ai = portal->p_ai;
182		ret = getnameinfo(ai->ai_addr, ai->ai_addrlen,
183		    hbuf, sizeof(hbuf), sbuf, sizeof(sbuf),
184		    NI_NUMERICHOST | NI_NUMERICSERV);
185		if (ret != 0) {
186			log_warnx("getnameinfo: %s", gai_strerror(ret));
187			continue;
188		}
189		switch (ai->ai_addr->sa_family) {
190		case AF_INET:
191			if (strcmp(hbuf, "0.0.0.0") == 0)
192				continue;
193			ret = asprintf(&buf, "%s:%s,%d", hbuf, sbuf,
194			    port->p_portal_group->pg_tag);
195			break;
196		case AF_INET6:
197			if (strcmp(hbuf, "::") == 0)
198				continue;
199			ret = asprintf(&buf, "[%s]:%s,%d", hbuf, sbuf,
200			    port->p_portal_group->pg_tag);
201			break;
202		default:
203			continue;
204		}
205		if (ret <= 0)
206		    log_err(1, "asprintf");
207		keys_add(response_keys, "TargetAddress", buf);
208		free(buf);
209	    }
210	}
211}
212
213static bool
214discovery_target_filtered_out(const struct connection *conn,
215    const struct port *port)
216{
217	const struct auth_group *ag;
218	const struct portal_group *pg;
219	const struct target *targ;
220	const struct auth *auth;
221	int error;
222
223	targ = port->p_target;
224	ag = port->p_auth_group;
225	if (ag == NULL)
226		ag = targ->t_auth_group;
227	pg = conn->conn_portal->p_portal_group;
228
229	assert(pg->pg_discovery_auth_group != PG_FILTER_UNKNOWN);
230
231	if (pg->pg_discovery_filter >= PG_FILTER_PORTAL &&
232	    auth_portal_check(ag, &conn->conn_initiator_sa) != 0) {
233		log_debugx("initiator does not match initiator portals "
234		    "allowed for target \"%s\"; skipping", targ->t_name);
235		return (true);
236	}
237
238	if (pg->pg_discovery_filter >= PG_FILTER_PORTAL_NAME &&
239	    auth_name_check(ag, conn->conn_initiator_name) != 0) {
240		log_debugx("initiator does not match initiator names "
241		    "allowed for target \"%s\"; skipping", targ->t_name);
242		return (true);
243	}
244
245	if (pg->pg_discovery_filter >= PG_FILTER_PORTAL_NAME_AUTH &&
246	    ag->ag_type != AG_TYPE_NO_AUTHENTICATION) {
247		if (conn->conn_chap == NULL) {
248			assert(pg->pg_discovery_auth_group->ag_type ==
249			    AG_TYPE_NO_AUTHENTICATION);
250
251			log_debugx("initiator didn't authenticate, but target "
252			    "\"%s\" requires CHAP; skipping", targ->t_name);
253			return (true);
254		}
255
256		assert(conn->conn_user != NULL);
257		auth = auth_find(ag, conn->conn_user);
258		if (auth == NULL) {
259			log_debugx("CHAP user \"%s\" doesn't match target "
260			    "\"%s\"; skipping", conn->conn_user, targ->t_name);
261			return (true);
262		}
263
264		error = chap_authenticate(conn->conn_chap, auth->a_secret);
265		if (error != 0) {
266			log_debugx("password for CHAP user \"%s\" doesn't "
267			    "match target \"%s\"; skipping",
268			    conn->conn_user, targ->t_name);
269			return (true);
270		}
271	}
272
273	return (false);
274}
275
276void
277discovery(struct connection *conn)
278{
279	struct pdu *request, *response;
280	struct keys *request_keys, *response_keys;
281	const struct port *port;
282	const struct portal_group *pg;
283	const char *send_targets;
284
285	pg = conn->conn_portal->p_portal_group;
286
287	log_debugx("beginning discovery session; waiting for Text PDU");
288	request = text_receive(conn);
289	request_keys = keys_new();
290	keys_load(request_keys, request);
291
292	send_targets = keys_find(request_keys, "SendTargets");
293	if (send_targets == NULL)
294		log_errx(1, "received Text PDU without SendTargets");
295
296	response = text_new_response(request);
297	response_keys = keys_new();
298
299	if (strcmp(send_targets, "All") == 0) {
300		TAILQ_FOREACH(port, &pg->pg_ports, p_pgs) {
301			if (discovery_target_filtered_out(conn, port)) {
302				/* Ignore this target. */
303				continue;
304			}
305			discovery_add_target(response_keys, port->p_target);
306		}
307	} else {
308		port = port_find_in_pg(pg, send_targets);
309		if (port == NULL) {
310			log_debugx("initiator requested information on unknown "
311			    "target \"%s\"; returning nothing", send_targets);
312		} else {
313			if (discovery_target_filtered_out(conn, port)) {
314				/* Ignore this target. */
315			} else {
316				discovery_add_target(response_keys, port->p_target);
317			}
318		}
319	}
320	keys_save(response_keys, response);
321
322	pdu_send(response);
323	pdu_delete(response);
324	keys_delete(response_keys);
325	pdu_delete(request);
326	keys_delete(request_keys);
327
328	log_debugx("done sending targets; waiting for Logout PDU");
329	request = logout_receive(conn);
330	response = logout_new_response(request);
331
332	pdu_send(response);
333	pdu_delete(response);
334	pdu_delete(request);
335
336	log_debugx("discovery session done");
337}
338