1 /* $OpenBSD: pdu.c,v 1.15 2025/01/23 12:17:48 claudio Exp $ */ 2 3 /* 4 * Copyright (c) 2009 Claudio Jeker <claudio@openbsd.org> 5 * 6 * Permission to use, copy, modify, and distribute this software for any 7 * purpose with or without fee is hereby granted, provided that the above 8 * copyright notice and this permission notice appear in all copies. 9 * 10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 15 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 16 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17 */ 18 #include <sys/types.h> 19 #include <sys/queue.h> 20 #include <sys/socket.h> 21 #include <sys/uio.h> 22 23 #include <scsi/iscsi.h> 24 25 #include <errno.h> 26 #include <event.h> 27 #include <limits.h> 28 #include <stdio.h> 29 #include <stdlib.h> 30 #include <string.h> 31 #include <unistd.h> 32 33 #include "iscsid.h" 34 #include "log.h" 35 36 size_t pdu_readbuf_read(struct pdu_readbuf *, void *, size_t); 37 size_t pdu_readbuf_len(struct pdu_readbuf *); 38 39 #define PDU_MIN(_x, _y) ((_x) < (_y) ? (_x) : (_y)) 40 41 void * 42 pdu_gethdr(struct pdu *p) 43 { 44 void *hdr; 45 46 if (!(hdr = calloc(1, sizeof(struct iscsi_pdu)))) 47 return NULL; 48 if (pdu_addbuf(p, hdr, sizeof(struct iscsi_pdu), PDU_HEADER)) { 49 free(hdr); 50 return NULL; 51 } 52 return hdr; 53 } 54 55 int 56 text_to_pdu(struct kvp *k, struct pdu *p) 57 { 58 char *buf, *s; 59 size_t len = 0, rem; 60 int n, nk; 61 62 if (k == NULL) 63 return 0; 64 65 nk = 0; 66 while(k[nk].key) { 67 len += 2 + strlen(k[nk].key) + strlen(k[nk].value); 68 nk++; 69 } 70 71 if (!(buf = pdu_alloc(len))) 72 return -1; 73 s = buf; 74 rem = len; 75 nk = 0; 76 while(k[nk].key) { 77 n = snprintf(s, rem, "%s=%s", k[nk].key, k[nk].value); 78 if (n < 0 || (size_t)n >= rem) 79 fatalx("text_to_pdu"); 80 rem -= n + 1; 81 s += n + 1; 82 nk++; 83 } 84 85 if (pdu_addbuf(p, buf, len, PDU_DATA)) 86 return -1; 87 return len; 88 } 89 90 struct kvp * 91 pdu_to_text(char *buf, size_t len) 92 { 93 struct kvp *k; 94 size_t n; 95 char *eq; 96 unsigned int nkvp = 0, i; 97 98 /* remove padding zeros */ 99 for (n = len; n > 0 && buf[n - 1] == '\0'; n--) 100 ; 101 if (n == len) { 102 log_debug("pdu_to_text: badly terminated text data"); 103 return NULL; 104 } 105 len = n + 1; 106 107 for(n = 0; n < len; n++) 108 if (buf[n] == '\0') 109 nkvp++; 110 111 if (!(k = calloc(nkvp + 1, sizeof(*k)))) 112 return NULL; 113 114 for (i = 0; i < nkvp; i++) { 115 eq = strchr(buf, '='); 116 if (!eq) { 117 log_debug("pdu_to_text: badly encoded text data"); 118 free(k); 119 return NULL; 120 } 121 *eq++ = '\0'; 122 k[i].key = buf; 123 k[i].value = eq; 124 buf = eq + strlen(eq) + 1; 125 } 126 return k; 127 } 128 129 /* Modified version of strtonum() to fit iscsid's need 130 * 131 * Copyright (c) 2004 Ted Unangst and Todd Miller 132 * All rights reserved. 133 * 134 * Permission to use, copy, modify, and distribute this software for any 135 * purpose with or without fee is hereby granted, provided that the above 136 * copyright notice and this permission notice appear in all copies. 137 * 138 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 139 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 140 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 141 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 142 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 143 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 144 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 145 */ 146 u_int64_t 147 text_to_num(const char *numstr, u_int64_t minval, u_int64_t maxval, 148 const char **errstrp) 149 { 150 unsigned long long ull = 0; 151 char *ep; 152 int error = 0; 153 struct errval { 154 const char *errstr; 155 int err; 156 } ev[4] = { 157 { NULL, 0 }, 158 { "invalid", EINVAL }, 159 { "too small", ERANGE }, 160 { "too large", ERANGE } 161 }; 162 #define INVALID 1 163 #define TOOSMALL 2 164 #define TOOLARGE 3 165 166 ev[0].err = errno; 167 errno = 0; 168 if (minval > maxval) 169 error = INVALID; 170 else { 171 ull = strtoull(numstr, &ep, 0); 172 if (numstr == ep || *ep != '\0') 173 error = INVALID; 174 else if (ull < minval) 175 error = TOOSMALL; 176 else if ((ull == ULLONG_MAX && errno == ERANGE) || ull > maxval) 177 error = TOOLARGE; 178 } 179 if (errstrp != NULL) 180 *errstrp = ev[error].errstr; 181 errno = ev[error].err; 182 if (error) 183 ull = 0; 184 185 return ull; 186 #undef INVALID 187 #undef TOOSMALL 188 #undef TOOLARGE 189 } 190 191 int 192 text_to_bool(const char *buf, const char **errstrp) 193 { 194 int val; 195 196 if (strcmp(buf, "Yes") == 0) 197 val = 1; 198 else if (strcmp(buf, "No") == 0) 199 val = 0; 200 else { 201 if (errstrp != NULL) 202 *errstrp = "invalid"; 203 return 0; 204 } 205 if (errstrp != NULL) 206 *errstrp = NULL; 207 return val; 208 } 209 210 int 211 text_to_digest(const char *buf, const char **errstrp) 212 { 213 int val = 0; 214 size_t len; 215 const char *p; 216 217 while (buf != NULL) { 218 p = strchr(buf, ','); 219 if (p == NULL) 220 len = strlen(buf); 221 else 222 len = p++ - buf; 223 224 if (strncmp(buf, "None", len) == 0) 225 val |= DIGEST_NONE; 226 else if (strncmp(buf, "CRC32C", len) == 0) 227 val |= DIGEST_CRC32C; 228 else { 229 if (errstrp != NULL) 230 *errstrp = "invalid"; 231 return 0; 232 } 233 buf = p; 234 } 235 if (errstrp != NULL) 236 *errstrp = NULL; 237 return val; 238 } 239 240 /* 241 * Internal functions to send/recv pdus. 242 */ 243 244 void 245 pdu_free_queue(struct pduq *channel) 246 { 247 struct pdu *p; 248 249 while ((p = TAILQ_FIRST(channel))) { 250 TAILQ_REMOVE(channel, p, entry); 251 pdu_free(p); 252 } 253 } 254 255 ssize_t 256 pdu_read(struct connection *c) 257 { 258 struct iovec iov[2]; 259 unsigned int niov = 1; 260 ssize_t n; 261 262 bzero(&iov, sizeof(iov)); 263 iov[0].iov_base = c->prbuf.buf + c->prbuf.wpos; 264 if (c->prbuf.wpos < c->prbuf.rpos) 265 iov[0].iov_len = c->prbuf.rpos - c->prbuf.wpos; 266 else { 267 iov[0].iov_len = c->prbuf.size - c->prbuf.wpos; 268 if (c->prbuf.rpos > 0) { 269 niov++; 270 iov[1].iov_base = c->prbuf.buf; 271 iov[1].iov_len = c->prbuf.rpos - 1; 272 } 273 } 274 275 if ((n = readv(c->fd, iov, niov)) == -1) 276 return -1; 277 if (n == 0) 278 /* XXX what should we do on close with remaining data? */ 279 return 0; 280 281 c->prbuf.wpos += n; 282 if (c->prbuf.wpos >= c->prbuf.size) 283 c->prbuf.wpos -= c->prbuf.size; 284 285 return n; 286 } 287 288 ssize_t 289 pdu_write(struct connection *c) 290 { 291 struct iovec iov[PDU_WRIOV]; 292 struct pdu *b, *nb; 293 unsigned int niov = 0, j; 294 size_t off, resid, size; 295 ssize_t n; 296 297 TAILQ_FOREACH(b, &c->pdu_w, entry) { 298 if (niov >= PDU_WRIOV) 299 break; 300 off = b->resid; 301 for (j = 0; j < PDU_MAXIOV && niov < PDU_WRIOV; j++) { 302 if (!b->iov[j].iov_len) 303 continue; 304 if (off >= b->iov[j].iov_len) { 305 off -= b->iov[j].iov_len; 306 continue; 307 } 308 iov[niov].iov_base = (char *)b->iov[j].iov_base + off; 309 iov[niov++].iov_len = b->iov[j].iov_len - off; 310 off = 0; 311 } 312 } 313 314 if ((n = writev(c->fd, iov, niov)) == -1) { 315 if (errno == EAGAIN || errno == ENOBUFS || 316 errno == EINTR) /* try later */ 317 return 0; 318 else { 319 log_warn("pdu_write"); 320 return -1; 321 } 322 } 323 if (n == 0) 324 return 0; 325 326 size = n; 327 for (b = TAILQ_FIRST(&c->pdu_w); b != NULL && size > 0; b = nb) { 328 nb = TAILQ_NEXT(b, entry); 329 resid = b->resid; 330 for (j = 0; j < PDU_MAXIOV; j++) { 331 if (resid >= b->iov[j].iov_len) 332 resid -= b->iov[j].iov_len; 333 else if (size >= b->iov[j].iov_len - resid) { 334 size -= b->iov[j].iov_len - resid; 335 b->resid += b->iov[j].iov_len - resid; 336 resid = 0; 337 } else { 338 b->resid += size; 339 size = 0; 340 break; 341 } 342 } 343 if (j == PDU_MAXIOV) { 344 /* all written */ 345 TAILQ_REMOVE(&c->pdu_w, b, entry); 346 pdu_free(b); 347 } 348 } 349 return n; 350 } 351 352 int 353 pdu_pending(struct connection *c) 354 { 355 if (TAILQ_EMPTY(&c->pdu_w)) 356 return 0; 357 else 358 return 1; 359 } 360 361 void 362 pdu_parse(struct connection *c) 363 { 364 struct pdu *p; 365 struct iscsi_pdu *ipdu; 366 char *ahb, *db; 367 size_t ahslen, dlen, off; 368 ssize_t n; 369 unsigned int j; 370 371 /* XXX XXX I DON'T LIKE YOU. CAN I REWRITE YOU? */ 372 373 do { 374 if (!(p = c->prbuf.wip)) { 375 /* get and parse base header */ 376 if (pdu_readbuf_len(&c->prbuf) < sizeof(*ipdu)) 377 return; 378 if (!(p = pdu_new())) 379 goto fail; 380 if (!(ipdu = pdu_gethdr(p))) 381 goto fail; 382 383 c->prbuf.wip = p; 384 /* 385 * XXX maybe a pdu_readbuf_peek() would allow a better 386 * error handling. 387 */ 388 pdu_readbuf_read(&c->prbuf, ipdu, sizeof(*ipdu)); 389 390 ahslen = ipdu->ahslen * sizeof(u_int32_t); 391 if (ahslen != 0) { 392 if (!(ahb = pdu_alloc(ahslen)) || 393 pdu_addbuf(p, ahb, ahslen, PDU_AHS)) 394 goto fail; 395 } 396 397 dlen = ipdu->datalen[0] << 16 | ipdu->datalen[1] << 8 | 398 ipdu->datalen[2]; 399 if (dlen != 0) { 400 if (!(db = pdu_alloc(dlen)) || 401 pdu_addbuf(p, db, dlen, PDU_DATA)) 402 goto fail; 403 } 404 405 p->resid = sizeof(*ipdu); 406 } else { 407 off = p->resid; 408 for (j = 0; j < PDU_MAXIOV; j++) { 409 if (off >= p->iov[j].iov_len) 410 off -= p->iov[j].iov_len; 411 else { 412 n = pdu_readbuf_read(&c->prbuf, 413 (char *)p->iov[j].iov_base + off, 414 p->iov[j].iov_len - off); 415 p->resid += n; 416 if (n == 0 || off + n != 417 p->iov[j].iov_len) 418 return; 419 } 420 } 421 p->resid = 0; /* reset resid so pdu can be reused */ 422 c->prbuf.wip = NULL; 423 task_pdu_cb(c, p); 424 } 425 } while (1); 426 fail: 427 fatalx("pdu_parse hit a space oddity"); 428 } 429 430 size_t 431 pdu_readbuf_read(struct pdu_readbuf *rb, void *ptr, size_t len) 432 { 433 size_t l; 434 435 if (rb->rpos == rb->wpos) { 436 return 0; 437 } else if (rb->rpos < rb->wpos) { 438 l = PDU_MIN(rb->wpos - rb->rpos, len); 439 memcpy(ptr, rb->buf + rb->rpos, l); 440 rb->rpos += l; 441 return l; 442 } else { 443 l = PDU_MIN(rb->size - rb->rpos, len); 444 memcpy(ptr, rb->buf + rb->rpos, l); 445 rb->rpos += l; 446 if (rb->rpos == rb->size) 447 rb->rpos = 0; 448 if (l < len) 449 return l + pdu_readbuf_read(rb, (char *)ptr + l, 450 len - l); 451 return l; 452 } 453 } 454 455 size_t 456 pdu_readbuf_len(struct pdu_readbuf *rb) 457 { 458 if (rb->rpos <= rb->wpos) 459 return rb->wpos - rb->rpos; 460 else 461 return rb->size - (rb->rpos - rb->wpos); 462 } 463 464 int 465 pdu_readbuf_set(struct pdu_readbuf *rb, size_t bsize) 466 { 467 char *nb; 468 469 if (bsize < rb->size) 470 /* can't shrink */ 471 return 0; 472 if ((nb = realloc(rb->buf, bsize)) == NULL) { 473 free(rb->buf); 474 return -1; 475 } 476 rb->buf = nb; 477 rb->size = bsize; 478 return 0; 479 } 480 481 void 482 pdu_readbuf_free(struct pdu_readbuf *rb) 483 { 484 free(rb->buf); 485 } 486