2 * Dolda Connect - Modular multiuser Direct Connect-style client
3 * Copyright (C) 2004 Fredrik Tolf <fredrik@dolda2000.com>
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
21 * Note: This code is still ugly, since I copied it almost verbatim
22 * from the daemon's parser. It would need serious cleanups, but at
23 * least it works for now.
38 #include <sys/socket.h>
39 #include <netinet/in.h>
40 #include <arpa/inet.h>
48 #include <arpa/nameser.h>
52 #include <doldaconnect/uilib.h>
55 #define DOLCON_SRV_NAME "_dolcon._tcp"
66 struct respclass *next;
76 struct respclass *classes;
86 int (*callback)(struct dc_response *resp);
90 void dc_uimisc_disconnected(void);
92 /* The first command must be the nameless connect command and second
93 * the notification command. */
94 static struct command *commands = NULL;
95 static struct qcmd *queue = NULL, *queueend = NULL;
96 static struct dc_response *respqueue = NULL, *respqueueend = NULL;
97 static int state = -1;
99 static iconv_t ichandle;
100 static int resetreader = 1;
101 static struct addrinfo *hostlist = NULL, *curhost = NULL;
107 /* char dc_srv_local_addr; */
108 char *dc_srv_local = (void *)&dc_srv_local;
110 static void message(int bits, char *format, ...)
119 if((v = getenv("LIBDCUI_MSG")) != NULL)
120 hb = strtol(v, NULL, 0) & 65535;
124 va_start(args, format);
125 vfprintf(stderr, format, args);
130 static char *formataddress(struct sockaddr *arg, socklen_t arglen)
132 struct sockaddr_in *ipv4;
134 struct sockaddr_in6 *ipv6;
136 static char *ret = NULL;
142 switch(arg->sa_family)
145 ret = sprintf2("Unix socket (%s)", ((struct sockaddr_un *)arg)->sun_path);
148 ipv4 = (struct sockaddr_in *)arg;
149 if(inet_ntop(AF_INET, &ipv4->sin_addr, buf, sizeof(buf)) == NULL)
151 ret = sprintf2("%s:%i", buf, (int)ntohs(ipv4->sin_port));
155 ipv6 = (struct sockaddr_in6 *)arg;
156 if(inet_ntop(AF_INET6, &ipv6->sin6_addr, buf, sizeof(buf)) == NULL)
158 ret = sprintf2("[%s]:%i", buf, (int)ntohs(ipv6->sin6_port));
162 errno = EPFNOSUPPORT;
167 static struct dc_response *makeresp(void)
169 struct dc_response *new;
171 new = smalloc(sizeof(*new));
184 static void freeqcmd(struct qcmd *qcmd)
186 if(qcmd->buf != NULL)
191 static void unlinkqueue(void)
195 if((qcmd = queue) == NULL)
203 static struct qcmd *makeqcmd(wchar_t *name)
213 for(cmd = commands; cmd != NULL; cmd = cmd->next)
215 if((cmd->name != NULL) && !wcscmp(cmd->name, name))
219 errno = ENOSYS; /* Bleh */
223 new = smalloc(sizeof(*new));
228 new->callback = NULL;
233 queue = queueend = new;
235 queueend->next = new;
241 static wchar_t *quoteword(wchar_t *word)
243 wchar_t *wp, *buf, *bp;
253 for(wp = word; *wp != L'\0'; wp++)
255 if(!dq && iswspace(*wp))
257 if((*wp == L'\\') || (*wp == L'\"'))
264 bp = buf = smalloc(sizeof(wchar_t) * (numc + numbs + (dq?2:0) + 1));
267 for(wp = word; *wp != L'\0'; wp++)
269 if((*wp == L'\\') || (*wp == L'\"'))
279 static struct command *makecmd(wchar_t *name)
283 new = smalloc(sizeof(*new));
286 new->next = commands;
291 static struct respclass *addresp(struct command *cmd, int code, ...)
293 struct respclass *new;
298 va_start(args, code);
300 while((resps[i++] = va_arg(args, int)) != RESP_END);
303 new = smalloc(sizeof(*new));
308 new->wordt = smalloc(sizeof(int) * i);
309 memcpy(new->wordt, resps, sizeof(int) * i);
313 new->next = cmd->classes;
318 #include "initcmds.h"
322 if((ichandle = iconv_open("wchar_t", "utf-8")) == (iconv_t)-1)
328 void dc_cleanup(void)
330 iconv_close(ichandle);
333 void dc_disconnect(void)
335 struct dc_response *resp;
343 while((resp = dc_getresp()) != NULL)
345 dc_uimisc_disconnected();
346 if(servinfo.hostname != NULL)
347 free(servinfo.hostname);
348 memset(&servinfo, 0, sizeof(servinfo));
351 void dc_freeresp(struct dc_response *resp)
355 for(i = 0; i < resp->numlines; i++)
357 for(o = 0; o < resp->rlines[i].argc; o++)
358 free(resp->rlines[i].argv[o]);
359 free(resp->rlines[i].argv);
365 struct dc_response *dc_getresp(void)
367 struct dc_response *ret;
369 if((ret = respqueue) == NULL)
371 respqueue = ret->next;
372 if(respqueue == NULL)
375 respqueue->prev = NULL;
379 struct dc_response *dc_gettaggedresp(int tag)
381 struct dc_response *resp;
383 for(resp = respqueue; resp != NULL; resp = resp->next)
387 if(resp->prev != NULL)
388 resp->prev->next = resp->next;
389 if(resp->next != NULL)
390 resp->next->prev = resp->prev;
391 if(resp == respqueue)
392 respqueue = resp->next;
393 if(resp == respqueueend)
394 respqueueend = resp->prev;
401 struct dc_response *dc_gettaggedrespsync(int tag)
404 struct dc_response *resp;
406 while((resp = dc_gettaggedresp(tag)) == NULL)
411 pfd.events |= POLLOUT;
412 if(poll(&pfd, 1, -1) < 0)
414 if((pfd.revents & POLLIN) && dc_handleread())
416 if((pfd.revents & POLLOUT) && dc_handlewrite())
422 int dc_wantwrite(void)
427 if((queue != NULL) && (queue->buflen > 0))
434 int dc_getstate(void)
439 int dc_queuecmd(int (*callback)(struct dc_response *), void *data, ...)
447 wchar_t *part, *tpart;
448 size_t bufsize, bufdata;
451 bufsize = bufdata = 0;
454 while((part = va_arg(al, wchar_t *)) != NULL)
456 if(!wcscmp(part, L"%a"))
458 for(toks = va_arg(al, wchar_t **); *toks != NULL; toks++)
462 if((tpart = quoteword(part)) != NULL)
468 bufcat(buf, part, wcslen(part));
477 if(!wcscmp(tpart, L"i"))
480 part = swprintf2(L"%i", va_arg(al, int));
481 } else if(!wcscmp(tpart, L"li")) {
483 part = swprintf2(L"%ji", (intmax_t)va_arg(al, dc_lnum_t));
484 } else if(!wcscmp(tpart, L"s")) {
486 part = icmbstowcs(sarg = va_arg(al, char *), NULL);
493 } else if(!wcscmp(tpart, L"ls")) {
495 part = va_arg(al, wchar_t *);
496 } else if(!wcscmp(tpart, L"ll")) {
498 part = swprintf2(L"%lli", va_arg(al, long long));
499 } else if(!wcscmp(tpart, L"f")) {
501 part = swprintf2(L"%f", va_arg(al, double));
502 } else if(!wcscmp(tpart, L"x")) {
504 part = swprintf2(L"%x", va_arg(al, int));
514 if((tpart = quoteword(part)) != NULL)
525 if((qcmd = makeqcmd(part)) == NULL)
533 qcmd->callback = callback;
537 bufcat(buf, part, wcslen(part));
543 bufcat(buf, L"\r\n\0", 3);
544 if((final = icwcstombs(buf, "utf-8")) == NULL)
552 qcmd->buflen = strlen(final);
556 int dc_handleread(void)
563 /* Ewww... this really is soo ugly. I need to clean this up some day. */
564 static int pstate = 0;
565 static char inbuf[128];
566 static size_t inbufdata = 0;
567 static wchar_t *cbuf = NULL;
568 static size_t cbufsize = 0, cbufdata = 0;
569 static wchar_t *pptr = NULL;
570 static wchar_t **argv = NULL;
572 static size_t args = 0;
573 static wchar_t *cw = NULL;
574 static size_t cwsize = 0, cwdata = 0;
575 static struct dc_response *curresp = NULL;
577 static int unlink = 0;
584 optlen = sizeof(ret);
585 getsockopt(fd, SOL_SOCKET, SO_ERROR, &ret, &optlen);
590 message(2, "could not connect to %s: %s\n", formataddress(curhost->ai_addr, curhost->ai_addrlen), strerror(ret));
591 for(curhost = curhost->ai_next; curhost != NULL; curhost = curhost->ai_next)
593 if((newfd = socket(curhost->ai_family, curhost->ai_socktype, curhost->ai_protocol)) < 0)
602 fcntl(fd, F_SETFL, fcntl(fd, F_GETFL) | O_NONBLOCK);
603 message(4, "connecting to %s\n", formataddress(curhost->ai_addr, curhost->ai_addrlen));
604 if(connect(fd, (struct sockaddr *)curhost->ai_addr, curhost->ai_addrlen))
606 if(errno == EINPROGRESS)
608 message(2, "could not connect to %s: %s\n", formataddress(curhost->ai_addr, curhost->ai_addrlen), strerror(ret));
620 if(curhost->ai_canonname != NULL)
621 servinfo.hostname = sstrdup(curhost->ai_canonname);
622 servinfo.family = curhost->ai_family;
636 dc_freeresp(curresp);
641 ret = read(fd, inbuf + inbufdata, 128 - inbufdata);
644 if((errno == EAGAIN) || (errno == EINTR))
650 } else if(ret == 0) {
659 if(cbufsize == cbufdata)
663 if((cbuf = realloc(cbuf, sizeof(wchar_t) * (cbufsize += 256))) == NULL)
673 p2 = (char *)(cbuf + cbufdata);
674 len = sizeof(wchar_t) * (cbufsize - cbufdata);
675 ret = iconv(ichandle, &p1, &inbufdata, &p2, &len);
676 memmove(inbuf, p1, inbufdata);
677 cbufdata = cbufsize - (len / sizeof(wchar_t));
683 /* XXX Is this really OK? */
705 while(!done && (pptr - cbuf < cbufdata))
714 if(pptr == cbuf + cbufdata - 1)
719 if(*(++pptr) == L'\n')
723 curresp = makeresp();
724 if((argc > 0) && ((curresp->code = wcstol(argv[0], NULL, 10)) >= 600))
726 curresp->cmdname = L".notify";
727 curresp->internal = commands->next;
731 if((curresp->cmdname = queue->cmd->name) == NULL)
732 curresp->cmdname = L".connect";
733 curresp->data = queue->data;
734 curresp->tag = queue->tag;
735 curresp->internal = (void *)(queue->cmd);
739 sizebuf(&curresp->rlines, &curresp->linessize, curresp->numlines + 1, sizeof(*(curresp->rlines)), 1);
740 curresp->rlines[curresp->numlines].argc = argc;
741 curresp->rlines[curresp->numlines].argv = argv;
747 if((curresp->code >= 600) || (queue == NULL) || (queue->callback == NULL))
750 ret = queue->callback(curresp);
753 if(respqueue == NULL)
755 respqueue = respqueueend = curresp;
757 curresp->next = NULL;
758 curresp->prev = respqueueend;
759 respqueueend->next = curresp;
760 respqueueend = curresp;
762 } else if(ret == 1) {
763 dc_freeresp(curresp);
769 wmemmove(cbuf, pptr, cbufdata -= (pptr - cbuf));
783 if(iswspace(*pptr) || ((argc == 0) && (*pptr == L'-')))
796 sizebuf(&argv, &args, argc + 1, sizeof(*argv), 1);
801 } else if(*pptr == L'\"') {
804 } else if(*pptr == L'\\') {
805 if(pptr == cbuf + cbufdata - 1)
810 addtobuf(cw, *(++pptr));
813 addtobuf(cw, *(pptr++));
820 } else if(*pptr == L'\\') {
821 addtobuf(cw, *(++pptr));
834 #if UNIX_AUTH_STYLE == 1
835 static void mkcreds(struct msghdr *msg)
838 static char buf[CMSG_SPACE(sizeof(*ucred))];
839 struct cmsghdr *cmsg;
841 msg->msg_control = buf;
842 msg->msg_controllen = sizeof(buf);
843 cmsg = CMSG_FIRSTHDR(msg);
844 cmsg->cmsg_level = SOL_SOCKET;
845 cmsg->cmsg_type = SCM_CREDENTIALS;
846 cmsg->cmsg_len = CMSG_LEN(sizeof(*ucred));
847 ucred = (struct ucred *)CMSG_DATA(cmsg);
848 ucred->pid = getpid();
849 ucred->uid = getuid();
850 ucred->gid = getgid();
851 msg->msg_controllen = cmsg->cmsg_len;
855 int dc_handlewrite(void)
865 if(queue->buflen > 0)
867 memset(&msg, 0, sizeof(msg));
868 msg.msg_iov = &bufvec;
870 bufvec.iov_base = queue->buf;
871 bufvec.iov_len = queue->buflen;
872 #if UNIX_AUTH_STYLE == 1
873 if((servinfo.family == PF_UNIX) && !servinfo.sentcreds)
876 servinfo.sentcreds = 1;
879 ret = sendmsg(fd, &msg, MSG_NOSIGNAL | MSG_DONTWAIT);
882 if((errno == EAGAIN) || (errno == EINTR))
890 memmove(queue->buf, queue->buf + ret, queue->buflen -= ret);
899 * It kind of sucks that libresolv doesn't have any DNS parsing
900 * routines. We'll have to do it manually.
902 static char *readname(unsigned char *msg, unsigned char *eom, unsigned char **p)
906 size_t namesize, namedata, len;
909 namesize = namedata = 0;
917 } else if(len == 0xc0) {
918 tp = msg + *((*p)++);
919 if((tname = readname(msg, eom, &tp)) == NULL)
925 bufcat(name, tname, strlen(tname));
929 } else if(*p + len >= eom) {
934 bufcat(name, *p, len);
940 static int skipname(unsigned char *msg, unsigned char *eom, unsigned char **p)
950 } else if(len == 0xc0) {
953 } else if(*p + len >= eom) {
960 static int getsrvrr(char *name, char **host, int *port)
963 char *name2, *rrname;
964 unsigned char *eom, *p;
965 unsigned char buf[1024];
966 int flags, num, class, type;
970 if(!(_res.options & RES_INIT))
975 /* res_querydomain doesn't work for some reason */
976 if(name[strlen(name) - 1] == '.')
977 name2 = sprintf2("%s.%s", DOLCON_SRV_NAME, name);
979 name2 = sprintf2("%s.%s.", DOLCON_SRV_NAME, name);
980 ret = res_query(name2, C_IN, T_SRV, buf, sizeof(buf));
988 * Assume transaction ID is correct.
990 * Flags check: FA0F masks in request/response flag, opcode,
991 * truncated flag and status code, and ignores authoritativeness,
992 * recursion flags and DNSSEC and reserved bits.
994 flags = (buf[2] << 8) + buf[3];
995 if((flags & 0xfa0f) != 0x8000)
1000 /* Skip the query entries */
1001 num = (buf[4] << 8) + buf[5];
1003 for(i = 0; i < num; i++)
1005 if(skipname(buf, eom, &p))
1010 p += 4; /* Type and class */
1013 num = (buf[6] << 8) + buf[7];
1014 for(i = 0; i < num; i++)
1016 if((rrname = readname(buf, eom, &p)) == NULL)
1023 class = *(p++) << 8;
1028 if((class == C_IN) && (type == T_SRV) && !strcmp(rrname, name2))
1032 /* Noone will want to have alternative DC servers, so
1033 * don't care about priority and weigth */
1039 *port = *(p++) << 8;
1044 if((rrname = readname(buf, eom, &p)) == NULL)
1057 static int getsrvrr(char *name, char **host, int *port)
1064 static struct addrinfo *gaicat(struct addrinfo *l1, struct addrinfo *l2)
1070 for(p = l1; p->ai_next != NULL; p = p->ai_next);
1075 /* This isn't actually correct, in any sense of the word. It only
1076 * works on systems whose getaddrinfo implementation saves the
1077 * sockaddr in the same malloc block as the struct addrinfo. Those
1078 * systems include at least FreeBSD and glibc-based systems, though,
1079 * so it should not be any immediate threat, and it allows me to not
1080 * implement a getaddrinfo wrapper. It can always be changed, should
1081 * the need arise. */
1082 static struct addrinfo *unixgai(int type, char *path)
1085 struct addrinfo *ai;
1086 struct sockaddr_un *un;
1088 buf = smalloc(sizeof(*ai) + sizeof(*un));
1089 memset(buf, 0, sizeof(*ai) + sizeof(*un));
1090 ai = (struct addrinfo *)buf;
1091 un = (struct sockaddr_un *)(buf + sizeof(*ai));
1093 ai->ai_family = AF_UNIX;
1094 ai->ai_socktype = type;
1095 ai->ai_protocol = 0;
1096 ai->ai_addrlen = sizeof(*un);
1097 ai->ai_addr = (struct sockaddr *)un;
1098 ai->ai_canonname = NULL;
1100 un->sun_family = PF_UNIX;
1101 strncpy(un->sun_path, path, sizeof(un->sun_path) - 1);
1105 static struct addrinfo *resolvtcp(char *name, int port)
1107 struct addrinfo hint, *ret;
1110 memset(&hint, 0, sizeof(hint));
1111 hint.ai_socktype = SOCK_STREAM;
1112 hint.ai_flags = AI_NUMERICSERV | AI_CANONNAME;
1113 snprintf(tmp, sizeof(tmp), "%i", port);
1114 if(!getaddrinfo(name, tmp, &hint, &ret))
1119 static struct addrinfo *resolvsrv(char *name)
1121 struct addrinfo *ret;
1125 if(getsrvrr(name, &realname, &port))
1127 message(4, "SRV RR resolved: %s -> %s\n", name, realname);
1128 ret = resolvtcp(realname, port);
1133 static struct addrinfo *resolvhost(char *host)
1136 struct addrinfo *ret;
1139 if(strchr(host, '/'))
1140 return(unixgai(SOCK_STREAM, host));
1141 if((strchr(host, ':') == NULL) && ((ret = resolvsrv(host)) != NULL))
1144 if((*host == '[') && ((p = strchr(host, ']')) != NULL))
1146 hp = memcpy(smalloc(p - host), host + 1, (p - host) - 1);
1147 hp[(p - host) - 1] = 0;
1148 if(strchr(hp, ':') != NULL) {
1154 ret = resolvtcp(hp, port);
1162 if((p = strrchr(hp, ':')) != NULL) {
1168 ret = resolvtcp(hp, port);
1175 static struct addrinfo *getlocalai(void)
1177 struct addrinfo *ret;
1182 if((getuid() != 0) && ((pwd = getpwuid(getuid())) != NULL))
1184 tmp = sprintf2("/tmp/doldacond-%s", pwd->pw_name);
1185 ret = unixgai(SOCK_STREAM, tmp);
1188 ret = gaicat(ret, unixgai(SOCK_STREAM, "/var/run/doldacond.sock"));
1192 static struct addrinfo *defaulthost(void)
1194 struct addrinfo *ret;
1198 if(((tmp = getenv("DCSERVER")) != NULL) && *tmp) {
1199 message(4, "using DCSERVER: %s\n", tmp);
1200 return(resolvhost(tmp));
1203 ret = gaicat(ret, resolvtcp("localhost", 1500));
1204 if(!getdomainname(dn, sizeof(dn)) && *dn && strcmp(dn, "(none)"))
1205 ret = gaicat(ret, resolvsrv(dn));
1209 static int dc_connectai(struct addrinfo *hosts, struct qcmd **cnctcmd)
1217 if(hostlist != NULL)
1218 freeaddrinfo(hostlist);
1220 for(curhost = hostlist; curhost != NULL; curhost = curhost->ai_next)
1222 if((fd = socket(curhost->ai_family, curhost->ai_socktype, curhost->ai_protocol)) < 0)
1225 freeaddrinfo(hostlist);
1230 fcntl(fd, F_SETFL, fcntl(fd, F_GETFL) | O_NONBLOCK);
1231 message(4, "connecting to %s\n", formataddress(curhost->ai_addr, curhost->ai_addrlen));
1232 if(connect(fd, (struct sockaddr *)curhost->ai_addr, curhost->ai_addrlen))
1234 if(errno == EINPROGRESS)
1239 message(2, "could not connect to %s: %s\n", formataddress(curhost->ai_addr, curhost->ai_addrlen), strerror(errno));
1243 if(curhost->ai_canonname != NULL)
1244 servinfo.hostname = sstrdup(curhost->ai_canonname);
1245 servinfo.family = curhost->ai_family;
1252 qcmd = makeqcmd(NULL);
1263 static int dc_connect2(char *host, struct qcmd **cnctcmd)
1265 struct addrinfo *ai;
1269 if(host == dc_srv_local) {
1270 message(4, "connect start: Unix\n");
1272 } else if(!host || !*host) {
1273 message(4, "connect start: default\n");
1276 message(4, "connect start: host %s\n", host);
1277 ai = resolvhost(host);
1281 ret = dc_connectai(ai, &qcmd);
1282 if((ret >= 0) && (cnctcmd != NULL))
1287 int dc_connect(char *host)
1289 return(dc_connect2(host, NULL));
1292 int dc_connectsync(char *host, struct dc_response **respbuf)
1296 struct dc_response *resp;
1298 if((ret = dc_connect2(host, &cc)) < 0)
1300 resp = dc_gettaggedrespsync(cc->tag);
1312 int dc_connectsync2(char *host, int rev)
1315 struct dc_response *resp;
1317 if((ret = dc_connectsync(host, &resp)) < 0)
1319 if(dc_checkprotocol(resp, rev))
1323 errno = EPROTONOSUPPORT;
1330 struct dc_intresp *dc_interpret(struct dc_response *resp)
1333 struct dc_intresp *iresp;
1334 struct command *cmd;
1335 struct respclass *cls;
1339 if((resp->numlines == 0) || (resp->rlines[0].argc == 0) || (resp->curline >= resp->numlines))
1341 code = wcstol(resp->rlines[0].argv[0], NULL, 10);
1342 cmd = (struct command *)(resp->internal);
1343 for(cls = cmd->classes; cls != NULL; cls = cls->next)
1345 if(cls->code == code)
1350 if(cls->nwords >= resp->rlines[resp->curline].argc)
1352 iresp = smalloc(sizeof(*iresp));
1357 for(i = 0; i < cls->nwords; i++)
1359 switch(cls->wordt[i])
1364 sizebuf(&(iresp->argv), &args, iresp->argc + 1, sizeof(*(iresp->argv)), 1);
1365 iresp->argv[iresp->argc].val.str = swcsdup(resp->rlines[resp->curline].argv[i + 1]);
1366 iresp->argv[iresp->argc].type = cls->wordt[i];
1370 sizebuf(&(iresp->argv), &args, iresp->argc + 1, sizeof(*(iresp->argv)), 1);
1371 iresp->argv[iresp->argc].val.num = wcstol(resp->rlines[resp->curline].argv[i + 1], NULL, 0);
1372 iresp->argv[iresp->argc].type = cls->wordt[i];
1376 sizebuf(&(iresp->argv), &args, iresp->argc + 1, sizeof(*(iresp->argv)), 1);
1377 iresp->argv[iresp->argc].val.flnum = wcstod(resp->rlines[resp->curline].argv[i + 1], NULL);
1378 iresp->argv[iresp->argc].type = cls->wordt[i];
1382 sizebuf(&(iresp->argv), &args, iresp->argc + 1, sizeof(*(iresp->argv)), 1);
1383 iresp->argv[iresp->argc].val.lnum = wcstoll(resp->rlines[resp->curline].argv[i + 1], NULL, 0);
1384 iresp->argv[iresp->argc].type = cls->wordt[i];
1393 void dc_freeires(struct dc_intresp *ires)
1397 for(i = 0; i < ires->argc; i++)
1399 if(ires->argv[i].type == RESP_STR)
1400 free(ires->argv[i].val.str);
1406 int dc_checkprotocol(struct dc_response *resp, int revision)
1408 struct dc_intresp *ires;
1411 if(resp->code != 201)
1414 if((ires = dc_interpret(resp)) == NULL)
1416 low = ires->argv[0].val.num;
1417 high = ires->argv[1].val.num;
1419 if((revision < low) || (revision > high))
1424 const char *dc_gethostname(void)
1426 return(servinfo.hostname);