/* * Copyright (c) 1993 by the University of Southern California * * For copying and distribution information, please see the file * . */ #include /* Definitions of constants */ #define MAX_NUM_GOPHER_LINKS 3000 /* maximum # of directory links to return before giving up. */ /* Archie server we redirect ftp:hostname@ queries to. */ /* Don't define this for now, since it can lead to odd results. */ /* #define ARCHIE_SERVER "ARCHIE.SURA.NET" */ #include #include /* includes SOL_SOCKET */ #include #include /* for writev(), struct iovec */ #include #include #include #include #include /* unixerrstr() prototype. */ #include /* assert, internal_error, prototypes */ #include #include #include #include "gopher.h" #include #ifdef PSRV_WAIS_GW #include #include /* For WAIS_SOURCE_DIR */ #endif /* 5 secs seems a bit short for this */ #ifndef G_OPEN_TIMEOUT #define G_OPEN_TIMEOUT 10 #endif #ifndef G_READ_TIMEOUT #define G_READ_TIMEOUT 10 #endif #ifndef GFTP_READ_TIMEOUT #define GFTP_READ_TIMEOUT 20 #endif #ifndef GSEARCH_READ_TIMEOUT #define GSEARCH_READ_TIMEOUT 30 #endif extern char hostwport[]; extern int quick_open_tcp_stream(char host[], int port, int timeout); extern char *quick_fgets(char *s, int n, FILE *stream, int timeout); static GLINK quick_get_menu(char *host, int port, char *selector, int tme); static void glinks_to_dirob(GLINK glist, P_OBJECT ob); static void atput(VLINK vl, char *name,...); static void ftp_check_am(GLINK gl, VLINK r, char *textorbin); static void ftp_check_dir(GLINK gl, VLINK r); static int wais_check_dir(GLINK gl, VLINK r); static void zap_trailing_spaces(char *host); /* This function is passed an empty object which has been initialized with oballoc(). It returns a possibly populated directory and DIRSRV_NOT_FOUND or PSUCCESS. Note that the directory will need to have links within it freed, even if an error code is returned. */ /* This sets p_warn_string and pwarn if an unrecognized gopher line is received. That is good. */ /* This code needs to return attributes in response to a GET-OBJECT-INFO query. */ int gopher_gw_dsdb(RREQ req, /* Request pointer (unused) */ char *hsoname, /* Name of the directory */ long version,/* Version #; currently ignored */ long magic_no, /* Magic #; currently ignored */ int flags, /* Currently only recognize DRO_VERIFY */ struct dsrobject_list_options *listopts, /* options (use *remcompp and *thiscompp) */ P_OBJECT ob) { /* Object to be filled in */ int tmp; char *cp; AUTOSTAT_CHARPP(hostp); /* For threads. */ int port; char type; char *selectorcp; /* a character pointer that might point to the first part of the selector or possibly the complete selector. */ char *selector; /* the final selector */ GLINK glist; /* list of gopher links */ int timeout; /* Timeout for lines (not connect) */ ob->version = 0; /* unversioned */ ob->inc_native = VDIN_PSEUDO; ob->flags = P_OBJECT_DIRECTORY; ob->magic_no = 0; ob->acl = NULL; tmp = qsscanf(hsoname, "GOPHER-GW/%&[^(](%d)/%c%r/%r", hostp, &port, &type, &cp, &selectorcp); if (tmp < 4) return DIRSRV_NOT_FOUND; if (type == '1') { if (tmp < 5) return DIRSRV_NOT_FOUND; selector = selectorcp; } else if (type == '7') { AUTOSTAT_CHARPP(sp); /* Memory allocated for selector */ if (tmp < 4) return DIRSRV_NOT_FOUND; else if (tmp == 4) { selectorcp = ""; } /* if tmp = 5, selectorcp already set. */ /* Now set the selector. */ if (!listopts || !listopts->thiscompp || !*listopts->thiscompp || strequal(*listopts->thiscompp, "") || strequal(*listopts->thiscompp, "*")) /* last test against * might be inappropriate */ return PSUCCESS; /* Display no contents for the directory */ /* Set the selector. */ selector = *sp = qsprintf_stcopyr(*sp, "%s%s%s", selectorcp, (*selectorcp) ? "\t" : "", *listopts->thiscompp); /* We just used up thiscompp, so we'd better reset it. */ /* Oops - && here was meaningless */ /* I changed this so that the else is not done if listopts->remcompp is null, in that case (as in when called by get_object_info) listopts->thiscompp is also null so "*listopts->thiscompp = NULL" generates a segment violation, if this is the case then we didnt use a component anyway, so no need to advance to the next */ if (listopts->remcompp) { if (*listopts->remcompp) { *listopts->thiscompp = (*listopts->remcompp)->token; *listopts->remcompp = (*listopts->remcompp)->next; } else { *listopts->thiscompp = NULL; } } /* Now that the selector is set, fall through. */ } else { /* unknown type for gatewaying */ return DIRSRV_NOT_FOUND; } /* don't bother retrieving the contents if just verifying. */ if (flags & DRO_VERIFY) return PSUCCESS; if (type == '7') { timeout = GSEARCH_READ_TIMEOUT; } else { if (strncmp(selector,"ftp:",4)== 0 ) { timeout = GFTP_READ_TIMEOUT; } else { timeout = G_READ_TIMEOUT; } } if ((glist = quick_get_menu(*hostp, port, selector, timeout)) == NULL && perrno) return perrno; glinks_to_dirob(glist, ob); gllfree(glist); return PSUCCESS; } static GLINK quick_next_menu_item(FILE * fp, int timeout); extern int p_open_tcp_stream(const char host[], int port); /* Returns NULL & sets perrno on error. */ static GLINK quick_get_menu(char *host, int port, char *selector, int timeout) { GLINK retval = NULL; GLINK gl; FILE *fp; /* used for reading only. Tried to use it for reading & writing without success; see comment below. */ int fd; int tmp; /* return value from write() and read()*/ int nlinks = 0; /* # of links in directory */ #ifndef NO_IOVEC struct iovec iov[2]; #endif fd = quick_open_tcp_stream(host, port, G_OPEN_TIMEOUT); /* Copied from vcache */ if (fd < 0) { perrno = DIRSRV_GOPHER; /* work on error reporting */ return NULL; } #if ! defined(NO_IOVEC) && ! defined(PFS_THREADS) /* Current implementation of threads does not include writev(). */ iov[0].iov_base = selector; iov[0].iov_len = strlen(selector); iov[1].iov_base = "\r\n"; iov[1].iov_len = 2; tmp = writev(fd, iov, 2); if (tmp != iov[0].iov_len + iov[1].iov_len) { #else tmp = write(fd, selector, strlen(selector)); if (tmp != strlen(selector)) { p_err_string = qsprintf_stcopyr(p_err_string, "Couldn't send selector to gopher \ connection to %s: Call to write(%d,%s,%d) failed: errno %d: \ %s", host, fd, selector, strlen(selector), errno, unixerrstr()); redo_close: if(close(fd) == -1 && errno == EINTR) goto redo_close; perrno = DIRSRV_GOPHER; return NULL; } tmp = write(fd, "\r\n", 2); if (tmp != 2) { p_err_string = qsprintf_stcopyr(p_err_string, "Couldn't send selector to gopher \ connection to %s: Call to write(%d, \"\\r\\n\", 2) failed: errno %d: %s", host, fd, errno, unixerrstr()); #endif redo_close2: if(close(fd) == -1 && errno == EINTR) goto redo_close2; perrno = DIRSRV_GOPHER; return NULL; } fp = fdopen(fd, "r"); /* open fp just for reading */ /* Comment from Alan Emtage of Bunyip Information Systems which explains why I'm treating the writing end as a raw file descriptor and the reading end through the stdio library: From: bajan@bunyip.com (Alan Emtage) Date: Sat, 28 Aug 1993 10:01:32 -0400 Hi Steve, You have a comment in the gopher GW code to the effect that "this experiment didn't work.... if you know why, tell me". Well I will :-) For some reason which I haven't figured out, you can't a _socket_ file descriptor with fdopen in any form of update mode (you were trying to do it with "r+"). However it can be done if you open the fd _twice_. One FILE * for reading one for writing. Then everything works fine. What is going on in the underlying layers I don't know, but I'd bet that the symptoms you got was that you were reading things that you had previously written to the FILE * (and thought had just gone down the pipe to the remote connection). Hope this helps. -Alan */ perrno = PSUCCESS; while (gl = quick_next_menu_item(fp,timeout)) { if (++nlinks > MAX_NUM_GOPHER_LINKS) { perrno = DIRSRV_TOO_MANY; gllfree(retval); fclose(fp); return NULL; } APPEND_ITEM(gl, retval); } fclose(fp); redo_close3: if(close(fd) == -1 && errno == EINTR) goto redo_close3; if (perrno != PSUCCESS) { gllfree(retval); return(NULL); /* Return error code from next_menu_item*/ } return retval; } #if 0 /* This client opens a TCP stream to the host/port & returns a FILE pointer. */ /* Returns NULL on failure; sets p_err_string */ FILE * fopen_stream(char *host, int port) { int fd = quick_open_tcp_stream(host, port, G_OPEN_TIMEOUT); if (fd < 0) return NULL; return fdopen(fd, "r+"); } #endif /* Reads the next valid menu item from the stream FP. Returns NULL if no more, or if more than 3 erroneous lines go by. If a line is sent longer than 1024 characters, it is definitely illegal according to the Gopher spec, which requests no more than 255 chars for the selector and 80 for the description. */ /* Need to work on the error reporting. */ static GLINK quick_next_menu_item(FILE * fp, int timeout) { GLINK r; char buf[1024]; int buflen; /* # of characters in this buffer. */ int nbad = 0; again: if (nbad > 3) return NULL; if (!quick_fgets(buf, sizeof buf, fp, timeout)) { if (errno = ETIMEDOUT) { p_err_string = qsprintf_stcopyr(p_err_string, "Timed out after %d secs waiting for response", timeout); perrno = DIRSRV_GOPHER; return NULL; } assert(FALSE); } buflen = strlen(buf); if (sizeof buf - 1 == buflen) { /* line too long; missed CRLF */ ++nbad; goto again; } r = glalloc(); /* The standard specifies a trailing CR, but apparantly e.g. archive.egr.msu.edu(70) this is not guarranteed */ assert(buf[buflen -1] == '\n'); if (buf[buflen - 1] == '\n') buf[--buflen] = '\0'; if (buf[buflen - 1] == '\r') buf[--buflen] = '\0'; /* Check for end of menu */ if (strequal(buf, ".")) { glfree(r); return NULL; /* the end has been spotted. */ } #if 0 if (qsscanf(buf, "%c%&[^\t]%*1[\t]%&(^\t)%*1[\t]%&[^\t]%*1[\t]%d", &r->type, &r->name, &r->selector, &r->host, &r->port) < 5) { /* Note that %&( is not yet implemented, so we're doing it this way instead. */ #else if ((qsscanf(buf, "%c%&[^\t]%*1[\t]%&[^\t]%*1[\t]%&[^\t]%*1[\t]%d", &r->type, &r->name, &r->selector, &r->host, &r->port) < 5) && ((r->selector = stcopyr("", r->selector)), (qsscanf(buf, "%c%&[^\t]%*1[\t]%*1[\t]%&[^\t]%*1[\t]%d", &r->type, &r->name, &r->host, &r->port) < 4))) { #endif ++nbad; p_warn_string = qsprintf_stcopyr(p_warn_string, "Encountered unrecognized Gopher message: %s", buf); pwarn = PWARNING; glfree(r); goto again; } nbad = 0; /* Some special cases, for error messages that appear to be menus */ if(strncmp(r->name,"Server error",12) == 0) { p_err_string = qsprintf_stcopyr(p_err_string,r->name); perrno = DIRSRV_GOPHER; return NULL; } r->protocol_mesg = stcopy(buf); return r; } static VLINK gltovl(GLINK gl); static void add_collation_order(int linknum, VLINK vl); /* It is ok to modify the GLINKs, as long as glist is left at the head of a list of stuff to be freed with gllfree() by the caller of this function. A lot of the hair in this function is here because we must make sure that Gopher replicas are returned as Prospero replicas. For now, the only way to make sure two objects are treated as replicas is for them to be links with the same positive ID REMOTE value. */ static void glinks_to_dirob(GLINK glist, P_OBJECT ob) { int menu_entry_number = 0; /* # of menu entries read */ int replica_list = 0; /* nonzero if handling replica list */ long current_magic_no = 0L; /* set magic starting hash */ VLINK vl = NULL; /* last vlink processed */ GLINK gl; /* index */ for (gl = glist; gl; gl = gl->next) { /* If we found this link is a replica, put a magic # on the head of the list. */ if (!replica_list && gl->type == '+') { if (!vl) continue; /* no previous gopher link for this to be a replica of; perhaps we could not convert the gopher link to a virtual link. */ if (!current_magic_no) current_magic_no = generate_magic(vl); while (magic_no_in_list(++current_magic_no, ob->links)) { /* Check for numeric overflow */ if (current_magic_no <= 0) current_magic_no = 1; } vl->f_magic_no = ++current_magic_no; /* new magic no */ } if (gl->type != '+') replica_list = 0; else { assert(gl->previous); gl->type = gl->previous->type; /* gets type from head of list. */ } if ((vl = gltovl(gl)) == NULL) { replica_list = 0; /* break the chain of replicas */ continue; /* can't convert it */ } if (replica_list) { vl->f_magic_no = current_magic_no; add_collation_order(menu_entry_number, vl); } else { add_collation_order(++menu_entry_number, vl); } APPEND_ITEM(vl, ob->links); } } #ifdef NEVERDEFINED /* I intend using this to return attributes for GIET-OBJECT-INFO calsl but I need docs on what to return first */ /* Converts a hsoname back to a gl */ static GLINK hsoname2gl(char *hsoname) { GLINK gl = glalloc(); tmp = qsscanf(hsoname, "GOPHER-GW/%&[^(](%d)/%c/%r", &gl->host, &gl->port, &gl->type, &gl->selectorcp); switch (&gl->type) { '0': if (tmp < 4) goto hsoname2gl_bad; break; '1': if (tmp < 4) goto hsoname2gl_bad; break; '7': if (tmp < 3) goto hsoname2gl_bad; break; default: return NULL; /* Unsupported type */ } /* name,selector,protocol_mesg arent set by this */ return gl; hsoname2gl_bad: glfree(gl); return gl; } #endif /* Returns NULL if couldn't convert the gopher link GL to a Prospero link VL. Otherwise returns a new VLINK. */ static VLINK gltovl(GLINK gl) { VLINK r = vlalloc(); PATTRIB at; /* check if something is an AFTP link. If so, handle it appropriately.*/ /* add AFTP Access method */ r->name = gl->name; gl->name = NULL; atput(r, "GOPHER-MENU-ITEM", gl->protocol_mesg, (char *) 0); switch (gl->type) { case '0': /* Text files */ case 'c': /* Calendar Text files (not in RFC1436) */ case 'e': /* Event Text files (not in RFC1436) */ /* This comes first because, in the current client implementation, the server is expected to return access methods in what it believes the preferred order is. We prefer direct AFTP connections to overloading the intermediary Gopher gateways. */ r->target = stcopyr("EXTERNAL", r->target); ftp_check_am(gl, r, "TEXT"); atput(r, "ACCESS-METHOD", "GOPHER", "", "", "", "", "TEXT", (char *) 0); r->host = qsprintf_stcopyr(r->host, "%s(%d)", gl->host, gl->port); r->hsoname = gl->selector; gl->selector = NULL; atput(r, "OBJECT-INTERPRETATION", "DOCUMENT", "TEXT", "ASCII", (char *) 0); break; case '1': /* Directories */ r->host = stcopy(hostwport); r->target = stcopyr("DIRECTORY", r->target); r->hsoname = qsprintf_stcopyr(r->hsoname, "GOPHER-GW/%s(%d)/%c/%s", gl->host, gl->port, gl->type, gl->selector); atput(r, "OBJECT-INTERPRETATION", "DIRECTORY", (char *) 0); ftp_check_dir(gl, r); break; case '4': /* Macintosh BinHex (.hqx) text file */ r->target = stcopyr("EXTERNAL", r->target); ftp_check_am(gl, r, "TEXT"); atput(r, "ACCESS-METHOD", "GOPHER", "", "", "", "", "TEXT", (char *) 0); r->host = qsprintf_stcopyr(r->host, "%s(%d)", gl->host, gl->port); r->hsoname = gl->selector; gl->selector = NULL; atput(r, "OBJECT-INTERPRETATION", "EMBEDDED", "BINHEX", "DATA", (char *) 0); break; case '5': /* PC-Dos binary files. */ case '9': /* Generic binary files */ r->target = stcopyr("EXTERNAL", r->target); ftp_check_am(gl, r, "BINARY"); atput(r, "ACCESS-METHOD", "GOPHER", "", "", "", "", "BINARY", (char *) 0); r->host = qsprintf_stcopyr(r->host, "%s(%d)", gl->host, gl->port); r->hsoname = gl->selector; gl->selector = NULL; atput(r, "OBJECT-INTERPRETATION", "DATA", (char *) 0); break; case '6': /* UNIX UUencoded file. */ r->target = stcopyr("EXTERNAL", r->target); ftp_check_am(gl, r, "TEXT"); atput(r, "ACCESS-METHOD", "GOPHER", "", "", "", "", "TEXT", (char *) 0); r->host = qsprintf_stcopyr(r->host, "%s(%d)", gl->host, gl->port); r->hsoname = gl->selector; gl->selector = NULL; atput(r, "OBJECT-INTERPRETATION", "EMBEDDED", "UUENCODE", "DATA", (char *) 0); break; case '7': /* Gopher searches */ r->target = stcopyr("DIRECTORY", r->target); r->host = stcopy(hostwport); atput(r, "OBJECT-INTERPRETATION", "SEARCH", (char *) 0); #ifdef PSRV_WAIS_GW /* wais_check_dir() rewrites wais searches as appropriate, and reformats the link to be a WAIS one. */ if (wais_check_dir(gl, r)) break; /* done */ #endif /* Write the link as a gopher search link. */ r->hsoname = qsprintf_stcopyr(r->hsoname, "GOPHER-GW/%s(%d)/7/%s", gl->host, gl->port, gl->selector); atput(r, "QUERY-METHOD", "gopher-query(search-words)", "${search-words}", "", (char *) 0); atput(r, "QUERY-ARGUMENT", "search-words", "Index word(s) to search for", "mandatory char*", "%s", "", (char *) 0); atput(r, "QUERY-DOCUMENTATION", "gopher-query()", "This is a Gopher \ protocol query exported through Prospero. Since it is not a native Prospero \ query, we have very little documentation available for you.", (char *) 0); atput(r, "QUERY-DOCUMENTATION", "search-words", "This string says \ what you're searching for or what information you're specifying.", "Type any string. Sometimes SPACEs are treated as implied \ 'and' operators. Sometimes the words 'and', 'or', and 'not' are recognized \ as boolean operators.", (char *) 0); break; case '8':{ /* TELNET session */ char *m = ""; /* message */ r->target = stcopyr("EXTERNAL", r->target); if (*(gl->selector)) m = qsprintf_stcopyr((char *) NULL, "Use the account name \"%s\" to log in", gl->selector); atput(r, "ACCESS-METHOD", "TELNET", "", "", "", "", m, (char *) 0); zap_trailing_spaces(gl->host); /* Gopher uses a telnet port of 0 to mean the default telnet port. */ if (gl->port == 0) { r->host = gl->host; gl->host = NULL; } else { r->host = qsprintf_stcopyr(r->host, "%s(%d)", gl->host, gl->port); } atput(r, "OBJECT-INTERPRETATION", "PORTAL", (char *) 0); if (*m) stfree(m); break; } case 'I': /* Generic Image. */ case ':': /* Gopher+ bitmap image type; not in RFC1436 */ r->target = stcopyr("EXTERNAL", r->target); ftp_check_am(gl, r, "BINARY"); atput(r, "ACCESS-METHOD", "GOPHER", "", "", "", "", "BINARY", (char *) 0); r->host = qsprintf_stcopyr(r->host, "%s(%d)", gl->host, gl->port); r->hsoname = gl->selector; gl->selector = NULL; atput(r, "OBJECT-INTERPRETATION", "IMAGE", (char *) 0); break; case 'M': /* MIME. Not in RFC1436. */ r->target = stcopyr("EXTERNAL", r->target); ftp_check_am(gl, r, "TEXT"); atput(r, "ACCESS-METHOD", "GOPHER", "", "", "", "", "TEXT", (char *) 0); r->host = qsprintf_stcopyr(r->host, "%s(%d)", gl->host, gl->port); r->hsoname = gl->selector; gl->selector = NULL; atput(r, "OBJECT-INTERPRETATION", "DOCUMENT", "MIME", (char *) 0); break; case 'T':{ /* TN3270 session */ char *m = ""; /* message */ r->target = stcopyr("EXTERNAL", r->target); zap_trailing_spaces(gl->host); /* Gopher uses a telnet port of 0 to mean the default telnet port. */ if (gl->port == 0) { r->host = gl->host; gl->host = NULL; } else { r->host = qsprintf_stcopyr(r->host, "%s(%d)", gl->host, gl->port); } if (*(gl->selector)) m = qsprintf_stcopyr((char *) NULL, "Use the account name \"%s\" to log in", gl->selector); atput(r, "ACCESS-METHOD", "TN3270", "", "", "", "", m, (char *) 0); atput(r, "OBJECT-INTERPRETATION", "PORTAL", (char *) 0); if (*m) stfree(m); break; } case 'g': /* Gif */ r->target = stcopyr("EXTERNAL", r->target); ftp_check_am(gl, r, "BINARY"); atput(r, "ACCESS-METHOD", "GOPHER", "", "", "", "", "BINARY", (char *) 0); r->host = qsprintf_stcopyr(r->host, "%s(%d)", gl->host, gl->port); r->hsoname = gl->selector; gl->selector = NULL; atput(r, "OBJECT-INTERPRETATION", "IMAGE", "GIF", (char *) 0); break; case 's': /* SOUND. Not in RFC1436. */ case '<': /* Gopher+ sound type; not in RFC1436 */ r->target = stcopyr("EXTERNAL", r->target); ftp_check_am(gl, r, "BINARY"); atput(r, "ACCESS-METHOD", "GOPHER", "", "", "", "", "BINARY", (char *) 0); r->host = qsprintf_stcopyr(r->host, "%s(%d)", gl->host, gl->port); r->hsoname = gl->selector; gl->selector = NULL; atput(r, "OBJECT-INTERPRETATION", "SOUND", (char *) 0); break; case 'i': /* the uncommon i type exists in the University of Iowa gopher variant, Panda. */ r->name = gl->name; gl->name = NULL; r->target = stcopyr("NULL", r->target); r->hosttype = stcopyr("NULL", r->hosttype); r->host = stcopyr("NULL", r->host); r->hsonametype = stcopyr("NULL", r->hsonametype); r->hsoname = stcopyr("NULL", r->hsoname); r->target = stcopyr("NULL", r->target); atput(r, "OBJECT-INTERPRETATION", "VOID", (char *) 0); break; case ';': /* Gopher+ MOVIE type. It is not at all clear whether this is being used yet.*/ r->target = stcopyr("EXTERNAL", r->target); ftp_check_am(gl, r, "BINARY"); atput(r, "ACCESS-METHOD", "GOPHER", "", "", "", "", "BINARY", (char *) 0); r->host = qsprintf_stcopyr(r->host, "%s(%d)", gl->host, gl->port); r->hsoname = gl->selector; gl->selector = NULL; atput(r, "OBJECT-INTERPRETATION", "VIDEO", (char *) 0); break; case '.': /* End of directory. */ goto ignore; break; /* go on */ case '2': /* CSO nameserver; No gateway (yet) */ p_warn_string = qsprintf_stcopyr(p_warn_string, "Encountered gopher link to CSO nameserver; we don't support \ this type yet: %s", gl->protocol_mesg); pwarn = PWARNING; goto ignore; break; case '3': /* Mitra thinks the error type should be ignored. I am willing to go along with this, given that the standard UNIX Gopher client ignores it. */ goto ignore; break; case 'w': /* whois server */ p_warn_string = qsprintf_stcopyr(p_warn_string, "Encountered gopher link to WHOIS server; we don't support \ this type yet: %s", gl->protocol_mesg); case '-': /* error message type? */ default: /* unknown type or type we can't process */ p_warn_string = qsprintf_stcopyr(p_warn_string, "Encountered unrecognized Gopher message: %s", gl->protocol_mesg); pwarn = PWARNING; ignore: vlfree(r); r = NULL; break; } return r; } /* Check if we're an AFTP link. If so, add an AFTP access method. */ /* This check should be made before other access methods are added. The server is expected to return access methods in what it believes the preferred order is. We prefer direct AFTP connections to overloading the intermediary Gopher gateways. */ /* Gopher AFTP links to files use the HSONAME format: ftp:@/file-path (no trailing slash) */ static void ftp_check_am(GLINK gl, VLINK r, char *textorbin) { static char *ftp_hostname = NULL; char *ftp_rest; /* rest of string */ if (qsscanf(gl->selector, "ftp:%&[^@]@%r", &ftp_hostname, &ftp_rest) == 2) { int len = strlen(ftp_rest); if (ftp_rest[len - 1] != '/') atput(r, "ACCESS-METHOD", "AFTP", "", ftp_hostname, "", ftp_rest, textorbin, (char *) 0); } } /* Check if we're an AFTP link. If so, rewrite the HOST and HSONAME to go through the Prospero server at ARCHIE_SERVER (this can be changed). */ /* Gopher AFTP links to directories use the HSONAME format: ftp:@/directory.../ */ static void ftp_check_dir(GLINK gl, VLINK r) { static char *ftp_hostname = NULL; int len; char *ftp_rest; /* rest of string */ if (qsscanf(gl->selector, "ftp:%&[^@]@%r", &ftp_hostname, &ftp_rest) == 2 #if 0 /* Commented out, doesnt need to be / after @ if gatewaying thru gopher*/ && *ftp_rest == '/' #endif ) { len = strlen(ftp_rest); if (ftp_rest[len - 1] == '/') { #if 0 /* commented out because of problem:not all archie servers index all AFTP sites. */ #ifdef ARCHIE_SERVER ftp_rest[len - 1] = '\0'; r->host = stcopyr(ARCHIE_SERVER, r->host); r->hsoname = qsprintf_stcopyr(r->hsoname, "ARCHIE/HOST/%s%s", ftp_hostname, ftp_rest); #endif #endif #ifdef GOPHER_GW_NEARBY_GOPHER_SERVER r->hsoname = qsprintf_stcopyr(r->hsoname, "GOPHER-GW/%s/1/ftp:%s@%s", GOPHER_GW_NEARBY_GOPHER_SERVER, ftp_hostname, ftp_rest); #endif } } } #ifdef PSRV_WAIS_GW static int wais_check_dir(GLINK gl, VLINK r) { char *w_db = NULL; /* Pointer into gl->selector e.g. zzz */ char *w_path = NULL; /* Pointer into gl->selector e.g. xxx/yyy/zzz */ AUTOSTAT_CHARPP(w_db_srcp); /* e.g. zzz.src */ char *cp = NULL; /* temporary pointer */ WAISSOURCE thissource; /* Points to source structure */ if (qsscanf(gl->selector, "waissrc:%r", &w_path) != 1) { return FALSE; } if ((w_db = strrchr(w_path, '/')) == NULL) { w_db = w_path; } else { w_db++; } if ((cp = strstr(w_db, ".src")) == NULL) { *w_db_srcp = qsprintf_stcopyr(*w_db_srcp, "%s%s", w_db, ".src"); } else { *w_db_srcp = stcopyr(w_db, *w_db_srcp); /* *w_db_srcp = zzz.src */ } /*!! Open and parse local src file */ /* While this may s_alloc the source DONT free it, its on a linked list of sources which persists while the server is up*/ if ((thissource = findsource(*w_db_srcp, WAIS_SOURCE_DIR)) == NULL) return FALSE; if ((cp = strstr(w_db, ".src")) != NULL) { /* Need to do this AFTER findsource, otherwise can modify gl->selector of non matching source */ cp[0] = '\0'; /* w_db = zzz */ } r->hsoname = qsprintf_stcopyr(r->hsoname, "WAIS-GW/%s(%s)/QUERY/%s", thissource->server, thissource->service, w_db); if (thissource->subjects) atput(r, "WAIS-SUBJECTS", thissource->subjects, (char *) 0); if (thissource->cost) atput(r, "WAIS-COST-NUM", thissource->cost, (char *) 0); if (thissource->units) atput(r, "WAIS-COST-UNIT", thissource->units, (char *) 0); if (thissource->maintainer) atput(r, "WAIS-COST-MAINTAINER", thissource->maintainer, (char *) 0); atput(r, "QUERY-METHOD", "wais-query(search-words)", "${search-words}", "", (char *) 0); atput(r, "QUERY-ARGUMENT", "search-words", "Index word(s) to search for", "mandatory char*", "%s", "", (char *) 0); if (thissource->description) atput(r, "QUERY-DOCUMENTATION", "wais-query()", thissource->description, (char *) 0); atput(r, "QUERY-DOCUMENTATION", "search-words", "This string says \ what you're searching for or what information you're specifying.", "Type any string. Sometimes SPACEs are treated as implied \ 'and' operators. Sometimes the words 'and', 'or', and 'not' are recognized \ as boolean operators.", (char *) 0); return (TRUE); } #endif static void zap_trailing_spaces(char *host) { int len = strlen(host); while (--len >= 0) { if (host[len] == ' ') host[len] = '\0'; else break; } } /* Add a collation-order attribute of the form NUMERIC to the link VL */ static void add_collation_order(int linknum, VLINK vl) { PATTRIB ca = atalloc(); char buf[40]; /* long enough to hold the ASCII representation of any int for the foreseeable future. */ ca->precedence = ATR_PREC_LINK; ca->nature = ATR_NATURE_APPLICATION; ca->avtype = ATR_SEQUENCE; ca->aname = stcopy("COLLATION-ORDER"); ca->value.sequence = tkappend("NUMERIC", ca->value.sequence); assert(qsprintf(buf, sizeof buf, "%d", linknum) <= sizeof buf); ca->value.sequence = tkappend(buf, ca->value.sequence); APPEND_ITEM(ca, vl->lattrib); } static void atput(VLINK vl, char *name,...) { va_list ap; char *s; PATTRIB at = atalloc(); va_start(ap, name); at->aname = stcopy(name); at->avtype = ATR_SEQUENCE; if (strequal(name, "ACCESS-METHOD")) at->nature = ATR_NATURE_FIELD; else at->nature = ATR_NATURE_APPLICATION; if (strequal(vl->target, "EXTERNAL")) at->precedence = ATR_PREC_REPLACE; /* still not clear what to use */ else at->precedence = ATR_PREC_OBJECT; while (s = va_arg(ap, char *)) { at->value.sequence = tkappend(s, at->value.sequence); } APPEND_ITEM(at, vl->lattrib); va_end(ap); }