/* SPDX-License-Identifier: GPL-2.0 */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "utils.h" unsigned debug_mask = 0; static const char *get_homedir() { const char *e; struct passwd *passwd; uid_t uid; e = getenv("HOME"); if (e && e[0] == '/') return e; uid = getuid(); if (uid == 0) return "/root"; passwd = getpwuid(uid); if (passwd && passwd->pw_dir[0] == '/') return passwd->pw_dir; return NULL; } int open_subdir(int dfd, const char *subdir, bool nofail) { int sfd; if (nofail && mkdirat(dfd, subdir, 0777) < 0 && errno != EEXIST) { error("Unable to create subdirectory %s: %m", subdir); return -1; } sfd = openat(dfd, subdir, O_PATH | O_CLOEXEC | O_DIRECTORY); if (sfd < 0 && nofail) error("Unable to open subdirectory %s: %m", subdir); return sfd; } int open_xdg_dir(const char *envname, const char *altpath, bool nofail, char **rpath) { const char *e; const char *h; int dfd, hfd; e = getenv(envname); if (e && e[0] == '/') { dfd = open(e, O_PATH | O_CLOEXEC | O_DIRECTORY); if (dfd < 0) error("Unable to open $%s(%s): %m", envname, e); else if (rpath) { *rpath = xstrdup(e); if (!rpath) { error("xstrdup: %m"); close(dfd); return -1; } } return dfd; } h = get_homedir(); if (!h) { error("Unable to determine home directory"); return -1; } hfd = open(h, O_PATH | O_CLOEXEC | O_DIRECTORY); if (hfd < 0) { error("Unable to open $HOME(%s): %m", h); return -1; } dfd = open_subdir(hfd, altpath, nofail); close(hfd); if (dfd >= 0 && rpath) { *rpath = xstrcat(h, "/", altpath, NULL); if (!*rpath) { error("xstrcat: %m"); close(dfd); return -1; } } return dfd; } DIR *__open_dir(const char *user_override, int (*base_dir)(bool nofail, char **rpath), const char *fallback, char **rpath) { _cleanup_close_ int xfd = -1; _cleanup_close_ int mfd = -1; _cleanup_free_ char *tmp = NULL; DIR *dir; /* First, use explicitly set path... */ if (user_override) { dir = opendir(user_override); if (dir && rpath) { *rpath = xstrdup(user_override); if (!*rpath) { closedir(dir); return NULL; } } return dir; } /* ...second, attempt per-user config dir... */ xfd = base_dir(false, &tmp); if (xfd < 0) goto fallback; mfd = openat(xfd, "minecproxy", O_CLOEXEC | O_DIRECTORY | O_RDONLY); if (mfd < 0) goto fallback; dir = fdopendir(mfd); if (dir) mfd = -1; if (rpath) { *rpath = xstrcat(tmp, "/minecproxy", NULL); if (!*rpath) { closedir(dir); return NULL; } } return dir; /* ...third, fallback on the system dir */ fallback: dir = opendir(fallback); if (dir && rpath) { *rpath = xstrdup(fallback); if (!*rpath) { closedir(dir); return NULL; } } return dir; } const char *ansi_red = ""; const char *ansi_green = ""; const char *ansi_yellow = ""; const char *ansi_blue = ""; const char *ansi_magenta = ""; const char *ansi_grey = ""; const char *ansi_normal = ""; void enable_colors() { ansi_red = ANSI_RED; ansi_green = ANSI_GREEN; ansi_yellow = ANSI_YELLOW; ansi_blue = ANSI_BLUE; ansi_magenta = ANSI_MAGENTA; ansi_grey = ANSI_GREY; ansi_normal = ANSI_NORMAL; } void free_password(char **password) { if (!password || !*password) return; explicit_bzero(*password, strlen(*password)); xfree(*password); *password = NULL; } void socket_set_low_latency(int sfd, bool keepalive, bool iptos, bool nodelay) { int option; assert_return(sfd >= 0); /* Probably not necessary, but can't hurt */ if (keepalive) { option = true; if (setsockopt(sfd, SOL_SOCKET, SO_KEEPALIVE, &option, sizeof(option)) < 0) error("setsockopt: %m"); } /* Doubtful if it has much effect, but can't hurt */ if (iptos) { option = IPTOS_LOWDELAY; if (setsockopt(sfd, IPPROTO_IP, IP_TOS, &option, sizeof(option)) < 0) error("setsockopt: %m"); } /* Nagle's algorithm is a poor fit for gaming */ if (nodelay) { option = true; if (setsockopt(sfd, IPPROTO_TCP, TCP_NODELAY, &option, sizeof(option)) < 0) error("setsockopt: %m"); } } uint16_t saddr_port(struct saddr *saddr) { // assert_return(saddr, 0); switch (saddr->st.ss_family) { case AF_INET: return ntohs(saddr->in4.sin_port); case AF_INET6: return ntohs(saddr->in6.sin6_port); default: return 0; } } char *saddr_addr(struct saddr *saddr, char *buf, size_t len) { // assert_return(saddr && buf && len > 0, NULL); switch (saddr->st.ss_family) { case AF_INET: if (inet_ntop(saddr->in4.sin_family, &saddr->in4.sin_addr, buf, len)) return buf; break; case AF_INET6: if (inet_ntop(saddr->in6.sin6_family, &saddr->in6.sin6_addr, buf, len)) return buf; break; default: break; } snprintf(buf, len, ""); return buf; } void saddr_set_ipv4(struct saddr *saddr, in_addr_t ip, in_port_t port) { // assert_return(saddr); memset(&saddr->in4, 0, sizeof(saddr->in4)); saddr->in4.sin_family = AF_INET; saddr->in4.sin_port = port; saddr->in4.sin_addr.s_addr = ip; saddr->addrlen = sizeof(saddr->in4); saddr_set_addrstr(saddr); } void saddr_set_ipv6(struct saddr *saddr, const struct in6_addr *ip, in_port_t port) { // assert_return(saddr && ip); memset(&saddr->in6, 0, sizeof(saddr->in6)); saddr->in6.sin6_family = AF_INET6; saddr->in6.sin6_port = port; if (ip) saddr->in6.sin6_addr = *ip; saddr->addrlen = sizeof(saddr->in6); saddr_set_addrstr(saddr); } void saddr_any_to_loopback(struct saddr *saddr) { switch (saddr->st.ss_family) { case AF_INET: if (saddr->in4.sin_addr.s_addr != htonl(INADDR_ANY)) return; saddr->in4.sin_addr.s_addr = htonl(INADDR_LOOPBACK); break; case AF_INET6: if (memcmp(&saddr->in6.sin6_addr, &in6addr_any, sizeof(in6addr_any))) return; saddr->in6.sin6_addr = in6addr_loopback; break; default: return; } saddr_set_addrstr(saddr); } void saddr_set_addrstr(struct saddr *saddr) { // assert_return(saddr); char abuf[ADDRSTRLEN]; switch (saddr->st.ss_family) { case AF_INET: snprintf(saddr->addrstr, sizeof(saddr->addrstr), "AF_INET4 %s %" PRIu16, saddr_addr(saddr, abuf, sizeof(abuf)), saddr_port(saddr)); break; case AF_INET6: snprintf(saddr->addrstr, sizeof(saddr->addrstr), "AF_INET6 %s %" PRIu16, saddr_addr(saddr, abuf, sizeof(abuf)), saddr_port(saddr)); break; default: snprintf(saddr->addrstr, sizeof(saddr->addrstr), "AF_UNKNOWN"); break; } } int strtou16_strict(const char *str, uint16_t *result) { char *end; long val; if (empty_str(str)) return -EINVAL; errno = 0; val = strtol(str, &end, 10); if (errno == ERANGE && (val == LONG_MAX || val == LONG_MIN)) return -EINVAL; if (errno != 0 && val == 0) return -EINVAL; if (end == str) return -EINVAL; if (*end != '\0') return -EINVAL; if (val < 1 || val > UINT16_MAX) return -EINVAL; if (result) *result = val; return 0; } char *xsprintf(size_t *rlen, const char *fmt, ...) { va_list ap; int len; char *str; va_start(ap, fmt); len = vsnprintf(NULL, 0, fmt, ap); va_end(ap); if (len < 0) return NULL; len++; str = zmalloc(len); if (!str) return NULL; va_start(ap, fmt); len = vsnprintf(str, len, fmt, ap); va_end(ap); if (len < 0) { xfree(str); return NULL; } if (rlen) *rlen = len; return str; } char *xstrcat(const char *a, ...) { size_t len; const char *b; char *str; char *to; va_list ap; if (!a) return NULL; len = strlen(a) + 1; va_start(ap, a); while ((b = va_arg(ap, const char *))) len += strlen(b); va_end(ap); str = zmalloc(len); if (!str) return NULL; to = stpcpy(str, a); va_start(ap, a); while ((b = va_arg(ap, const char *))) to = stpcpy(to, b); va_end(ap); return str; }