/* Maintain server configuration files though HTML forms. * The script coordinates the configuration (.conf) file with an edit form via * use of a form template (.template) file along with special directives * in the configuration file (.DEFINE, .EXPAND, etc). * * Author: David Jones * Date: 2-JUL-1996 * Revised: 7-AUG-1996 fix bugs in collect_symbols() and .iterate * expansion for .authorize list. * Revised: 14-AUG-1996 More bugs in .iterate expansion * Revised: 16-AUG-1996 Inhibit ":80" port, some browsers treat * host and host:80 and independant realms. * Revised: 17-AUG-1996 Allow .authorize rule to specify remote * user of '*'. * Revised: 13-JAN-1997 Attempt to fix problem with extra blank lines * inserted when no .next's accompany .iterate. * Support continuation lines in rule file * (last character on line is backslash). * Revised: 19-JUL-1997 Allow double-quotes in form values, replace * with "''" in displayed forms to get around * bugs in browser support for "e; * Revised: 22-FEB-1998 Add reflex hack support. */ #include #include #include #include #include #include #include "cgilib.h" struct field_def { struct field_def *next; char *key, *value; int klen; }; typedef struct field_def *field_def_p; static char *entify_string ( char *source ); static int output_started; static void error_abort ( char *stsline, char *reason, void *arg ) { char fmt[256]; if ( !output_started ) output_started = cgi_begin_output(1); printf("error abort: '%s' '%s'\n", stsline, reason ); sprintf ( fmt, "content-type: text/plain\nstatus: %s\n\nFailure: %s", stsline, reason ); cgi_printf ( fmt, arg, arg ); exit ( 1 ); } static char *url_host ( ) { /* * Return port number string and delimiter for building redirects. */ static char buffer[300]; char *port, *host; int length, total; /* * If port number is 80, just return host name. */ port = cgi_info ( "SERVER_PORT" ); host = cgi_info ( "SERVER_NAME" ); if ( !port ) port = "80"; if ( strcmp ( port, "80" ) == 0 ) return host; /* * construct host:port in static storage. */ length = strlen ( host ); total = length + 2 + strlen(port); if ( total > sizeof(buffer) ) return host; strcpy ( buffer, host ); buffer[length++] = ':'; strcpy ( &buffer[length], port ); return buffer; } struct file_rec { struct file_rec *next; struct file_rec *related; int length, type; char s[1]; /* string */ }; typedef struct file_rec *file_recp; struct symdef { file_recp source; char *symbol; char *value; }; struct rec_group { char *tag; file_recp first; file_recp last; int count; }; static struct file_rec rule_file, form_file; static time_t rule_file_mdate; static struct rec_group file_dir[] = { { "ALL", &rule_file, &rule_file, 0 }, /* 0 */ { ".DEFINE", (file_recp) 0, (file_recp) 0, 0 }, /* 1 */ { ".EXPAND", (file_recp) 0, (file_recp) 0, 0 }, /* 2 */ { ".IGNORE", (file_recp) 0, (file_recp) 0, 0 }, /* 3 */ { ".ITERATE", (file_recp) 0, (file_recp) 0, 0 }, /* 4 */ { ".NEXT", (file_recp) 0, (file_recp) 0, 0 }, /* 5 */ { ".AUTHORIZE", (file_recp) 0, (file_recp) 0, 0 }, /* 6 */ { ".FORM", (file_recp) 0, (file_recp) 0, 0 }, /* 7 */ { (char *) 0, (file_recp) 0, (file_recp) 0, 0 } }; static void authenticate_user ( struct file_rec *auth_recs, char *fname ); static char *load_configuration ( char *method, char *path_info, char **mancmd ); static char *load_template ( char *fname ); static void generate_form(char *, char*); static void update_configuration(char *); extern void server_query ( char * ); static int expand_form_tag ( char *tag, char *, int tag_length, struct symdef * ); /**************************************************************************/ int main ( int argc, char **argv ) { int i, status, manage_flag, SYS$SETEF(); char *path_info, *fname, *method, *query, *manage_cmd; /* * Use low-level cgi_init to allow additional scriptserver transactions. * (will call cgi_begin_output(1) when ready to generate CGI output. */ status = cgi_init_env ( argc, argv ); if ( (status&1) == 0 ) return status; output_started = 0; /* * Break out configfile name and mtime from path_info. */ method = cgi_info ( "REQUEST_METHOD" ); path_info = cgi_info ( "PATH_INFO" ); if ( !path_info ) error_abort ( "500 no path", "No path", 0 ); fname = load_configuration ( method, path_info, &manage_cmd ); /* * authorize access. */ if ( file_dir[4].count > 0 ) { /* * Test .iterate records for .authorize expansion */ char iter_line[32]; file_recp rec, tmp; for ( rec = file_dir[4].first; rec; rec = rec->related ) { if ( rec->length < 20 ) continue; for ( i = 0; i < 20; i++ ) iter_line[i] = toupper(rec->s[i]); if ( strncmp ( iter_line, ".ITERATE .AUTHORIZE ", 20 ) == 0 ) { /* Append .next records for iterate to .authorize list */ for ( rec = rec->next; rec; rec = rec->next ) { if ( rec->type == 4 ) break; /* next .iterate */ if ( rec->type == 5 ) { /* * Record is of type .next, reformat as new .authorize * record. */ tmp = malloc ( sizeof(struct file_rec)+rec->length+6 ); tmp->next = tmp->related = (file_recp) 0; tmp->type = 4; tmp->length = rec->length + 5; strcpy ( tmp->s, ".AUTHORIZE" ); strncpy ( &tmp->s[10], &rec->s[5], rec->length-5 ); tmp->s[tmp->length] = '\0'; if ( file_dir[6].count == 0 ) { file_dir[6].first = file_dir[6].last = tmp; } else { file_dir[6].last->related = tmp; file_dir[6].last = tmp; } file_dir[6].count++; } } break; } } } if ( file_dir[6].count == 0 ) error_abort ( "403 noauth", "Rule file %s contains no .AUTHORIZE directives so cannot be accessed", fname ); authenticate_user ( file_dir[6].first, fname ); /* * read template file. */ if ( file_dir[7].count == 0 ) error_abort ( "500 noform", "No .FORM directive in %s", fname ); else if ( file_dir[7].count > 1 ) error_abort ( "500 duplicate form", "More that one .FORM directive in %s", fname ); load_template ( &file_dir[7].first->s[6] ); /* * Action depends upon method. */ if ( strncmp ( method, "POST", 5 ) == 0 ) { output_started = cgi_begin_output(1); update_configuration(fname); } else if ( strncmp ( method, "GET", 4 ) == 0 ) { /* * Generate form or perform action. */ if ( manage_cmd ) { server_query ( manage_cmd ); } else { int cgi_begin_preprocessed(), cgi_end_preprocessed(); char full_path[256]; sprintf ( full_path, "/www_system/%s.conf", fname ); output_started = /* cgi_begin_output(1); */ cgi_begin_preprocessed ( argv[1], full_path ); generate_form(path_info, fname); cgi_end_preprocessed(); } } else if ( strncmp ( method, "HEAD", 5 ) == 0 ) { output_started = cgi_begin_output(1); cgi_printf ( "Content-type: text/plain\n\nHTML form goes here\n" ); } else { error_abort ( "401 unsupported", "Method %s unsupported", method ); } return 1; } /************************************************************************/ /* Read specified file into global structures. */ static char *load_configuration ( char *method, char *path_info, char **mancmd ) { int i; unsigned long mtime; char *cp, *fname, *mod_date, line[4096]; FILE *rfile; stat_t rstat; struct file_rec *rec; fname = (char *) malloc ( strlen(path_info)+1 ); strcpy ( fname, (path_info[0] == '/') ? &path_info[1] : path_info ); mod_date = strchr ( fname, '/' ); if ( mod_date ) *mod_date++ = '\0'; else mod_date = "0"; *mancmd = (char *) 0; /* * open file and validate mtime in path, redirecting if needed. */ if ( strlen(fname) > 250 ) error_abort("500 invfile", "fname invalid", 0 ); sprintf ( line, "/www_system/%s.conf", fname ); rfile = fopen ( line, "r" ); if ( !rfile ) error_abort ( "500 open", "open failure on %s\n", line ); if ( fstat ( fileno(rfile), &rstat ) < 0 ) error_abort ( "500 stat", "stat() failure on %s\n", fname ); if ( 0 == strncmp ( mod_date, "manage/", 7 ) ) { *mancmd = &mod_date[7]; mtime = rstat.st_mtime; } else { mtime = atoi ( mod_date ); } if ( mtime != rstat.st_mtime ) { if ( strcmp(method,"POST") == 0 ) error_abort ( "500 invfile", "Mismatch on file times, can't update %s", fname ); /* Redirect request */ output_started = cgi_begin_output(1); #ifdef NOREFLEX_HACK cgi_printf ( "location: http://%s%s/%s/%d\n\n", url_host(), cgi_info("SCRIPT_NAME"), fname, rstat.st_mtime ); #else /* Server will fill in correct information for '*://*:*' */ cgi_printf ( "location: *://*:*%s/%s/%d\n\n", cgi_info("SCRIPT_NAME"), fname, rstat.st_mtime ); #endif exit(1); } rule_file_mdate = rstat.st_mtime; /* * Load file into memory. */ rule_file.length = rule_file.type = 0; rule_file.next = rule_file.related = (struct file_rec *) 0; while ( fgets ( line, sizeof(line), rfile ) ) { char *nl; int length; nl = strchr ( line, '\n' ); if ( nl ) *nl = '\0'; length = strlen(line); if ( length > 0 ) while ( line[length-1] == '\\' ) { /* * Continuation line, concatentate additional lines but * preserve linefeeds. */ line[length++] = '\n'; line[length] = '\0'; if (!fgets(&line[length], sizeof(line)-length, rfile)) break; nl = strchr ( &line[length], '\n' ); if (nl) *nl = '\0'; length += strlen(&line[length]); } rec = (struct file_rec *) malloc ( sizeof(struct file_rec) + length ); if ( !rec ) error_abort ( "500 mem", "malloc failed", 0 ); file_dir[0].last->next = rec; file_dir[0].last = rec; rec->next = (struct file_rec *) 0; rec->related = (struct file_rec *) 0; rec->length = length; strcpy ( rec->s, line ); /* * If line begins with period, group into related list. */ rec->type = 0; if ( rec->length < 1 ) continue; if ( rec->s[0] != '.' ) continue; for (cp = &rec->s[1]; *cp && !isspace(*cp); cp++) *cp = toupper(*cp); *cp = '\0'; for ( i = 1; file_dir[i].tag; i++ ) { if ( 0 == strcmp ( rec->s, file_dir[i].tag ) ) { if ( file_dir[i].last ) file_dir[i].last->related = rec; else file_dir[i].first = rec; file_dir[i].last = rec; break; } } file_dir[i].count++; rec->type = i; *cp = ' '; } fclose ( rfile ); return fname; } /*************************************************************************/ /* Read template file used to generate HTML form. Template is parsed into * a linked list of tagged segments for subsequent processing. The head * of this list is the global variable form_file. * * Segment types: * 0 Text. * 1 Text with newline at end. * 2 Input marker, delimited in template by square bracket. * 3 Control marker, special input marker of form [$command]. */ static char *load_template ( char *fname ) { int j, i; char line[4096]; FILE *tfile; struct file_rec *rec; /* * open file */ while ( *fname && isspace(*fname) ) fname++; if ( strlen(fname) > 250 ) error_abort("500 invfile", "fname invalid", 0 ); tfile = fopen ( fname, "r" ); if ( !tfile ) error_abort ( "500 open", "Open failure on '%s'", fname ); /* * Load file into memory. */ form_file.length = form_file.type = 0; form_file.next = form_file.related = (struct file_rec *) 0; for ( rec=(&form_file); fgets ( line, sizeof(line), tfile ); ) { int length, level, seg_type; /* * Parse file into text segments(types 0,1) separated by marker/control * segments, type 2/3. */ level = 0; for ( j = i = 0; i < sizeof(line); i++ ) { if ( line[i] == '[' ) { level++; if ( level > 1 ) continue; seg_type = 0; } else if ( line[i] == ']' ) { --level; if ( level > 0 ) continue; seg_type = 2; if ( line[j] == '$' ) seg_type = 3; } else if ( (line[i] == '\n') || (line[i] == '\0') ) { seg_type = 1; } else continue; /* * Copy portion of read record to new segment if j has been * updated. */ if ( j < i ) { length = i - j; rec->next = (struct file_rec *) malloc ( sizeof(struct file_rec) + length ); if ( !rec ) error_abort ( "500 mem", "malloc failed", 0 ); rec = rec->next; rec->next = rec->related = (struct file_rec *) 0; strncpy ( rec->s, &line[j], length ); rec->s[length] = '\0'; rec->length = length; rec->type = seg_type; } j = i + 1; if ( seg_type == 1 ) break; } } fclose ( tfile ); return fname; } /****************************************************************************/ /* Convert pluses and escaped characters in string, also convert * '' to " (special hack since browser support for "e; sucks) */ static int htmlStrcpy ( char *out, char *in) { int i, j; /* printf("htmlstr raw string: %s\n", in ); */ for ( j=0; *in; in++) { if (*in == '%') { int xxx ; if ( (in[1]=='\0') || (in[2]=='\0') ) break; /* too short */ sscanf (&in[1], "%02x", &xxx) ; if ( xxx == '\'' ) { if ( strncmp ( &in[3], "%27", 3 ) == 0 ) { xxx = '"'; in += 3; /* convert '' to " */ } } out[j++] = (char) xxx ; in += 2 ; } else if ( *in == '+' ) { out[j++] = ' '; } else if ( (*in == '\'') && (in[1] == '\'') ) { in++; /* skip */ out[j++] = '"'; } else { out[j++] = *in; } } out[j] = '\0'; return j; } /****************************************************************************/ /* Read form data and parse into list of key/value pairs. Plus-to-space * conversion is performed and escaped chars are converted. * Full CGI responses are generated on error. */ static field_def_p form_data() { char *method, *content_data, *cp; field_def_p field_list, fld; int i, content_length, status; /* * Determine what form data is being delivered by client. */ method = cgi_info ( "REQUEST_METHOD" ); if ( 0 == strcmp ( method, "POST" ) ) { /* * Data was posted. */ content_length = atoi(cgi_info ("CONTENT_LENGTH")); if (content_length == 0) error_abort ( "500 missing content", "No data in CONTENT_LENGTH (%s)", cgi_info("CONTENT_LENGTH") ); content_data = (char *) malloc(sizeof(char)*(content_length+1)); status = cgi_read(content_data, content_length); if (status == 0) error_abort ( "500 missing content", "No data from cgi_read", 0 ); content_data[content_length] = '\0'; } else { error_abort("501 unsupported method","Method %s is unsupported", method ); } /* * Parse the content into key/value pairs. */ field_list = (field_def_p) 0; if ( content_data[0] != '&' ) { fld = (field_def_p) malloc ( sizeof(struct field_def) ); fld->next = field_list; fld->key = content_data; fld->klen = 0; field_list = fld; } for ( i = 0; i < content_length; i++ ) if ( content_data[i] == '&' ) { fld = (field_def_p) malloc ( sizeof(struct field_def) ); fld->next = field_list; fld->key = &content_data[i+1]; fld->klen = 0; content_data[i] = '\0'; field_list = fld; } for ( fld = field_list; fld; fld = fld->next ) { fld->value = ""; for ( cp = fld->key, i = 0; cp[i]; i++ ) { if ( cp[i] == '=' ) { cp[i] = '\0'; fld->klen = i; fld->value = &cp[i+1]; } } htmlStrcpy ( fld->value, fld->value ); /* printf("Parsed key '%s' = '%s'\n", fld->key, fld->value ); */ } return field_list; } /*****************************************************************************/ /* Return table of symbol definitions of a given line type (specified by * index). Each line is parsed into a symbol name and value. * * If copy is true, new memory is allocated to hold the string data, otherwise * the exsiing strings are modified. */ struct symdef *collect_symbols ( int ndx, int copy ) { struct symdef *table; file_recp rec; int i, start_offset; char *line, *tok; /* * Allocate table, the load procedure tracked how many lines of each * type there were. */ start_offset = strlen ( file_dir[ndx].tag ); table = (struct symdef *) malloc ( (file_dir[ndx].count+1)*sizeof(struct symdef)); if ( !table ) error_abort ( "500 malloc", "malloc failed", 0 ); /* * Scan all records of type ndx. */ for ( i = 0, rec = file_dir[ndx].first; rec; rec = rec->related ) { table[i].source = rec; if ( copy ) { /* * Make copy of line so the parsing doesnt destroy the original. */ line = malloc ( rec->length+1 ); if ( !line ) printf("error in malloc, length: %d\n", rec->length); strncpy ( line, rec->s, rec->length ); line[rec->length] = '\0'; } else line = rec->s; /* * Parse of first token after .type, which is symbol name. */ tok = &line[start_offset]; while ( *tok && isspace(*tok) ) tok++; table[i].symbol = tok; if ( *tok ) { /* * Find symbol name's end and terminate it. */ while ( *tok && !isspace(*tok) ) tok++; if ( *tok ) { *tok++ = '\0'; /* Skip to start of value */ while ( *tok && isspace(*tok) ) tok++; } } table[i].value = tok; i++; } table[i].symbol = table[i].value = (char *) 0; return table; } /***************************************************************************/ /* * Decode block of 4 characters to 1,2 or 3 characters. Return 0 if * block is invalid. */ static int decode_char ( char **inp, int *context, unsigned char *out ) { char *enc_table = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="; int context_bits, context_count, i, j; char *p, c; context_bits = *context; context_count = (context_bits>>12); context_bits = context_bits&4095; p = *inp; while ( context_count < 8 ) { c = *p++; if ( c == '=' ) return 0; if ( c == '\0' ) return 0; for ( j = 0; (enc_table[j] != c); j++ ) if ( !enc_table[j] ) return 0; context_bits = (context_bits&63)<<6 | j; context_count += 6; } if ( context_count == 8 ) *out = (context_bits&255); else *out = (context_bits>>(context_count-8))&255; *context = (context_bits&4095) | ((context_count-8)<<12); *inp = p; return 1; } /**************************************************************************/ /* Validate the user has access rights to file. Program aborts on failure. * * .authorize formats: * user[@host-addr] User must match REMOTE_USER variable * (Script must be protected) * *[@host-addr] User must have been authenticated via protect * rule on script. * user[@host-addr] pwd User and password must match that decoded * from .authorize statement AND server port must * be non-privileged (>1024). */ static void authenticate_user ( struct file_rec *auth_recs, char *fname ) { struct symdef *user_list; char *auth_header, *decode_buf, *user, *password, *remote_user; char *host_addr, *remote_addr, *remote_host; char *authenticate = "WWW-authenticate: Basic realm=\"ServerMaint\""; int i, j, ctx; unsigned char c; /* * Get authorization header and decode user/password; */ remote_user = cgi_info ( "REMOTE_USER" ); remote_addr = cgi_info ( "REMOTE_ADDR" ); remote_host = cgi_info ( "REMOTE_HOST" ); auth_header = cgi_info ( "HTTP_AUTHORIZATION" ); if ( !auth_header ) error_abort ( "401 authorization failed, none provided\n%s", "no auth header", authenticate ); decode_buf = malloc ( strlen ( auth_header ) ); for ( i = 0; auth_header[i] && !isspace(auth_header[i]); i++ ) { decode_buf[i] = tolower(auth_header[i]); if ( i > 5 ) break; } decode_buf[i] = '\0'; if ( strcmp ( decode_buf, "basic" ) ) error_abort ( "401 authorization failed, wrong type\n%s", "Wrong auth. type", authenticate ); while ( auth_header[i] && isspace(auth_header[i]) ) i++; user = &auth_header[i]; password = (char *) 0; for ( i = ctx = 0; decode_char(&user, &ctx, &c); i++ ) { decode_buf[i] = toupper(c); if ( !password ) if ( c == ':' ) { decode_buf[i] = '\0'; password = &decode_buf[i+1]; } } decode_buf[i] = '\0'; if ( !password ) error_abort ( "401 authorization failed, syntax\n%s", "Syntax error", authenticate ); user = decode_buf; /* * Scan file for .authorize directive that matches. */ user_list = collect_symbols ( 6, 1 ); /* parse .authorize list */ /* printf("%s records: %d, cand: '%s' pwd: '%s'\n", file_dir[6].tag, file_dir[6].count, user, password ); */ for ( i = 0; user_list[i].symbol; i++ ) { /* * Upcase the username and trim off hostname */ host_addr = (char *) 0; for ( j=0; user_list[i].symbol[j]; j++ ) { user_list[i].symbol[j] = toupper(user_list[i].symbol[j]); if ( user_list[i].symbol[j] == '@' ) { host_addr = &user_list[i].symbol[j+1]; user_list[i].symbol[j] = '\0'; break; } } if ( host_addr ) { /* Skip this record if host doesn't match */ if ( strcmp ( host_addr, remote_host ) && strcmp ( host_addr, remote_addr ) ) continue; } if ( user_list[i].value[0] ) { /* Verify password and succeed if matches */ if ( atoi ( cgi_info("SERVER_PORT") ) < 1024 ) continue; for ( j=0; user_list[i].value[j]; j++ ) user_list[i].value[j] = toupper(user_list[i].value[j]); if ( strcmp ( user_list[i].value, password ) == 0 ) return; } else if ( remote_user ) { /* No password, user must match remote user or '*' */ if ( *remote_user && ( 0 == strcmp ("*", user_list[i].symbol ) ) ) return; if ( 0 == strcmp ( remote_user, user_list[i].symbol ) ) { /* User verified */ return; } } } error_abort ( "401 authorization failed\n%s", "authorization failed", authenticate ); } /**************************************************************************/ static int update_definition ( char *tag, int tag_length, field_def_p field_list, struct symdef *symbols, char *suffix ) { int i, j, k, state, nlen; char *tmp, *t_value, *value, name[128]; field_def_p fld; /* * tag fomats: * [name;size;max] * [name|opt1|opt2|...] */ tag[tag_length] = '\0'; value = ""; for ( i = 0; i < tag_length; i++ ) { if ( (tag[i] == ';') || (tag[i] == '?') || (tag[i] == '|') ) { /* * Lookup symbol in .define list. */ for ( j = 0; symbols[j].symbol; j++ ) { if ( 0 == strncmp ( tag, symbols[j].symbol, i ) ) { if ( symbols[j].symbol[i] == '\0' ) break; } } if ( !symbols[j].symbol ) return 1; /* no .define */ /* * Lookup suffixed symbol in form data. */ nlen = strlen(suffix); if ( i > (sizeof(name)- 1 - nlen) ) break; strncpy ( name, tag, i ); strcpy ( &name[i], suffix ); nlen += i; for (fld=field_list; fld; fld=fld->next) if (fld->klen == nlen) { if ( strncmp(name, fld->key, nlen) == 0 ) { value = fld->value; break; } } break; } } switch ( tag[i] ) { case ';': /* text field or select field */ case '|': if ( fld ) { symbols[j].value = fld->value; } else symbols[j].value = ""; break; case '?': /* check box */ /* Get value tag following ? or : */ t_value = &tag[i+1]; tag_length = tag_length - i - 1; for ( k=0; (k < tag_length) && (t_value[k] != ':'); k++); if ( strncmp ( value, "on", 3 ) != 0 ) { if ( t_value[k] ) { t_value = &t_value[k+1]; k = tag_length - k - 1; } else { t_value = &t_value[k]; k = 0; } } symbols[j].value = malloc ( k+1 ); strncpy ( symbols[j].value, t_value, k ); symbols[j].value[k] = '\0'; break; default: break; } return 1; } /**************************************************************************/ /* Setup symbol table for iteration arguments. Return value is pointer * to .iterate record that matched. The edit and end variable may * be specified as NULL. * * The symbol table is re-ordered to match the order of the .iterate command. */ file_recp setup_iteration ( file_recp begin, struct symdef *symbols, file_recp *edit, file_recp *end ) { int i, j, count, state, start, k; file_recp rec, config_rec; int map[16]; if ( edit ) *edit = (file_recp) 0; /* initialize */ if ( end ) *end = (file_recp) 0; /* * Scan ahead in template for segment type 2 and 3, looking for * symbols referenced and $END. */ i = 0; for ( rec = begin->next; rec; rec = rec->next ) /*if ( rec->type > 1 )*/ { /* printf("setup iteration, rec type: %d, string: '%s'\n", rec->type, rec->s );*/ if ( rec->type == 2 ) { /* * Parse symbol name from tag and add to local symbol table. * symbol names will have an .nn suffix attached so include * romm for it. */ for ( j = 0; j < rec->length; j++ ) if ( strchr(";|:?",rec->s[j]) ) break; symbols[i].symbol = malloc ( j+10 ); if ( !symbols[i].symbol ) break; strncpy ( symbols[i].symbol, rec->s, j ); symbols[i].symbol[j] = '\0'; symbols[i].value = ""; i++; if ( i > 14 ) break; } else if ( rec->type == 3 ) { if ( strcmp ( rec->s, "$END" ) == 0 ) { if ( end ) *end = rec; break; } else if ( strncmp ( rec->s, "$EDIT", 5 ) == 0 ) { if ( edit ) *edit = rec; } } } count = i; symbols[i].symbol = (char *) 0; /* terminate symbol list */ /* printf("setup iteration, local symbols: %d\n", count ); */ /* * Scan .iterate records in configuration file for one with dummy * argument names that match our symbol list. Symbols on .iterate * line amy not match order they appear in template, re-order to match. * ( NOT DONE YET) */ config_rec = (file_recp) 0; for ( rec = file_dir[4].first; rec; rec = rec->related ) { char *line, symname; state = i = 0; /* * Parse the line's tokens for referenced variables. */ config_rec = rec; line = &rec->s[1]; for ( j = 8; j < rec->length; j++ ) switch ( state ) { case 0: if ( !isspace(line[j]) ) { if ( line[j] == '$' ) { start = j+1; state = 2; } else state = 1; /* skip */ } break; case 1: if ( isspace(line[j]) ) state = 0; break; case 2: /* * Find end of token, count a backslash as whitespace. */ if ( !isspace(line[j]) && line[j] && (line[j] != '\\') ) break; /* * search symbol table for symname. */ map[i] = -1; for ( k = 0; symbols[k].symbol; k++ ) { if ( 0 == strncmp(&line[start], symbols[k].symbol, (j-start)) && !symbols[k].symbol[j-start] ) { map[i] = k; break; } } if ( !symbols[k].symbol ) { /* no match */ config_rec = (file_recp) 0; break; } i++; state = 0; default: break; } if ( config_rec ) { /* use map to re-order the symbol table */ for ( k = 0; k < i; k++ ) { while ( k != map[k] ) { symbols[i] = symbols[map[k]]; symbols[map[k]] = symbols[k]; symbols[k] = symbols[i]; map[i] = map[map[k]]; map[map[k]] = map[k]; map[k] = map[i]; } } break; } } return config_rec; } /**************************************************************************/ /* Handle the form files [$command] markers in POST operation. */ static file_recp control_update_function ( file_recp rec, field_def_p field_list ) { int ndx, j, length, rec_type; char *edit_control, field_name[128], suffix[12]; static file_recp config_rec, edit, end; file_recp c_rec, t_rec; field_def_p fld; static struct symdef local_symbols[16]; /* printf("control function: '%s'\n", rec->s ); */ if ( strcmp ( rec->s, "$BEGIN" ) == 0 ) { /* * Build symbol table from referenced varaible names and locate * the matching .iterate record. */ config_rec = setup_iteration ( rec, local_symbols, &edit, &end ); if ( !config_rec ) return config_rec; /* * Scan forward of config_rec for all related .next records, * and edit them. */ ndx = 0; /* current index number for form fields */ for (c_rec = config_rec->next; ndx==0 || c_rec; c_rec = c_rec->next ) { rec_type = c_rec ? c_rec->type : 0; if ( (ndx > 0) && rec_type == 4 ) break; /* new .iterate record */ if ( rec_type == 5 ) { ndx++; sprintf(suffix, ".%d", ndx ); /* * Look for edit control. */ if ( edit ) { sprintf ( field_name, "edtctl%s%s", &edit->s[5], suffix ); j = strlen(field_name); for ( fld = field_list; fld; fld = fld->next ) { if ( j != fld->klen ) continue; if ( strcmp ( fld->key, field_name) == 0 ) { edit_control = fld->value; break; } } if ( !fld ) continue; /* no changes */ if ( strcmp ( edit_control, "..." ) == 0 ) continue; if ( strcmp ( edit_control, "Delete" ) == 0 ) { /* Change type of record to make output pass ignore it */ c_rec->type = 1; continue; } } else { /* default to'modified' */ edit_control = "Modify"; } /* * Replay records to update symbol table values. */ for ( t_rec = rec; t_rec; t_rec = t_rec->next ) { if ( t_rec->type == 2 ) { update_definition ( t_rec->s, t_rec->length, field_list, local_symbols, suffix ); } else if ( t_rec->type == 3 ) { if ( strncmp ( t_rec->s, "$END", 4 ) == 0 ) break; } } /* * Copy the new symbols to a new string; */ for ( length = j = 0; local_symbols[j].symbol; j++ ) { length = length + strlen ( local_symbols[j].value ) + 1; } /* * Update the .modified. or insert new. */ if ( (strcmp ( edit_control, "Modify" ) == 0) || (strcmp ( edit_control, "Create" ) == 0) ) { c_rec->type = 1; /* hide this record */ } else if ( strcmp ( edit_control, "Insert" ) == 0 ) { } t_rec = (file_recp) malloc (sizeof(struct file_rec) +length+6); t_rec->next = c_rec->next; t_rec->related = c_rec->related; t_rec->type = 5; c_rec->next = t_rec; c_rec->related = t_rec; c_rec = t_rec; length = 6; strcpy ( c_rec->s, ".next " ); for ( j = 0; local_symbols[j].symbol; j++ ) { if ( j > 0 ) c_rec->s[length++] = ' '; strcpy ( &c_rec->s[length], local_symbols[j].value ); length += strlen ( local_symbols[j].value ); } c_rec->length = length; } /* * Check for case of no records. */ if ( ndx == 0 ) { j = c_rec ? (c_rec->next ? c_rec->next->type : 4) : 4; if ( j == 4 ) { /* Append a dummy .next after the .iterate */ t_rec = (file_recp) malloc ( sizeof(struct file_rec) ); t_rec->next = config_rec->next; t_rec->related = (file_recp) 0; t_rec->type = 5; t_rec->length = 0; t_rec->s[0] = '\0'; config_rec->next = t_rec; c_rec = config_rec; } } } /* * Return the end marker so we don't re-hash this ground. */ rec = end; } return rec; } /**************************************************************************/ void update_configuration ( char *fname ) { struct symdef *symbols; FILE *cfile; char full_name[256]; file_recp rec; stat_t rstat; int i, j, k, status; field_def_p field_list, fld; /* * Read form data submitted by client and symbol table of current * rule file values. */ field_list = form_data(); /* printf("Form data:\n"); for ( fld = field_list; fld; fld = fld->next ) { printf(" '%s' = '%s'\n", fld->key, fld->value ); } */ symbols = collect_symbols ( 1, 1 ); /* * Scan template file for markers and update corresponding .define * symbols from the form data field_list. */ for (rec = form_file.next; rec; rec = rec->next) if ( rec->type > 1 ) { if ( rec->type == 2 ) { if ( !update_definition ( rec->s, rec->length, field_list, symbols, "" ) ) { } } else if ( rec->type == 3 ) { rec = control_update_function ( rec, field_list ); } } /* * Open output file. */ sprintf(full_name,"/www_system/%s.conf", fname ); cfile = fopen ( full_name, "w" ); if ( !cfile ) error_abort ( "500 fopen", "error opening output file %s", full_name ); /* * Begin file with .ignore directive for servermaint's extensions and * folllow with dump of symbol table as .define directives. */ fprintf(cfile,".ignore"); for (i=6; file_dir[i].tag; i++) fprintf(cfile, " %s", file_dir[i].tag); fprintf(cfile,"\n"); for ( i = 0; symbols[i].symbol; i++ ) { /* * Generate .define rule to set value. */ fprintf(cfile,".define %s %s\n", symbols[i].symbol, symbols[i].value); } /* * Copy records of original to new file, modifying as needed. */ for ( rec = rule_file.next; rec; rec = rec->next ) { if ( rec->type == 1 ) { /* .define record */ /* All defines collected and output above. */ continue; } else if ( rec->type == 3 ) { /* .ignore record */ continue; } else if ( rec->type == 4 ) { /* .iterate record */ } else if ( rec->type == 5 ) { /* .next record */ if ( rec->length == 0 ) continue; /* don't write empty records */ } rec->s[rec->length] = '\n'; status = fwrite ( rec->s, rec->length+1, 1, cfile ); rec->s[rec->length] = '\0'; if ( status != 1 ) error_abort ( "500 fwrite", "file write error '%s'", rec->s ); } fclose ( cfile ); /* * Get new file update time and redirect to regenerate form. */ if ( stat ( full_name, &rstat ) < 0 ) error_abort ( "500 stat", "stat() failure on %s\n", fname ); #ifdef NOREFLEX_HACK cgi_printf("location: http://%s%s/%s/%d\n\n", url_host(), cgi_info("SCRIPT_NAME"), fname, rstat.st_mtime ); #else /* Server will fill in correct information for '*://*:*' */ cgi_printf ( "location: *://*:*%s/%s/%d\n\n", cgi_info("SCRIPT_NAME"), fname, rstat.st_mtime ); #endif } /**************************************************************************/ /* Scan for next .iterate record and load its argument into symbol table. */ static file_recp load_iteration_symbols ( file_recp cur_rec, struct symdef *symbols ) { int i, j, length, pos, state; char *period; static char line[4096]; /* * Locate the next .next record following the current record. */ if ( !cur_rec ) printf("Load_iteration_symbols called with null cur_rec\n"); if ( !cur_rec ) return cur_rec; for ( cur_rec = cur_rec->next; cur_rec; cur_rec = cur_rec->next ) { if ( cur_rec->type == 5 ) break; if ( cur_rec->type == 4 ) { /* A new .iterate was encounter, killing this iteration set */ return (file_recp) 0; } } if ( !cur_rec ) return cur_rec; /* * Copy the line to local static storage so we can break it up into * nul-terminated strings. */ length = cur_rec->length; if ( length >= sizeof(line) ) length = sizeof(line) - 1; strncpy ( line, cur_rec->s, length ); line[length++] = '\0'; /* cgi_printf("--rec: '%s', l=%d--
\n", line, length ); */ /* * Parse the values on the line and assign each to next symbol in table. */ state = pos = 0; for ( i = strlen(file_dir[5].tag); i < length; i++ ) { if ( isspace(line[i]) || (line[i] == '\0') ) { if ( state == 1 ) { /* found end, terminate string*/ line[i] = '\0'; state = 0; } } else { if ( state == 0 ) { /* Found start, save its position */ symbols[pos].value = &line[i]; pos++; if ( !symbols[pos].symbol ) break; /* no more symbols */ state = 1; } } } /* Blank out remaining values in symbol table. */ while ( symbols[pos].symbol ) symbols[pos++].value = ""; return cur_rec; } /**************************************************************************/ static file_recp control_function ( file_recp rec, struct symdef **symbols, char *suffix ) { int i, j; static char *edit_options; static struct symdef *original_symbols; static file_recp begin_rec; static int cur_ndx; static struct symdef local_symbols[16]; static file_recp config_rec; if ( strcmp ( rec->s, "$BEGIN" ) == 0 ) { /* * Begin an iteration loop. */ original_symbols = *symbols; begin_rec = rec; cur_ndx = 0; edit_options = "\n", tmp ); } else { cgi_printf ( "\n", tmp ); } tag[i] = '|'; } else continue; tmp = &tag[i+1]; } else if ( tag[i] == '?' ) { tag[i] = '\0'; if ( state == 0 ) { for ( j = 0; symbols[j].symbol; j++ ) { if ( strcmp ( tag, symbols[j].symbol ) == 0 ) { value = symbols[j].value; break; } } cgi_printf("", tmp); break; case 2: cgi_printf(" maxlength=\"%s\">", tmp); break; case 3: case 5: cgi_printf(">"); break; case 4: if ( strncmp ( value, tmp, 100 ) == 0 ) { cgi_printf ( "\n", tmp ); } else { cgi_printf ( "\n", tmp ); } break; default: cgi_printf ( "-Syntax error in template-" ); } return 1; } /**************************************************************************/ /* Output HTML form from template, loading input fields with current * values for configuration file. */ static void generate_form ( char *path_info, char *fname ) { int i, j, k, status; file_recp rec; char suffix[32]; struct symdef *symbols; symbols = collect_symbols ( 1, 0 ); suffix[0] = '\0'; /* Appended to all input field names */ cgi_printf("\n"); cgi_printf("Server Configuration File %s\n\n", fname ); cgi_printf("
\n", url_host(), cgi_info("SCRIPT_NAME"), path_info ); /* * Process each record segment; */ for ( rec = form_file.next; rec; rec = rec->next ) { char *cp; cp = rec->s; if ( rec->type == 0 ) { /* text segment */ status = cgi_printf ( "%s", rec->s ); } else if ( rec->type == 1 ) { /* Last text segment on a line */ status = cgi_printf ( "%s\n", rec->s ); } else if ( rec->type == 2 ) { /* Input marker */ status = expand_form_tag ( rec->s, suffix, rec->length, symbols ); } else { /* control tag, may reset the position. */ rec = control_function ( rec, &symbols, suffix ); if ( !rec ) return; } if ( (status&1) == 0 ) return; } cgi_printf ( "
ServerMaint 1.2, %s last modified %s\n", fname, ctime ( &rule_file_mdate ) ); return; } /* * Convert special characters to 'entity' forms: <>&". If no expansions, * function value returned is pointer to source string, otherwise return value is * pointer to malloc'ed string which caller must deallocate. * pointer to ori */ static char *entify_string ( char *source ) { int i, j,brack_count; char *dest; dest = source; brack_count = 0; for ( i = 0; source[i]; i++ ) { if ( (source[i] == '<') || (source[i] == '>') || (source[i] == '&') || (source[i] == '"') ) brack_count++; } if ( brack_count > 0 ) { dest = malloc ( i + brack_count*6 + 1 ); if ( !dest ) return source; for ( i = j = 0; source[i]; i++ ) { if ( source[i] == '<' ) { dest[j++] = '&'; dest[j++] = 'l'; dest[j++] = 't'; dest[j++] = ';'; } else if ( source[i] == '>' ) { dest[j++] = '&'; dest[j++] = 'g'; dest[j++] = 't'; dest[j++] = ';'; } else if ( source[i] == '&' ) { dest[j++] = '&'; dest[j++] = 'a'; dest[j++] = 'm'; dest[j++] = 'p'; dest[j++] = ';'; } else if ( source[i] == '"' ) { /* * NOTE: We should really be producing ""e;" here, but * browser's are broken so use our own convention of "''". */ dest[j++] = '\''; dest[j++] = '\''; } else dest[j++] = source[i]; } dest[j] = '\0'; } return dest; }