/* * 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 #include #include #include #include #include #include #include #include "sunrpc.h" extern const char *progname; struct portmap_call { struct rpc_call rpc; uint32_t program; uint32_t version; uint32_t proto; uint32_t port; }; 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 int dummy_portmap(int sock, FILE *portmap_file) { struct sockaddr_in sin; int pktlen, addrlen; union { struct portmap_call c; unsigned char b[65536]; /* Max UDP packet size */ } pkt; struct portmap_reply rply; for (;;) { addrlen = sizeof sin; pktlen = recvfrom(sock, &pkt.c.rpc.hdr.udp, sizeof pkt, 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 (pkt.c.rpc.hdr.udp.msg_type != htonl(RPC_CALL)) continue; /* Bad packet */ memset(&rply, 0, sizeof rply); rply.rpc.hdr.udp.xid = pkt.c.rpc.hdr.udp.xid; rply.rpc.hdr.udp.msg_type = htonl(RPC_REPLY); if (pkt.c.rpc.rpc_vers != htonl(2)) { rply.rpc.reply_state = htonl(REPLY_DENIED); /* state <- RPC_MISMATCH == 0 */ } else if (pkt.c.rpc.program != htonl(PORTMAP_PROGRAM)) { rply.rpc.reply_state = htonl(PROG_UNAVAIL); } else if (pkt.c.rpc.prog_vers != htonl(2)) { rply.rpc.reply_state = htonl(PROG_MISMATCH); } else if (pkt.c.rpc.cred_len != 0 || pkt.c.rpc.vrf_len != 0) { /* Can't deal with credentials data; the kernel won't send them */ rply.rpc.reply_state = htonl(SYSTEM_ERR); } else { switch (ntohl(pkt.c.rpc.proc)) { case PMAP_PROC_NULL: break; case PMAP_PROC_SET: if (pkt.c.proto == htonl(IPPROTO_TCP) || pkt.c.proto == htonl(IPPROTO_UDP)) { if (portmap_file) fprintf(portmap_file, "%u %u %s %u\n", ntohl(pkt.c.program), ntohl(pkt.c.version), protoname(pkt.c.proto), ntohl(pkt.c.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 { fprintf(stderr, "%s: portmap spoofing failed\n", progname); return -1; } } spoof_portmap = fork(); if (spoof_portmap == -1) { 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; } }