/* Package dnslink implements a dns link resolver. dnslink is a basic standard for placing traversable links in dns itself. See dnslink.info A dnslink is a path link in a dns TXT record, like this: dnslink=/ipfs/QmR7tiySn6vFHcEjBeZNtYGAFh735PJHfEMdVEycj9jAPy For example: > dig TXT ipfs.io ipfs.io. 120 IN TXT dnslink=/ipfs/QmR7tiySn6vFHcEjBeZNtYGAFh735PJHfEMdVEycj9jAPy This package eases resolving and working with thse dns links. For example: import ( dnslink "github.com/jbenet/go-dnslink" ) link, err := dnslink.Resolve("ipfs.io") // link = "/ipfs/QmR7tiySn6vFHcEjBeZNtYGAFh735PJHfEMdVEycj9jAPy" It even supports recursive resolution. Suppose you have three domains with dnslink records like these: > dig TXT foo.com foo.com. 120 IN TXT dnslink=/dns/bar.com/f/o/o > dig TXT bar.com bar.com. 120 IN TXT dnslink=/dns/long.test.baz.it/b/a/r > dig TXT long.test.baz.it long.test.baz.it. 120 IN TXT dnslink=/b/a/z Expect these resolutions: dnslink.ResolveN("long.test.baz.it", 0) // "/dns/long.test.baz.it" dnslink.Resolve("long.test.baz.it") // "/b/a/z" dnslink.ResolveN("bar.com", 1) // "/dns/long.test.baz.it/b/a/r" dnslink.Resolve("bar.com") // "/b/a/z/b/a/r" dnslink.ResolveN("foo.com", 1) // "/dns/bar.com/f/o/o/" dnslink.ResolveN("foo.com", 2) // "/dns/long.test.baz.it/b/a/r/f/o/o/" dnslink.Resolve("foo.com") // "/b/a/z/b/a/r/f/o/o" */ #include #include #ifdef __MINGW32__ #include #define u_char unsigned char #define u_int16_t uint16_t #define u_int32_t uint32_t #define NS_MAXDNAME 1025 /*%< maximum domain name */ #define ns_c_in 1 #define ns_t_txt 16 typedef enum __ns_sect { ns_s_qd = 0, /*%< Query: Question. */ ns_s_zn = 0, /*%< Update: Zone. */ ns_s_an = 1, /*%< Query: Answer. */ ns_s_pr = 1, /*%< Update: Prerequisites. */ ns_s_ns = 2, /*%< Query: Name servers. */ ns_s_ud = 2, /*%< Update: Update. */ ns_s_ar = 3, /*%< Query|Update: Additional records. */ ns_s_max = 4 } ns_sect; typedef struct __ns_msg { const unsigned char *_msg, *_eom; u_int16_t _id, _flags, _counts[ns_s_max]; const unsigned char *_sections[ns_s_max]; ns_sect _sect; int _rrnum; const unsigned char *_msg_ptr; } ns_msg; typedef struct __ns_rr { char name[NS_MAXDNAME]; u_int16_t type; u_int16_t rr_class; u_int32_t ttl; u_int16_t rdlength; const u_char * rdata; } ns_rr; #else #include #include #include #endif #include "ipfs/namesys/namesys.h" #define IPFS_DNSLINK_C #include "ipfs/dnslink/dnslink.h" #include "ipfs/cid/cid.h" #include "ipfs/path/path.h" int ipfs_dns (int argc, char **argv) { #ifdef __MINGW32__ fprintf (stderr, "Sorry, dns has not yet been implemented for the Windows version.\n"); return -1; } #else int err, r=0, i; char **txt, *path, *param; if (argc == 4 && strcmp ("-r", argv[2])==0) { r = 1; argc--; argv++; } if (argc != 3) { fprintf (stderr, "usage: ipfs dns [-r] dns.name.com\n"); return -1; } param = malloc (strlen (argv[2]) + 1); if (!param) { fprintf (stderr, "memory allocation failed.\n"); return 1; } strcpy (param, argv[2]); for (i = 0 ; i < DefaultDepthLimit ; i++) { if (memcmp(param, "/ipns/", 6) == 0) { err = ipfs_dnslink_resolv_lookupTXT (&txt, param+6); } else { err = ipfs_dnslink_resolv_lookupTXT (&txt, param); } free (param); if (err) { fprintf (stderr, "dns lookupTXT: %s\n", Err[err]); return err; } err = ipfs_dnslink_parse_txt(&path, *txt); if (err) { free (*txt); free (txt); fprintf (stderr, "dns parse_txt: %s\n", Err[err]); return err; } free (*txt); free (txt); param = path; if (! r) { // not recursive. break; } if (memcmp(path, "/ipfs/", 6) == 0) { break; } } while (--r); fprintf (stdout, "%s\n", param); free (param); return 0; } #endif // MINGW // ipfs_dnslink_resolve resolves the dnslink at a particular domain. It will // recursively keep resolving until reaching the defaultDepth of Resolver. If // the depth is reached, ipfs_dnslink_resolve will return the last value // retrieved, and ErrResolveLimit. If TXT records are found but are not valid // dnslink records, ipfs_dnslink_resolve will return ErrInvalidDNSLink. // ipfs_dnslink_resolve will check every TXT record returned. If resolution // fails otherwise, ipfs_dnslink_resolve will return ErrResolveFailed int ipfs_dnslink_resolve (char **p, char *domain) { return ipfs_dnslink_resolve_n (p, domain, DefaultDepthLimit); } // ipfs_dnslink_lookup_txt is a function that looks up a TXT record in some dns resolver. // This is useful for testing or passing your own dns resolution process, which // could take into account non-standard TLDs like .bit, .onion, .ipfs, etc. int (*ipfs_dnslink_lookup_txt)(char ***txt, char *name) = NULL; // ipfs_dnslink_resolve_n is just like Resolve, with the option to specify a // maximum resolution depth. int ipfs_dnslink_resolve_n (char **p, char *d, int depth) { int err, i, l; char *rest, **link, tail[500], buf[500], domain[500]; char dns_prefix[] = "/dns/"; domain[sizeof(domain)-1] = '\0'; strncpy (domain, d, sizeof(domain) - 1); for (i=0 ; i < depth ; i++) { err = ipfs_dnslink_resolve_once (&link, domain); if (err) { return err; } // if does not have /dns/ as a prefix, done. if (memcmp (*link, dns_prefix, sizeof(dns_prefix) - 1)!=0) { l = strlen(*link) + strlen(tail); *p = malloc(l + 1); if (!*p) { free(*link); free(link); return ErrAllocFailed; } *p[l] = '\0'; strncpy(*p, *link, l); free(*link); free(link); strncat(*p, tail, l - strlen(*p)); return 0; // done } // keep resolving err = ipfs_dnslink_parse_link_domain (&d, &rest, *link); free (*link); free (link); if (err) { *p = NULL; return err; } strncpy (domain, d, sizeof(domain) - 1); free (d); strncpy (buf, tail, sizeof(buf) - 1); strncpy (tail, rest, sizeof(tail) - 1); strncat (tail, buf, sizeof(tail) - 1 - strlen(tail)); } strncpy (buf, tail, sizeof(buf) - 1); strncpy (tail, dns_prefix, sizeof(tail) - 1); strncat (tail, domain, sizeof(tail) - 1 - strlen(tail)); strncat (tail, buf, sizeof(tail) - 1 - strlen(tail)); return ErrResolveLimit; } #ifndef __MINGW32__ // lookup using libresolv -lresolv int ipfs_dnslink_resolv_lookupTXT(char ***txt, char *domain) { char buf[4096], *p; int responseLength; int i, l, n = 0; ns_msg query_parse_msg; ns_rr query_parse_rr; u_char responseByte[4096]; // Use res_query from libresolv to retrieve TXT record from DNS server. if ((responseLength = res_query(domain,ns_c_in,ns_t_txt,responseByte,sizeof(responseByte))) < 0 || ns_initparse(responseByte,responseLength,&query_parse_msg) < 0) { return ErrResolveFailed; } else { l = sizeof (buf); buf[--l] = '\0'; p = buf; // save every TXT record to buffer separating with a \0 for (i=0 ; i < ns_msg_count(query_parse_msg,ns_s_an) ; i++) { if (ns_parserr(&query_parse_msg,ns_s_an,i,&query_parse_rr)) { return ErrResolveFailed; } else { const unsigned char *rdata = ns_rr_rdata(query_parse_rr); memcpy(p, rdata+1, *rdata); // first byte is record length p += *rdata; // update pointer *p++ = '\0'; // mark end-of-record and update pointer to next record. n++; // update record count } } // allocate array for all records + NULL pointer terminator. *txt = calloc(n+1, sizeof(void*)); if (!*txt) { return ErrAllocFailed; } l = p - buf; // length of all records in buffer. p = malloc(l); // allocate memory that will be used as string data at *txt array. if (!p) { free(*txt); *txt = NULL; return ErrAllocFailed; } memcpy(p, buf, l); // transfer from buffer to allocated memory. for (i = 0 ; i < n ; i++) { *txt[i] = p; // save position of current record at *txt array. p = memchr(p, '\0', l - (p - *txt[0])) + 1; // find next record position after next \0 } } return 0; } #endif // ipfs_dnslink_resolve_once implements resolver. int ipfs_dnslink_resolve_once (char ***p, char *domain) { int err, i; char **txt; if (!p || !domain) { return ErrInvalidParam; } *p = NULL; if (!ipfs_isdomain_is_domain (domain)) { return ErrInvalidDomain; } #ifndef __MINGW32__ if (!ipfs_dnslink_lookup_txt) { // if not set ipfs_dnslink_lookup_txt = ipfs_dnslink_resolv_lookupTXT; // use default libresolv } err = ipfs_dnslink_lookup_txt (&txt, domain); #endif if (err) { return err; } err = ErrResolveFailed; for (i=0 ; txt[i] ; i++) { err = ipfs_dnslink_parse_txt(*p, txt[i]); if (!err) { break; } } free(*txt); free(txt); return err; } // ipfs_dnslink_parse_txt parses a TXT record value for a dnslink value. // The TXT record must follow the dnslink format: // TXT dnslink= // TXT dnslink=/foo/bar/baz // ipfs_dnslink_parse_txt will return ErrInvalidDNSLink if parsing fails. int ipfs_dnslink_parse_txt (char **path, char *txt) { char **parts; if (!path || !txt) { return ErrInvalidParam; } parts = ipfs_path_split_n (txt, "=", 2); if (!parts) { return ErrAllocFailed; } if (ipfs_path_segments_length (parts) == 2 && strcmp(parts[0], "dnslink")==0 && memcmp(parts[1], "/", 1)==0) { *path = ipfs_path_clean_path(parts[1]); if (path) { ipfs_path_free_segments (&parts); return 0; } } ipfs_path_free_segments (&parts); *path = NULL; return ErrInvalidDNSLink; } // ipfs_dnslink_parse_link_domain parses a domain from a dnslink path. // The link path must follow the dnslink format: // /dns// // /dns/ipfs.io // /dns/ipfs.io/blog/0-hello-worlds // ipfs_dnslink_parse_link_domain will return ErrInvalidDNSLink if parsing // fails, and ErrInvalidDomain if the domain is not valid. int ipfs_dnslink_parse_link_domain (char **domain, char**rest, char *txt) { char **parts; int parts_len; if (!domain || !rest || !txt) { return ErrInvalidParam; } *domain = *rest = NULL; parts = ipfs_path_split_n (txt, "/", 4); parts_len = ipfs_path_segments_length(parts); if (!parts || parts_len < 3 || parts[0][0]!='\0' || strcmp(parts[1], "dns") != 0) { return ErrInvalidDNSLink; } if (! ipfs_isdomain_is_domain (parts[2])) { ipfs_path_free_segments (&parts); return ErrInvalidDomain; } *domain = malloc(strlen (parts[2]) + 1); if (!*domain) { ipfs_path_free_segments (&parts); return ErrAllocFailed; } strcpy(*domain, parts[2]); if (parts_len > 3) { *rest = malloc(strlen (parts[3]) + 1); if (!*rest) { ipfs_path_free_segments (&parts); free (*domain); *domain = NULL; return ErrAllocFailed; } strcpy(*rest, parts[3]); } return 0; }