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