/*************************************** This is part of frox: A simple transparent FTP proxy Copyright (C) 2000 James Hollingshead This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA config.c: configuration file parsing %_REPLACE_COMMENT_% ***************************************/ #include #include #include #include #include #include #include "common.h" void usage(int option); void set_defaults(void); struct options config; enum vartype { BOOL, FILENAME, DIRECTRY, STRING, ADDRESS, ADDRPRT, INT, PRTRNGE, ACL, SUBSECT }; struct option_array { const char *name; enum vartype type; void *var; char cmdline; int reloadable; int essential; }; struct option_array opts[] = { /* %_REPLACE_OPTIONS_INIT_% */ }; void print_config(void); int name2addr(sstr * str, struct in_addr *addr); int store_acl(sstr * str, struct acl_list *acls); int store_subsect(sstr * str, struct subsect_list *list); int set_opt(struct option_array *opt, sstr * str); u_int16_t *get_portlist(sstr * str); int process_opts(void); int parse_line(sstr * buf); int match_acl(struct acl_item *acl, struct sockaddr_in *src, struct sockaddr_in *dst, const char *user); static char cmdline_set[100] = { 0 }; static int rereading = FALSE; static int sublevel = 0; static int line_no; /* ------------------------------------------------------------- ** ** Functions to read / parse the config file. ** ------------------------------------------------------------- */ int process_cmdline(int argc, char *argv[]) { int i; struct option_array *opt; sstr *arg; char *optp, *argp; set_defaults(); if (argc <= 1) return 0; arg = sstr_init(0); i = 1; optp = argv[i] + 1; while (i < argc) { if (argv[i][0] != '-') usage(-1); opt = opts; if (*optp == '-') usage(-1); /*?TODO longopts */ else { while (opt->name != NULL && opt->cmdline != *optp) opt++; if (opt->name == NULL) usage(*optp); if (strlen(cmdline_set) < 99) cmdline_set[strlen(cmdline_set)] = *optp; if (opt->type == BOOL) { sstr_cpy2(arg, "yes"); set_opt(opt, arg); if (*(++optp) == 0) { i++; optp = argv[i] + 1; } } else { argp = optp + 1; if (*argp == 0) { if (i >= argc - 1 || *(argp = argv[++i]) == '-') usage(*optp); } sstr_cpy2(arg, argp); set_opt(opt, arg); i++; optp = argv[i] + 1; } } } sstr_free(arg); return (0); } /*This is out here as a global so that if config is copied to a backup location read_config can be called recursively to read a subsection.*/ FILE *fp = NULL; int read_config() { struct option_array *opt; sstr *buf; if (!fp) { if (config.config_file == NULL) fp = fopen(CONFIG_FILE, "r"); else fp = fopen(config.config_file, "r"); if (fp == NULL) return (-1); line_no=0; } buf = sstr_init(0); while (sstr_fgets(buf, fp) != NULL) { line_no++; switch (parse_line(buf)) { case -1: sstr_free(buf); if (sublevel == 0) fclose(fp); return (-1); case 0: break; case 1: /* end subsection */ sstr_free(buf); return (0); } }; sstr_free(buf); fclose(fp); fp = NULL; opt = opts; while (opt->name != NULL && !opt->essential) opt++; if (opt->name != NULL) { fprintf(stderr, "Essential option \"%s\" not specified.\n", opt->name); return (-1); } process_opts(); #ifdef DEBUG if(!config.inetd) print_config(); #endif return (0); } int parse_line(sstr * buf) { struct option_array *opt; sstr *tok; tok = sstr_init(0); if (sstr_getchar(buf, 0) == '#') return (0); if (sstr_token(buf, tok, " =\t\r\n", 0) == -1) return (0); if (!sstr_casecmp2(tok, "EndSection")) return (sublevel > 0 ? 1 : -1); opt = opts; while (opt->name != NULL && sstr_casecmp2(tok, opt->name)) opt++; if (opt->name == NULL) { fprintf(stderr,"Unrecognised option \"%s\" at line %d of %s\n", sstr_buf(tok), line_no, config.config_file); return (-1); } if (opt->cmdline && strchr(cmdline_set, opt->cmdline)) { fprintf(stderr, "\"%s\" specified on the command line: ignored\n", opt->name); return (0); } if (set_opt(opt, buf) == -1) { fprintf(stderr, "Invalid argument to %s at line %d of %s\n", opt->name, line_no, config.config_file); return (-1); } return (0); } void usage(int option) { if (option != 'h' && option != -1) fprintf(stderr, "Unknown option %c\n", option); fprintf(stderr, "Usage frox [-f config_file] [-h]\n"); exit(option == 'h' ? 0 : -1); } void set_defaults(void) { sstr *tmp; tmp = sstr_init(0); /* %_REPLACE_OPTIONS_DEFAULTS_% */ sstr_free(tmp); } /*Any final processing that needs to be done*/ int process_opts(void) { if (config.chroot) { /*So that filename stripping works properly */ if (config.chroot[strlen(config.chroot) - 1] == '/') config.chroot[strlen(config.chroot) - 1] = 0; } /* Get uid/gid before we chroot */ if (config.group) { struct group *grp; if ((grp = getgrnam(config.group)) == NULL) { fprintf(stderr, "Unable to find group %s\n", config.group); return (-1); } config.gid = grp->gr_gid; } if (config.user) { struct passwd *pw; if ((pw = getpwnam(config.user)) == NULL) { fprintf(stderr, "Unable to find user %s\n", config.user); return (-1); } config.uid = pw->pw_uid; } config.listen_address.sin_addr = config.listen; config.listen_address.sin_port = htons(config.lport); config.listen_address.sin_family = AF_INET; return (0); } int reread_config(void) { int i; struct acl_list acls; acls = config.acls; config.acls.num = 0; config.acls.list = NULL; rereading = TRUE; sublevel = 0; i = read_config(); if (i == -1) config.acls = acls; else free(acls.list); return (i); } int set_opt(struct option_array *opt, sstr * str) { sstr *tok; int sep; if (rereading && !opt->reloadable) return (0); tok = sstr_init(0); sstr_ncat2(str, "\n", 1); /*For sstr_token */ opt->essential = FALSE; switch (opt->type) { case BOOL: sstr_token(str, tok, " \t\r\n", 0); if (!sstr_casecmp2(tok, "yes")) *(int *) opt->var = TRUE; else if (!sstr_casecmp2(tok, "no")) *(int *) opt->var = FALSE; else return (-1); break; case FILENAME: case STRING: sstr_token(str, tok, " \t\r\n", SSTR_QTOK); *(char **) opt->var = strdup(sstr_buf(tok)); break; case DIRECTRY: sstr_token(str, tok, " \t\r\n", SSTR_QTOK); if (sstr_getchar(tok, sstr_len(tok) - 1) != '/') sstr_ncat2(tok, "/", 1); *(char **) opt->var = strdup(sstr_buf(tok)); break; case ADDRESS: sstr_token(str, tok, " \t\r\n", 0); return name2addr(tok, (struct in_addr *) opt->var); break; case ADDRPRT: sep = sstr_token(str, tok, " :\t\r\n", 0); if( name2addr(tok, &((struct sockaddr_in *) opt->var)->sin_addr)==-1) return(-1); if (sep == ':') ((struct sockaddr_in *) opt->var)->sin_port = ntohs(sstr_atoi(str)); ((struct sockaddr_in *) opt->var)->sin_family = AF_INET; break; case INT: sstr_token(str, tok, " \t\r\n", 0); *(int *) opt->var = sstr_atoi(tok); break; case PRTRNGE: sstr_token(str, tok, " \t-,\r\n", 0); ((int *) opt->var)[0] = sstr_atoi(tok); ((int *) opt->var)[1] = sstr_atoi(str); if(!valid_uint16(((int *) opt->var)[0]) || !valid_uint16(((int *) opt->var)[1])) { fprintf(stderr, "Port out of range\n"); return -1; } if(((int *) opt->var)[0] >= ((int *) opt->var)[1]) { fprintf(stderr, "Port range is inverted\n"); return -1; } break; case ACL: return store_acl(str, (struct acl_list *) opt->var); break; case SUBSECT: return store_subsect(str, (struct subsect_list *) opt->var); } sstr_free(tok); return (0); } int name2addr(sstr * str, struct in_addr *addr) { int c; c = sstr_getchar(str, 0); if (sstr_len(str) == 1 && c == '*') { addr->s_addr = INADDR_ANY; return (0); } if (c < '0' || c > '9') { struct hostent *hostinfo; hostinfo = gethostbyname(sstr_buf(str)); if (hostinfo == NULL) { fprintf(stderr, "Unable to resolve host %s\n", sstr_buf(str)); return (-1); } *addr = *((struct in_addr *) *hostinfo->h_addr_list); } else { if (inet_aton(sstr_buf(str), addr) == 0) return (-1); } return (0); } int addrange_match(struct in_addr addr, char *range) { char *s; if(*range=='*') return TRUE; else if((s=strchr(range, '/'))) { struct in_addr a, m; s = strchr(range, '/'); *s++='\0'; if(inet_aton(range, &a) == 0) { write_log(ERROR, "Unable to parse ACL. Ignoring."); return FALSE; } if(strchr(s, '.')) { if (inet_aton(s, &m) == 0) { write_log(ERROR, "Unable to parse ACL. Ignoring."); return FALSE; } } else { int i = atoi(s); m.s_addr = htonl(0xFFFFFFFF << (32-i)); } if((addr.s_addr & m.s_addr) == (a.s_addr & m.s_addr)) return TRUE; else return FALSE; } else if(*range >= '0' && *range<='9') { struct in_addr a; if(inet_aton(range, &a) == 0) { write_log(ERROR, "Unable to parse ACL. Ignoring."); return FALSE; } if(a.s_addr == addr.s_addr) return TRUE; else return FALSE; } else { struct hostent *hostinfo; struct in_addr *ap; int i; hostinfo = gethostbyname(range); if (hostinfo == NULL) { write_log(ERROR, "Unable to resolve host %s in ACL.", range); return FALSE; } for(i=0;hostinfo->h_addr_list[i];i++){ ap = (struct in_addr *) hostinfo->h_addr_list[i]; if(ap->s_addr == addr.s_addr) return TRUE; } return FALSE; } } int store_addressrange(sstr * str, struct in_addr *addr, struct in_addr *msk) { sstr *tok; int sep, i; tok = sstr_init(0); sep = sstr_token(str, tok, " /->\r\n", 0); if(name2addr(tok, addr)==-1) return(-1); if (sep == '/') { sep = sstr_token(str, tok, " /->\r\n", 0); if (sstr_chr(tok, '.')==-1) { /* x.x.x.x/yy */ i = sstr_atoi(tok); msk->s_addr = htonl(0xFFFFFFFF << (32 - i)); } else if(name2addr(tok, msk)==-1) return(-1); } else { /*No mask given */ if (addr->s_addr == INADDR_ANY) msk->s_addr = INADDR_ANY; else msk->s_addr = INADDR_BROADCAST; } sstr_free(tok); return (0); } u_int16_t *get_portlist(sstr * str) { sstr *tok; int i = 0, sep; u_int16_t *ports = NULL; tok = sstr_init(0); do { ports = realloc(ports, (i + 2) * sizeof(u_int16_t)); if(!ports) die(ERROR, "Out of memory reading config file", 0, 0, -1); sep = sstr_token(str, tok, " \t,-\r\n", 0); if (sep == -1) break; if (!sstr_cmp2(tok, "*")) { ports[i] = 1; ports[i + 1] = 0xFFFF; i += 2; break; } ports[i] = ports[i + 1] = sstr_atoi(tok); if (sep == '-') { sep = sstr_token(str, tok, " \t,\n", 0); if (sep == -1) ports[i + 1] = 0xFFFF; else ports[i + 1] = sstr_atoi(tok); } i += 2; } while (sep != -1); ports[i] = ports[i + 1] = 0; sstr_free(tok); return (ports); } void parse_match(sstr *str, struct acl_item *item) { sstr *tok; tok = sstr_init(0); item->user = NULL; sstr_token(str, tok, " \t\n", 0); item->src = strdup(sstr_buf(tok)); sstr_token(str, NULL, " \t\n", 0); /*Removes the " - " from the ACL*/ if(sstr_chr(str, '@')!=-1) { sstr_token(str, tok, "@", 0); item->user = strdup(sstr_buf(tok)); config.fakentp = TRUE; } sstr_token(str, tok, " \t\n", 0); item->dst = strdup(sstr_buf(tok)); item->ntp_ports = get_portlist(str); sstr_free(tok); } int store_acl(sstr * str, struct acl_list *acls) { sstr *tok; struct acl_item item; tok = sstr_init(0); sstr_token(str, tok, " \t\n", 0); if (!sstr_casecmp2(tok, "Allow")) item.action = ALLOW; else if (!sstr_casecmp2(tok, "Deny")) item.action = DENY; else return (-1); sstr_free(tok); parse_match(str, &item); acls->list = realloc(acls->list, (acls->num + 1) * sizeof(struct acl_item)); if(!acls->list) return(-1); memcpy(acls->list + acls->num, &item, sizeof(struct acl_item)); acls->num++; return (0); } /* This is quite horrible, but it does seem to work... We make a shallow copy of config, and then recall read_config. config will now be changed with any additional options in the subsection - we copy it where we want it, and restore config. The copy is only a shallow one, but we get away with it as other funcs overwrite pointers not their contents. */ int store_subsect(sstr * str, struct subsect_list *subsecs) { struct options tmp; struct subsect item; parse_match(str, &item.match); memcpy(&tmp, &config, sizeof(struct options)); sublevel++; config.acls.list = NULL; config.subsecs.list = NULL; config.acls.num = config.subsecs.num = 0; if (read_config() == -1) return (-1); sublevel--; if(process_opts()==-1) return(-1); memcpy(&item.config, &config, sizeof(struct options)); memcpy(&config, &tmp, sizeof(struct options)); subsecs->list = realloc(subsecs->list, (subsecs->num + 1) * sizeof(struct subsect)); memcpy(subsecs->list + subsecs->num, &item, sizeof(struct subsect)); subsecs->num++; return (0); } /* ------------------------------------------------------------- ** ** Alter absolute pathnames to still work after chroot. ** ------------------------------------------------------------- */ void strip_filenames(void) { struct option_array *opt = opts; char *filename; if (config.chroot == NULL) return; do { if (opt->type != DIRECTRY && opt->type != FILENAME) continue; filename = *(char **) opt->var; if (filename == NULL) continue; debug2("stripping %s: ", opt->name); debug2("%s-->", filename); if (strncmp(config.chroot, filename, strlen(config.chroot))) *filename = 0; else { memmove(filename, filename + strlen(config.chroot), strlen(filename) - strlen(config.chroot) + 1); if(opt->type == DIRECTRY && filename[strlen(filename)-1]!='/') strcat(filename, "/"); } debug2("%s\n", filename); } while ((++opt)->name != NULL); free(config.chroot); config.chroot="/"; } /* ------------------------------------------------------------- ** ** Functions to return config file options. ** ------------------------------------------------------------- */ int match_acl(struct acl_item *acl, struct sockaddr_in *src, struct sockaddr_in *dst, const char *user) { u_int16_t port; int i; char ftpstr[4]; const char *userp; if(!strcasecmp(user, "anonymous")) { strcpy(ftpstr, "ftp"); userp=ftpstr; } else userp = user; port = ntohs(dst->sin_port); if (addrange_match(src->sin_addr, acl->src) && addrange_match(dst->sin_addr, acl->dst) && (!acl->user || !strcasecmp(userp, acl->user))) { for (i = 0; acl->ntp_ports[i] != 0; i += 2) if (port >= acl->ntp_ports[i] && port <= acl->ntp_ports[i + 1]) { return (TRUE); } if (i == 0) return (TRUE); } return (FALSE); } int config_connectionok(struct sockaddr_in *src, struct sockaddr_in *dst, const char *user) { int i; for (i = 0; i < config.acls.num; i++) if (match_acl(config.acls.list + i, src, dst, user)) break; if (i != config.acls.num) { if (config.acls.list[i].action == ALLOW) { config_change(src, dst, user); return (TRUE); } else return (FALSE); } return (FALSE); } void config_change(struct sockaddr_in *src, struct sockaddr_in *dst, const char *user) { int i; for (i = 0; i < config.subsecs.num; i++) if (match_acl(&config.subsecs.list[i].match, src, dst, user)) break; if (i != config.subsecs.num) { write_log(VERBOSE, "Changing to config file subsection %d", i+1); memcpy(&config, &config.subsecs.list[i].config, sizeof(config)); } } int config_pasvok(struct sockaddr_in *addr) { if (addr->sin_addr.s_addr == 0) return (FALSE); if (config.sameaddress) { if (addr->sin_addr.s_addr != info->server_control.address.sin_addr.s_addr) { return (FALSE); } } return (TRUE); } int config_portok(struct sockaddr_in *addr) { if (addr->sin_addr.s_addr == 0) return (FALSE); if (config.sameaddress) if (addr->sin_addr.s_addr != info->client_control.address.sin_addr.s_addr) return (FALSE); if (config.bdefend) if (ntohs(addr->sin_port) < 1024) return (FALSE); return (TRUE); } #ifdef DEBUG void print_config(void) { struct option_array *opt; struct subsect *p; int i, j; opt = opts; while (opt->name != NULL) { switch (opt->type) { case BOOL: fprintf(stderr, "%s = %s\n", opt->name, *(int *) opt->var == TRUE ? "TRUE" : "FALSE"); break; case FILENAME: case DIRECTRY: case STRING: fprintf(stderr, "%s = %s\n", opt->name, *(char **) opt->var); break; case ADDRESS: fprintf(stderr, "%s = %s\n", opt->name, inet_ntoa(*(struct in_addr *) opt->var)); break; case ADDRPRT: fprintf(stderr, "%s = %s:%d\n", opt->name, inet_ntoa(((struct sockaddr_in *) opt-> var)->sin_addr), htons(((struct sockaddr_in *) opt->var)-> sin_port)); break; case INT: fprintf(stderr, "%s = %d\n", opt->name, *(int *) opt->var); break; case PRTRNGE: fprintf(stderr, "%s = %d-%d\n", opt->name, ((int *) opt->var)[0], ((int *) opt->var)[1]); break; case ACL: case SUBSECT: break; } opt++; } for (i = 0; i < config.acls.num; i++) { fprintf(stderr, "ACL: %s", config.acls.list[i].src); fprintf(stderr, " --> %s @ %s", config.acls.list[i].user ? config.acls.list[i].user : "*", config.acls.list[i].dst); fprintf(stderr, ": %s ", config.acls.list[i].action == DENY ? "DENY" : "ALLOW"); for (j = 0; config.acls.list[i].ntp_ports[j] != 0; j += 2) { fprintf(stderr, "%d-%d,", config.acls.list[i].ntp_ports[j], config.acls.list[i].ntp_ports[j + 1]); } fprintf(stderr, "\n"); } for (i = 0; i < config.subsecs.num; i++) { struct options tmp; p = config.subsecs.list + i; fprintf(stderr, "Subsection: %s", p->match.src); fprintf(stderr, " --> %s @ %s", p->match.user ? p->match.user : "*", p->match.dst); for (j = 0; p->match.ntp_ports[j] != 0; j += 2) { fprintf(stderr, "%d-%d,", p->match.ntp_ports[j], p->match.ntp_ports[j + 1]); } fprintf(stderr, " {\n"); memcpy(&tmp, &config, sizeof(struct options)); memcpy(&config, &p->config, sizeof(struct options)); print_config(); memcpy(&config, &tmp, sizeof(struct options)); fprintf(stderr, "}\n"); } } #endif