summaryrefslogtreecommitdiffstats
path: root/usr/kinit/nfsmount/dummypmap.c
diff options
context:
space:
mode:
Diffstat (limited to 'usr/kinit/nfsmount/dummypmap.c')
-rw-r--r--usr/kinit/nfsmount/dummypmap.c281
1 files changed, 281 insertions, 0 deletions
diff --git a/usr/kinit/nfsmount/dummypmap.c b/usr/kinit/nfsmount/dummypmap.c
new file mode 100644
index 0000000..07210c5
--- /dev/null
+++ b/usr/kinit/nfsmount/dummypmap.c
@@ -0,0 +1,281 @@
+/*
+ * Enough portmapper functionality that mount doesn't hang trying
+ * to start lockd. Enables nfsroot with locking functionality.
+ *
+ * Note: the kernel will only speak to the local portmapper
+ * using RPC over UDP.
+ */
+
+#include <sys/types.h>
+#include <netinet/in.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <unistd.h>
+#include <string.h>
+#include <sys/socket.h>
+
+#include "dummypmap.h"
+#include "sunrpc.h"
+
+extern const char *progname;
+
+struct portmap_args {
+ uint32_t program;
+ uint32_t version;
+ uint32_t proto;
+ uint32_t port;
+};
+
+struct portmap_call {
+ struct rpc_call rpc;
+ struct portmap_args args;
+};
+
+struct portmap_reply {
+ struct rpc_reply rpc;
+ uint32_t port;
+};
+
+static int bind_portmap(void)
+{
+ int sock = socket(PF_INET, SOCK_DGRAM, 0);
+ struct sockaddr_in sin;
+
+ if (sock < 0)
+ return -1;
+
+ memset(&sin, 0, sizeof sin);
+ sin.sin_family = AF_INET;
+ sin.sin_addr.s_addr = htonl(0x7f000001); /* 127.0.0.1 */
+ sin.sin_port = htons(RPC_PMAP_PORT);
+ if (bind(sock, (struct sockaddr *)&sin, sizeof sin) < 0) {
+ int err = errno;
+ close(sock);
+ errno = err;
+ return -1;
+ }
+
+ return sock;
+}
+
+static const char *protoname(uint32_t proto)
+{
+ switch (ntohl(proto)) {
+ case IPPROTO_TCP:
+ return "tcp";
+ case IPPROTO_UDP:
+ return "udp";
+ default:
+ return NULL;
+ }
+}
+
+static void *get_auth(struct rpc_auth *auth)
+{
+ switch (ntohl(auth->flavor)) {
+ case AUTH_NULL:
+ /* Fallthrough */
+ case AUTH_UNIX:
+ return (char *)&auth->body + ntohl(auth->len);
+ default:
+ return NULL;
+ }
+}
+
+static int check_unix_cred(struct rpc_auth *cred)
+{
+ uint32_t len;
+ int quad_len;
+ uint32_t node_name_len;
+ int quad_name_len;
+ uint32_t *base;
+ uint32_t *pos;
+ int ret = -1;
+
+ len = ntohl(cred->len);
+ quad_len = (len + 3) >> 2;
+ if (quad_len < 6)
+ /* Malformed creds */
+ goto out;
+
+ base = pos = cred->body;
+
+ /* Skip timestamp */
+ pos++;
+
+ /* Skip node name: only localhost can succeed. */
+ node_name_len = ntohl(*pos++);
+ quad_name_len = (node_name_len + 3) >> 2;
+ if (pos + quad_name_len + 3 > base + quad_len)
+ /* Malformed creds */
+ goto out;
+ pos += quad_name_len;
+
+ /* uid must be 0 */
+ if (*pos++ != 0)
+ goto out;
+
+ /* gid must be 0 */
+ if (*pos++ != 0)
+ goto out;
+
+ /* Skip remaining gids */
+ ret = 0;
+
+out:
+ return ret;
+}
+
+static int check_cred(struct rpc_auth *cred)
+{
+ switch (ntohl(cred->flavor)) {
+ case AUTH_NULL:
+ return 0;
+ case AUTH_UNIX:
+ return check_unix_cred(cred);
+ default:
+ return -1;
+ }
+}
+
+static int check_vrf(struct rpc_auth *vrf)
+{
+ return (vrf->flavor == htonl(AUTH_NULL)) ? 0 : -1;
+}
+
+#define MAX_UDP_PACKET 65536
+
+static int dummy_portmap(int sock, FILE *portmap_file)
+{
+ enum { PAYLOAD_SIZE = MAX_UDP_PACKET + offsetof(struct rpc_header, udp) };
+ struct sockaddr_in sin;
+ int pktlen, addrlen;
+ union {
+ struct rpc_call rpc;
+ /* Max UDP packet size + unused TCP fragment size */
+ char payload[PAYLOAD_SIZE];
+ } pkt;
+ struct rpc_call *rpc = &pkt.rpc;
+ struct rpc_auth *cred;
+ struct rpc_auth *vrf;
+ struct portmap_args *args;
+ struct portmap_reply rply;
+
+ for (;;) {
+ addrlen = sizeof sin;
+ pktlen = recvfrom(sock, &rpc->hdr.udp, MAX_UDP_PACKET,
+ 0, (struct sockaddr *)&sin, &addrlen);
+
+ if (pktlen < 0) {
+ if (errno == EINTR)
+ continue;
+
+ return -1;
+ }
+
+ /* +4 to skip the TCP fragment header */
+ if (pktlen + 4 < sizeof(struct portmap_call))
+ continue; /* Bad packet */
+
+ if (rpc->hdr.udp.msg_type != htonl(RPC_CALL))
+ continue; /* Bad packet */
+
+ memset(&rply, 0, sizeof rply);
+
+ rply.rpc.hdr.udp.xid = rpc->hdr.udp.xid;
+ rply.rpc.hdr.udp.msg_type = htonl(RPC_REPLY);
+
+ cred = (struct rpc_auth *) &rpc->cred_flavor;
+ if (rpc->rpc_vers != htonl(2)) {
+ rply.rpc.reply_state = htonl(REPLY_DENIED);
+ /* state <- RPC_MISMATCH == 0 */
+ } else if (rpc->program != htonl(PORTMAP_PROGRAM)) {
+ rply.rpc.reply_state = htonl(PROG_UNAVAIL);
+ } else if (rpc->prog_vers != htonl(2)) {
+ rply.rpc.reply_state = htonl(PROG_MISMATCH);
+ } else if (!(vrf = get_auth(cred)) ||
+ (char *)vrf > ((char *)&rpc->hdr.udp + pktlen - 8 -
+ sizeof(*args)) ||
+ !(args = get_auth(vrf)) ||
+ (char *)args > ((char *)&rpc->hdr.udp + pktlen -
+ sizeof(*args)) ||
+ check_cred(cred) || check_vrf(vrf)) {
+ /* Can't deal with credentials data; the kernel
+ won't send them */
+ rply.rpc.reply_state = htonl(SYSTEM_ERR);
+ } else {
+ switch (ntohl(rpc->proc)) {
+ case PMAP_PROC_NULL:
+ break;
+ case PMAP_PROC_SET:
+ if (args->proto == htonl(IPPROTO_TCP) ||
+ args->proto == htonl(IPPROTO_UDP)) {
+ if (portmap_file)
+ fprintf(portmap_file,
+ "%u %u %s %u\n",
+ ntohl(args->program),
+ ntohl(args->version),
+ protoname(args->proto),
+ ntohl(args->port));
+ rply.port = htonl(1); /* TRUE = success */
+ }
+ break;
+ case PMAP_PROC_UNSET:
+ rply.port = htonl(1); /* TRUE = success */
+ break;
+ case PMAP_PROC_GETPORT:
+ break;
+ case PMAP_PROC_DUMP:
+ break;
+ default:
+ rply.rpc.reply_state = htonl(PROC_UNAVAIL);
+ break;
+ }
+ }
+
+ sendto(sock, &rply.rpc.hdr.udp, sizeof rply - 4, 0,
+ (struct sockaddr *)&sin, addrlen);
+ }
+}
+
+pid_t start_dummy_portmap(const char *file)
+{
+ FILE *portmap_filep;
+ int sock;
+ pid_t spoof_portmap;
+
+ portmap_filep = fopen(file, "w");
+ if (!portmap_filep) {
+ fprintf(stderr, "%s: cannot write portmap file: %s\n",
+ progname, file);
+ return -1;
+ }
+
+ sock = bind_portmap();
+ if (sock == -1) {
+ if (errno == EINVAL || errno == EADDRINUSE)
+ return 0; /* Assume not needed */
+ else {
+ fclose(portmap_filep);
+ fprintf(stderr, "%s: portmap spoofing failed\n",
+ progname);
+ return -1;
+ }
+ }
+
+ spoof_portmap = fork();
+ if (spoof_portmap == -1) {
+ fclose(portmap_filep);
+ fprintf(stderr, "%s: cannot fork\n", progname);
+ return -1;
+ } else if (spoof_portmap == 0) {
+ /* Child process */
+ dummy_portmap(sock, portmap_filep);
+ _exit(255); /* Error */
+ } else {
+ /* Parent process */
+ close(sock);
+ return spoof_portmap;
+ }
+}