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#include <sys/types.h>
32#include <netinet/in.h>
33
34#include <stdlib.h>
35#include <string.h>
36
37#include <iscsi_proto.h>
38#include "libiscsiutil.h"
39
40/* Construct a new TextRequest PDU. */
41static struct pdu *
42text_new_request(struct connection *conn, uint32_t ttt)
43{
44	struct pdu *request;
45	struct iscsi_bhs_text_request *bhstr;
46
47	request = pdu_new(conn);
48	bhstr = (struct iscsi_bhs_text_request *)request->pdu_bhs;
49	bhstr->bhstr_opcode = ISCSI_BHS_OPCODE_TEXT_REQUEST |
50	    ISCSI_BHS_OPCODE_IMMEDIATE;
51	bhstr->bhstr_flags = BHSTR_FLAGS_FINAL;
52	bhstr->bhstr_initiator_task_tag = 0;
53	bhstr->bhstr_target_transfer_tag = ttt;
54
55	bhstr->bhstr_cmdsn = conn->conn_cmdsn;
56	bhstr->bhstr_expstatsn = htonl(conn->conn_statsn + 1);
57
58	return (request);
59}
60
61/* Receive a TextRequest PDU from a connection. */
62static struct pdu *
63text_receive_request(struct connection *conn)
64{
65	struct pdu *request;
66	struct iscsi_bhs_text_request *bhstr;
67
68	request = pdu_new(conn);
69	pdu_receive(request);
70	if ((request->pdu_bhs->bhs_opcode & ~ISCSI_BHS_OPCODE_IMMEDIATE) !=
71	    ISCSI_BHS_OPCODE_TEXT_REQUEST)
72		log_errx(1, "protocol error: received invalid opcode 0x%x",
73		    request->pdu_bhs->bhs_opcode);
74	bhstr = (struct iscsi_bhs_text_request *)request->pdu_bhs;
75
76	/*
77	 * XXX: Implement the C flag some day.
78	 */
79	if ((bhstr->bhstr_flags & (BHSTR_FLAGS_FINAL | BHSTR_FLAGS_CONTINUE)) !=
80	    BHSTR_FLAGS_FINAL)
81		log_errx(1, "received TextRequest PDU with invalid "
82		    "flags: %u", bhstr->bhstr_flags);
83	if (ISCSI_SNLT(ntohl(bhstr->bhstr_cmdsn), conn->conn_cmdsn)) {
84		log_errx(1, "received TextRequest PDU with decreasing CmdSN: "
85		    "was %u, is %u", conn->conn_cmdsn, ntohl(bhstr->bhstr_cmdsn));
86	}
87	conn->conn_cmdsn = ntohl(bhstr->bhstr_cmdsn);
88	if ((bhstr->bhstr_opcode & ISCSI_BHS_OPCODE_IMMEDIATE) == 0)
89		conn->conn_cmdsn++;
90
91	return (request);
92}
93
94/* Construct a new TextResponse PDU in reply to a request. */
95static struct pdu *
96text_new_response(struct pdu *request, uint32_t ttt, bool final)
97{
98	struct pdu *response;
99	struct connection *conn;
100	struct iscsi_bhs_text_request *bhstr;
101	struct iscsi_bhs_text_response *bhstr2;
102
103	bhstr = (struct iscsi_bhs_text_request *)request->pdu_bhs;
104	conn = request->pdu_connection;
105
106	response = pdu_new_response(request);
107	bhstr2 = (struct iscsi_bhs_text_response *)response->pdu_bhs;
108	bhstr2->bhstr_opcode = ISCSI_BHS_OPCODE_TEXT_RESPONSE;
109	if (final)
110		bhstr2->bhstr_flags = BHSTR_FLAGS_FINAL;
111	else
112		bhstr2->bhstr_flags = BHSTR_FLAGS_CONTINUE;
113	bhstr2->bhstr_lun = bhstr->bhstr_lun;
114	bhstr2->bhstr_initiator_task_tag = bhstr->bhstr_initiator_task_tag;
115	bhstr2->bhstr_target_transfer_tag = ttt;
116	bhstr2->bhstr_statsn = htonl(conn->conn_statsn++);
117	bhstr2->bhstr_expcmdsn = htonl(conn->conn_cmdsn);
118	bhstr2->bhstr_maxcmdsn = htonl(conn->conn_cmdsn);
119
120	return (response);
121}
122
123/* Receive a TextResponse PDU from a connection. */
124static struct pdu *
125text_receive_response(struct connection *conn)
126{
127	struct pdu *response;
128	struct iscsi_bhs_text_response *bhstr;
129	uint8_t flags;
130
131	response = pdu_new(conn);
132	pdu_receive(response);
133	if (response->pdu_bhs->bhs_opcode != ISCSI_BHS_OPCODE_TEXT_RESPONSE)
134		log_errx(1, "protocol error: received invalid opcode 0x%x",
135		    response->pdu_bhs->bhs_opcode);
136	bhstr = (struct iscsi_bhs_text_response *)response->pdu_bhs;
137	flags = bhstr->bhstr_flags & (BHSTR_FLAGS_FINAL | BHSTR_FLAGS_CONTINUE);
138	switch (flags) {
139	case BHSTR_FLAGS_CONTINUE:
140		if (bhstr->bhstr_target_transfer_tag == 0xffffffff)
141			log_errx(1, "received continue TextResponse PDU with "
142			    "invalid TTT 0x%x",
143			    bhstr->bhstr_target_transfer_tag);
144		break;
145	case BHSTR_FLAGS_FINAL:
146		if (bhstr->bhstr_target_transfer_tag != 0xffffffff)
147			log_errx(1, "received final TextResponse PDU with "
148			    "invalid TTT 0x%x",
149			    bhstr->bhstr_target_transfer_tag);
150		break;
151	default:
152		log_errx(1, "received TextResponse PDU with invalid "
153		    "flags: %u", bhstr->bhstr_flags);
154	}
155	if (ntohl(bhstr->bhstr_statsn) != conn->conn_statsn + 1) {
156		log_errx(1, "received TextResponse PDU with wrong StatSN: "
157		    "is %u, should be %u", ntohl(bhstr->bhstr_statsn),
158		    conn->conn_statsn + 1);
159	}
160	conn->conn_statsn = ntohl(bhstr->bhstr_statsn);
161
162	return (response);
163}
164
165/*
166 * Send a list of keys from the initiator to the target in a
167 * TextRequest PDU.
168 */
169void
170text_send_request(struct connection *conn, struct keys *request_keys)
171{
172	struct pdu *request;
173
174	request = text_new_request(conn, 0xffffffff);
175	keys_save_pdu(request_keys, request);
176	if (request->pdu_data_len == 0)
177		log_errx(1, "No keys to send in a TextRequest");
178	if (request->pdu_data_len >
179	    (size_t)conn->conn_max_send_data_segment_length)
180		log_errx(1, "Keys to send in TextRequest are too long");
181
182	pdu_send(request);
183	pdu_delete(request);
184}
185
186/*
187 * Read a list of keys from the target in a series of TextResponse
188 * PDUs.
189 */
190struct keys *
191text_read_response(struct connection *conn)
192{
193	struct keys *response_keys;
194	char *keys_data;
195	size_t keys_len;
196	uint32_t ttt;
197
198	keys_data = NULL;
199	keys_len = 0;
200	ttt = 0xffffffff;
201	for (;;) {
202		struct pdu *request, *response;
203		struct iscsi_bhs_text_response *bhstr;
204
205		response = text_receive_response(conn);
206		bhstr = (struct iscsi_bhs_text_response *)response->pdu_bhs;
207		if (keys_data == NULL) {
208			ttt = bhstr->bhstr_target_transfer_tag;
209			keys_data = response->pdu_data;
210			keys_len = response->pdu_data_len;
211			response->pdu_data = NULL;
212		} else {
213			keys_data = realloc(keys_data,
214			    keys_len + response->pdu_data_len);
215			if (keys_data == NULL)
216				log_err(1, "failed to grow keys block");
217			memcpy(keys_data + keys_len, response->pdu_data,
218			    response->pdu_data_len);
219			keys_len += response->pdu_data_len;
220		}
221		if ((bhstr->bhstr_flags & BHSTR_FLAGS_FINAL) != 0) {
222			pdu_delete(response);
223			break;
224		}
225		if (bhstr->bhstr_target_transfer_tag != ttt)
226			log_errx(1, "received non-final TextRequest PDU with "
227			    "invalid TTT 0x%x",
228			    bhstr->bhstr_target_transfer_tag);
229		pdu_delete(response);
230
231		/* Send an empty request. */
232		request = text_new_request(conn, ttt);
233		pdu_send(request);
234		pdu_delete(request);
235	}
236
237	response_keys = keys_new();
238	keys_load(response_keys, keys_data, keys_len);
239	free(keys_data);
240	return (response_keys);
241}
242
243/*
244 * Read a list of keys from the initiator in a TextRequest PDU.
245 */
246struct keys *
247text_read_request(struct connection *conn, struct pdu **requestp)
248{
249	struct iscsi_bhs_text_request *bhstr;
250	struct pdu *request;
251	struct keys *request_keys;
252
253	request = text_receive_request(conn);
254	bhstr = (struct iscsi_bhs_text_request *)request->pdu_bhs;
255	if (bhstr->bhstr_target_transfer_tag != 0xffffffff)
256		log_errx(1, "received TextRequest PDU with invalid TTT 0x%x",
257		    bhstr->bhstr_target_transfer_tag);
258	if (ntohl(bhstr->bhstr_expstatsn) != conn->conn_statsn) {
259		log_errx(1, "received TextRequest PDU with wrong ExpStatSN: "
260		    "is %u, should be %u", ntohl(bhstr->bhstr_expstatsn),
261		    conn->conn_statsn);
262	}
263
264	request_keys = keys_new();
265	keys_load_pdu(request_keys, request);
266	*requestp = request;
267	return (request_keys);
268}
269
270/*
271 * Send a response back to the initiator as a series of TextResponse
272 * PDUs.
273 */
274void
275text_send_response(struct pdu *request, struct keys *response_keys)
276{
277	struct connection *conn = request->pdu_connection;
278	char *keys_data;
279	size_t keys_len;
280	size_t keys_offset;
281	uint32_t ttt;
282
283	keys_save(response_keys, &keys_data, &keys_len);
284	keys_offset = 0;
285	ttt = keys_len;
286	for (;;) {
287		struct pdu *request2, *response;
288		struct iscsi_bhs_text_request *bhstr;
289		size_t todo;
290		bool final;
291
292		todo = keys_len - keys_offset;
293		if (todo > (size_t)conn->conn_max_send_data_segment_length) {
294			final = false;
295			todo = conn->conn_max_send_data_segment_length;
296		} else {
297			final = true;
298			ttt = 0xffffffff;
299		}
300
301		response = text_new_response(request, ttt, final);
302		response->pdu_data = keys_data + keys_offset;
303		response->pdu_data_len = todo;
304		keys_offset += todo;
305
306		pdu_send(response);
307		response->pdu_data = NULL;
308		pdu_delete(response);
309
310		if (final)
311			break;
312
313		/*
314		 * Wait for an empty request.
315		 *
316		 * XXX: Linux's Open-iSCSI initiator doesn't update
317		 * ExpStatSN when receiving a TextResponse PDU.
318		 */
319		request2 = text_receive_request(conn);
320		bhstr = (struct iscsi_bhs_text_request *)request2->pdu_bhs;
321		if ((bhstr->bhstr_flags & BHSTR_FLAGS_FINAL) == 0)
322			log_errx(1, "received continuation TextRequest PDU "
323			    "without F set");
324		if (pdu_data_segment_length(request2) != 0)
325			log_errx(1, "received non-empty continuation "
326			    "TextRequest PDU");
327		if (bhstr->bhstr_target_transfer_tag != ttt)
328			log_errx(1, "received TextRequest PDU with invalid "
329			    "TTT 0x%x", bhstr->bhstr_target_transfer_tag);
330		pdu_delete(request2);
331	}
332	free(keys_data);
333}
334