X-Git-Url: http://dolda2000.com/gitweb/?p=doldaconnect.git;a=blobdiff_plain;f=lib%2Fuilib.c;h=a2993956d6dfece156fae3fc44a7c6f942dc98dc;hp=2bd9575313eb91150e2d457b20257ead0014d34b;hb=ae93c710feb83661705620d14e9712f7b3a7879b;hpb=26d72b0d840b28325c88bc1f8ce7bac47f42eb6a diff --git a/lib/uilib.c b/lib/uilib.c index 2bd9575..a299395 100644 --- a/lib/uilib.c +++ b/lib/uilib.c @@ -1,6 +1,6 @@ /* * Dolda Connect - Modular multiuser Direct Connect-style client - * Copyright (C) 2004 Fredrik Tolf (fredrik@dolda2000.com) + * Copyright (C) 2004 Fredrik Tolf * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -38,22 +38,28 @@ #include #include #include +#include #include #include #include +#include +#include #ifdef HAVE_RESOLVER #include #include #endif #include -#include +#include + +#define DOLCON_SRV_NAME "_dolcon._tcp" #define RESP_END -1 #define RESP_DSC 0 #define RESP_STR 1 #define RESP_INT 2 #define RESP_FLOAT 3 +#define RESP_LNUM 4 struct respclass { @@ -92,10 +98,72 @@ static int state = -1; static int fd = -1; static iconv_t ichandle; static int resetreader = 1; -static char *dchostname = NULL; static struct addrinfo *hostlist = NULL, *curhost = NULL; -static int servport; +struct { + char *hostname; + int family; + int sentcreds; +} servinfo; +char dc_srv_local_addr; +char *dc_srv_local = &dc_srv_local_addr; + +static void message(int bits, char *format, ...) +{ + static int hb = -1; + char *v; + va_list args; + + if(hb == -1) + { + hb = 0; + if((v = getenv("LIBDCUI_MSG")) != NULL) + hb = strtol(v, NULL, 0) & 65535; + } + if(hb & bits) + { + va_start(args, format); + vfprintf(stderr, format, args); + va_end(args); + } +} +static char *formataddress(struct sockaddr *arg, socklen_t arglen) +{ + struct sockaddr_in *ipv4; +#ifdef HAVE_IPV6 + struct sockaddr_in6 *ipv6; +#endif + static char *ret = NULL; + char buf[1024]; + + if(ret != NULL) + free(ret); + ret = NULL; + switch(arg->sa_family) + { + case AF_UNIX: + ret = sprintf2("Unix socket (%s)", ((struct sockaddr_un *)arg)->sun_path); + break; + case AF_INET: + ipv4 = (struct sockaddr_in *)arg; + if(inet_ntop(AF_INET, &ipv4->sin_addr, buf, sizeof(buf)) == NULL) + return(NULL); + ret = sprintf2("%s:%i", buf, (int)ntohs(ipv4->sin_port)); + break; +#ifdef HAVE_IPV6 + case AF_INET6: + ipv6 = (struct sockaddr_in6 *)arg; + if(inet_ntop(AF_INET6, &ipv6->sin6_addr, buf, sizeof(buf)) == NULL) + return(NULL); + ret = sprintf2("[%s]:%i", buf, (int)ntohs(ipv6->sin6_port)); + break; +#endif + default: + errno = EPFNOSUPPORT; + break; + } + return(ret); +} static struct dc_response *makeresp(void) { struct dc_response *new; @@ -147,8 +215,10 @@ static struct qcmd *makeqcmd(wchar_t *name) if((cmd->name != NULL) && !wcscmp(cmd->name, name)) break; } - if(cmd == NULL) + if(cmd == NULL) { + errno = ENOSYS; /* Bleh */ return(NULL); + } } new = smalloc(sizeof(*new)); new->tag = tag++; @@ -273,9 +343,9 @@ void dc_disconnect(void) while((resp = dc_getresp()) != NULL) dc_freeresp(resp); dc_uimisc_disconnected(); - if(dchostname != NULL) - free(dchostname); - dchostname = NULL; + if(servinfo.hostname != NULL) + free(servinfo.hostname); + memset(&servinfo, 0, sizeof(servinfo)); } void dc_freeresp(struct dc_response *resp) @@ -371,7 +441,7 @@ int dc_queuecmd(int (*callback)(struct dc_response *), void *data, ...) struct qcmd *qcmd; int num, freepart; va_list al; - char *final; + char *final, *sarg; wchar_t **toks; wchar_t *buf; wchar_t *part, *tpart; @@ -383,7 +453,7 @@ int dc_queuecmd(int (*callback)(struct dc_response *), void *data, ...) va_start(al, data); while((part = va_arg(al, wchar_t *)) != NULL) { - if(!wcscmp(part, L"%%a")) + if(!wcscmp(part, L"%a")) { for(toks = va_arg(al, wchar_t **); *toks != NULL; toks++) { @@ -403,25 +473,41 @@ int dc_queuecmd(int (*callback)(struct dc_response *), void *data, ...) } else { if(*part == L'%') { - /* This demands that all arguments that are passed to the - * function are of equal length, that of an int. I know - * that GCC does that on IA32 platforms, but I do not know - * which other platforms and compilers that it applies - * to. If this breaks your platform, please mail me about - * it. - */ - part = vswprintf2(tpart = (part + 1), al); - for(; *tpart != L'\0'; tpart++) + tpart = part + 1; + if(!wcscmp(tpart, L"i")) { - if(*tpart == L'%') + freepart = 1; + part = swprintf2(L"%i", va_arg(al, int)); + } else if(!wcscmp(tpart, L"li")) { + freepart = 1; + part = swprintf2(L"%ji", (intmax_t)va_arg(al, dc_lnum_t)); + } else if(!wcscmp(tpart, L"s")) { + freepart = 1; + part = icmbstowcs(sarg = va_arg(al, char *), NULL); + if(part == NULL) { - if(tpart[1] == L'%') - tpart++; - else - va_arg(al, int); + if(buf != NULL) + free(buf); + return(-1); } + } else if(!wcscmp(tpart, L"ls")) { + freepart = 0; + part = va_arg(al, wchar_t *); + } else if(!wcscmp(tpart, L"ll")) { + freepart = 1; + part = swprintf2(L"%lli", va_arg(al, long long)); + } else if(!wcscmp(tpart, L"f")) { + freepart = 1; + part = swprintf2(L"%f", va_arg(al, double)); + } else if(!wcscmp(tpart, L"x")) { + freepart = 1; + part = swprintf2(L"%x", va_arg(al, int)); + } else { + if(buf != NULL) + free(buf); + errno = EINVAL; + return(-1); } - freepart = 1; } else { freepart = 0; } @@ -500,10 +586,8 @@ int dc_handleread(void) if(ret) { int newfd; - struct sockaddr_storage addr; - struct sockaddr_in *ipv4; - struct sockaddr_in6 *ipv6; + message(2, "could not connect to %s: %s\n", formataddress(curhost->ai_addr, curhost->ai_addrlen), strerror(ret)); for(curhost = curhost->ai_next; curhost != NULL; curhost = curhost->ai_next) { if((newfd = socket(curhost->ai_family, curhost->ai_socktype, curhost->ai_protocol)) < 0) @@ -516,23 +600,12 @@ int dc_handleread(void) dup2(newfd, fd); close(newfd); fcntl(fd, F_SETFL, fcntl(fd, F_GETFL) | O_NONBLOCK); - memcpy(&addr, curhost->ai_addr, curhost->ai_addrlen); - if(addr.ss_family == AF_INET) - { - ipv4 = (struct sockaddr_in *)&addr; - ipv4->sin_port = htons(servport); - } -#ifdef HAVE_IPV6 - if(addr.ss_family == AF_INET6) - { - ipv6 = (struct sockaddr_in6 *)&addr; - ipv6->sin6_port = htons(servport); - } -#endif - if(connect(fd, (struct sockaddr *)&addr, curhost->ai_addrlen)) + message(4, "connecting to %s\n", formataddress(curhost->ai_addr, curhost->ai_addrlen)); + if(connect(fd, (struct sockaddr *)curhost->ai_addr, curhost->ai_addrlen)) { if(errno == EINPROGRESS) return(0); + message(2, "could not connect to %s: %s\n", formataddress(curhost->ai_addr, curhost->ai_addrlen), strerror(ret)); } else { break; } @@ -544,6 +617,9 @@ int dc_handleread(void) return(-1); } } + if(curhost->ai_canonname != NULL) + servinfo.hostname = sstrdup(curhost->ai_canonname); + servinfo.family = curhost->ai_family; state = 1; resetreader = 1; break; @@ -755,17 +831,52 @@ int dc_handleread(void) return(0); } +#if UNIX_AUTH_STYLE == 1 +static void mkcreds(struct msghdr *msg) +{ + struct ucred *ucred; + static char buf[CMSG_SPACE(sizeof(*ucred))]; + struct cmsghdr *cmsg; + + msg->msg_control = buf; + msg->msg_controllen = sizeof(buf); + cmsg = CMSG_FIRSTHDR(msg); + cmsg->cmsg_level = SOL_SOCKET; + cmsg->cmsg_type = SCM_CREDENTIALS; + cmsg->cmsg_len = CMSG_LEN(sizeof(*ucred)); + ucred = (struct ucred *)CMSG_DATA(cmsg); + ucred->pid = getpid(); + ucred->uid = getuid(); + ucred->gid = getgid(); + msg->msg_controllen = cmsg->cmsg_len; +} +#endif + int dc_handlewrite(void) { int ret; int errnobak; + struct msghdr msg; + struct iovec bufvec; switch(state) { case 1: if(queue->buflen > 0) { - ret = send(fd, queue->buf, queue->buflen, MSG_NOSIGNAL | MSG_DONTWAIT); + memset(&msg, 0, sizeof(msg)); + msg.msg_iov = &bufvec; + msg.msg_iovlen = 1; + bufvec.iov_base = queue->buf; + bufvec.iov_len = queue->buflen; +#if UNIX_AUTH_STYLE == 1 + if((servinfo.family == PF_UNIX) && !servinfo.sentcreds) + { + mkcreds(&msg); + servinfo.sentcreds = 1; + } +#endif + ret = sendmsg(fd, &msg, MSG_NOSIGNAL | MSG_DONTWAIT); if(ret < 0) { if((errno == EAGAIN) || (errno == EINTR)) @@ -784,6 +895,10 @@ int dc_handlewrite(void) } #ifdef HAVE_RESOLVER +/* + * It kind of sucks that libresolv doesn't have any DNS parsing + * routines. We'll have to do it manually. + */ static char *readname(unsigned char *msg, unsigned char *eom, unsigned char **p) { char *name, *tname; @@ -858,15 +973,10 @@ static int getsrvrr(char *name, char **host, int *port) return(-1); } /* res_querydomain doesn't work for some reason */ - name2 = smalloc(strlen("_dolcon._tcp.") + strlen(name) + 2); - strcpy(name2, "_dolcon._tcp."); - strcat(name2, name); - len = strlen(name2); - if(name2[len - 1] != '.') - { - name2[len] = '.'; - name2[len + 1] = 0; - } + if(name[strlen(name) - 1] == '.') + name2 = sprintf2("%s.%s", DOLCON_SRV_NAME, name); + else + name2 = sprintf2("%s.%s.", DOLCON_SRV_NAME, name); ret = res_query(name2, C_IN, T_SRV, buf, sizeof(buf)); if(ret < 0) { @@ -874,12 +984,20 @@ static int getsrvrr(char *name, char **host, int *port) return(-1); } eom = buf + ret; + /* + * Assume transaction ID is correct. + * + * Flags check: FA0F masks in request/response flag, opcode, + * truncated flag and status code, and ignores authoritativeness, + * recursion flags and DNSSEC and reserved bits. + */ flags = (buf[2] << 8) + buf[3]; if((flags & 0xfa0f) != 0x8000) { free(name2); return(-1); } + /* Skip the query entries */ num = (buf[4] << 8) + buf[5]; p = buf + 12; for(i = 0; i < num; i++) @@ -889,8 +1007,9 @@ static int getsrvrr(char *name, char **host, int *port) free(name2); return(-1); } - p += 4; + p += 4; /* Type and class */ } + /* Parse answers */ num = (buf[6] << 8) + buf[7]; for(i = 0; i < num; i++) { @@ -903,7 +1022,7 @@ static int getsrvrr(char *name, char **host, int *port) type += *(p++); class = *(p++) << 8; class += *(p++); - p += 4; + p += 4; /* TTL */ len = *(p++) << 8; len += *(p++); if((class == C_IN) && (type == T_SRV) && !strcmp(rrname, name2)) @@ -942,98 +1061,272 @@ static int getsrvrr(char *name, char **host, int *port) } #endif -int dc_connect(char *host, int port) +static struct addrinfo *gaicat(struct addrinfo *l1, struct addrinfo *l2) { - struct addrinfo hint; - struct sockaddr_storage addr; - struct sockaddr_in *ipv4; -#ifdef HAVE_IPV6 - struct sockaddr_in6 *ipv6; -#endif - struct qcmd *qcmd; - char *newhost; - int getsrv, freehost; - int errnobak; + struct addrinfo *p; + + if(l1 == NULL) + return(l2); + for(p = l1; p->ai_next != NULL; p = p->ai_next); + p->ai_next = l2; + return(l1); +} + +/* This isn't actually correct, in any sense of the word. It only + * works on systems whose getaddrinfo implementation saves the + * sockaddr in the same malloc block as the struct addrinfo. Those + * systems include at least FreeBSD and glibc-based systems, though, + * so it should not be any immediate threat, and it allows me to not + * implement a getaddrinfo wrapper. It can always be changed, should + * the need arise. */ +static struct addrinfo *unixgai(int type, char *path) +{ + void *buf; + struct addrinfo *ai; + struct sockaddr_un *un; + + buf = smalloc(sizeof(*ai) + sizeof(*un)); + memset(buf, 0, sizeof(*ai) + sizeof(*un)); + ai = (struct addrinfo *)buf; + un = (struct sockaddr_un *)(buf + sizeof(*ai)); + ai->ai_flags = 0; + ai->ai_family = AF_UNIX; + ai->ai_socktype = type; + ai->ai_protocol = 0; + ai->ai_addrlen = sizeof(*un); + ai->ai_addr = (struct sockaddr *)un; + ai->ai_canonname = NULL; + ai->ai_next = NULL; + un->sun_family = PF_UNIX; + strncpy(un->sun_path, path, sizeof(un->sun_path) - 1); + return(ai); +} + +static struct addrinfo *resolvtcp(char *name, int port) +{ + struct addrinfo hint, *ret; + char tmp[32]; - if(fd >= 0) - dc_disconnect(); - state = -1; - freehost = 0; - if(port < 0) - { - port = 1500; - getsrv = 1; - } else { - getsrv = 0; - } memset(&hint, 0, sizeof(hint)); hint.ai_socktype = SOCK_STREAM; - if(getsrv) + hint.ai_flags = AI_NUMERICSERV | AI_CANONNAME; + snprintf(tmp, sizeof(tmp), "%i", port); + if(!getaddrinfo(name, tmp, &hint, &ret)) + return(ret); + return(NULL); +} + +static struct addrinfo *resolvsrv(char *name) +{ + struct addrinfo *ret; + char *realname; + int port; + + if(getsrvrr(name, &realname, &port)) + return(NULL); + message(4, "SRV RR resolved: %s -> %s\n", name, realname); + ret = resolvtcp(realname, port); + free(realname); + return(ret); +} + +static struct addrinfo *resolvhost(char *host) +{ + char *p, *hp; + struct addrinfo *ret; + int port; + + if(strchr(host, '/')) + return(unixgai(SOCK_STREAM, host)); + if((strchr(host, ':') == NULL) && ((ret = resolvsrv(host)) != NULL)) + return(ret); + ret = NULL; + if((*host == '[') && ((p = strchr(host, ']')) != NULL)) { - if(!getsrvrr(host, &newhost, &port)) - { - host = newhost; - freehost = 1; + hp = memcpy(smalloc(p - host), host + 1, (p - host) - 1); + hp[(p - host) - 1] = 0; + if(strchr(hp, ':') != NULL) { + port = 0; + if(*(++p) == ':') + port = atoi(p + 1); + if(port == 0) + port = 1500; + ret = resolvtcp(hp, port); } + free(hp); } - servport = port; - if(hostlist != NULL) - freeaddrinfo(hostlist); - if(getaddrinfo(host, NULL, &hint, &hostlist)) + if(ret != NULL) + return(ret); + hp = sstrdup(host); + port = 0; + if((p = strrchr(hp, ':')) != NULL) { + *(p++) = 0; + port = atoi(p); + } + if(port == 0) + port = 1500; + ret = resolvtcp(hp, port); + free(hp); + if(ret != NULL) + return(ret); + return(NULL); +} + +static struct addrinfo *getlocalai(void) +{ + struct addrinfo *ret; + struct passwd *pwd; + char *tmp; + + ret = NULL; + if((getuid() != 0) && ((pwd = getpwuid(getuid())) != NULL)) { - errno = ENONET; - if(freehost) - free(host); - return(-1); + tmp = sprintf2("/tmp/doldacond-%s", pwd->pw_name); + ret = unixgai(SOCK_STREAM, tmp); + free(tmp); } + ret = gaicat(ret, unixgai(SOCK_STREAM, "/var/run/doldacond.sock")); + return(ret); +} + +static struct addrinfo *defaulthost(void) +{ + struct addrinfo *ret; + char *tmp; + char dn[1024]; + + if(((tmp = getenv("DCSERVER")) != NULL) && *tmp) { + message(4, "using DCSERVER: %s\n", tmp); + return(resolvhost(tmp)); + } + ret = getlocalai(); + ret = gaicat(ret, resolvtcp("localhost", 1500)); + if(!getdomainname(dn, sizeof(dn)) && *dn && strcmp(dn, "(none)")) + ret = gaicat(ret, resolvsrv(dn)); + return(ret); +} + +static int dc_connectai(struct addrinfo *hosts, struct qcmd **cnctcmd) +{ + struct qcmd *qcmd; + int errnobak; + + if(fd >= 0) + dc_disconnect(); + state = -1; + if(hostlist != NULL) + freeaddrinfo(hostlist); + hostlist = hosts; for(curhost = hostlist; curhost != NULL; curhost = curhost->ai_next) { if((fd = socket(curhost->ai_family, curhost->ai_socktype, curhost->ai_protocol)) < 0) { errnobak = errno; - if(freehost) - free(host); + freeaddrinfo(hostlist); + hostlist = NULL; errno = errnobak; return(-1); } fcntl(fd, F_SETFL, fcntl(fd, F_GETFL) | O_NONBLOCK); - memcpy(&addr, curhost->ai_addr, curhost->ai_addrlen); - if(addr.ss_family == AF_INET) - { - ipv4 = (struct sockaddr_in *)&addr; - ipv4->sin_port = htons(port); - } -#ifdef HAVE_IPV6 - if(addr.ss_family == AF_INET6) - { - ipv6 = (struct sockaddr_in6 *)&addr; - ipv6->sin6_port = htons(port); - } -#endif - if(connect(fd, (struct sockaddr *)&addr, curhost->ai_addrlen)) + message(4, "connecting to %s\n", formataddress(curhost->ai_addr, curhost->ai_addrlen)); + if(connect(fd, (struct sockaddr *)curhost->ai_addr, curhost->ai_addrlen)) { if(errno == EINPROGRESS) { state = 0; break; } + message(2, "could not connect to %s: %s\n", formataddress(curhost->ai_addr, curhost->ai_addrlen), strerror(errno)); close(fd); fd = -1; } else { + if(curhost->ai_canonname != NULL) + servinfo.hostname = sstrdup(curhost->ai_canonname); + servinfo.family = curhost->ai_family; state = 1; break; } } - qcmd = makeqcmd(NULL); - resetreader = 1; - if(dchostname != NULL) - free(dchostname); - dchostname = sstrdup(host); - if(freehost) - free(host); + if(state != -1) + { + qcmd = makeqcmd(NULL); + if(cnctcmd != NULL) + *cnctcmd = qcmd; + resetreader = 1; + } else { + free(hostlist); + hostlist = NULL; + } return(fd); } +static int dc_connect2(char *host, struct qcmd **cnctcmd) +{ + struct addrinfo *ai; + struct qcmd *qcmd; + int ret; + + if(host == dc_srv_local) { + message(4, "connect start: Unix\n"); + ai = getlocalai(); + } else if(!host || !*host) { + message(4, "connect start: default\n"); + ai = defaulthost(); + } else { + message(4, "connect start: host %s\n", host); + ai = resolvhost(host); + } + if(ai == NULL) + return(-1); + ret = dc_connectai(ai, &qcmd); + if((ret >= 0) && (cnctcmd != NULL)) + *cnctcmd = qcmd; + return(ret); +} + +int dc_connect(char *host) +{ + return(dc_connect2(host, NULL)); +} + +int dc_connectsync(char *host, struct dc_response **respbuf) +{ + int ret; + struct qcmd *cc; + struct dc_response *resp; + + if((ret = dc_connect2(host, &cc)) < 0) + return(-1); + resp = dc_gettaggedrespsync(cc->tag); + if(resp == NULL) { + dc_disconnect(); + return(-1); + } + if(respbuf == NULL) + dc_freeresp(resp); + else + *respbuf = resp; + return(ret); +} + +int dc_connectsync2(char *host, int rev) +{ + int ret; + struct dc_response *resp; + + if((ret = dc_connectsync(host, &resp)) < 0) + return(-1); + if(dc_checkprotocol(resp, rev)) + { + dc_freeresp(resp); + dc_disconnect(); + errno = EPROTONOSUPPORT; + return(-1); + } + dc_freeresp(resp); + return(ret); +} + struct dc_intresp *dc_interpret(struct dc_response *resp) { int i; @@ -1085,6 +1378,12 @@ struct dc_intresp *dc_interpret(struct dc_response *resp) iresp->argv[iresp->argc].type = cls->wordt[i]; iresp->argc++; break; + case RESP_LNUM: + sizebuf(&(iresp->argv), &args, iresp->argc + 1, sizeof(*(iresp->argv)), 1); + iresp->argv[iresp->argc].val.lnum = wcstoll(resp->rlines[resp->curline].argv[i + 1], NULL, 0); + iresp->argv[iresp->argc].type = cls->wordt[i]; + iresp->argc++; + break; } } resp->curline++; @@ -1104,7 +1403,30 @@ void dc_freeires(struct dc_intresp *ires) free(ires); } +int dc_checkprotocol(struct dc_response *resp, int revision) +{ + struct dc_intresp *ires; + int low, high; + + if(resp->code != 201) + return(-1); + resp->curline = 0; + if((ires = dc_interpret(resp)) == NULL) + return(-1); + low = ires->argv[0].val.num; + high = ires->argv[1].val.num; + dc_freeires(ires); + if((revision < low) || (revision > high)) + return(-1); + return(0); +} + const char *dc_gethostname(void) { - return(dchostname); + return(servinfo.hostname); +} + +int dc_getfd(void) +{ + return(fd); }