X-Git-Url: https://www.chiark.greenend.org.uk/ucgi/~mdw/git/preload-hacks/blobdiff_plain/a6d9626bd8b7689ec679e0cdedef48be795dfd9e..72d55d579f7aa6a971699add35d467abe2f0c49b:/noip.c diff --git a/noip.c b/noip.c index 2f7fd14..04abe93 100644 --- a/noip.c +++ b/noip.c @@ -1,8 +1,36 @@ +/* -*-c-*- + * + * Make programs use Unix-domain sockets instead of IP + * + * (c) 2008 Straylight/Edgeware + */ + +/*----- Licensing notice --------------------------------------------------* + * + * This file is part of the preload-hacks package. + * + * Preload-hacks are free software; you can redistribute it and/or modify + * them under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + * + * Preload-hacks distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with mLib; if not, write to the Free Software Foundation, Inc., 59 Temple + * Place - Suite 330, Boston, MA 02111-1307, USA. + */ + #define _GNU_SOURCE #undef sun #undef SUN #define DEBUG +/*----- Header files ------------------------------------------------------*/ + #include #include #include @@ -25,9 +53,13 @@ #include #include -enum { UNUSED, STALE, USED }; -enum { WANT_FRESH, WANT_EXISTING }; -enum { DENY, ALLOW }; +/*----- Data structures ---------------------------------------------------*/ + +enum { UNUSED, STALE, USED }; /* Unix socket status values */ +enum { WANT_FRESH, WANT_EXISTING }; /* Socket address dispositions */ +enum { DENY, ALLOW }; /* ACL verdicts */ + +/* Access control list nodes */ typedef struct aclnode { struct aclnode *next; int act; @@ -35,19 +67,24 @@ typedef struct aclnode { unsigned short minport, maxport; } aclnode; +/* Local address records */ #define MAX_LOCAL_IPADDRS 16 static struct in_addr local_ipaddrs[MAX_LOCAL_IPADDRS]; static int n_local_ipaddrs; +/* General configuration */ static uid_t uid; static char *sockdir = 0; static int debug = 0; +static unsigned minautoport = 16384, maxautoport = 65536; +/* Access control lists */ static aclnode *bind_real, **bind_tail = &bind_real; static aclnode *connect_real, **connect_tail = &connect_real; -/* --- Import magic --- */ +/*----- Import the real versions of functions -----------------------------*/ +/* The list of functions to immport. */ #define IMPORTS(_) \ _(socket, int, (int, int, int)) \ _(socketpair, int, (int, int, int, int *)) \ @@ -66,11 +103,12 @@ static aclnode *connect_real, **connect_tail = &connect_real; _(recvmsg, ssize_t, (int, struct msghdr *, int)) \ _(close, int, (int)) +/* Function pointers to set up. */ #define DECL(imp, ret, args) static ret (*real_##imp) args; IMPORTS(DECL) #undef DECL -static void setup(void) __attribute__((constructor)); +/* Import the system calls. */ static void import(void) { #define IMPORT(imp, ret, args) \ @@ -79,35 +117,42 @@ static void import(void) #undef IMPORT } -/* --- Support --- */ +/*----- Utilities ---------------------------------------------------------*/ +/* Socket address casts */ #define SA(sa) ((struct sockaddr *)(sa)) #define SIN(sa) ((struct sockaddr_in *)(sa)) #define SUN(sa) ((struct sockaddr_un *)(sa)) +/* Raw bytes */ #define UC(ch) ((unsigned char)(ch)) +/* Memory allocation */ #define NEW(x) ((x) = xmalloc(sizeof(*x))) #define NEWV(x, n) ((x) = xmalloc(sizeof(*x) * (n))) +/* Debugging */ #ifdef DEBUG # define D(body) { if (debug) { body } } #else # define D(body) ; #endif +/* Preservation of error status */ #define PRESERVING_ERRNO(body) do { \ int _err = errno; { body } errno = _err; \ } while (0) +/* Allocate N bytes of memory; abort on failure. */ static void *xmalloc(size_t n) { void *p; if (!n) return (0); - if ((p = malloc(n)) == 0) { perror("malloc"); exit(1); } + if ((p = malloc(n)) == 0) { perror("malloc"); exit(127); } return (p); } +/* Allocate a copy of the null-terminated string P; abort on failure. */ static char *xstrdup(const char *p) { size_t n = strlen(p) + 1; @@ -115,42 +160,11 @@ static char *xstrdup(const char *p) memcpy(q, p, n); return (q); } - -static int unix_socket_status(struct sockaddr_un *sun, int quick_p) -{ - struct stat st; - FILE *fp = 0; - size_t len, n; - int rc; - char buf[256]; - - rc = UNUSED; - if (stat(sun->sun_path, &st) && errno == ENOENT) - goto done; - rc = USED; - if (quick_p) - goto done; - - if ((fp = fopen("/proc/net/unix", "r")) == 0) - goto done; - fgets(buf, sizeof(buf), fp); /* skip header */ - len = strlen(sun->sun_path); - while (fgets(buf, sizeof(buf), fp)) { - n = strlen(buf); - if (n >= len + 2 && buf[n - len - 2] == ' ' && buf[n - 1] == '\n' && - memcmp(buf + n - len - 1, sun->sun_path, len) == 0) - goto done; - } - if (ferror(fp)) - goto done; - rc = STALE; -done: - if (fp) fclose(fp); - return (rc); -} +/*----- Access control lists ----------------------------------------------*/ #ifdef DEBUG +/* Write to standard error a description of the ACL node A. */ static void dump_aclnode(aclnode *a) { char minbuf[16], maxbuf[16]; @@ -177,8 +191,21 @@ static void dump_aclnode(aclnode *a) fputc('\n', stderr); } +static void dump_acl(aclnode *a) +{ + int act = ALLOW; + + for (; a; a = a->next) { + dump_aclnode(a); + act = a->act; + } + fprintf(stderr, "noip: [default policy: %s]\n", + act == ALLOW ? "DENY" : "ALLOW"); +} + #endif +/* Returns nonzero if the ACL A allows the IP socket SIN. */ static int acl_allows_p(aclnode *a, const struct sockaddr_in *sin) { unsigned long addr = ntohl(sin->sin_addr.s_addr); @@ -202,28 +229,70 @@ static int acl_allows_p(aclnode *a, const struct sockaddr_in *sin) return (!act); } -#ifdef DEBUG +/*----- Socket address conversion -----------------------------------------*/ -static void dump_acl(aclnode *a) +/* Return a uniformly distributed integer between MIN and MAX inclusive. */ +static unsigned randrange(unsigned min, unsigned max) { - int act = ALLOW; - - for (; a; a = a->next) { - dump_aclnode(a); - act = a->act; - } - fprintf(stderr, "noip: [default policy: %s]\n", - act == ALLOW ? "DENY" : "ALLOW"); + unsigned mask, i; + + /* It's so nice not to have to care about the quality of the generator + much! */ + max -= min; + for (mask = 1; mask < max; mask = (mask << 1) | 1) + ; + do i = rand() & mask; while (i > max); + return (i + min); } -#endif +/* Return the status of Unix-domain socket address SUN. Returns: UNUSED if + * the socket doesn't exist; USED if the path refers to an active socket, or + * isn't really a socket at all, or we can't tell without a careful search + * and QUICKP is set; or STALE if the file refers to a socket which isn't + * being used any more. + */ +static int unix_socket_status(struct sockaddr_un *sun, int quickp) +{ + struct stat st; + FILE *fp = 0; + size_t len, n; + int rc; + char buf[256]; + if (stat(sun->sun_path, &st)) + return (errno == ENOENT ? UNUSED : USED); + if (!S_ISSOCK(st.st_mode) || quickp) + return (USED); + rc = USED; + if ((fp = fopen("/proc/net/unix", "r")) == 0) + goto done; + fgets(buf, sizeof(buf), fp); /* skip header */ + len = strlen(sun->sun_path); + while (fgets(buf, sizeof(buf), fp)) { + n = strlen(buf); + if (n >= len + 2 && buf[n - len - 2] == ' ' && buf[n - 1] == '\n' && + memcmp(buf + n - len - 1, sun->sun_path, len) == 0) + goto done; + } + if (ferror(fp)) + goto done; + rc = STALE; +done: + if (fp) fclose(fp); + return (rc); +} + +/* Encode the Internet address SIN as a Unix-domain address SUN. If WANT is + * WANT_FRESH, and SIN->sin_port is zero, then we pick an arbitrary local + * port. Otherwise we pick the port given. There's an unpleasant hack to + * find servers bound to INADDR_ANY. Returns zero on success; -1 on failure. + */ static int encode_inet_addr(struct sockaddr_un *sun, const struct sockaddr_in *sin, int want) { int i; - int desperate_p = 0; + int desperatep = 0; char buf[INET_ADDRSTRLEN]; int rc; @@ -244,12 +313,18 @@ static int encode_inet_addr(struct sockaddr_un *sun, if (unix_socket_status(sun, 0) == STALE) unlink(sun->sun_path); } } else { - for (desperate_p = 0; desperate_p < 2; desperate_p++) { - for (i = 16384; i < 65536; i++) { + for (i = 0; i < 10; i++) { + snprintf(sun->sun_path, sizeof(sun->sun_path), "%s/%s:%u", sockdir, + inet_ntop(AF_INET, &sin->sin_addr, buf, sizeof(buf)), + randrange(minautoport, maxautoport)); + if (unix_socket_status(sun, 1) == UNUSED) goto found; + } + for (desperatep = 0; desperatep < 2; desperatep++) { + for (i = minautoport; i <= maxautoport; i++) { snprintf(sun->sun_path, sizeof(sun->sun_path), "%s/%s:%u", sockdir, inet_ntop(AF_INET, &sin->sin_addr, buf, sizeof(buf)), (unsigned)i); - rc = unix_socket_status(sun, !desperate_p); + rc = unix_socket_status(sun, !desperatep); switch (rc) { case STALE: unlink(sun->sun_path); case UNUSED: goto found; @@ -265,9 +340,15 @@ static int encode_inet_addr(struct sockaddr_un *sun, return (0); } +/* Decode the Unix address SUN to an Internet address SIN. If + * DECODE_UNBOUND_P is nonzero, an empty address (indicative of an unbound + * Unix-domain socket) is translated to a wildcard Internet address. Returns + * zero on success; -1 on failure (e.g., it wasn't one of our addresses). + */ static int decode_inet_addr(struct sockaddr_in *sin, const struct sockaddr_un *sun, - socklen_t len) + socklen_t len, + int decode_unbound_p) { char buf[INET_ADDRSTRLEN + 16]; char *p; @@ -282,7 +363,7 @@ static int decode_inet_addr(struct sockaddr_in *sin, if (len < sizeof(sun)) ((char *)sun)[len] = 0; D( fprintf(stderr, "noip: decode (%d) `%s'", *sun->sun_path, sun->sun_path); ) - if (!sun->sun_path[0]) { + if (decode_unbound_p && !sun->sun_path[0]) { sin->sin_family = AF_INET; sin->sin_addr.s_addr = INADDR_ANY; sin->sin_port = 0; @@ -317,6 +398,10 @@ static int decode_inet_addr(struct sockaddr_in *sin, return (0); } +/* SK is (or at least might be) a Unix-domain socket we created when an + * Internet socket was asked for. We've decided it should be an Internet + * socket after all, so convert it. + */ static int fixup_real_ip_socket(int sk) { int nsk; @@ -344,7 +429,7 @@ static int fixup_real_ip_socket(int sk) len = sizeof(sun); if (real_getsockname(sk, SA(&sun), &len)) return (-1); - if (decode_inet_addr(&sin, &sun, len)) + if (decode_inet_addr(&sin, &sun, len, 1)) return (0); /* Not one of ours */ len = sizeof(type); if (real_getsockopt(sk, SOL_SOCKET, SO_TYPE, &type, &len) < 0 || @@ -377,6 +462,9 @@ static int fixup_real_ip_socket(int sk) return (0); } +/* The socket SK is about to be used to communicate with the remote address + * SA. Assign it a local address so that getpeername does something useful. + */ static int do_implicit_bind(int sk, const struct sockaddr **sa, socklen_t *len, struct sockaddr_un *sun) { @@ -393,7 +481,7 @@ static int do_implicit_bind(int sk, const struct sockaddr **sa, if (mylen < sizeof(*sun)) ((char *)sun)[mylen] = 0; if (!sun->sun_path[0]) { sin.sin_family = AF_INET; - sin.sin_addr.s_addr = INADDR_LOOPBACK; + sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK); sin.sin_port = 0; encode_inet_addr(sun, &sin, WANT_FRESH); if (real_bind(sk, SA(sun), SUN_LEN(sun))) @@ -407,13 +495,19 @@ static int do_implicit_bind(int sk, const struct sockaddr **sa, return (0); } +/* We found the real address SA, with length LEN; if it's a Unix-domain + * address corresponding to a fake socket, convert it to cover up the + * deception. Whatever happens, put the result at FAKE and store its length + * at FAKELEN. + */ static void return_fake_name(struct sockaddr *sa, socklen_t len, struct sockaddr *fake, socklen_t *fakelen) { struct sockaddr_in sin; socklen_t alen; - if (sa->sa_family == AF_UNIX && !decode_inet_addr(&sin, SUN(sa), len)) { + if (sa->sa_family == AF_UNIX && + !decode_inet_addr(&sin, SUN(sa), len, 0)) { sa = SA(&sin); len = sizeof(sin); } @@ -425,18 +519,24 @@ static void return_fake_name(struct sockaddr *sa, socklen_t len, *fakelen = alen; } -/* --- Configuration --- */ +/*----- Configuration -----------------------------------------------------*/ +/* Return the process owner's home directory. */ static char *home(void) { char *p; struct passwd *pw; - if ((p = getenv("HOME")) != 0) return (p); - else if ((pw = getpwuid(uid)) != 0) return (pw->pw_dir); - else return "/notexist"; + if (getuid() == uid && + (p = getenv("HOME")) != 0) + return (p); + else if ((pw = getpwuid(uid)) != 0) + return (pw->pw_dir); + else + return "/notexist"; } +/* Return a good temporary directory to use. */ static char *tmpdir(void) { char *p; @@ -446,6 +546,7 @@ static char *tmpdir(void) else return ("/tmp"); } +/* Return the user's name, or at least something distinctive. */ static char *user(void) { static char buf[16]; @@ -461,13 +562,20 @@ static char *user(void) } } +/* Skip P over space characters. */ #define SKIPSPC do { while (*p && isspace(UC(*p))) p++; } while (0) + +/* Set Q to point to the next word following P, null-terminate it, and step P + * past it. */ #define NEXTWORD(q) do { \ SKIPSPC; \ q = p; \ while (*p && !isspace(UC(*p))) p++; \ if (*p) *p++ = 0; \ } while (0) + +/* Set Q to point to the next dotted-quad address, store the ending delimiter + * in DEL, null-terminate it, and step P past it. */ #define NEXTADDR(q, del) do { \ SKIPSPC; \ q = p; \ @@ -475,6 +583,9 @@ static char *user(void) del = *p; \ if (*p) *p++ = 0; \ } while (0) + +/* Set Q to point to the next decimal number, store the ending delimiter in + * DEL, null-terminate it, and step P past it. */ #define NEXTNUMBER(q, del) do { \ SKIPSPC; \ q = p; \ @@ -482,36 +593,47 @@ static char *user(void) del = *p; \ if (*p) *p++ = 0; \ } while (0) + +/* Push the character DEL back so we scan it again, unless it's zero + * (end-of-file). */ #define RESCAN(del) do { if (del) *--p = del; } while (0) + +/* Evaluate true if P is pointing to the word KW (and not some longer string + * of which KW is a prefix). */ + #define KWMATCHP(kw) (strncmp(p, kw, sizeof(kw) - 1) == 0 && \ !isalnum(UC(p[sizeof(kw) - 1])) && \ (p += sizeof(kw) - 1)) - + +/* Parse a port list, starting at *PP. Port lists have the form + * [:LOW[-HIGH]]: if omitted, all ports are included; if HIGH is omitted, + * it's as if HIGH = LOW. Store LOW in *MIN, HIGH in *MAX and set *PP to the + * rest of the string. + */ static void parse_ports(char **pp, unsigned short *min, unsigned short *max) { char *p = *pp, *q; int del; SKIPSPC; - if (*p != ':') { - *min = 0; - *max = 0xffff; - } else { + if (*p != ':') + { *min = 0; *max = 0xffff; } + else { p++; - NEXTNUMBER(q, del); - *min = atoi(q); - RESCAN(del); + NEXTNUMBER(q, del); *min = strtoul(q, 0, 0); RESCAN(del); SKIPSPC; - if (*p == '-') { - NEXTNUMBER(q, del); - *max = atoi(q); - RESCAN(del); - } else + if (*p == '-') + { p++; NEXTNUMBER(q, del); *max = strtoul(q, 0, 0); RESCAN(del); } + else *max = *min; } *pp = p; } +/* Make a new ACL node. ACT is the verdict; MINADDR and MAXADDR are the + * ranges on IP addresses; MINPORT and MAXPORT are the ranges on port + * numbers; TAIL is the list tail to attach the new node to. + */ #define ACLNODE(tail_, act_, \ minaddr_, maxaddr_, minport_, maxport_) do { \ aclnode *a_; \ @@ -522,6 +644,12 @@ static void parse_ports(char **pp, unsigned short *min, unsigned short *max) *tail_ = a_; tail_ = &a_->next; \ } while (0) +/* Parse an ACL line. *PP points to the end of the line; *TAIL points to + * the list tail (i.e., the final link in the list). An ACL entry has the + * form +|- [any | local | ADDR | ADDR - ADDR | ADDR/ADDR | ADDR/INT] PORTS + * where PORTS is parsed by parse_ports above; an ACL line consists of a + * comma-separated sequence of entries.. + */ static void parse_acl_line(char **pp, aclnode ***tail) { struct in_addr addr; @@ -576,7 +704,7 @@ static void parse_acl_line(char **pp, aclnode ***tail) if (inet_pton(AF_INET, q, &addr) <= 0) goto bad; mask = ntohl(addr.s_addr); } else { - n = atoi(q); + n = strtoul(q, 0, 0); mask = (~0ul << (32 - n)) & 0xffffffff; } RESCAN(del); @@ -600,32 +728,63 @@ bad: return; } +/* Parse the autoports configuration directive. Syntax is MIN - MAX. */ +static void parse_autoports(char **pp) +{ + char *p = *pp, *q; + unsigned x, y; + int del; + + SKIPSPC; + NEXTNUMBER(q, del); x = strtoul(q, 0, 0); RESCAN(del); + SKIPSPC; + if (*p != '-') goto bad; p++; + NEXTNUMBER(q, del); y = strtoul(q, 0, 0); RESCAN(del); + minautoport = x; maxautoport = y; + return; + +bad: + D( fprintf(stderr, "bad port range (ignored)\n"); ) + return; +} + +/* Parse an ACL from an environment variable VAR, attaching it to the list + * TAIL. */ static void parse_acl_env(const char *var, aclnode ***tail) { - char *p; + char *p, *q; if ((p = getenv(var)) != 0) { - p = xstrdup(p); - parse_acl_line(&p, tail); + p = q = xstrdup(p); + parse_acl_line(&q, tail); free(p); } } +/* Read the configuration from the config file and environment. */ static void readconfig(void) { FILE *fp; char buf[1024]; size_t n; - char *p, *cmd; + char *p, *q, *cmd; + parse_acl_env("NOIP_REALBIND_BEFORE", &bind_tail); + parse_acl_env("NOIP_REALCONNECT_BEFORE", &connect_tail); + if ((p = getenv("NOIP_AUTOPORTS")) != 0) { + p = q = xstrdup(p); + parse_autoports(&q); + free(p); + } if ((p = getenv("NOIP_CONFIG")) == 0) snprintf(p = buf, sizeof(buf), "%s/.noip", home()); D( fprintf(stderr, "noip: config file: %s\n", p); ) - if ((fp = fopen(p, "r")) == 0) - goto done; - parse_acl_env("NOIP_REALBIND_BEFORE", &bind_tail); - parse_acl_env("NOIP_REALCONNECT_BEFORE", &connect_tail); + if ((fp = fopen(p, "r")) == 0) { + D( fprintf(stderr, "noip: couldn't read config: %s\n", + strerror(errno)); ) + goto done; + } while (fgets(buf, sizeof(buf), fp)) { n = strlen(buf); p = buf; @@ -643,6 +802,8 @@ static void readconfig(void) parse_acl_line(&p, &bind_tail); else if (strcmp(cmd, "realconnect") == 0) parse_acl_line(&p, &connect_tail); + else if (strcmp(cmd, "autoports") == 0) + parse_autoports(&p); else if (strcmp(cmd, "debug") == 0) debug = *p ? atoi(p) : 1; else @@ -662,22 +823,29 @@ done: snprintf(buf, sizeof(buf), "%s/noip-%s", tmpdir(), user()); sockdir = xstrdup(buf); } - D( fprintf(stderr, "noip: sockdir: %s\n", sockdir); + D( fprintf(stderr, "noip: socketdir: %s\n", sockdir); + fprintf(stderr, "noip: autoports: %u-%u\n", + minautoport, maxautoport); fprintf(stderr, "noip: realbind acl:\n"); dump_acl(bind_real); fprintf(stderr, "noip: realconnect acl:\n"); dump_acl(connect_real); ) } -/* --- Hooks --- */ +/*----- Overridden system calls -------------------------------------------*/ int socket(int pf, int ty, int proto) { - if (pf == PF_INET) { - pf = PF_UNIX; - proto = 0; + switch (pf) { + case PF_INET: + pf = PF_UNIX; + proto = 0; + case PF_UNIX: + return real_socket(pf, ty, proto); + default: + errno = EAFNOSUPPORT; + return -1; } - return real_socket(pf, ty, proto); } int socketpair(int pf, int ty, int proto, int *sk) @@ -693,7 +861,7 @@ int bind(int sk, const struct sockaddr *sa, socklen_t len) { struct sockaddr_un sun; - if (sa->sa_family == AF_INET) { + if (sa->sa_family == AF_INET) { PRESERVING_ERRNO({ if (acl_allows_p(bind_real, SIN(sa))) { if (fixup_real_ip_socket(sk)) @@ -711,13 +879,22 @@ int bind(int sk, const struct sockaddr *sa, socklen_t len) int connect(int sk, const struct sockaddr *sa, socklen_t len) { struct sockaddr_un sun; + int fixup_p = 0; + int rc; if (sa->sa_family == AF_INET) { PRESERVING_ERRNO({ do_implicit_bind(sk, &sa, &len, &sun); + fixup_p = 1; }); } - return real_connect(sk, sa, len); + rc = real_connect(sk, sa, len); + if (rc < 0) { + switch (errno) { + case ENOENT: errno = ECONNREFUSED; break; + } + } + return rc; } ssize_t sendto(int sk, const void *buf, size_t len, int flags, @@ -859,20 +1036,31 @@ int setsockopt(int sk, int lev, int opt, const void *p, socklen_t len) return real_setsockopt(sk, lev, opt, p, len); } -/* --- Initialization --- */ +/*----- Initialization ----------------------------------------------------*/ +/* Clean up the socket directory, deleting stale sockets. */ static void cleanup_sockdir(void) { DIR *dir; struct dirent *d; + struct sockaddr_in sin; struct sockaddr_un sun; + struct stat st; if ((dir = opendir(sockdir)) == 0) return; + sun.sun_family = AF_UNIX; while ((d = readdir(dir)) != 0) { if (d->d_name[0] == '.') continue; snprintf(sun.sun_path, sizeof(sun.sun_path), "%s/%s", sockdir, d->d_name); + if (decode_inet_addr(&sin, &sun, SUN_LEN(&sun), 0) || + stat(sun.sun_path, &st) || + !S_ISSOCK(st.st_mode)) { + D( fprintf(stderr, "noip: ignoring unknown socketdir entry `%s'\n", + sun.sun_path); ) + continue; + } if (unix_socket_status(&sun, 0) == STALE) { D( fprintf(stderr, "noip: clearing away stale socket %s\n", d->d_name); ) @@ -882,6 +1070,9 @@ static void cleanup_sockdir(void) closedir(dir); } +/* Find the addresses attached to local network interfaces, and remember them + * in a table. + */ static void get_local_ipaddrs(void) { struct if_nameindex *ifn; @@ -907,18 +1098,57 @@ static void get_local_ipaddrs(void) close(sk); } +/* Print the given message to standard error. Avoids stdio. */ +static void printerr(const char *p) { write(STDERR_FILENO, p, strlen(p)); } + +/* Create the socket directory, being careful about permissions. */ +static void create_sockdir(void) +{ + struct stat st; + + if (stat(sockdir, &st)) { + if (errno == ENOENT) { + if (mkdir(sockdir, 0700)) { + perror("noip: creating socketdir"); + exit(127); + } + if (!stat(sockdir, &st)) + goto check; + } + perror("noip: checking socketdir"); + exit(127); + } +check: + if (!S_ISDIR(st.st_mode)) { + printerr("noip: bad socketdir: not a directory\n"); + exit(127); + } + if (st.st_uid != uid) { + printerr("noip: bad socketdir: not owner\n"); + exit(127); + } + if (st.st_mode & 077) { + printerr("noip: bad socketdir: not private\n"); + exit(127); + } +} + +/* Initialization function. */ +static void setup(void) __attribute__((constructor)); static void setup(void) { PRESERVING_ERRNO({ char *p; import(); - uid = getuid(); + uid = geteuid(); if ((p = getenv("NOIP_DEBUG")) && atoi(p)) debug = 1; get_local_ipaddrs(); readconfig(); - mkdir(sockdir, 0700); + create_sockdir(); cleanup_sockdir(); }); } + +/*----- That's all, folks -------------------------------------------------*/