diff options
Diffstat (limited to 'usr/kinit/nfsmount/dummypmap.c')
-rw-r--r-- | usr/kinit/nfsmount/dummypmap.c | 281 |
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; + } +} |