diff --git a/21-sendfd/client.c b/21-sendfd/client.c
index d93f588..70c6888 100644
--- a/21-sendfd/client.c
+++ b/21-sendfd/client.c
@@ -19,12 +19,88 @@
 // buf, bufsize: Buffer for the received message
 // fd:           Where to store the file descriptor
 int recvfd(int sock_fd, char *buf, size_t bufsize, int *fd) {
-    return 0;
+    // We prepare the msghdr like we do in the server, but do not
+    // fill the cmsg of the msghdr. For details, please look at server.c
+    struct iovec data = { .iov_base = buf, .iov_len = bufsize };
+    union {
+        char buf[CMSG_SPACE(sizeof(*fd))];
+        struct cmsghdr align;
+    } auxdata;
+
+    struct msghdr msgh = {
+        .msg_iov        = &data,
+        .msg_iovlen     = 1,
+        .msg_control    = auxdata.buf,
+        .msg_controllen = sizeof(auxdata.buf),
+    };
+
+    // We received from our socket a message (with space for an
+    // auxiliary cmsg). As we use SOCK_SEQPACKET, this will either receive the whole message or fail.
+    int len = recvmsg(sock_fd, &msgh, 0);
+    if (len < 0) die("recvmsg");
+
+    // We check that the received auxiliary data is of the correct
+    // type and that contains exactly one file descriptor.
+    struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msgh);
+    if (   cmsg->cmsg_level != SOL_SOCKET
+        || cmsg->cmsg_type  != SCM_RIGHTS
+        || cmsg->cmsg_len   != CMSG_LEN(sizeof(*fd)))
+        die("recvmsg/SOL_SOCKET");
+
+    // Extract the file descriptor and store it to the user provided
+    // location.
+    int *cmsg_fd = (int*)CMSG_DATA(cmsg);
+    *fd = *cmsg_fd;
+
+    // Length of message in buf. len <= bufsize
+    return len;
 }
 
 int main(int argc, char *argv[]) {
-    // FIXME: Create a socket(2) with AF_UNIX, SOCK_SEQPACKET
-    // FIXME: Connect to the domain socket "./socket"
-    // FIXME: Receive the file descriptor with recvfd()
-    // FIXME: Add a read/write loop that transfers data from your stdin to the received file descriptor (like cat)
+    // Create an connection-oriented AF_UNIX socket that preserves
+    // message boundaries.
+    int sock_fd = socket(AF_UNIX, SOCK_SEQPACKET, 0);
+    if (sock_fd < 0) die("socket");
+
+    // Create an socket address that points to the UNIX socket file
+    char *sock_name = "./socket";
+    struct sockaddr_un addr = { .sun_family = AF_UNIX };
+    strncpy(addr.sun_path, sock_name, sizeof(addr.sun_path)-1);
+
+    // Connect to that address on our socket
+    if (connect(sock_fd, (struct sockaddr*)&addr, sizeof(addr)) == -1)
+        die("connect");
+
+    // Receive message and file descriptor from the server
+    int stdout_fd;
+    char buf[4096];
+    int len = recvfd(sock_fd, buf, sizeof(buf)-1, &stdout_fd);
+
+    // Truncate the message until the first null byte and print out some information about the descriptor
+    printf("Received message: `%s'", buf);
+
+    // Resolve the link /proc/self/fd/<NUMBER> to give the user an idea what we have received
+    len = snprintf(buf, sizeof(buf), "/proc/self/fd/%d", stdout_fd);
+    char *linkname = buf + len + 1; 
+    if (readlink(buf, linkname, sizeof(buf) - (linkname - buf)) < 0)
+        die("readlink");
+    printf(" with fd=%d: %s -> %s\n", stdout_fd, buf, linkname);
+
+    // In an endless loop, we copy data from our stdin to the received file descriptor.
+    while (true) {
+        // We read a buffer full of data
+        size_t len = read(STDIN_FILENO, buf, sizeof(buf));
+        if (len < 0) die("read");
+        if (len == 0) break;
+
+        // As writes can be short, we issue writes until our buffer
+        // becomes empty.
+        int to_write = len;
+        char *ptr = buf;
+        do {
+            int written = write(stdout_fd, ptr, to_write);
+            to_write -= written;
+            ptr   += len;
+        } while (to_write > 0);
+    }
 }
