/* * Copyright (c) 1992, 1993 by the University of Southern California * * For copying and distribution information, please see the file * * * Written by bcn 1989 modified 1989-1992 * Modified by swa 3Q/92 V5 support, modularize, quoting, multiple names * Modified by bcn 1/20/93 support multiple database prefixes */ #include #define SERVER_SEND_NEW_MCOMP #define REALLY_NEW_FIELD #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "dirsrv.h" TOKEN p__tokenize_midstyle_mcomp(char *nextname); /* Uncomment this for debugging and demonstration purposes. */ /* Leave it enabled for now, since it won't hurt anything. */ #define SERVER_PREDEFINED_IDENTITY_FILTER #ifdef SERVER_PREDEFINED_IDENTITY_FILTER static identity_filter(P_OBJECT dir, TOKEN args); #endif static int list_name(RREQ req, char *command, char arg_client_dir[], int dir_magic_no, TOKEN components, int verify_dir, int localexp, int non_object_attrib_fl, int alllinks, FILTER filters); #ifdef ARCHIE extern int archie_supported_version; #endif int list(RREQ req, char **commandp, char **next_wordp, INPUT in, char client_dir[], int dir_magic_no) { /* Flags */ int non_object_attrib_fl; /* Send back atribues in list */ int localexp; /* OK to exp ul for remcomp */ int verify_dir; /* Only verifying the directory */ int filterfl = 0; /* specified the filter flag. */ FILTER filters = NULL; /* filters to apply */ optdecl; int tmp; AUTOSTAT_CHARPP(t_optionsp); char *nextname; /* Next name to be resolved. */ int retval; /* Value returned by list_name. */ long dummy_magic_no; /* XXX currently ignored; shouldn't be. */ CHECK_MEM(); nextname = NULL; /* Assume we can freely overwrite the buffer. */ tmp = qsscanf(*next_wordp, "%'&s COMPONENTS %r", &*t_optionsp, &nextname); if (tmp < 0) interr_buffer_full(); if (in_select(in, &dummy_magic_no)) return error_reply(req, "LIST: %'s", p_err_string); /* At this point, *t_optionsp is a '+'-separated list of options and nextname should point to a space-separated list of names. */ CHECK_MEM(); /* Parse the options. */ optstart(*t_optionsp); verify_dir = opttest("VERIFY") ? 1 : 0; /* If EXPAND specified, remeber that fact */ CHECK_MEM(); if (opttest("EXPAND") || opttest("LEXPAND")) localexp = 2; else localexp = 0; if (opttest("ATTRIBUTES")) non_object_attrib_fl = DSRD_ATTRIBUTES; else non_object_attrib_fl = 0; CHECK_MEM(); if (opttest("FILTER")) { /* leave commandp set the way it was, since we need it to report errors. */ char *line, *next_word; filterfl = 1; if (localexp) return error_reply(req, "FILTER flag cannot be specified with \ LEXPAND or EXPAND flags: %'s\n", *commandp); CHECK_MEM(); while (in_nextline(in) && strnequal(in_nextline(in), "FILTER", 6)) { FILTER cur_fil; if (in_line(in, &line, &next_word)) { fllfree(filters); RETURNPFAILURE; /* error reporting done by in_line() */ } CHECK_MEM(); if (in_filter(in, line, next_word, 0, &cur_fil)) { fllfree(filters); return error_reply(req, "Could not parse a filter: %'s", p_err_string, 0); } CHECK_MEM(); if (cur_fil->execution_location != FIL_SERVER || cur_fil->link || (cur_fil->type != FIL_DIRECTORY && cur_fil->type != FIL_HIERARCHY) || cur_fil->pre_or_post != FIL_PRE) { fllfree(filters); flfree(cur_fil); return error_reply(req, "All filters to be applied at the\ server must have an execution location of SERVER and be PRE-expansion and be\ DIRECTORY or HIERARCHY filters and be PREDEFINED. This one\ does not meet those criteria: %'s", line); } APPEND_ITEM(cur_fil, filters); /* Checking for whether this filter is defined on this server occurs in list_name(), when we dispatch to the particular filter. */ } CHECK_MEM(); if (!filters) return error_reply(req, "LIST had FILTER option specified, but no \ filters were given."); } else { filterfl = 0; } CHECK_MEM(); plog(L_DIR_REQUEST, req, "L%s %s %s", (verify_dir ? "V" : " "), client_dir, (nextname ? nextname : "*")); /* Specifying no component is exactly equivalent to specifying '*' */ { int listall_flag = 0; TOKEN comps; #ifdef DONT_ACCEPT_OLD_MCOMP /* No clients left that transmit old styles, so guaranteed not to need to adapt to both. */ if (nextname) comps = qtokenize(nextname); #else /* There are some old-style clients. (accept both styles; transmit old or new). */ if (nextname) comps = p__tokenize_midstyle_mcomp(nextname); #endif else { listall_flag = 1; /* COMPS shouldn't ever be looked at when listall flag is passed, but it is. */ comps = tkalloc("*"); } VLDEBUGBEGIN; retval = list_name(req, *commandp, client_dir, dir_magic_no, comps, verify_dir, localexp, non_object_attrib_fl, listall_flag, filters); VLDEBUGEND; tklfree(comps); } fllfree(filters); return retval; } static int list_process_filters_err_reply(RREQ req, P_OBJECT ob, FILTER filters); static int list_expand_local_ulinks(RREQ req, P_OBJECT ob); static int list_name(RREQ req, char *command, char arg_client_dir[], int dir_magic_no, TOKEN remcomp, int verify_dir, int localexp, int non_object_attrib_fl, int alllinks, FILTER filters) { char *thiscomp; /* component being worked on */ AUTOSTAT_CHARPP(client_dirp); /* Current directory. We make this a temporary, because it is modified by list_name. */ int item_count = 0; /* Count of returned items */ FILTER cfil; /* For iterating through filters. */ VLINK clink; /* For stepping through links */ VLINK crep; /* For stepping through replicas */ long dir_version = 0; /* Directory version nbr-currently ignored */ AUTOSTAT_CHARPP(dir_typep); /* Type of dir name (Currently, the only supported value is ASCII) */ P_OBJECT ob = oballoc(); /* allocate an object to read into. */ P_OBJECT lastob = NULL; /* last object used. */ int retval; /* Return value from subfunctions */ int laclchkl; /* Cached ACL check */ int daclchkl; /* Cached ACL check */ int laclchkr; /* Cached ACL check */ int daclchkr; /* Cached ACL check */ PATTRIB ca; /* Current Attribute */ OUTPUT_ST out_st; OUTPUT out = &out_st; int orig_localexp = localexp; /* don't change this cached copy. */ struct dsrobject_list_options listopts; reqtoout(req, out); *client_dirp = stcopyr(arg_client_dir, *client_dirp); *dir_typep = stcopyr("ASCII", *dir_typep); /* XXX must generalize this. */ list_start: /* list_start always is entered with a clean directory. */ thiscomp = remcomp->token; remcomp = remcomp->next; /* THISCOMP refers to a single name-component. REMCOMP may refer to additional components of a multiple-component name that should be resolved. */ /* If only expanding last component, clear the flag */ if (localexp == 1) localexp = 0; /* If remaining components, expand union links for this component only */ if (remcomp && !localexp) localexp = 1; p_clear_errors(); dsrobject_list_options_init(&listopts); listopts.thiscompp = &thiscomp; listopts.remcompp = &remcomp; listopts.requested_attrs = "#INTERESTING"; listopts.req_link_ats.interesting = 1; /* just request INTERESTING. */ listopts.filters = filters; /* dsrobject() might expand multiple components. */ VLDEBUGBEGIN; retval = dsrobject(req, *dir_typep, *client_dirp, dir_version, dir_magic_no, verify_dir? DRO_VERIFY_DIR : 0, &listopts, ob); VLDEBUGOB(ob); VLDEBUGEND; #ifdef DIRECTORYCACHING if (retval) dsrobject_fail++; #endif if (retval == DSRFINFO_FORWARDED) { /* We are NOT in the process of expanding a union link, since if we were, the above test would have caught it. */ if (lastob) { /* if exploring a subcomponent */ /* XXX We should update the local link to point to the new location too */ if (verify_dir) { reply(req, "NONE-FOUND\n"); if (lastob) obfree(lastob); if (ob) obfree(ob); return PSUCCESS; } retval = oblinkforwarded(req, out, *client_dirp, dir_magic_no, ob, thiscomp); if (retval) { if (lastob) obfree(lastob); obfree(ob); return retval; } if (remcomp) { #ifdef SERVER_SEND_NEW_MCOMP replyf(req, "UNRESOLVED"); out_sequence(out, remcomp); #else TOKEN acp; replyf(req, "UNRESOLVED %'s", remcomp->token); for (acp = remcomp->next; acp; acp = acp->next) replyf(req, "/%'s", acp->token); reply(req, "\n"); #endif } } else { retval = obforwarded(req, *client_dirp, dir_magic_no, ob); if (lastob) obfree(lastob); obfree(ob); return retval; } if (lastob) obfree(lastob); obfree(ob); return PSUCCESS; } /* Even if we were exploring a subcomponent, still cool to return these error messages. */ /* If not a directory, say so */ if (retval == DIRSRV_NOT_FOUND || retval == PSUCCESS && !(ob->flags & P_OBJECT_DIRECTORY)) { creply(req, "FAILURE NOT-A-DIRECTORY\n"); if (lastob) obfree(lastob); obfree(ob); RETURNPFAILURE; } /* If not authorized, say so */ if (retval == DIRSRV_NOT_AUTHORIZED) { if (*p_err_string) creplyf(req, "FAILURE NOT-AUTHORIZED %'s\n", p_err_string, 0); else creply(req, "FAILURE NOT-AUTHORIZED\n"); if (lastob) obfree(lastob); obfree(ob); RETURNPFAILURE; } /* If too many links in response, say so */ if (retval == DIRSRV_TOO_MANY) { if (*p_err_string) creplyf(req, "FAILURE TOO-MANY %'s\n", p_err_string, 0); else creply(req, "FAILURE TOO-MANY\n"); if (lastob) obfree(lastob); obfree(ob); RETURNPFAILURE; } /* If some other failure, say so */ if (retval) { if (*p_err_string) creplyf(req, "FAILURE SERVER-FAILED %'s %'s\n", p_err_text[retval], p_err_string); else creplyf(req, "FAILURE SERVER-FAILED %'s\n", p_err_text[retval]); if (lastob) obfree(lastob); obfree(ob); return (retval); } if (*p_warn_string) { replyf(req, "WARNING MESSAGE %'s\n", p_warn_string); *p_warn_string = '\0'; } list_expand_local_ulinks(req, ob); /* pass request just to give the security context. */ /* Cache the default answers for ACL checks */ daclchkl = srv_check_acl(ob->acl, ob->acl, req, "l", SCA_LINKDIR,*client_dirp,NULL); daclchkr = srv_check_acl(ob->acl, ob->acl, req, "r", SCA_LINKDIR,*client_dirp,NULL); if (retval = list_process_filters_err_reply(req, ob, filters)) { if (lastob) obfree(lastob); obfree(ob); return retval; } /* When we reach this point, there's no going back. Just spit out the links and clean up. */ /* Here we must send back the links, excluding those that do */ /* not match the component name. For each link, we must also */ /* send back any replicas or links with conflicting names */ for (clink = ob->links; clink; clink = clink->next) { crep = clink; while (crep) { /* Check individual ACL only if necessary */ if (crep->acl) { laclchkl = srv_check_acl(crep->acl, ob->acl, req, "l", SCA_LINK,NULL,NULL); laclchkr = srv_check_acl(crep->acl, ob->acl, req, "r", SCA_LINK,NULL,NULL); } else { laclchkl = daclchkl; laclchkr = daclchkr; } if (!verify_dir && wcmatch(crep->name, thiscomp) && crep->linktype != '-' && (laclchkl || (laclchkr && (strcmp(crep->name, thiscomp) == 0)))) { if (laclchkr) { if (remcomp && strequal(crep->host, hostwport) && !(crep->filters) && !item_count) { /* If components remain on this host */ /* And links haven't already been returned */ /* don't reply, but continue searching */ /* Here's where we start to resolve additional components */ /* Set the directory for the next component */ /* At this point, clink contains the link for the next directory, and the directory itself is still filled in. We should save away the old directory information in case we need to backtrack */ dir_version = clink->version; dir_magic_no = clink->f_magic_no; *dir_typep = stcopyr(clink->hsonametype, *dir_typep); *client_dirp = stcopyr(clink->hsoname, *client_dirp); if (lastob) obfree(lastob); lastob = ob; #if 0 /* Save last remcomp in case this lookup fails. This is actually not currently being used. */ last_remcomp = remcomp; #endif ob = oballoc(); goto list_start; } qoprintf(out, "LINK "); out_link(out, crep, 0, (TOKEN) NULL); /* If link attributes are to be returned, do so */ /* For now, only link attributes returned */ /* XXX this is not all of what we want. We need to be able to explicitly read the values of attributes such as DEST-EXP and ID. But this is all we need to get Gopher running. */ if (non_object_attrib_fl) { for (ca = crep->lattrib; ca; ca = ca->next) { /* For now return all attributes. To be done: */ /* return only those requested */ if (1) { out_atr(out, ca, 0); } } } else { /* No attribute flag specified. Return ACCESS-METHOD for EXTERNAL links anyway. */ if (strequal(crep->target, "EXTERNAL")) for (ca = crep->lattrib; ca; ca = ca->next) if (strequal(ca->aname, "ACCESS-METHOD")) out_atr(out, ca, 0); } /* If there are any filters, send them back too */ if (laclchkr) { for (cfil = crep->filters; cfil; cfil = cfil->next) { #ifdef REALLY_NEW_FIELD qoprintf(out, "ATTRIBUTE LINK FIELD FILTER FILTER "); #else qoprintf(out, "ATTRIBUTE LINK FIELD FILTER "); #endif out_filter(out, cfil, 0); } } } else { /* If list access but no read access, just acknowledge that the link exists. */ replyf(req, "LINK L NULL %s NULL NULL NULL NULL 0\n", crep->name); } item_count++; } /* Replicas are linked through next, not replicas */ /* But the primary link is linked to the replica */ /* list through replicas */ if (crep == clink) crep = crep->replicas; else crep = crep->next; } } /* here we must send back the unexpanded union links */ for(clink = ob->ulinks; clink && !verify_dir; clink = clink->next) { /* Neither return nor expand links that have been successfully expanded or that are dummies. */ if (clink->expanded == ULINK_PLACEHOLDER || clink->expanded == TRUE) continue; /* never return or process this link. */ /* If the user explicitly requested that union links be expanded (original localexp == 2) or if we ended up expanding these links as part of the intermediate stages of processing a multi- component name (localexp == 1) then the user doesn't care about seeing union links for the current directory, nor does he or she care about getting two union links that agree in hsoname and host but disagree in their NAME field. However, if the user wants to see all the contents of the current directory, then they very much do care to see conflicting union links. */ if (clink->expanded == ULINK_DONT_EXPAND && orig_localexp) continue; /* Return any readable union links. */ if (srv_check_acl(clink->acl, ob->acl, req, "r", SCA_LINK,NULL,NULL)) { /* Once one union link is going to be returned, don't do any more expanding. */ localexp = 0; qoprintf(out, "LINK "); out_link(out, clink, 0, (TOKEN) NULL); /* If link attributes are to be returned, do so */ /* For now, only link attributes returned */ /* XXX this is not all of what we want. We need to be able to explicitly read the values of attributes such as DEST-EXP and ID. But this is all we need to get Gopher running. */ if (non_object_attrib_fl) { for (ca = clink->lattrib; ca; ca = ca->next) { /* For now return all attributes. To be done: */ /* return only those requested */ if (1) { out_atr(out, ca, 0); } } } item_count++; /* if there are any filters */ for (cfil = clink->filters; cfil; cfil = cfil->next) { #ifdef REALLY_NEW_FIELD qoprintf(out, "ATTRIBUTE LINK FIELD FILTER FILTER "); #else qoprintf(out, "ATTRIBUTE LINK FIELD FILTER "); #endif out_filter(out, cfil, 1); } } } /* If none match, say so */ if (!item_count) reply(req, "NONE-FOUND\n"); /* Otherwise, if components remain say so */ else if (remcomp) { #ifdef SERVER_SEND_NEW_MCOMP replyf(req, "UNRESOLVED"); out_sequence(out, remcomp); #else TOKEN acp; replyf(req, "UNRESOLVED %'s", remcomp->token); for (acp = remcomp->next; acp; acp = acp->next) replyf(req, "/%'s", acp->token); reply(req, "\n"); #endif } /* Free the directory links */ if (lastob) obfree(lastob); if (ob) obfree(ob); return PSUCCESS; } static int list_process_filters_err_reply(RREQ req, P_OBJECT ob, FILTER filters) { #ifdef ARCHIE int already_applied_ar_domain = 0; int already_applied_ar_pathcomp = 0; #endif /* PUT IN CODE HERE TO PROCESS PREDEFINED SERVER FILTERS */ for (; filters; filters = filters->next) { if (filters->pre_or_post == FIL_ALREADY) { #ifdef ARCHIE if (strequal(filters->name, "AR_DOMAIN")) ++already_applied_ar_domain; if (strequal(filters->name, "AR_PATHCOMP")) ++already_applied_ar_pathcomp; #endif continue; } if (filters->pre_or_post == FIL_FAILED) { if (filters->errmesg) { creplyf(req, "FAILURE FILTER-APPLICATION Applying the %'s \ PREDEFINED SERVER filter yielded an error: %'s\n", filters->name, filters->errmesg); } else { creplyf(req, "FAILURE FILTER-APPLICATION Applying the %'s \ PREDEFINED SERVER filter yielded an error.\n", filters->name); } } #ifdef SERVER_PREDEFINED_IDENTITY_FILTER /* this only exists for debugging and demonstration purposes. */ if (strequal(filters->name, "IDENTITY")) { int retval = identity_filter(ob, filters->args); if (retval) { creplyf(req, "FAILURE FILTER-APPLICATION Applying the %'s \ PREDEFINED SERVER filter yielded an error: %'s\n", filters->name, p_err_string); return retval; } } else #endif #ifdef ARCHIE /* If specified should have been applied as part of query. If it were applied as part of the archie database query, it would have been taken care of by the test above against FIL_ALREADY. */ if (strequal(filters->name, "AR_DOMAIN") || strequal(filters->name, "AR_PATHCOMP")) { if (!already_applied_ar_domain || !already_applied_ar_pathcomp) creplyf(req, "FAILURE NOT-FOUND FILTER version %d archie servers do not support the PREDEFINED SERVER filter named %'s.\n", archie_supported_version, filters->name); else creplyf(req, "FAILURE FILTER-APPLICATION The filter %'s \ cannot be applied more than once.\n", filters->name); RETURNPFAILURE; } else #endif /* ARCHIE */ { /* Filter not found. */ creplyf(req, "FAILURE NOT-FOUND FILTER This server does not \ support the PREDEFINED SERVER filter named %'s.\n", filters->name); RETURNPFAILURE; } } return PSUCCESS; /* all were succesfully processed */ } #ifdef SERVER_PREDEFINED_IDENTITY_FILTER /* Return PSUCCESS on success; failure indication otherwise & set p_err_string. This is a sample filter to demonstrate how one writes such things. It's also useful for debugging filters. */ static int identity_filter(P_OBJECT dirob, TOKEN args) { dirob->inc_native = VDIN_MUNGED; return PSUCCESS; } #endif /* SERVER_PREDEFINED_IDENTITY_FILTER */ /* Pass the request just to give the security context. */ /* Expand all local union links where we have 'r' permission on the link in the context of this directory. */ static int list_expand_local_ulinks(RREQ req, P_OBJECT ob) { ob->inc_native = VDIN_MUNGED; #if 0 /* Make sure that ob has enough information in it (or enough info. is passed down to expand_local_ulinks) so that we can be sure to keep track of what the starting directory for expansion was. */ if (localexp) { /* Add a placeholder union link to indicate that the current directory is already being expanded, so don't expand it again. This handles recursive union links (a fairly frequent case) more efficiently than not including this check. */ uexp = vlalloc(); assert(uexp); uexp->host = stcopyr(hostwport, uexp->host); uexp->hsoname = stcopyr(*client_dirp, uexp->hsoname); uexp->f_magic_no = dir_magic_no; uexp->expanded = ULINK_PLACEHOLDER; dir->ulinks = uexp; } #endif return PSUCCESS; }