/* * Copyright (c) 2000, Boris Popov * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by Boris Popov. * 4. Neither the name of the author nor the names of any co-contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * $Id: rcfile.c,v 1.1.1.2 2001/07/06 22:38:43 conrad Exp $ */ #pragma ident "%Z%%M% %I% %E% SMI" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "rcfile_priv.h" SLIST_HEAD(rcfile_head, rcfile); static struct rcfile_head pf_head = {NULL}; static struct rcfile *rc_cachelookup(const char *filename); struct rcsection *rc_findsect(struct rcfile *rcp, const char *sectname); static struct rcsection *rc_addsect(struct rcfile *rcp, const char *sectname); static int rc_freesect(struct rcfile *rcp, struct rcsection *rsp); struct rckey *rc_sect_findkey(struct rcsection *rsp, const char *keyname); static struct rckey *rc_sect_addkey(struct rcsection *rsp, const char *name, const char *value); static void rc_key_free(struct rckey *p); static void rc_parse(struct rcfile *rcp); int insecure_nsmbrc; /* * open rcfile and load its content, if already open - return previous handle */ int rc_open(const char *filename, const char *mode, struct rcfile **rcfile) { struct rcfile *rcp; FILE *f; struct stat statbuf; rcp = rc_cachelookup(filename); if (rcp) { *rcfile = rcp; return (0); } f = fopen(filename, mode); if (f == NULL) return (errno); insecure_nsmbrc = 0; if (fstat(fileno(f), &statbuf) >= 0 && (statbuf.st_mode & 077) != 0) insecure_nsmbrc = 1; rcp = malloc(sizeof (struct rcfile)); if (rcp == NULL) { fclose(f); return (ENOMEM); } bzero(rcp, sizeof (struct rcfile)); rcp->rf_name = strdup(filename); rcp->rf_f = f; SLIST_INSERT_HEAD(&pf_head, rcp, rf_next); rc_parse(rcp); *rcfile = rcp; return (0); } int rc_merge(const char *filename, struct rcfile **rcfile) { struct rcfile *rcp = *rcfile; FILE *f, *t; insecure_nsmbrc = 0; if (rcp == NULL) { return (rc_open(filename, "r", rcfile)); } f = fopen(filename, "r"); if (f == NULL) return (errno); t = rcp->rf_f; rcp->rf_f = f; rc_parse(rcp); rcp->rf_f = t; fclose(f); return (0); } int rc_merge_pipe(const char *command, struct rcfile **rcfile) { struct rcfile *rcp = *rcfile; FILE *f, *t; insecure_nsmbrc = 0; f = popen(command, "r"); if (f == NULL) return (errno); if (rcp == NULL) { rcp = malloc(sizeof (struct rcfile)); if (rcp == NULL) { fclose(f); return (ENOMEM); } *rcfile = rcp; bzero(rcp, sizeof (struct rcfile)); rcp->rf_name = strdup(command); rcp->rf_f = f; SLIST_INSERT_HEAD(&pf_head, rcp, rf_next); rc_parse(rcp); } else { t = rcp->rf_f; rcp->rf_f = f; rc_parse(rcp); rcp->rf_f = t; } fclose(f); return (0); } int rc_close(struct rcfile *rcp) { struct rcsection *p, *n; fclose(rcp->rf_f); for (p = SLIST_FIRST(&rcp->rf_sect); p; ) { n = p; p = SLIST_NEXT(p, rs_next); rc_freesect(rcp, n); } free(rcp->rf_name); SLIST_REMOVE(&pf_head, rcp, rcfile, rf_next); free(rcp); return (0); } static struct rcfile * rc_cachelookup(const char *filename) { struct rcfile *p; SLIST_FOREACH(p, &pf_head, rf_next) if (strcmp(filename, p->rf_name) == 0) return (p); return (0); } /* static */ struct rcsection * rc_findsect(struct rcfile *rcp, const char *sectname) { struct rcsection *p; SLIST_FOREACH(p, &rcp->rf_sect, rs_next) if (strcasecmp(p->rs_name, sectname) == 0) return (p); return (NULL); } static struct rcsection * rc_addsect(struct rcfile *rcp, const char *sectname) { struct rcsection *p; p = rc_findsect(rcp, sectname); if (p) return (p); p = malloc(sizeof (*p)); if (!p) return (NULL); p->rs_name = strdup(sectname); SLIST_INIT(&p->rs_keys); SLIST_INSERT_HEAD(&rcp->rf_sect, p, rs_next); return (p); } static int rc_freesect(struct rcfile *rcp, struct rcsection *rsp) { struct rckey *p, *n; SLIST_REMOVE(&rcp->rf_sect, rsp, rcsection, rs_next); for (p = SLIST_FIRST(&rsp->rs_keys); p; ) { n = p; p = SLIST_NEXT(p, rk_next); rc_key_free(n); } free(rsp->rs_name); free(rsp); return (0); } /* static */ struct rckey * rc_sect_findkey(struct rcsection *rsp, const char *keyname) { struct rckey *p; SLIST_FOREACH(p, &rsp->rs_keys, rk_next) if (strcmp(p->rk_name, keyname) == 0) return (p); return (NULL); } static struct rckey * rc_sect_addkey(struct rcsection *rsp, const char *name, const char *value) { struct rckey *p; p = rc_sect_findkey(rsp, name); if (!p) { p = malloc(sizeof (*p)); if (!p) return (NULL); SLIST_INSERT_HEAD(&rsp->rs_keys, p, rk_next); p->rk_name = strdup(name); p->rk_value = value ? strdup(value) : strdup(""); } return (p); } #if 0 void rc_sect_delkey(struct rcsection *rsp, struct rckey *p) { SLIST_REMOVE(&rsp->rs_keys, p, rckey, rk_next); rc_key_free(p); } #endif static void rc_key_free(struct rckey *p) { free(p->rk_value); free(p->rk_name); free(p); } enum { stNewLine, stHeader, stSkipToEOL, stGetKey, stGetValue}; int home_nsmbrc = 0; static char *minauth[] = { "kerberos", "ntlmv2", "ntlm", "lm", "none", NULL }; static int eval_minauth(char *auth) { int i; for (i = 0; minauth[i]; i++) if (strcmp(auth, minauth[i]) == 0) break; return (i); } /* * Ensure that "minauth" is set to the highest level (lowest array offset) */ static void set_value(struct rcfile *rcp, struct rcsection *rsp, struct rckey *rkp, char *ptr) { int now, new; if (strcmp(rkp->rk_name, "minauth") == 0) { now = eval_minauth(rkp->rk_value); new = eval_minauth(ptr); if (new >= now) { #ifdef DEBUG printf("set_value: rejecting %s=%s from %s\n", rkp->rk_name, ptr, home_nsmbrc ? "user file" : "SMF"); #endif return; } } #ifdef DEBUG printf("set_value: applying %s=%s from %s\n", rkp->rk_name, ptr, home_nsmbrc ? "user file" : "SMF"); #endif rkp->rk_value = strdup(ptr); } static void rc_parse(struct rcfile *rcp) { FILE *f = rcp->rf_f; int state = stNewLine, c; struct rcsection *rsp = NULL; struct rckey *rkp = NULL; char buf[2048]; char *next = buf, *last = &buf[sizeof (buf)-1]; while ((c = getc(f)) != EOF) { if (c == '\r') continue; if (state == stNewLine) { next = buf; if (isspace(c)) continue; /* skip leading junk */ if (c == '[') { state = stHeader; rsp = NULL; continue; } if (c == '#' || c == ';') { state = stSkipToEOL; } else { /* something meaningfull */ state = stGetKey; } } /* ignore long lines */ if (state == stSkipToEOL || next == last) { if (c == '\n') { state = stNewLine; next = buf; } continue; } if (state == stHeader) { if (c == ']') { *next = 0; next = buf; rsp = rc_addsect(rcp, buf); state = stSkipToEOL; } else *next++ = c; continue; } if (state == stGetKey) { /* side effect: 'key name=' */ if (c == ' ' || c == '\t') continue; /* become 'keyname=' */ if (c == '\n') { /* silently ignore ... */ state = stNewLine; continue; } if (c != '=') { *next++ = c; continue; } *next = 0; if (rsp == NULL) { fprintf(stderr, dgettext(TEXT_DOMAIN, "Key '%s' defined before section\n"), buf); state = stSkipToEOL; continue; } if (home_nsmbrc && (strcmp(buf, "nbns") == 0 || strcmp(buf, "nbns_enable") == 0 || strcmp(buf, "nbns_broadcast") == 0)) { fprintf(stderr, dgettext(TEXT_DOMAIN, "option %s may not be set " "in user .nsmbrc file\n"), buf); next = buf; state = stNewLine; continue; } if (insecure_nsmbrc && (strcmp(buf, "password") == 0)) { fprintf(stderr, dgettext(TEXT_DOMAIN, "Warning: .nsmbrc file not secure, " "ignoring passwords\n")); next = buf; state = stNewLine; continue; } rkp = rc_sect_addkey(rsp, buf, NULL); next = buf; state = stGetValue; continue; } /* only stGetValue left */ if (state != stGetValue) { fprintf(stderr, dgettext(TEXT_DOMAIN, "Well, I can't parse file '%s'\n"), rcp->rf_name); state = stSkipToEOL; } if (c != '\n') { *next++ = c; continue; } *next = 0; set_value(rcp, rsp, rkp, buf); state = stNewLine; rkp = NULL; } /* while */ if (c == EOF && state == stGetValue) { *next = 0; set_value(rcp, rsp, rkp, buf); } } int rc_getstringptr(struct rcfile *rcp, const char *section, const char *key, char **dest) { struct rcsection *rsp; struct rckey *rkp; *dest = NULL; rsp = rc_findsect(rcp, section); if (!rsp) return (ENOENT); rkp = rc_sect_findkey(rsp, key); if (!rkp) return (ENOENT); *dest = rkp->rk_value; return (0); } int rc_getstring(struct rcfile *rcp, const char *section, const char *key, size_t maxlen, char *dest) { char *value; int error; error = rc_getstringptr(rcp, section, key, &value); if (error) return (error); if (strlen(value) >= maxlen) { fprintf(stdout, dgettext(TEXT_DOMAIN, "line too long for key '%s' in section '%s', max = %d\n"), key, section, maxlen); return (EINVAL); } strcpy(dest, value); return (0); } int rc_getint(struct rcfile *rcp, const char *section, const char *key, int *value) { struct rcsection *rsp; struct rckey *rkp; rsp = rc_findsect(rcp, section); if (!rsp) return (ENOENT); rkp = rc_sect_findkey(rsp, key); if (!rkp) return (ENOENT); errno = 0; *value = strtol(rkp->rk_value, NULL, 0); if (errno) { fprintf(stdout, dgettext(TEXT_DOMAIN, "invalid int value '%s' for key '%s' in section '%s'\n"), rkp->rk_value, key, section); return (errno); } return (0); } /* * 1,yes,true * 0,no,false */ int rc_getbool(struct rcfile *rcp, const char *section, const char *key, int *value) { struct rcsection *rsp; struct rckey *rkp; char *p; rsp = rc_findsect(rcp, section); if (!rsp) return (ENOENT); rkp = rc_sect_findkey(rsp, key); if (!rkp) return (ENOENT); p = rkp->rk_value; while (*p && isspace(*p)) p++; if (*p == '0' || strcasecmp(p, "no") == 0 || strcasecmp(p, "false") == 0) { *value = 0; return (0); } if (*p == '1' || strcasecmp(p, "yes") == 0 || strcasecmp(p, "true") == 0) { *value = 1; return (0); } fprintf(stderr, dgettext(TEXT_DOMAIN, "invalid boolean value '%s' for key '%s' in section '%s' \n"), p, key, section); return (EINVAL); } /* * Unified command line/rc file parser */ int opt_args_parse(struct rcfile *rcp, struct opt_args *ap, const char *sect, opt_callback_t *callback) { int len, error; for (; ap->opt; ap++) { switch (ap->type) { case OPTARG_STR: if (rc_getstringptr(rcp, sect, ap->name, &ap->str) != 0) break; len = strlen(ap->str); if (len > ap->ival) { fprintf(stdout, dgettext(TEXT_DOMAIN, "rc: argument for option '%c' (%s) too long\n"), ap->opt, ap->name); return (EINVAL); } callback(ap); break; case OPTARG_BOOL: error = rc_getbool(rcp, sect, ap->name, &ap->ival); if (error == ENOENT) break; if (error) return (EINVAL); callback(ap); break; case OPTARG_INT: if (rc_getint(rcp, sect, ap->name, &ap->ival) != 0) break; if (((ap->flag & OPTFL_HAVEMIN) && ap->ival < ap->min) || ((ap->flag & OPTFL_HAVEMAX) && ap->ival > ap->max)) { fprintf(stdout, dgettext(TEXT_DOMAIN, "rc: argument for option '%c' (%s) " "should be in [%d-%d] range\n"), ap->opt, ap->name, ap->min, ap->max); return (EINVAL); } callback(ap); break; default: break; } } return (0); } int opt_args_parseopt(struct opt_args *ap, int opt, char *arg, opt_callback_t *callback) { int len; for (; ap->opt; ap++) { if (ap->opt != opt) continue; switch (ap->type) { case OPTARG_STR: ap->str = arg; if (arg) { len = strlen(ap->str); if (len > ap->ival) { fprintf(stdout, dgettext(TEXT_DOMAIN, "opt: Argument for option '%c' (%s) too long\n"), ap->opt, ap->name); return (EINVAL); } callback(ap); } break; case OPTARG_BOOL: ap->ival = 0; callback(ap); break; case OPTARG_INT: errno = 0; ap->ival = strtol(arg, NULL, 0); if (errno) { fprintf(stdout, dgettext(TEXT_DOMAIN, "opt: Invalid integer value for " "option '%c' (%s).\n"), ap->opt, ap->name); return (EINVAL); } if (((ap->flag & OPTFL_HAVEMIN) && (ap->ival < ap->min)) || ((ap->flag & OPTFL_HAVEMAX) && (ap->ival > ap->max))) { fprintf(stdout, dgettext(TEXT_DOMAIN, "opt: Argument for option '%c' (%s) " "should be in [%d-%d] range\n"), ap->opt, ap->name, ap->min, ap->max); return (EINVAL); } callback(ap); break; default: break; } break; } return (0); }