/* qi.c - UIUC CCSO nameserver query interpreter */ /* Bruce Tanner - Cerritos College */ /* Version history: */ /* 1.0 1993/08/15 Initial version */ /* 1.1 1993/08/25 Add field instance attribute, conditionalize options */ /* 1.2 1993/09/08 Soundex is now an indexed explicit field; exact match mode */ /* 1.3 1993/09/15 Added ID to index key to remove duplicate index records */ /* 2.0 1993/09/16 Add login mode (login, answer, clear, logout) */ /* 2.1 1993/09/20 Interactive mode */ #include stdio #include string #include ctype #include ssdef #include descrip #include time #include stdarg #include rms #include dvidef #include dcdef #include "qi.h" Fields fields[MAX_FIELD]; /* field attributes, global */ int mode = DEFAULT_MODE; /* global mode flags */ extern int db_status; /* status of database */ char login_alias[KEYWORD_SIZE + 1]; /* current login */ char login_challenge[CHALLENGE_SIZE + 1]; int login_mode = MODE_ANONYMOUS; void db_open(); void db_close(); int read_fields(char *); int fields_cmd(char *, int); char *new_string(char *); char *getlogical(char *); void qilog(int, char *, ...); extern int query(char *, int); extern char *get_value(int, char*, char*, char *); int quit(char *, int); Arg *parse_cmd(char *, int); Arg *make_arg(char *, int, char *, int); void free_args(Arg *); void swrite(int, char *, ...); void writestring(int, char *); void qiabort(int, char *); int id_cmd(char *, int); int stat_cmd(char *, int); int set_cmd(char *, int); int site_cmd(char *, int); int login_cmd(char *, int); int answer_cmd(char *, int); int clear_cmd(char *, int); int logout_cmd(char *, int); char *challenge(int size); struct verb_struct { char *name; int mode; /* requires login, etc */ int (*proc)(char *, int); } verbs[] = {{"quit", MODE_ANONYMOUS | MODE_LOGIN, quit}, {"stop", MODE_ANONYMOUS | MODE_LOGIN, quit}, {"exit", MODE_ANONYMOUS | MODE_LOGIN, quit}, {"fields", MODE_ANONYMOUS | MODE_LOGIN, fields_cmd}, {"query", MODE_ANONYMOUS | MODE_LOGIN, query}, {"ph", MODE_ANONYMOUS | MODE_LOGIN, query}, {"status", MODE_ANONYMOUS | MODE_LOGIN, stat_cmd}, {"id", MODE_ANONYMOUS | MODE_LOGIN, id_cmd}, {"set", MODE_ANONYMOUS | MODE_LOGIN, set_cmd}, {"siteinfo", MODE_ANONYMOUS | MODE_LOGIN, site_cmd}, {"login", MODE_ANONYMOUS, login_cmd}, {"answer", MODE_PASSWORD, answer_cmd}, {"clear", MODE_PASSWORD, clear_cmd}, {"logout", MODE_LOGIN, logout_cmd} }; #define MAX_VERBS (sizeof(verbs) / sizeof(struct verb_struct)) /* This program is designed to run as an 'inetd' detached process */ main() { int status, sock, class; unsigned short chan; $DESCRIPTOR(sysin_dsc, "SYS$INPUT"); /* open a channel to an INET device */ status = sys$assign(&sysin_dsc, &chan, 0, 0, 0); if ((status & 1) != SS$_NORMAL) { qilog(-1, "Open fail status = %d", status); exit(status); } sock = (int) chan; status = lib$getdvi(&DVI$_DEVCLASS, &chan, 0, &class, 0, 0); /* load fields */ if (read_fields(getlogical(CONFIG_NAME)) == False) exit(4); /* initialize randomness */ srand((int) time((time_t *) NULL)); db_open(); while (process(sock, class == DC$_TERM)); closenet(sock); db_close(); } /* process a command stream */ int process(int sock, int interactive) { int status, ind, length; static int bad_cmd = 0; char *cp, inputline[MAX_INPUT], *vp, verb[MAX_INPUT]; strncpy(inputline, "", MAX_INPUT); if (interactive) { printf("qi> "); fgets(inputline, MAX_INPUT, stdin); length = strlen(inputline); } else length = readline(sock, inputline, MAX_INPUT); /** Get the line **/ ZapCRLF(inputline); qilog(sock, "Cmd: %s", inputline); if (length <= 0) { qilog(sock, "Remote end shutdown"); return False; } if (strlen(inputline) == 0) /* ignore blank lines */ return (++bad_cmd < MAX_BAD); /* return False if too many null cmds */ /* put first word of input in verb as lowercase */ strncpy(verb, "", sizeof(verb)); vp = verb; for (cp = inputline; *cp; cp++) if (*cp == ' ') break; /* break on space */ else *vp++ = _tolower(*cp); /* copy lower case char to verb */ for (ind = 0; ind < MAX_VERBS; ind++) if (strcmp(verb, verbs[ind].name) == 0) break; if (ind == MAX_VERBS) { qilog(sock, "Unknown command: /%s/%s/", verb, inputline); writestring(sock, "514:Unknown command.\r\n"); return (++bad_cmd < MAX_BAD); /* return False if too many bad cmds */ } if (((verbs[ind].mode & login_mode) == 0) && (login_mode == MODE_ANONYMOUS)) { qilog(sock, "Not logged in: %s", inputline); writestring(sock, "506:Request refused; must be logged in to execute.\r\n"); return (++bad_cmd < MAX_BAD); /* return False if too many bad cmds */ } if (((verbs[ind].mode & login_mode) == 0) && (login_mode == MODE_PASSWORD)) { qilog(sock, "Not answer or clear: %s", inputline); writestring(sock, "523:Expecting 'answer' or 'clear'\r\n"); return (++bad_cmd < MAX_BAD); /* return False if too many bad cmds */ } if ((ind < MAX_VERBS) && (verbs[ind].mode & login_mode)) status = (*verbs[ind].proc)(inputline, sock); bad_cmd = 0; /* reset bad command count */ return status; } int quit(char *cmd, int sock) { writestring(sock, "200:Bye!\r\n"); return False; } char * get_field(char *ptr, char *field, int lower) { int ind; for (ind= 0; *ptr != '\0' && *ptr != ':'; ptr++, ind++) field[ind] = lower ? _tolower(*ptr) : *ptr; field[ind] = '\0'; if (*ptr == ':') ptr++; /* skip over terminating ":" */ return ptr; } int read_fields(char *file) { FILE *cnf; char *ptr, line[256], field[128]; int ind, field_idx, count = 0; for (ind = 0; ind < MAX_FIELD; ind++) { fields[ind].number = NULL; fields[ind].name = NULL; fields[ind].desc = NULL; fields[ind].attrib = 0; } cnf = fopen(file, "r", "shr=get"); if (cnf == NULL) return (False); while (fgets(line, sizeof(line), cnf)) { ZapCRLF(line); ptr = line; if ((*ptr == '#') || (*ptr == '\0')) /* comment or blank? */ continue; /* yes, skip line */ count++; ptr = get_field(ptr, field, False); /* field number */ field_idx = atoi(field); fields[field_idx].number = new_string(field); ptr = get_field(ptr, field, True); /* field name */ fields[field_idx].name = new_string(field); ptr = get_field(ptr, field, False); /* field size (ignore) */ ptr = get_field(ptr, field, False); /* field description */ fields[field_idx].desc = new_string(field); ptr = get_field(ptr, field, False); /* field option (ignore) */ for (;;) { ptr = get_field(ptr, field, True); /* get attribute */ if (strlen(field) == 0) break; /* no more attributes */ fields[field_idx].attrib |= field_attrib(field); } if (fields[field_idx].number < 1) qilog(-1, "Field \"%s\" has illegal field number", fields[field_idx].name); } fclose(cnf); return True; } int write_afield(int field_num, int sock) { char line[128]; int aidx; if (fields[field_num].name == NULL) return False; sprintf(line, "-200:%d:%s:max %d", field_num, fields[field_num].name, DATA_SIZE); for (aidx = 0; aidx < MAX_ATTRIBUTES; aidx++) if (fields[field_num].attrib & attributes[aidx].value) { strcat(line, " "); strcat(line, attributes[aidx].name); } strcat(line, "\r\n"); writestring(sock, line); swrite(sock, "-200:%d:%s:%s\r\n", field_num, fields[field_num].name, fields[field_num].desc); return True; } int fields_cmd(char *cmd, int sock) { int fidx, aidx, count = 0; char line[256]; Arg *list, *listp; list = listp = parse_cmd(cmd, sock); if (list == NULL) /* null arg list means all fields */ for (fidx = 0; fidx < MAX_FIELD; fidx++) count += write_afield(fidx, sock); else for (; listp; listp = listp->next) if (listp->field > -1) count += write_afield(listp->field, sock); else writestring(sock, "507:Field does not exist.\r\n"); free_args(list); writestring(sock, "200:Ok.\r\n"); if (DEBUG) qilog(sock, "Sent %d field definitions", count); return True; } id_cmd(char *cmd, int sock) { writestring(sock, "200:Thanks, but we don't use ids here.\r\n"); return True; } stat_cmd(char *cmd, int sock) { if ((db_status & 1) == SS$_NORMAL) writestring(sock, "200:Database ready.\r\n"); else writestring(sock, "475:Database unavailable; try later.\r\n"); return True; } /* set global mode flags on/off */ int set_cmd(char *cmd, int sock) { Arg *list, *listp; int index; list = listp = parse_cmd(cmd, sock); for (; listp; listp = listp->next) { for (index = 0; index < MAX_MODES; index++) if (listp->name && strcmp(listp->name, modes[index].name) == 0) { switch (listp->type & TYPE_MASK) { case TYPE_ON: mode |= modes[index].value; writestring(sock, "200:Done.\r\n"); break; case TYPE_OFF: mode &= ~modes[index].value; writestring(sock, "200:Done.\r\n"); break; default: writestring(sock, "513:Option must be ON or OFF.\r\n"); break; } break; } if (index == MAX_MODES) swrite(sock, "513:Unknown mode %s\r\n", listp && listp->name ? listp->name : ""); } free_args(list); return True; } /* return some arbitrary site info */ int site_cmd(char *cmd, int sock) { FILE *fd; char line[128]; swrite(sock, "-200:0:version:%s\r\n", VERSION); if ((fd = fopen(getlogical(SITEINFO_NAME), "r", "shr=get")) == NULL) swrite(sock, "525:No siteinfo available.\r\n"); else { while (fgets(line, sizeof(line), fd)) { ZapCRLF(line); swrite(sock, "-200:%s\r\n", line); } swrite(sock, "200:Ok.\r\n"); fclose(fd); } return True; } /* set current alias */ int login_cmd(char *cmd, int sock) { Arg *list; char *ap; list = parse_cmd(cmd, sock); if ((list == NULL) || (list->next) || (list->type != TYPE_VALUE)) { free_args(list); writestring(sock, "599:Syntax error\r\n"); return True; } if ((ap = get_value(sock, list->value, ALIAS_FIELD, ALIAS_FIELD)) == NULL) { free_args(list); swrite(sock, "500:Alias does not exist\r\n"); return True; } free_args(list); strcpy(login_alias, ap); if (get_value(sock, login_alias, ALIAS_FIELD, PASSWORD_FIELD) == NULL) { swrite(sock, "500:Password does not exist\r\n"); return True; } strcpy(login_challenge, challenge(CHALLENGE_SIZE)); swrite(sock, "301:%s\r\n", login_challenge); login_mode = MODE_PASSWORD; return True; } int clear_cmd(char *cmd, int sock) { char *cp, *pw; cp = strchr(cmd, ' '); /* skip verb */ for (;cp && *cp && (*cp == ' '); cp++); /* skip spaces after verb */ if (cp == NULL) { writestring(sock, "599:Syntax error\r\n"); login_mode = MODE_ANONYMOUS; return True; } if ((pw = get_value(sock, login_alias, ALIAS_FIELD, PASSWORD_FIELD)) == NULL) { swrite(sock, "500:Password does not exist\r\n"); login_mode = MODE_ANONYMOUS; return True; } if (strcmp(cp, pw) == 0) { swrite(sock, "200:%s:Password accepted\r\n", login_alias); login_mode = MODE_LOGIN; } else { swrite(sock, "500:Login failed\r\n", login_alias); login_mode = MODE_ANONYMOUS; } return True; } int answer_cmd(char *cmd, int sock) { char *cp, decrypted[128]; cp = strchr(cmd, ' '); /* skip verb */ for (;cp && *cp && (*cp == ' '); cp++); /* skip spaces after verb */ if (cp == NULL) { writestring(sock, "599:Syntax error\r\n"); login_mode = MODE_ANONYMOUS; return True; } crypt_start(get_value(sock, login_alias, ALIAS_FIELD, PASSWORD_FIELD)); decrypt(decrypted, cp); if ((cp = strchr(decrypted, '\r')) || (cp = strchr(decrypted, '\n'))) *cp = '\0'; /* truncate at cr or lf */ if (DEBUG) swrite(sock, "%s decrypted into\r\n%s compared with\r\n%s\r\n", cp, decrypted, login_challenge); if (strcmp(decrypted, login_challenge) == 0) { swrite(sock, "200:%s:Password accepted\r\n", login_alias); login_mode = MODE_LOGIN; } else { swrite(sock, "500:Login failed\r\n"); login_mode = MODE_ANONYMOUS; } return True; } int logout_cmd(char *cmd, int sock) { strncpy(login_alias, "", sizeof(login_alias)); login_mode = MODE_ANONYMOUS; swrite(sock, "200:Done.\r\n"); return True; } char *challenge(int size) { char *ptr, *base; base = calloc(size + 1, sizeof(char)); for (ptr = base; size; ptr++, size--) *ptr = (rand() & 0x3f) + 0x21; return (base); } /* return the attribute value for the given field name */ int field_attrib(char *str) { int ind; for (ind = 0; ind < MAX_ATTRIBUTES; ind++) if (*str == _tolower(*attributes[ind].name)) /* check only first char */ return (attributes[ind].value); return (0); /* no match = no bits */ } /* return the field_number for the given field name */ int field_number(char *str) { int ind; for (ind = 0; ind < MAX_FIELD; ind++) if (fields[ind].name && (strcmp(str, fields[ind].name) == 0)) return (atoi(fields[ind].number)); return (-1); /* no field number */ } /* get a token as part of the 'field=value' clause */ /* return pointer to terminator */ char *get_token(char *cp, char *dp) { int in_quote = False; if (*cp) { while (isspace(*cp)) cp++; /* skip space */ while (*cp && (in_quote || ((*cp != ' ') && (*cp != '=')))) if (*cp == '"') { in_quote = !in_quote; cp++; } else *dp++ = in_quote ? *cp++ : tolower(*cp++); } *dp = '\0'; return cp; } /* cmd = 'verb [field=]value ...' */ Arg *parse_cmd(char *cmd, int sock) { int index; char *cp, token[128]; Arg *start = NULL, *end = NULL; cp = strchr(cmd, ' '); /* skip verb */ while (cp) { cp = get_token(++cp, token); if (strlen(token) == 0) return start; if (start == NULL) start = end = make_arg(NULL, -1, NULL, 0); else { end->next = make_arg(NULL, -1, NULL, 0); end = end->next; } if (*cp == '=') { end->name = new_string(token); end->field = field_number(token); end->type |= TYPE_NAME | TYPE_EQUAL; cp = get_token(++cp, token); } if (strlen(token)) { end->value = new_string(token); end->type |= TYPE_VALUE; } if (strcmp(token, "return") == 0) /* check for special names */ end->type |= TYPE_RETURN; else if (strcmp(token, "on") == 0) end->type |= TYPE_ON; else if (strcmp(token, "off") == 0) end->type |= TYPE_OFF; if (end->field == -1) /* if there were no field name given */ end->field = field_number(token); /* try the field value as a field name */ if (DEBUG) swrite(sock, "-100: Parse >> %s (field %d) = %s\r\n", end->name ? end->name : "", end->field, end->value ? end->value : ""); } return start; /* should only get here on null list */ } Arg *make_arg(char * name, int field, char *value, int type) { Arg *ptr; ptr = malloc(sizeof (Arg)); ptr->type = type; ptr->name = name; ptr->field = field; ptr->value = value; ptr->next = (Arg *) 0; return ptr; } void free_args(Arg *ptr) { Arg *next; while (ptr) { next = ptr->next; free(ptr); ptr = next; } } /* copy string into malloc'ed space */ char *new_string(char *str) { char *ptr; ptr = (char *) malloc(strlen(str) + 1); strcpy(ptr, str); return (ptr); } void swrite(int sock, char *fmt, ...) { char buf[512]; va_list arg_ptr; va_start(arg_ptr, fmt); vsprintf(buf, fmt, arg_ptr); va_end(arg_ptr); writestring(sock, buf); } void qilog(int sock, char *fmt, ...) { FILE *logfd; char host_name[256]; time_t Now; char NowBuf[26]; char *cp; char buf[512]; va_list arg_ptr; va_start(arg_ptr, fmt); vsprintf(buf, fmt, arg_ptr); va_end(arg_ptr); host_name[0] = '\0'; if (sock > -1) inet_netnames(sock, host_name); time(&Now); cp = (char *) ctime(&Now); ZapCRLF(cp); cp = strcpy(NowBuf, cp); if ((logfd = fopen(getlogical(LOG_NAME), "a", "dna=qi.log", "shr=put")) != NULL) { if (strlen(fmt)) { fprintf(logfd, "%s %s : %s\n", cp, host_name, buf); fflush(logfd); } fclose(logfd); } } void qiabort(int sock, char *str) { writestring(sock, str); ZapCRLF(str); qilog(sock, "Abort: %s", str); exit(2); } void *my_realloc(void *mem, int size) { if ((mem == (void *) 0)) return ((void *) malloc(size)); else return ((void *) realloc(mem, size)); } /* translate an exec mode logical name */ char *getlogical(char *name) { #include psldef #include lnmdef typedef struct { short length; short item_code; char *bufadr; short *ret_len_addr; } item_desc; struct { item_desc string; int terminator; } trnlst; static char result[128]; /* the object returned */ short ret_len = 0; int acmode = PSL$C_EXEC; $DESCRIPTOR(table_dsc, "LNM$SYSTEM_TABLE"); struct dsc$descriptor_s log_dsc = { strlen(name), DSC$K_DTYPE_T, DSC$K_CLASS_S, name }; trnlst.string.bufadr = result; trnlst.string.length = sizeof(result); trnlst.string.item_code = LNM$_STRING; trnlst.string.ret_len_addr = &ret_len; trnlst.terminator = 0; sys$trnlnm(0, &table_dsc, &log_dsc, &acmode, &trnlst); result[ret_len] = '\0'; return result; } .