/* This file is in the public domain. */ #include #include #include #include #include #include #include #include #include #include #include extern const char *__progname; typedef enum { DT_UNSET = 1, DT_INT, DT_FLOAT, } DATATYPE; typedef enum { SPRV_GOOD = 1, SPRV_UNKNOWN, SPRV_ERROR, } SPRV; typedef unsigned long long int TIMEINTVL; typedef struct config CONFIG; typedef struct repo REPO; typedef struct listen LISTEN; typedef struct lep LEP; typedef struct lepfd LEPFD; typedef struct lep_ops LEP_OPS; typedef struct map MAP; typedef struct map_ops MAP_OPS; typedef struct linereader LINEREADER; typedef struct sect_ops SECT_OPS; typedef struct config_parse_temp CONFIG_PARSE_TEMP; struct linereader { unsigned int flags; #define LRF_EOF 0x00000001 #define LRF_ERROR 0x00000002 int fd; unsigned char ibuf[8192]; int ibfill; int ibptr; unsigned char *lb; int ll; int la; int lno; } ; struct config { REPO *repos; LISTEN *listens; struct stat confstat; CONFIG_PARSE_TEMP *pt; } ; struct config_parse_temp { SECT_OPS *ops; void *priv; void (*err_throw)(char *); } ; struct listen { LISTEN *link; unsigned int permit; #define OP_NEW 0x00000001 #define OP_FETCH 0x00000002 LEP *endpoints; MAP *mappings; } ; struct lep { LEP *link; LEP_OPS *ops; void *priv; } ; struct map { MAP *link; MAP_OPS *ops; void *priv; } ; struct repo { REPO *link; char *name; char *filename; DATATYPE type; TIMEINTVL maxgap; } ; struct lep_ops { int dummy; } ; #define LEP_OPS_INIT(name) { 0 } struct map_ops { void *(*match)(const char *); } ; struct sect_ops { void *(*start)(const char *, CONFIG *); SPRV (*parse)(void *, const char *); void (*done)(void *); } ; #define SECT_OPS_INIT(name) { §_##name##_start, §_##name##_parse, §_##name##_done } #define UCisspace(c) isspace((unsigned char)(c)) static const char *configfile = "scd.conf"; static CONFIG *conf; static SECT_OPS sectops_repo; static SECT_OPS sectops_listen; static SECT_OPS *sects[] = { §ops_repo, §ops_listen }; static void handleargs(int ac, char **av) { int skip; int errs; skip = 0; errs = 0; for (ac--,av++;ac;ac--,av++) { if (skip > 0) { skip --; continue; } if (**av != '-') { fprintf(stderr,"%s: extra argument `%s'\n",__progname,*av); errs = 1; continue; } if (0) { needarg:; fprintf(stderr,"%s: %s needs a following argument\n",__progname,*av); errs = 1; continue; } #define WANTARG() do { if (++skip >= ac) goto needarg; } while (0) if (!strcmp(*av,"-config")) { WANTARG(); configfile = av[skip]; continue; } #undef WANTARG fprintf(stderr,"%s: unrecognized option `%s'\n",__progname,*av); errs = 1; } if (errs) exit(1); } static void init(void) { conf = 0; } static LINEREADER *lr_setup(int fd) { LINEREADER *lr; lr = malloc(sizeof(LINEREADER)); lr->flags = 0; lr->fd = fd; lr->ibfill = 0; lr->ibptr = 0; lr->lb = 0; lr->ll = 0; lr->la = 0; lr->lno = 0; return(lr); } static void lr_done(LINEREADER *lr) { free(lr->lb); free(lr); } typedef enum { LRGL_LINE = 1, LRGL_EOF, LRGL_ERROR, } LRGLSTAT; static LRGLSTAT lr_getline(LINEREADER *lr) { int c; void save(char ch) { if (lr->ll >= lr->la) lr->lb = realloc(lr->lb,lr->la=lr->ll+8); lr->lb[lr->ll++] = ch; } if (lr->flags & LRF_EOF) return(LRGL_EOF); if (lr->flags & LRF_ERROR) return(LRGL_ERROR); lr->ll = 0; while <"reading"> (1) { if (lr->ibptr >= lr->ibfill) { lr->ibptr = 0; lr->ibfill = read(lr->fd,&lr->ibuf[0],sizeof(lr->ibuf)); if (lr->ibfill < 0) { lr->flags |= LRF_ERROR; return(LRGL_ERROR); } if (lr->ibfill == 0) { lr->flags |= LRF_EOF; if (lr->ll > 0) break <"reading">; return(LRGL_EOF); } } c = lr->ibuf[lr->ibptr++]; if (c == '\n') break <"reading">; save(c); } save('\0'); lr->ll --; lr->lno ++; return(LRGL_LINE); } static const char *lr_ptr(LINEREADER *lr) { return(lr->lb); } static int lr_lineno(LINEREADER *lr) { return(lr->lno); } static void config_err(CONFIG *, const char *, ...) __attribute__((__format__(__printf__,2,3),__noreturn__)); static void config_err(CONFIG *cfg, const char *fmt, ...) { va_list ap; char *s; va_start(ap,fmt); asprintf(&s,fmt,ap); va_end(ap); if (! cfg->pt) abort(); (*cfg->pt->err_throw)(s); } static void config_err_post(void (*)(void), CONFIG *, const char *, ...) __attribute__((__format__(__printf__,3,4),__noreturn__)); static void config_err_post(void (*post)(void), CONFIG *cfg, const char *fmt, ...) { va_list ap; char *s; va_start(ap,fmt); asprintf(&s,fmt,ap); va_end(ap); (*post)(); if (! cfg->pt) abort(); (*cfg->pt->err_throw)(s); } static void config_prep(CONFIG *cfg) { cfg->repos = 0; cfg->listens = 0; cfg->pt = malloc(sizeof(CONFIG_PARSE_TEMP)); cfg->pt->ops = 0; cfg->pt->priv = 0; cfg->pt->err_throw = 0; } static int config_process(CONFIG *cfg, const char *line) { int i; if (cfg->pt->ops) { switch ((*cfg->pt->ops->parse)(cfg->pt->priv,line)) { case SPRV_GOOD: return(0); break; case SPRV_UNKNOWN: break; case SPRV_ERROR: return(1); break; default: abort(); break; } } for (i=(sizeof(sects)/sizeof(sects[0]))-1;i>=0;i--) { void *pp; pp = (*sects[i]->start)(line,cfg); if (pp) { char *errmsg; void free_errmsg(void) { free(errmsg); } errmsg = 0; if (cfg->pt->ops) { __label__ errjmp; void err_throw(char *msg) { errmsg = msg; goto errjmp; } void (*saved_err_throw)(char *); saved_err_throw = cfg->pt->err_throw; cfg->pt->err_throw = &err_throw; (*cfg->pt->ops->done)(cfg->pt->priv); errjmp:; cfg->pt->err_throw = saved_err_throw; } cfg->pt->ops = sects[i]; cfg->pt->priv = pp; if (errmsg) config_err_post(&free_errmsg,cfg,"previous section: %s",errmsg); } } if (cfg->pt->ops) { config_err(cfg,"not valid in this section and not a valid section start line"); } else { config_err(cfg,"not a valid section start line"); } } static void config_free(CONFIG *cfg) { cfg=cfg; /* XXX XXX XXX */ } static void config_replace(CONFIG *old, CONFIG *new) { old=old; new=new; /* XXX XXX XXX */ } static int reload_config(void) { __label__ errjmp; CONFIG *cfg; int fd; LINEREADER *lr; int err; int errs; void err_throw(char *msg) { fprintf(stderr,"%s: line %d: %s\n",configfile,lr_lineno(lr),msg); free(msg); goto errjmp; } fd = open(configfile,O_RDONLY,0); if (fd < 0) { fprintf(stderr,"%s: %s: open: %s\n",__progname,configfile,strerror(errno)); return(-1); } lr = lr_setup(fd); cfg = malloc(sizeof(CONFIG)); config_prep(cfg); if (fstat(fd,&cfg->confstat) < 0) { fprintf(stderr,"%s: %s: fstat %s\n",__progname,configfile,strerror(errno)); lr_done(lr); free(cfg); close(fd); return(-1); } errs = 0; while <"lines"> (1) { switch (lr_getline(lr)) { case LRGL_ERROR: errs = 1; break <"lines">; case LRGL_EOF: break <"lines">; case LRGL_LINE: break; default: abort(); break; } cfg->pt->err_throw = &err_throw; err = config_process(cfg,lr_ptr(lr)); if (0) { errjmp:; err = 1; } cfg->pt->err_throw = 0; if (err) errs = 1; if (err < 0) break; } lr_done(lr); if (errs) { config_free(cfg); return(-1); } else { config_replace(conf,cfg); conf = cfg; return(0); } } static char *blk_to_nulterm(const void *buf, int len) { char *s; s = malloc(len+1); if (len > 0) bcopy(buf,s,len); s[len] = '\0'; return(s); } static TIMEINTVL parse_time_interval(const char *s, void (*err)(const char *, ...)) { struct { char letter; unsigned int val; int have; } specs[] = { { 'y' }, #define PTI_SX_Y 0 { 'm' }, #define PTI_SX_M1 1 { 'w' }, #define PTI_SX_W 2 { 'd' }, #define PTI_SX_D 3 { 'h' }, #define PTI_SX_H 4 { 'm' }, #define PTI_SX_M2 5 { 's' } }; #define PTI_SX_S 6 #define PTI_N_SPECS (sizeof(specs)/sizeof(specs[0])) int sx; unsigned long int ulv; unsigned int uv; char *e; for (sx=PTI_N_SPECS-1;sx>=0;sx--) { specs[sx].have = 0; specs[sx].val = 0; } while (1) { while (*s && UCisspace(*s)) s ++; if (! *s) break; ulv = strtoul(s,&e,10); if (e == s) (*err)("bad number in time interval"); if (! *e) (*err)("missing unit in time interval"); uv = ulv; if (uv != ulv) (*err)("out-of-range number in time interval"); while (1) { if (sx >= PTI_N_SPECS) (*err)("bad unit `%c' in time interval",*e); if (*e == specs[sx].letter) break; sx ++; } specs[sx].have = 1; specs[sx].val = uv; sx ++; } if ( specs[PTI_SX_M1].have && !specs[PTI_SX_M2].have && !specs[PTI_SX_W].have && !specs[PTI_SX_D].have && !specs[PTI_SX_H].have ) { if (specs[PTI_SX_Y].have && !specs[PTI_SX_S].have) { /* do nothing, already correct */ } else if (specs[PTI_SX_S].have && !specs[PTI_SX_Y].have) { specs[PTI_SX_M2].have = 1; specs[PTI_SX_M2].val = specs[PTI_SX_M1].val; specs[PTI_SX_M1].have = 0; specs[PTI_SX_M1].val = 0; } else { (*err)("ambiguous `m' in time interval"); } } return( (specs[PTI_SX_Y ].val * 31556952) + (specs[PTI_SX_M1].val * 2629746) + (specs[PTI_SX_W ].val * 604800) + (specs[PTI_SX_D ].val * 86400) + (specs[PTI_SX_H ].val * 3600) + (specs[PTI_SX_M2].val * 60) + (specs[PTI_SX_S ].val * 1) ); } static void repo_free(REPO *r) { free(r->name); free(r->filename); free(r); } static void config_add_repo(CONFIG *cfg, REPO *r) { r->link = cfg->repos; cfg->repos = r; } typedef struct sect_repo_priv SECT_REPO_PRIV; struct sect_repo_priv { unsigned int have; /* no HAVE_NAME; the name is on the repo line. */ #define SRP_HAVE_FILENAME 0x00000001 #define SRP_HAVE_TYPE 0x00000002 #define SRP_HAVE_GAP 0x00000004 REPO *r; CONFIG *cfg; } ; static void *sect_repo_start(const char *line, CONFIG *cfg) { int i; SECT_REPO_PRIV *p; int j; int k; i = 0; while (line[i] && UCisspace(line[i])) i ++; if (strncmp(line+i,"repo",4) || (line[i+4] && !UCisspace(line[i+4]))) return(0); i += 4; while (line[i] && UCisspace(line[i])) i ++; if (! line[i]) config_err(cfg,"missing repo name"); j = i; while (line[i] && !UCisspace(line[i])) i ++; k = i; while (line[i] && UCisspace(line[i])) i ++; if (line[i]) config_err(cfg,"space in repo name"); p = malloc(sizeof(SECT_REPO_PRIV)); p->have = 0; p->r = malloc(sizeof(REPO)); p->r->name = blk_to_nulterm(line+j,k-j); p->r->filename = 0; p->cfg = cfg; return(p); } static SPRV sect_repo_parse(void *pv, const char *line) { int i; int j; int k; SECT_REPO_PRIV *p; p = pv; i = 0; while (line[i] && UCisspace(line[i])) i ++; j = i; while (line[i] && !UCisspace(line[i])) i ++; k = i; while (line[i] && UCisspace(line[i])) i ++; if ((k-j == 4) && !bcmp(line+j,"file",4)) { if (p->have & SRP_HAVE_FILENAME) config_err(p->cfg,"extra `file' line in repo"); if (! line[i]) config_err(p->cfg,"missing filename on repo `file' line"); p->r->filename = strdup(line+i); p->have |= SRP_HAVE_FILENAME; return(SPRV_GOOD); } else if ((k-j == 4) && !bcmp(line+j,"type",4)) { if (p->have & SRP_HAVE_TYPE) config_err(p->cfg,"extra `type' line in repo"); if (! line[i]) config_err(p->cfg,"missing type on repo `type' line"); if (!strcmp(line+i,"int")) { p->r->type = DT_INT; } else if (!strcmp(line+i,"float")) { p->r->type = DT_FLOAT; } else { config_err(p->cfg,"bad type on repo `type' line"); } p->have |= SRP_HAVE_TYPE; return(SPRV_GOOD); } else if ((k-j == 3) && !bcmp(line+j,"gap",3)) { auto void err(const char *, ...) __attribute__((__format__(__printf__,1,2))); auto void err(const char *fmt, ...) { char *s; va_list ap; void free_s(void) { free(s); } va_start(ap,fmt); vasprintf(&s,fmt,ap); va_end(ap); config_err_post(&free_s,p->cfg,"%s",s); } if (p->have & SRP_HAVE_GAP) config_err(p->cfg,"extra `gap' line in repo"); if (! line[i]) config_err(p->cfg,"missing time interval on repo `gap' line"); p->r->maxgap = parse_time_interval(line+i,&err); p->have |= SRP_HAVE_GAP; return(SPRV_GOOD); } else { return(SPRV_UNKNOWN); } } static void sect_repo_done(void *pv) { SECT_REPO_PRIV *p; const char *msg; p = pv; switch (p->have & (SRP_HAVE_FILENAME|SRP_HAVE_TYPE|SRP_HAVE_GAP)) { case 0: msg = "incomplete repo spec (no file, type, or gap lines)"; break; case SRP_HAVE_GAP: msg = "incomplete repo spec: no file or type lines)"; break; case SRP_HAVE_TYPE : msg = "incomplete repo spec (no file or gap lines)"; break; case SRP_HAVE_TYPE | SRP_HAVE_GAP: msg = "incomplete repo spec (no file line)"; break; case SRP_HAVE_FILENAME : msg = "incomplete repo spec (no type or gap lines)"; break; case SRP_HAVE_FILENAME | SRP_HAVE_GAP: msg = "incomplete repo spec (no type line)"; break; case SRP_HAVE_FILENAME | SRP_HAVE_TYPE : msg = "incomplete repo spec (no gap line)"; break; case SRP_HAVE_FILENAME | SRP_HAVE_TYPE | SRP_HAVE_GAP: msg = 0; break; } if (msg) { repo_free(p->r); } else { config_add_repo(p->cfg,p->r); } free(p); if (msg) config_err(p->cfg,"%s",msg); } static SECT_OPS sectops_repo = SECT_OPS_INIT(repo); typedef struct sect_listen_priv SECT_LISTEN_PRIV; struct sect_listen_priv { LISTEN *l; CONFIG *cfg; } ; typedef struct listen_local_priv LISTEN_LOCAL_PRIV; struct listen_local_priv { char *path; int fd; } ; static LEP_OPS lep_ops_local = LEP_OPS_INIT(local); static void listen_add_local(SECT_LISTEN_PRIV *p, const char *path) { LEP *lep; LISTEN_LOCAL_PRIV *llp; if (! *path) config_err(p->cfg,"missing path on `local' listen line"); llp = malloc(sizeof(LISTEN_LOCAL_PRIV)); llp->path = strdup(path); llp->fd = -1; lep = malloc(sizeof(LEP)); lep->ops = &lep_ops_local; lep->priv = llp; lep->link = p->l->endpoints; p->l->endpoints = lep; } typedef struct listen_ip_priv LISTEN_IP_PRIV; struct listen_ip_priv { LISTEN_IP_PRIV *link; struct sockaddr *sa; socklen_t salen; /* thank you linux...not! */ int af; int socktype; int proto; int fd; } ; static LEP_OPS lep_ops_ip = LEP_OPS_INIT(ip); static void listen_add_ip(SECT_LISTEN_PRIV *p, const char *arg, int (*test)(const struct sockaddr *), const char *adj) { const char *slash; char *addrstr; const char *portstr; struct addrinfo hints; struct addrinfo *ai0; struct addrinfo *ai; int e; LISTEN_IP_PRIV *list; LISTEN_IP_PRIV *le; LEP *lep; slash = index(arg,'/'); if (slash) { addrstr = blk_to_nulterm(arg,slash-arg); portstr = slash + 1; } else { addrstr = 0; portstr = arg; } hints.ai_flags = 0; hints.ai_family = PF_UNSPEC; hints.ai_socktype = SOCK_STREAM; hints.ai_protocol = 0; hints.ai_addrlen = 0; hints.ai_addr = 0; hints.ai_canonname = 0; hints.ai_next = 0; e = getaddrinfo(addrstr,portstr,&hints,&ai0); free(addrstr); switch (e) { case 0: if (ai0) break; e = EAI_NODATA; /* fall through */ default: config_err(p->cfg,"error looking up %s: %s",arg,gai_strerror(e)); break; } list = 0; for (ai=ai0;ai;ai=ai->ai_next) { if (! (*test)(ai->ai_addr)) continue; le = malloc(sizeof(LISTEN_IP_PRIV)); le->sa = malloc(ai->ai_addrlen); bcopy(ai->ai_addr,le->sa,ai->ai_addrlen); le->salen = ai->ai_addrlen; le->af = ai->ai_family; le->socktype = ai->ai_socktype; le->proto = ai->ai_protocol; le->fd = -1; le->link = list; list = le; } freeaddrinfo(ai0); if (! list) config_err(p->cfg,"error looking up %s: no %s addresses",arg,adj); lep = malloc(sizeof(LEP)); lep->ops = &lep_ops_ip; lep->priv = list; lep->link = p->l->endpoints; p->l->endpoints = lep; } static void *sect_listen_start(const char *line, CONFIG *cfg) { int i; SECT_LISTEN_PRIV *p; i = 0; while (line[i] && UCisspace(line[i])) i ++; if (strncmp(line+i,"listen",6) || (line[i+6] && !UCisspace(line[i+6]))) return(0); i += 6; while (line[i] && UCisspace(line[i])) i ++; if (line[i]) config_err(cfg,"junk on listen line"); p = malloc(sizeof(SECT_LISTEN_PRIV)); p->l = malloc(sizeof(LISTEN)); p->l->permit = 0; p->l->endpoints = 0; p->l->mappings = 0; p->cfg = cfg; return(p); } static SPRV sect_listen_parse(void *pv, const char *line) { int i; int j; int k; SECT_LISTEN_PRIV *p; p = pv; i = 0; while (line[i] && UCisspace(line[i])) i ++; j = i; while (line[i] && !UCisspace(line[i])) i ++; k = i; while (line[i] && UCisspace(line[i])) i ++; if ((k-j == 6) && !bcmp(line+j,"permit",6)) { while (line[i]) { j = i; while (line[i] && !UCisspace(line[i])) i ++; k = i; while (line[i] && UCisspace(line[i])) i ++; if ((k-j == 8) && !bcmp(line+j,"new-data",8)) { p->l->permit |= OP_NEW; } else if ((k-j == 5) && !bcmp(line+j,"fetch",5)) { p->l->permit |= OP_FETCH; } else if ((k-j == 1) && (line[j] == '*')) { p->l->permit |= OP_NEW | OP_FETCH; } else { config_err(p->cfg,"invalid operation `%.*s' on `permit' line",k-j,line+j); } } } else if ((k-j == 5) && !bcmp(line+j,"local",5)) { listen_add_local(p,line+i); } else if ((k-j == 2) && !bcmp(line+j,"ip",2)) { int accept_ip(const struct sockaddr *sa) { switch (sa->sa_family) { case AF_INET: case AF_INET6: return(1); break; } return(0); } listen_add_ip(p,line+i,&accept_ip,"IP"); } else if ((k-j == 3) && !bcmp(line+j,"ip4",3)) { int accept_ip4(const struct sockaddr *sa) { return(sa->sa_family==AF_INET); } listen_add_ip(p,line+i,&accept_ip4,"IPv4"); } else if ((k-j == 3) && !bcmp(line+j,"ip6",3)) { int accept_ip6(const struct sockaddr *sa) { return(sa->sa_family==AF_INET6); } listen_add_ip(p,line+i,&accept_ip6,"IPv6"); } else if ((k-j == 3) && !bcmp(line+j,"map",3)) { /* map trivial map error EXTNAME map simple EXTNAME INTNAME map regex /EXT/INT XXX XXX XXX */ } else { return(SPRV_UNKNOWN); } return(SPRV_GOOD); } static void sect_listen_done(void *pv) { /* XXX XXX XXX */ free(pv); } static SECT_OPS sectops_listen = SECT_OPS_INIT(listen); int main(int, char **); int main(int ac, char **av) { handleargs(ac,av); init(); reload_config(); return(0); }