/* grecs - Gray's Extensible Configuration System Copyright (C) 2007-2014 Sergey Poznyakoff Grecs is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. Grecs is 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 Grecs. If not, see . */ /* Network-specific functions */ #ifdef HAVE_CONFIG_H # include #endif #include #include #include #include #include #include #include #include #include #include "grecs.h" struct grecs_sockaddr * grecs_sockaddr_new(size_t s) { struct grecs_sockaddr *sp = grecs_malloc(sizeof(*sp)); sp->next = NULL; sp->sa = grecs_zalloc(s); sp->len = s; return sp; } void grecs_sockaddr_free(struct grecs_sockaddr *p) { while (p) { struct grecs_sockaddr *next = p->next; free(p->sa); free(p); p = next; } } static int parse_unix(struct grecs_sockaddr **ret, const char *arg, const char *addrstr, struct grecs_sockaddr_hints *gh, grecs_locus_t const *locus) { struct sockaddr_un *s_un; size_t slen = strlen(addrstr); struct grecs_sockaddr *sp; if (slen >= sizeof s_un->sun_path) { grecs_error(locus, 0, _("socket path name too long: %s"), arg); return -1; } sp = grecs_sockaddr_new(sizeof(s_un[0])); s_un = (struct sockaddr_un *) sp->sa; s_un->sun_family = AF_UNIX; strcpy(s_un->sun_path, addrstr); *ret = sp; return 0; } static int parse_inet(struct grecs_sockaddr **ret, int family, const char *arg, const char *addrstr, struct grecs_sockaddr_hints *gh, grecs_locus_t const *locus) { int rc; struct addrinfo hints; struct addrinfo *res, *ap; const char *node = NULL; char *nodebuf = NULL; const char *service = NULL; struct grecs_sockaddr *head = NULL, *tail = NULL; char portbuf[64]; memset(&hints, 0, sizeof(hints)); hints.ai_family = family; hints.ai_socktype = SOCK_STREAM; if ((family == AF_INET6 || family == AF_UNSPEC) && addrstr[0] == '[') { char *p = strchr(addrstr + 1, ']'); if (p && p > addrstr + 1) { size_t len; addrstr++; len = p - addrstr; nodebuf = grecs_malloc(len + 1); memcpy(nodebuf, addrstr, len); nodebuf[len] = 0; node = nodebuf; service = p + 1; family = AF_INET6; } else service = strchr(addrstr, ':'); } else service = strrchr(addrstr, ':'); if (service && *service) { if (*service != ':') { grecs_error(locus, 0, _("%s: garbage near %s"), arg, service); return -1; } service++; } if (!node) { if (service) { size_t len = service - addrstr - 1; if (len == 0) node = NULL; else { nodebuf = grecs_malloc(len + 1); memcpy(nodebuf, addrstr, len); nodebuf[len] = 0; node = nodebuf; } } else { if (grecs_str_is_ipaddr(addrstr)) node = addrstr; else if (grecs_str_is_num(addrstr)) { service = addrstr; hints.ai_flags |= AI_NUMERICSERV; } } } if (!service || !*service) { if (!node && addrstr[0]) node = addrstr; if (gh->flags & GRECS_HINT_SERVICE) { service = gh->service; } else if (gh->flags & GRECS_HINT_PORT) { snprintf(portbuf, sizeof portbuf, "%hu", gh->port); service = portbuf; hints.ai_flags |= AI_NUMERICSERV; } else { grecs_error(locus, 0, _("service not specified: %s"), arg); return -1; } } if (!node) { if (gh->flags & GRECS_AH_PASSIVE) hints.ai_flags |= AI_PASSIVE; } rc = getaddrinfo(node, service, &hints, &res); free(nodebuf); switch (rc) { case 0: break; case EAI_SYSTEM: grecs_error(locus, 0, _("%s: cannot parse address: %s"), arg, strerror(errno)); break; case EAI_BADFLAGS: case EAI_SOCKTYPE: grecs_error(locus, 0, "%s:%d: internal error converting %s", __FILE__,__LINE__,arg); break; case EAI_MEMORY: grecs_alloc_die(); default: grecs_error(locus, 0, "%s: %s", arg, gai_strerror(rc)); return -1; } for (ap = res; ap; ap = ap->ai_next) { if (family == AF_UNSPEC || ap->ai_addr->sa_family == family) { struct grecs_sockaddr *sp = grecs_sockaddr_new(ap->ai_addrlen); memcpy(sp->sa, ap->ai_addr, ap->ai_addrlen); sp->len = ap->ai_addrlen; if (!head) head = sp; else tail->next = sp; tail = sp; } } freeaddrinfo(res); *ret = head; return 0; } static int parse_inet4(struct grecs_sockaddr **ret, const char *arg, const char *addrstr, struct grecs_sockaddr_hints *gh, grecs_locus_t const *locus) { return parse_inet(ret, AF_INET, arg, addrstr, gh, locus); } static int parse_inet6(struct grecs_sockaddr **ret, const char *arg, const char *addrstr, struct grecs_sockaddr_hints *gh, grecs_locus_t const *locus) { return parse_inet(ret, AF_INET6, arg, addrstr, gh, locus); } struct schemetab { const char *scheme; size_t len; int (*parser)(struct grecs_sockaddr **ret, const char *arg, const char *addr, struct grecs_sockaddr_hints *gh, grecs_locus_t const *locus); }; struct schemetab schemetab[] = { { "inet", 4, parse_inet4 }, { "inet4", 5, parse_inet4 }, { "inet6", 5, parse_inet6 }, { "unix", 4, parse_unix }, { NULL } }; int grecs_str_to_sockaddr(struct grecs_sockaddr **sap, const char *arg, struct grecs_sockaddr_hints *gh, grecs_locus_t const *locus) { char *p; struct grecs_sockaddr_hints ghints; if (!gh) { memset(&ghints, 0, sizeof(ghints)); if (grecs_default_port) { ghints.flags = GRECS_HINT_PORT; ghints.port = ntohs(grecs_default_port); } gh = &ghints; } p = strchr(arg, ':'); if (p && p > arg && p[1] == '/' && p[2] == '/') { size_t len = p - arg; struct schemetab *sp; for (sp = schemetab; sp->scheme; sp++) if (len == sp->len && memcmp(arg, sp->scheme, len) == 0) return sp->parser(sap, arg, p + 3, gh, locus); grecs_error(locus, 0, _("unknown or unsupported scheme: %s"), arg); return -1; } if (arg[0] == '/') return parse_unix(sap, arg, arg, gh, locus); else if (strlen(arg) > 5 && memcmp(arg, "unix:", 5) == 0) { if (arg[5] != '/') grecs_error(locus, 0, _("%s: UNIX socket must be an absolute file name"), arg); return parse_unix(sap, arg, arg + 5, gh, locus); } return parse_inet(sap, AF_UNSPEC, arg, arg, gh, locus); }