/* This software is copyrighted by the University of Pennsylvania. Read COPYRIGHT for details. */ /* Assume mostly the same strategy as the TechInfo server and put all data into a buffer for the connection. However, if the transaction requires connecting to gopher, then fork a child, which will exec a new program with the right argv[], and parent will create c_helper for the connection. When the server receives the signal that the child is done, it will read the child's output file, translate it and write a TechInfo formatted file and call nio_send_file(). Then remove_helper for the connection. If a file containing the gopher information is already cached and isn't too old, use it instead of connecting to gopher. This is basically a complete rewrite of MIT's transact.c code. I *really* disliked the extensive use of variables whose scope was the entire module. I like to pass parameter lists, even if they get cumbersome. Less confusion in the long run about what gets used, what gets changed. */ #include #include #include #include #include #include #include #include #include #include #include /**/ #include #include "gophernodes.h" /* new for techinpher gateway */ #include "network.h" #include "pdb.h" #include "messages.h" #include "node.h" void admin(int numf, char **fields, int sock); void change_format(int *fmt, int sock, int numf, char **fields); void find(CONN *conn, char ch, int numf, char **fields ); char *find_abbrev(char ch); void find_ver(CONN *conn, int numf, char **fields); int hdl_transact(CONN *conn); void log_trans(char *line); char *nodeinfostr (long nodeid, struct s1 *nd, int flgsfmt); void proc_trans(int numf, char ch, char **fields, CONN *conn); void send_connections(int sock); void send_empty_document(int sock); void send_empty_menu(int sock); void send_file(CONN *conn, char ttype, int numf, char **fields); void send_gopher_info(struct s1 *nd, int numf, char **fields, CONN *currcon); void send_help(int sock); void send_node(int flgsfmt, int sock, int numf, char **transfields); int send_reserved_node (char trans_type, int numf, char **fields, long nodeid, int flgsfmt, int cur_sock); void send_src_info(int sock); void send_servers_list(int sock); int test_admin(char *passwd); void traverse(CONN *conn, char ch, int numf, char **fields); extern char *msglist[]; /* the list of error messages */ extern int debug; extern short todaysdate; extern int tables_changed; extern int lastused_changed; extern void parsefields(); extern struct s1 *getnode_bynodeid(long nid); extern void catch_child(); extern void nio_send_file(int s, char *fn, long stpt, long req, int isnlist); extern void send_msg (char *string, int sock); extern int sendbuf (char *cp, int sz, int sock); int max_stale_time; /* not used, but it's in server.c */ static char *curr_uid; /* should get rid of this one too */ static char in_buff[BUFSIZ]; /* holds transaction from client */ static char nodeinfobuf[BUFSIZ]; struct gopherabbrev gopher_abbrev[] = { /* leaving out ERROR and DUPSRV */ {GOPHTYP_TEXT, "."}, {GOPHTYP_MENU, "/"}, {GOPHTYP_CSO, " "}, {GOPHTYP_MACHQX, " "}, {GOPHTYP_DOSBIN, " "}, {GOPHTYP_UUENC, " "}, {GOPHTYP_SEARCH, " "}, {GOPHTYP_TELNET, " "}, {GOPHTYP_BINARY, " "}, {GOPHTYP_GIF, " "}, {GOPHTYP_IMAGE, " "}, {GOPHTYP_TN3270, " <3270>"}, {GOPHTYP_SOUND, " <)"}, {GOPHTYP_EVENT, " "}, {GOPHTYP_CALENDAR, " "}, {GOPHTYP_MIME, " "}, {GOPHTYP_HTML, " "} }; #define NUM_GOPHER_ABBREV 17 #define gopher_abbrev_unk " " int hdl_transact(CONN *conn) { char log_line[BUFSIZ]; int length; int f; int cur_sock; char *transfields[MAX_TRANS_FIELDS]; int num_transfields; char trans_type; curr_uid = conn->c_uid; conn->c_trans_cnt++; cur_sock = conn->c_socket; bzero(in_buff, BUFSIZ); length = read(cur_sock, in_buff, BUFSIZ); if (length <= 0) { if (length) perror("socket read"); /* if (errno == EWOULDBLOCK) return 0; else This caused looping error -ST */ return 1; } if (in_buff[length - 1] == LF) length--; if (in_buff[length - 1] == CR) length--; in_buff[length] = '\0'; trans_type = *in_buff; parsefields(in_buff, transfields, &num_transfields, DLM, MAX_TRANS_FIELDS-1); if (length == 0 || trans_type == T_QUIT) return 1; /* quit and close connection */ if (trans_type == T_ADMIN) sprintf(log_line, "%s:%d:%c", curr_uid, cur_sock, trans_type); else if (trans_type == T_TRYPROVIDER) sprintf(log_line, "%s:%d:%c:%s", curr_uid,cur_sock,trans_type, num_transfields < 2 ? "" : transfields[1]); else { sprintf (log_line, "%s:%d", curr_uid, cur_sock); for (f=0; f < num_transfields; f++) { strcat (log_line, ":"); strcat (log_line, transfields[f]); } } log_trans(log_line); /* log the transaction */ (void) proc_trans (num_transfields, trans_type, transfields, conn); return 0; /* ok */ } /* Switch on transaction type, calling handler procedure */ static void proc_trans(int num_transfields, char trans_type, char **transfields, CONN *conn) { int flgsfmt; int cur_sock; flgsfmt = conn->c_output_fmt; cur_sock = conn->c_socket; switch (trans_type) { /* No provider functions */ case T_ADDLINK: case T_ADDNODE: case T_CHG_SRC_INFO: case T_GETFILE: case T_REORDER_AFTER: case T_REORDER_BEFORE: case T_REPLACENODE: case T_RMLINK: case T_RMNODE: case T_SOURCE: send_msg(msglist[ NOT_AUTH ], cur_sock); break; case T_TRYPROVIDER: send_msg(msglist[ BAD_PASSWD ], cur_sock); break; case T_NODE_FORMAT: change_format ( &(conn->c_output_fmt), cur_sock, num_transfields, transfields ); break; case T_VERSION: /* find the current version number */ find_ver(conn, num_transfields, transfields); break; case T_SRC_INFO: send_src_info(cur_sock); break; case T_SENDFILE: send_file(conn, trans_type, num_transfields, transfields); break; case T_SENDNODE: send_node(flgsfmt, cur_sock, num_transfields, transfields); break; case T_TRAVERSE: /* do a traverse + return list */ traverse(conn, trans_type, num_transfields, transfields); break; case T_HELP: send_reserved_node (trans_type, num_transfields, transfields, HELP_MENU_NODE, flgsfmt, cur_sock); break; case T_FULL_TXT_SEARCH: /* no difference in Gopher between the two */ case T_FIND: /* do a find + return list */ case T_TITLE_SRCH: find(conn, trans_type, num_transfields, transfields); break; /* ADMIN functions */ case T_ADMIN: admin(num_transfields, transfields, cur_sock); break; case T_ENDPROVIDER: if (test_admin(curr_uid)) { /* NOT PROVIDER, but admin */ strcpy (curr_uid, ""); send_msg(msglist[ OK ], cur_sock); } else send_msg(msglist[ NOT_AUTH ], cur_sock); break; case T_RELOAD: if (test_admin(curr_uid)) { datastruct_load(1); /* 1 == free old stuff first */ send_msg(msglist[ OK ], cur_sock); } else send_msg(msglist[ NOT_AUTH ], cur_sock); break; case T_SAVEWEB: /* save the web to disk */ { if (test_admin(curr_uid)) { save_datastruct(); send_msg(msglist[ OK ], cur_sock); } else send_msg(msglist[ NOT_AUTH ], cur_sock); break; } case T_SHOW_CONN: send_connections(cur_sock); /* NOT an admin function */ break; case T_GET_SERVER_INFO: send_servers_list(cur_sock); break; /* Not implementing the following */ case T_CHG_BANNER: case T_SHUTDOWN: case T_OUTPUTFMT: case T_CHGD_SINCE: case T_FINDKEY: case T_SET_DATES: case T_SOURCE_SRCH: default: send_msg(msglist[ HUH ], cur_sock); break; } /* switch on transtype */ } static void send_servers_list(int cur_sock) { FILE *fopen(), *srvfile; char buf[BUFSIZ],*cp; int num = 0; srvfile = fopen(SERVER_FILE, "r"); if (!srvfile) { perror(SERVER_FILE); send_msg(msglist[ CANT_FIND_SERVER_FILE ], cur_sock); return; } /* how many lines in the file? */ for (num = 0; fgets(buf,BUFSIZ-1,srvfile) != NULL; num++); rewind (srvfile); cp = (char *) domalloc ((num+1) * BUFSIZ); /* a little extra room */ sprintf (cp, "%d:servers\n", num); while (fgets(buf, BUFSIZ-1, srvfile) != NULL) strcat (cp, buf); strcat (cp, ".\r\n"); fclose(srvfile); sendbuf (cp, strlen(cp), cur_sock); return; } /* send_nlistmsg_trav should only be used when the TI client is expecting an nlist as the response to T_TRAVERSE. TI client is expecting: #nodes: 0:parent 1:child1 1:child2 . */ static void send_nlistmsg_trav (long par, long child, int repeatpar, int flgsfmt, int cur_sock) { char *nlistptr; if (repeatpar) { nlistptr = (char *) domalloc (3*BUFSIZ); sprintf (nlistptr, "3:\n0:"); } else { nlistptr = (char *) domalloc (2*BUFSIZ); sprintf (nlistptr, "2:\n0:"); } strcat (nlistptr, nodeinfostr(par, (struct s1 *)NULL, flgsfmt)); strcat (nlistptr, "\n1:"); if (repeatpar) { strcat (nlistptr, nodeinfostr(par, (struct s1 *)NULL, flgsfmt)); strcat (nlistptr, "\n1:"); } strcat (nlistptr, nodeinfostr(child, (struct s1 *) NULL, flgsfmt)); strcat (nlistptr, "\n.\r\n"); sendbuf (nlistptr, strlen(nlistptr), cur_sock); /* Assumption: sendbuf will free the nlistptr allocated memory */ } /* send_nlistmsg_find should only be used when the TI client is expecting an nlist as the response to T_FIND. TI client is expecting: #nodes: 0:node1 0:node2 . */ static void send_nlistmsg_find (long par, long child, int flgsfmt, int cur_sock) { char *nlistptr; nlistptr = (char *) domalloc (2*BUFSIZ); sprintf (nlistptr, "2:\n0:"); strcat (nlistptr, nodeinfostr(par, (struct s1 *)NULL, flgsfmt)); strcat (nlistptr, "\n0:"); strcat (nlistptr, nodeinfostr(child, (struct s1 *) NULL, flgsfmt) ); strcat (nlistptr, "\n.\r\n"); sendbuf (nlistptr, strlen(nlistptr), cur_sock); /* Assumption: sendbuf will free the nlistptr allocated memory */ } /* send the file associated with a certain node to the client. */ static void send_file(CONN *conn, char trans_type, int num_transfields, char **transfields) { int nodeid; struct s1 *nd; int cur_sock = conn->c_socket; int flgsfmt = conn->c_output_fmt; if (num_transfields < 2) nodeid = 0; else nodeid = atoi (transfields[1]); if (!send_reserved_node(trans_type, num_transfields, transfields, nodeid, flgsfmt, cur_sock)) { nd = getnode_bynodeid(nodeid); if (nd == NULL) { send_empty_document(cur_sock); return; } switch (nd->gophertype) { case GOPHTYP_GIF: case GOPHTYP_IMAGE: case GOPHTYP_TEXT: send_gopher_info(nd, num_transfields, transfields, conn); break; case GOPHTYP_CSO: case GOPHTYP_MACHQX: case GOPHTYP_DOSBIN: case GOPHTYP_UUENC: case GOPHTYP_BINARY: case GOPHTYP_SOUND: case GOPHTYP_EVENT: case GOPHTYP_CALENDAR: case GOPHTYP_HTML: case GOPHTYP_MIME: send_reserved_node (trans_type, num_transfields, transfields, GOPHERFILETYPES_UNAVAILABLE, flgsfmt, cur_sock); break; case GOPHTYP_TELNET: case GOPHTYP_TN3270: send_reserved_node (trans_type, num_transfields, transfields, CLIENT_NEEDS_TELNET, flgsfmt, cur_sock); break; case GOPHTYP_MENU: case GOPHTYP_SEARCH: send_msg (msglist[ NOT_DOCUMENT ], cur_sock); break; default: send_reserved_node (trans_type, num_transfields, transfields, UNKNOWN_DOC_TYPE, flgsfmt, cur_sock); break; } } } void send_empty_document(int sock) { nio_send_file (sock, "/dev/null", 0, 32000, 0); } void send_empty_menu (int sock) { send_msg ("0:", sock); } static int compute_nodeflags (struct s1 *nd, int format_type) { if (format_type == NO_FLAGS_FORMAT) return 0; else if (nd == NULL) return N_FAKE; /* Not a real node */ else { switch (nd->gophertype) { /* Most of the N_ flags don't appear here even though MIT has put them into the node.h file in the source distribution. None of their code handles the N_ flags yet (except for N_IMAGE). N_TELNETSESSION is my own creation. --lam */ case GOPHTYP_GIF: case GOPHTYP_IMAGE: return N_IMAGE; break; case GOPHTYP_TELNET: case GOPHTYP_TN3270: return N_TELNETSESSION; break; default: return 0; /* N_TEXT and N_MENU flags are ignored by TI clients at the moment (Feb 1993), so I'm leaving them out. */ break; } } } static char *reserved_nodeinfostr(long nid, int flgfmt) /* ASSUMPTION: the values in reserved nodes don't contain any extraneous DLM characters */ { int flags; extern struct resvnode *get_resvnode(); struct resvnode *rnode; rnode = get_resvnode (nid); if (rnode == NULL) { rnode = get_resvnode(DUMMY_NODE); } /* MIT's code doesn't seem to handle these N_ flags...so just use 0 as the flags and ignore flgsfmt if (cur_format_type == NO_FLAGS_FORMAT) flags = 0; else if (*(rnode->file)) flags = N_TEXT; else flags = N_MENU; */ flags = 0; /*nodeid:flags:date:keyword list:title:source:locker:file:parents:children*/ if (*(rnode->file)) sprintf (nodeinfobuf, "%d:%d:%d::%s.:%s:none:%s::", nid, flags, todaysdate, rnode->title, GW_SOURCENAME, rnode->file); else sprintf (nodeinfobuf, "%d:%d:%d::%s/:%s::::", nid, flags, todaysdate, rnode->title, GW_SOURCENAME); return (nodeinfobuf); } char *nodeinfostr (long nodeid, struct s1 *nd, int flgsfmt) { long nodeflags; char gophertype; char gophertitle[BUFSIZ]; char gopherpath[BUFSIZ]; char gopherserver[BUFSIZ]; char gopherport[BUFSIZ]; if (is_reserved_node (nodeid)) return (reserved_nodeinfostr(nodeid, flgsfmt)); if (nd == NULL) { /* wasn't already resolved, so find it */ nd = getnode_bynodeid(nodeid); } nodeflags = compute_nodeflags (nd, flgsfmt); if (nd == (struct s1 *) NULL) { /* still not found; it's a bogus nodeid */ return (reserved_nodeinfostr(DUMMY_NODE, flgsfmt)); } else { /* there is no way to quote DLM character in the TechInfo protocol at this time. So convert it to something else and let's hope it all works out */ substi_char (nd->gophertitle, gophertitle, DLM, ';'); substi_char (nd->gopherpath, gopherpath, DLM, ';'); substi_char (nd->gopherserver, gopherserver, DLM, ';'); substi_char (nd->gopherport, gopherport, DLM, ';'); if (nd->gophertype == DLM) gophertype = ';'; else gophertype = nd->gophertype; switch (nd->gophertype) { case GOPHTYP_MENU: case GOPHTYP_SEARCH: sprintf (nodeinfobuf, "%d:%d:%d:%c %s %s %s:%s%s:%s::::", nd->nodeid, nodeflags, todaysdate, gophertype, gopherpath, gopherserver, gopherport, /* YUCK! use keywords fields for gopher info */ gophertitle, find_abbrev(nd->gophertype), GW_SOURCENAME); /* no locker, no filename, no parents, no children */ break; case GOPHTYP_CSO: case GOPHTYP_TEXT: case GOPHTYP_GIF: case GOPHTYP_IMAGE: case GOPHTYP_MACHQX: case GOPHTYP_DOSBIN: case GOPHTYP_BINARY: case GOPHTYP_UUENC: case GOPHTYP_TELNET: default: sprintf (nodeinfobuf, "%d:%d:%d:%c %s:%s%s:%s:%s:%s %s::", nd->nodeid, nodeflags, todaysdate, gophertype, gopherport, /* YUCK!!! use keywords field for some gopher info */ gophertitle, find_abbrev(nd->gophertype), GW_SOURCENAME, "none", /* put rest of gopher info in filename */ gopherpath, gopherserver); /* no parents, no children */ break; } } /* not a bogus nodeid */ return (nodeinfobuf); /* static buf */ } static void send_node(int flgsfmt, int cur_sock, int num_transfields, char **transfields) /* send all information about a particular node */ { long nodeid; /* send node information: Unfortunately, we don't have a record of the parents & children because we don't really have a web */ if (num_transfields < 2) nodeid = MAINMENUNODE; else nodeid = atoi (transfields[1]); send_msg (nodeinfostr(nodeid, (struct s1 *) NULL, flgsfmt), cur_sock); } /* write the transaction to the log file */ void log_trans(char *line) { char logline[BUFSIZ]; time_t secs; int dfd; time(&secs); sprintf(logline, "%s:%s", line, ctime(&secs)); if ((dfd = open(TRANS_LOG, O_APPEND | O_CREAT | O_WRONLY, 0644)) == 0) printf("error logging transaction\n"); write(dfd, logline, strlen(logline)); close(dfd); if (debug) fprintf(stderr, "Transaction: %s", logline); return; } /* search--search for target. If no node is given, use global Gopher search node(veronica). Called for keyword search, wais-index text search, title search. */ static void find(CONN *conn, char trans_type, int num_transfields, char **transfields) { long nodeid; struct s1 *nd; char tmpstr[BUFSIZ]; int cur_sock = conn->c_socket; int flgsfmt = conn->c_output_fmt; /* blank target string? send empty menu */ if (num_transfields < 2 || strlen(transfields[1]) < 1) { send_empty_menu(cur_sock); return; } if (num_transfields > 2) { nodeid = atoi(transfields[2]); if (nodeid == MAINMENUNODE) nodeid = GLOBGOPH_SRCH_NODE; } else nodeid = GLOBGOPH_SRCH_NODE; if (is_reserved_node(nodeid)) { send_nlistmsg_find (nodeid, FILE_NOT_SEARCHABLE, flgsfmt, cur_sock); return; } nd = getnode_bynodeid (nodeid); if (nd == NULL) send_empty_menu(cur_sock); else { switch (nd->gophertype) { case GOPHTYP_SEARCH: send_gopher_info(nd, num_transfields, transfields, conn); break; default: send_nlistmsg_find (nodeid, FILE_NOT_SEARCHABLE, flgsfmt, cur_sock); break; } } } /* Change output format */ static void change_format(int *fmt, int cur_sock, int num_transfields, char **transfields) { int format; if (num_transfields < 2) { send_msg(msglist[ UNKNOWN_FORMAT ], cur_sock); return; } format = atoi(transfields[1]); if ((format < 1) || (format > NUM_FORMATS)) { send_msg(msglist[ UNKNOWN_FORMAT ], cur_sock); return; } *fmt = format; send_msg(msglist[ OK ], cur_sock); } static void traverse(CONN *conn, char trans_type, int num_transfields, char **transfields) { int direction; long nodeid; int levels; struct s1 *nd; int cur_sock = conn->c_socket; int flgsfmt = conn->c_output_fmt; if (num_transfields < 4) { /* if not enough fields, send empty list */ send_empty_menu (cur_sock); return; } direction = atoi(transfields[1]); nodeid = atoi(transfields[2]); levels = atoi(transfields[3]); if (direction == TRAV_UP) send_nlistmsg_trav (nodeid, NO_SHOW_PATH, 0, flgsfmt, cur_sock); else if (direction == TRAV_OUT) send_nlistmsg_trav (nodeid, NO_SHOW_PATH, 0, flgsfmt, cur_sock); else if (levels > 1) send_nlistmsg_trav (nodeid, NO_OUTLINE, 0, flgsfmt, cur_sock); else { /* traverse down ONE level */ if (!send_reserved_node(trans_type, num_transfields, transfields, nodeid, flgsfmt, cur_sock)) { nd = getnode_bynodeid (nodeid); if (nd == NULL) /* client gave unknown nodeid, it loses */ send_empty_menu(cur_sock); else { switch (nd->gophertype) { case GOPHTYP_MENU: send_gopher_info (nd, num_transfields, transfields, conn); break; case GOPHTYP_SEARCH: send_nlistmsg_trav (nodeid,TRY_SEARCHCMD, 1, flgsfmt, cur_sock); break; default: send_nlistmsg_trav (nodeid, FILE_NOT_AMENU, 1, flgsfmt, cur_sock); break; } } /* not unknown */ } /* handle reserved */ } /* traverse down one level */ } /* find the version number of the client associated with a certain machine */ static void find_ver(CONN *conn, int num_transfields, char **transfields) { FILE *fopen(), *verfile; char verline[ADMIN_LN_SZ]; int len; int cur_sock = conn->c_socket; verfile = fopen(VER_FILE, "r"); if (!verfile) { send_msg(msglist[ VERSION_NOT_FOUND ], cur_sock); return; } if (num_transfields < 2 || strlen(transfields[1]) == 0) { conn->c_type = ""; send_msg(msglist[ VERSION_NOT_FOUND ], cur_sock); return; } len = strlen(transfields[1]); /* set c_type in conntab */ conn->c_type = domalloc((unsigned int) len+1); strcpy(conn->c_type, transfields[1]); while (fgets(verline, ADMIN_LN_SZ, verfile) != '\0') { verline[strlen(verline) - 1] = '\0'; if (strncasecmp(transfields[1], verline, len) == 0) { fclose(verfile); send_msg(verline, cur_sock); return; } } fclose(verfile); send_msg(msglist[ VERSION_NOT_FOUND ], cur_sock); return; /* close file */ } static void send_src_info(int cur_sock) { char sourceline[BUFSIZ]; /* source:longsrc:name:phone:email */ sprintf (sourceline, "%s:%s:::%s", GW_SOURCENAME, GW_LONGSRCNAME, GW_EMAILADDR); send_msg (sourceline, cur_sock); } /* BUFFERSIZE must be big enough to handle all possible connections + a header */ #define BUFFERSIZE (200 + FD_SETSIZE*110) /* Send a description of the current connections */ void send_connections(int cur_sock) { int i; char *buf, *prov, *cp; time_t secs; extern CONN conntab[]; buf = (char *) domalloc((unsigned) BUFFERSIZE); bzero(buf, BUFFERSIZE); sprintf(buf,"\ %-5s %-5s %-7s %-8s %-25s %-15s %s\n", "Conn#", "Sock", "Sesslen", "Inactive", "Host", "ClientType", "Prov"); time(&secs); for (i = 0; i < FD_SETSIZE; i++) { if (conntab[i].c_socket != -1) { cp = index(buf,'\0'); if (conntab[i].c_flags & C_PROVIDER) prov = "PROV"; else prov = ""; sprintf(cp,"\ %-5d %-5d %-7.1f %-8.1f %-25s %-15s %s\n" ,i,conntab[i].c_socket, (float) ((secs - conntab[i].c_made) /60.0), (float) ((secs - conntab[i].c_last) /60.0), conntab[i].c_hostname, conntab[i].c_type, prov); } } /* * Put End-of-Message on buffer & send it out. sendbuf() frees the * buffer for us. */ strcat(buf, EOM); sendbuf(buf, strlen(buf), cur_sock); } #undef BUFFERSIZE /* test to see if a uid is an admin */ static int test_admin(char *passwd) { FILE *fopen(), *adminfile; char adminline[ADMIN_LN_SZ]; adminfile = fopen(ADMIN_FILE, "r"); if (!adminfile) { perror(ADMIN_FILE); return 0; } while (fgets(adminline, ADMIN_LN_SZ, adminfile) != '\0') { adminline[strlen(adminline) - 1] = '\0'; if (strcasecmp(passwd, adminline) == 0) { fclose(adminfile); return 1; } } fclose(adminfile); return 0; /* close file */ } /* If the client's uid is ok, establish him as an administrator. */ static void admin(int num_transfields, char **transfields, int cur_sock) { if (num_transfields < 2 || *(transfields[1]) == 0) send_msg(msglist[ NOT_AUTH ], cur_sock); else if (test_admin(transfields[1])) { strcpy(curr_uid, transfields[1]); send_msg(msglist[ OK ], cur_sock); } else send_msg(msglist[ NOT_AUTH ], cur_sock); } /* nd is the parent or the search node. if nd's gophertype is menu, write numnodes+1 into the buf because the parent node itself should be in there. nlist is the list of children (or a results list). */ static void send_nlist (struct s1 *nd, long *nlist, int numnodes, int flgsfmt, int cur_sock) { char *buf; char *levelstr; int n; struct s1 *node; if (nd == NULL) { fprintf (stderr, "send_nlist() called with null nd parameter\n"); return; } if (nd->lastused != todaysdate) { nd->lastused = todaysdate; /* node was selected */ tables_changed++; lastused_changed++; } /* assumption: average length of nodeinfo is less than 500 chars */ buf = (char *) malloc (500 * (numnodes+1)); if (nd->gophertype == GOPHTYP_MENU) { sprintf (buf, "%d:\n0:%s\n", numnodes+1, nodeinfostr(nd->nodeid, nd, flgsfmt)); levelstr = "1:"; } else { sprintf (buf, "%d:\n", numnodes); levelstr = "0:"; } for (n = 0; n < numnodes; n++) { strcat (buf, levelstr); node = getnode_bynodeid (nlist[n]); /* item appeared on a menu, so update its "usedness" */ if (node != NULL) { if (node->lastused != todaysdate) { node->lastused = todaysdate; /* node is used as part of a menu */ tables_changed++; lastused_changed++; } } strcat (buf, nodeinfostr(nlist[n], node, flgsfmt)); strcat (buf, "\n"); } strcat (buf, ".\r\n"); sendbuf(buf, strlen(buf), cur_sock); /* assuming sendbuf will free the allocated buf */ } send_cached_nlist (struct s1 *nd, int cur_sock, char *cachefilename, int flgsfmt) { int numnodes; FILE *cafile; char line[100]; long *nlist; int n; cafile = fopen(cachefilename, "r"); if (cafile == NULL) { /* not sure what to do at this point */ fprintf (stderr, "Unable to read cache file %s!\n", cachefilename); return; } for (numnodes=0; fgets(line, sizeof(line)-1, cafile) != NULL; ) numnodes++; nlist = (long *) domalloc ((unsigned int) numnodes * sizeof (long)); n = 0; for (rewind(cafile); fgets(line, sizeof(line)-1, cafile) != NULL; ) { nlist[n] = atol(line); n++; } send_nlist (nd, nlist, numnodes, flgsfmt, cur_sock); free(nlist); fclose (cafile); } /* handles 5 types: MENU, TEXT, GIF, IMAGE, SEARCH */ /* side effect: forks child, sets conn->c_hptr */ static void send_gopher_info(struct s1 *nd, int num_transfields, char **transfields, CONN *curr_conn) { struct stat stbuf; char cachefilename[MAXPATHLEN]; long startpoint = 0, requested = 32000; WAITFORHELPER *hptr; pid_t pid; char *path; int cur_sock = curr_conn->c_socket; int flgsfmt = curr_conn->c_output_fmt; if (nd == NULL) return; /* should probably send a message or something */ if (nd->lastused != todaysdate) { nd->lastused = todaysdate; /* node was selected */ tables_changed++; lastused_changed++; } if (nd->gophertype == GOPHTYP_TEXT || nd->gophertype == GOPHTYP_GIF || nd->gophertype == GOPHTYP_IMAGE) { /* not for menus & searches */ /* get startpt & length from transaction */ if (num_transfields > 2) startpoint = atoi(transfields[2]); if (num_transfields > 3) requested = atoi(transfields[3]); } if (nd->gophertype == GOPHTYP_SEARCH) { /* assuming calling code already checked for blank search string */ path = domalloc(strlen(transfields[1]) + 1 + strlen(nd->gopherpath) + 1); sprintf (path, "%s\t%s", nd->gopherpath, transfields[1]); sprintf (cachefilename, "%s%d.%c.%s", CACHEDIR, nd->nodeid, nd->gophertype, transfields[1]); } else { /* if type is something other than SEARCH */ path = domalloc(strlen(nd->gopherpath) + 1); sprintf (path, "%s", nd->gopherpath); sprintf (cachefilename, "%s%d.%c", CACHEDIR, nd->nodeid, nd->gophertype); } /* Can we use cached file? */ if (stat(cachefilename, &stbuf) == 0 && ((time(0) - stbuf.st_mtime) < CACHETIMEOUT)) { /* use cached file */ if (nd->gophertype == GOPHTYP_MENU || nd->gophertype == GOPHTYP_SEARCH) send_cached_nlist (nd, cur_sock, cachefilename, flgsfmt); else /* TEXT, GIF, or IMAGE */ nio_send_file (cur_sock, cachefilename, startpoint, requested, 0); return; /* DONE ! */ } /* use cache file */ /* if we got here, we couldn't use the cache file. Need to call a helper. Create c_hptr for the connection */ /* Create and initialize c_hptr for curr_conn */ hptr = (WAITFORHELPER *) domalloc ((unsigned int)sizeof (WAITFORHELPER)); curr_conn->c_hptr = hptr; hptr->file = tempnam(CACHEDIR, "GOPHER"); hptr->node = nd; /* ASSUMING that nothing will remove nd from the tables between now and the time that the help returns ! */ hptr->startpoint = startpoint; hptr->requested = requested; hptr->cachefile = (char *) domalloc (strlen(cachefilename) + 1); strcpy (hptr->cachefile, cachefilename); pid = fork (); if (pid == 0) { /* child */ execlp (HELPER, HELPER, nd->gopherserver, nd->gopherport, path, hptr->file, (char *)0); fprintf (stderr, "EXECLP FAILED!!!\n"); perror (HELPER); exit (-1); /* child had a problem */ } else if (pid < 0) { /* parent, but no child */ /* what to send to TI client ???*/ fprintf (stderr, "Unable to fork child\n"); perror ("fork"); } else { /* parent */ (curr_conn->c_hptr)->pid = pid; signal (SIGCHLD, catch_child); fprintf (stderr, "\nT=%d. Set up helper pid %d for sock %d\n", time(0), (curr_conn->c_hptr)->pid, curr_conn->c_socket); do_free(path); } } static char *find_abbrev(char ch) { int x; for (x = 0; x < NUM_GOPHER_ABBREV && ch != gopher_abbrev[x].gophtyp; x++); if (ch == gopher_abbrev[x].gophtyp) return (gopher_abbrev[x].gophabbrev); else return (gopher_abbrev_unk); } /* ASSUMPTION: T_SENDFILE, T_TRAVERSE, and T_HELP call send_reserved_node */ static int send_reserved_node (char trans_type, int num_transfields, char **transfields, long nodeid, int flgsfmt, int cur_sock) { struct resvnode *rnode; int child; int starting = 0; int requested = 32000; char *nlistbuf; if (!is_reserved_node(nodeid)) return 0; if (trans_type == T_SENDFILE) { /* t:nodeid:st:en */ if (num_transfields > 2) starting = atoi (transfields[2]); if (num_transfields > 3) requested = atoi (transfields[3]); } rnode = get_resvnode(nodeid); if (rnode == NULL) { if (trans_type == T_SENDFILE) send_empty_document(cur_sock); else if (trans_type == T_TRAVERSE) send_empty_menu(cur_sock); return 1; } else { /* found reserved node */ if (trans_type == T_SENDFILE) { if (*(rnode->file)) { nio_send_file(cur_sock, rnode->file, starting, requested, 0); } else send_msg (msglist[ NOT_DOCUMENT ], cur_sock); } /* add code around here to dynamically generate an nlist of all the gopher nodes which are GOPHTYP_SEARCH */ else if (trans_type == T_TRAVERSE || trans_type == T_HELP) { nlistbuf = (char *) domalloc ((rnode->numchildren + 1)* BUFSIZ); /*ASSUMPTION: the average length of the nodeinfo for the reserved node and its children is less than BUFSIZ*/ /*Assumption: we aren't automatically adding HelpMenu and Veronica to reserved node menus */ sprintf (nlistbuf, "%d:\n", rnode->numchildren + 1); strcat (nlistbuf, "0:"); strcat (nlistbuf, reserved_nodeinfostr(rnode->nodeid, flgsfmt)); strcat (nlistbuf, "\n"); for (child=0; child < rnode->numchildren; child++) { strcat (nlistbuf, "1:"); strcat (nlistbuf, nodeinfostr(rnode->children[child], (struct s1 *) NULL, flgsfmt)); strcat (nlistbuf, "\n"); } strcat (nlistbuf, ".\r\n"); sendbuf (nlistbuf, strlen(nlistbuf), cur_sock); /* Assumption: sendbuf & its cronies will free the allocated space for nlistbuf */ } } return 1; }