diff --git a/21-sendfd/server.c b/21-sendfd/server.c
index 5fce383..0733ef1 100644
--- a/21-sendfd/server.c
+++ b/21-sendfd/server.c
@@ -20,14 +20,83 @@
 // buf, buflen: Message to send, arbitrary data
 // fd:          file descriptor to transfer
 void sendfd(int sockfd, void *buf, size_t buflen, int fd) {
-    // FIXME: Prepare a struct msghdr with an msg_control buffer
-    // FIXME: Attach the file descriptor as cmsg (see cmsg(3), unix(7))
-    // FIXME: Use sendmsg(2) to send the file descriptor and the message to the other process.
+    // We use sendmsg, which requires an iovec to describe the data
+    // sent. We already know this structure from the writev exercise
+    struct iovec data = { .iov_base = buf, .iov_len = buflen};
+
+    // Ancillary data buffer. We wrap it into an anonymous union to
+    // ensure correct alignment.
+    union {
+        // Buffer with enough space to hold a single descriptor
+        char buf[CMSG_SPACE(sizeof(fd))];
+        struct cmsghdr align;
+    } auxdata;
+
+    // We create a message header that points to our data buffer and
+    // to our auxdata buffer.
+    struct msghdr msgh = {
+        // Normal data to send
+        .msg_iov        = &data,
+        .msg_iovlen     = 1,
+        // Buffer for control data. See cmsg(3) for more details
+        .msg_control    = auxdata.buf,
+        .msg_controllen = sizeof(auxdata.buf),
+    };
+
+    // We prepare the auxiliary data
+    struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msgh);
+    cmsg->cmsg_level = SOL_SOCKET;
+    cmsg->cmsg_type  = SCM_RIGHTS; // See unix(7), SCM_RIGHTS
+    cmsg->cmsg_len   = CMSG_LEN(sizeof(fd));
+
+    // Set the file descriptor into the cmsg data
+    int *cmsg_fd = (int*)CMSG_DATA(cmsg);
+    *cmsg_fd = fd;
+
+    // Send our prepared message with sendmsg(2)
+    if (sendmsg(sockfd, &msgh, 0) == -1)
+        die("sendmsg");
 }
 
 int main() {
-    // FIXME: Create an socket with AF_UNIX and SOCK_SEQPACKET
-    // FIXME: Bind it to a filename and listen
-    // FIXME: Accept clients, send your STDOUT, and directly close the connection again
+    // Create a new UNIX domain socket. We use the SOCK_SEQPACKET
+    // socket type as it is a connection oriented socket (others
+    // connect to it), but it ensures message boundaries. Otherwise,
+    // the other side has to read exactly as many bytes as we write in
+    // order to get the auxiliary data correctly.
+    int sock_fd = socket(AF_UNIX, SOCK_SEQPACKET, 0);
+    if (sock_fd < 0) die("socket");
+
+    // Bind the socket to a filename. We already have seen that in the
+    // postbox exercise.
+    char *sock_name = "./socket";
+    struct sockaddr_un sockaddr = {.sun_family = AF_UNIX };
+    strcpy(sockaddr.sun_path, sock_name);
+
+    int rc = unlink(sock_name);
+    if (rc < 0 && errno != ENOENT) die("unlink/socket");
+
+    rc = bind(sock_fd, (struct sockaddr *) &sockaddr, sizeof(sockaddr));
+    if (rc == -1) die("bind/socket");
+
+    // As we are connection oriented, we listen on the socket.
+    rc = listen(sock_fd, 10);
+    if (rc < 0) die("listen/socket");
+
+    printf("Please connect: ./client\n");
+
+    while (true) {
+        // We wait for a client and establish a connection.
+        int client_fd = accept(sock_fd, NULL, NULL);
+        if (client_fd < 0) die("accept");
+
+        printf("Client on fd=%d. Sending STDOUT\n", client_fd);
+
+        // Send our STDOUT file descriptor with an accompanying message
+        sendfd(client_fd, "STDOUT", strlen("STDOUT"), STDOUT_FILENO);
+
+        // Directly close the client fd again.
+        close(client_fd);
+    }
 }