| /* |
| * 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) |
| { |
| struct sockaddr_in sin; |
| int pktlen, addrlen; |
| union { |
| struct rpc_call rpc; |
| /* Max UDP packet size + unused TCP fragment size */ |
| char payload[MAX_UDP_PACKET + offsetof(struct rpc_header, udp)]; |
| } 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; |
| } |
| } |