/* $Id: interface.c 7277 2005-06-07 04:40:16Z eagle $ ** ** Storage Manager interface */ #include "config.h" #include "clibrary.h" #include #include #include #include #include "conffile.h" #include "inn/innconf.h" #include "inn/wire.h" #include "interface.h" #include "libinn.h" #include "methods.h" #include "paths.h" typedef enum {INIT_NO, INIT_DONE, INIT_FAIL} INITTYPE; typedef struct { INITTYPE initialized; bool configured; bool selfexpire; bool expensivestat; } METHOD_DATA; METHOD_DATA method_data[NUM_STORAGE_METHODS]; static STORAGE_SUB *subscriptions = NULL; static unsigned int typetoindex[256]; int SMerrno; char *SMerrorstr = NULL; static bool ErrorAlloc = false; static bool Initialized = false; bool SMopenmode = false; bool SMpreopen = false; /* ** Checks to see if the token is valid */ bool IsToken(const char *text) { const char *p; if (!text) return false; if (strlen(text) != (sizeof(TOKEN) * 2) + 2) return false; if (text[0] != '@') return false; if (text[(sizeof(TOKEN) * 2) + 1] != '@') return false; for (p = text + 1; *p != '@'; p++) if (!isxdigit((int)*p)) return false; return true; } /* ** Converts a token to a textual representation for error messages ** and the like. */ char * TokenToText(const TOKEN token) { static const char hex[] = "0123456789ABCDEF"; static char result[(sizeof(TOKEN) * 2) + 3]; const char *p; char *q; size_t i; result[0] = '@'; for (q = result + 1, p = (const char *) &token, i = 0; i < sizeof(TOKEN); i++, p++) { *q++ = hex[(*p & 0xF0) >> 4]; *q++ = hex[*p & 0x0F]; } *q++ = '@'; *q++ = '\0'; return result; } /* ** Converts a hex digit and converts it to a int */ static int hextodec(const int c) { return isdigit(c) ? (c - '0') : ((c - 'A') + 10); } /* ** Converts a textual representation of a token back to a native ** representation */ TOKEN TextToToken(const char *text) { const char *p; char *q; int i; TOKEN token; if (text[0] == '@') p = &text[1]; else p = text; for (q = (char *)&token, i = 0; i != sizeof(TOKEN); i++) { q[i] = (hextodec(*p) << 4) + hextodec(*(p + 1)); p += 2; } return token; } /* ** Given an article and length in non-wire format, return a malloced region ** containing the article in wire format. Set *newlen to the length of the ** new article. */ char * ToWireFmt(const char *article, size_t len, size_t *newlen) { size_t bytes; char *newart; const char *p; char *dest; bool atstartofline=true; /* First go thru article and count number of bytes we need. */ for (bytes = 0, p=article ; p < &article[len] ; ++p) { if (*p == '.' && atstartofline) ++bytes; /* 1 byte for escaping . */ ++bytes; if (*p == '\n') { ++bytes; /* need another byte for CR */ atstartofline = true; /* next char starts new line */ } else { atstartofline = false; } } bytes += 3; /* for .\r\n */ newart = xmalloc(bytes + 1); *newlen = bytes; /* now copy the article, making changes */ atstartofline = true; for (p=article, dest=newart ; p < &article[len] ; ++p) { if (*p == '\n') { *dest++ = '\r'; *dest++ = '\n'; atstartofline = true; } else { if (atstartofline && *p == '.') *dest++ = '.'; /* add extra . */ *dest++ = *p; atstartofline = false; } } *dest++ = '.'; *dest++ = '\r'; *dest++ = '\n'; *dest = '\0'; return newart; } char * FromWireFmt(const char *article, size_t len, size_t *newlen) { size_t bytes; char *newart; const char *p; char *dest; bool atstartofline = true; /* First go thru article and count number of bytes we need */ for (bytes = 0, p=article ; p < &article[len] ; ) { /* check for terminating .\r\n and if so break */ if (p == &article[len-3] && *p == '.' && p[1] == '\r' && p[2] == '\n') break; /* check for .. at start-of-line */ if (atstartofline && p < &article[len-1] && *p == '.' && p[1] == '.') { bytes++; /* only output 1 byte */ p+=2; atstartofline = false; } else if (p < &article[len-1] && *p == '\r' && p[1] == '\n') { bytes++; /* \r\n counts as only one byte in output */ p += 2; atstartofline = true; } else { bytes++; p++; atstartofline = false; } } newart = xmalloc(bytes + 1); *newlen = bytes; for (p = article, dest = newart ; p < &article[len]; ) { /* check for terminating .\r\n and if so break */ if (p == &article[len-3] && *p == '.' && p[1] == '\r' && p[2] == '\n') break; if (atstartofline && p < &article[len-1] && *p == '.' && p[1] == '.') { *dest++ = '.'; p += 2; atstartofline = false; } else if (p < &article[len-1] && *p == '\r' && p[1] == '\n') { *dest++ = '\n'; p += 2; atstartofline = true; } else { *dest++ = *p++; atstartofline = false; } } *dest = '\0'; return newart; } /* ** get Xref header without pathhost */ static char * GetXref(ARTHANDLE *art) { const char *p, *p1; const char *q; char *buff; bool Nocr = false; p = wire_findheader(art->data, art->len, "xref"); if (p == NULL) return NULL; q = p; for (p1 = NULL; p < art->data + art->len; p++) { if (p1 != (char *)NULL && *p1 == '\r' && *p == '\n') { Nocr = false; break; } if (*p == '\n') { Nocr = true; break; } p1 = p; } if (p >= art->data + art->len) return NULL; if (!Nocr) p = p1; /* skip pathhost */ for (; (*q == ' ') && (q < p); q++); if (q == p) return NULL; if ((q = memchr(q, ' ', p - q)) == NULL) return NULL; for (q++; (*q == ' ') && (q < p); q++); if (q == p) return NULL; buff = xmalloc(p - q + 1); memcpy(buff, q, p - q); buff[p - q] = '\0'; return buff; } /* ** Split newsgroup and returns artnum ** or 0 if there are no newsgroup. */ static ARTNUM GetGroups(char *Xref) { char *p; if ((p = strchr(Xref, ':')) == NULL) return 0; *p++ = '\0'; return ((ARTNUM)atoi(p)); } STORAGE_SUB *SMGetConfig(STORAGETYPE type, STORAGE_SUB *sub) { if (sub == (STORAGE_SUB *)NULL) sub = subscriptions; else sub = sub->next; for (;sub != NULL; sub = sub->next) { if (sub->type == type) { return sub; } } return (STORAGE_SUB *)NULL; } static time_t ParseTime(char *tmbuf) { char *startnum; time_t ret; int tmp; ret = 0; startnum = tmbuf; while (*tmbuf) { if (!isdigit((int)*tmbuf)) { tmp = atol(startnum); switch (*tmbuf) { case 'M': ret += tmp*60*60*24*31; break; case 'd': ret += tmp*60*60*24; break; case 'h': ret += tmp*60*60; break; case 'm': ret += tmp*60; break; case 's': ret += tmp; break; default: return(0); } startnum = tmbuf+1; } tmbuf++; } return(ret); } #define SMlbrace 1 #define SMrbrace 2 #define SMmethod 10 #define SMgroups 11 #define SMsize 12 #define SMclass 13 #define SMexpire 14 #define SMoptions 15 #define SMexactmatch 16 static CONFTOKEN smtoks[] = { { SMlbrace, "{" }, { SMrbrace, "}" }, { SMmethod, "method" }, { SMgroups, "newsgroups:" }, { SMsize, "size:" }, { SMclass, "class:" }, { SMexpire, "expires:" }, { SMoptions, "options:" }, { SMexactmatch, "exactmatch:" }, { 0, 0 } }; /* Open the config file and parse it, generating the policy data */ static bool SMreadconfig(void) { CONFFILE *f; CONFTOKEN *tok; int type; int i; char *p; char *q; char *path; char *method = NULL; char *pattern = NULL; size_t minsize = 0; size_t maxsize = 0; time_t minexpire = 0; time_t maxexpire = 0; int class = 0; STORAGE_SUB *sub = NULL; STORAGE_SUB *prev = NULL; char *options = 0; int inbrace; bool exactmatch = false; /* if innconf isn't already read in, do so. */ if (innconf == NULL) { if (!innconf_read(NULL)) { SMseterror(SMERR_INTERNAL, "ReadInnConf() failed"); return false; } } for (i = 0; i < NUM_STORAGE_METHODS; i++) { method_data[i].initialized = INIT_NO; method_data[i].configured = false; } path = concatpath(innconf->pathetc, _PATH_STORAGECTL); f = CONFfopen(path); if (f == NULL) { SMseterror(SMERR_UNDEFINED, NULL); syslog(L_ERROR, "SM Could not open %s: %m", path); free(path); return false; } free(path); inbrace = 0; while ((tok = CONFgettoken(smtoks, f)) != NULL) { if (!inbrace) { if (tok->type != SMmethod) { SMseterror(SMERR_CONFIG, "Expected 'method' keyword"); syslog(L_ERROR, "SM expected 'method' keyword, line %d", f->lineno); return false; } if ((tok = CONFgettoken(0, f)) == NULL) { SMseterror(SMERR_CONFIG, "Expected method name"); syslog(L_ERROR, "SM expected method name, line %d", f->lineno); return false; } method = xstrdup(tok->name); if ((tok = CONFgettoken(smtoks, f)) == NULL || tok->type != SMlbrace) { SMseterror(SMERR_CONFIG, "Expected '{'"); syslog(L_ERROR, "SM Expected '{', line %d", f->lineno); return false; } inbrace = 1; /* initialize various params to defaults. */ minsize = 0; maxsize = 0; /* zero means no limit */ class = 0; pattern = NULL; options = NULL; minexpire = 0; maxexpire = 0; exactmatch = false; } else { type = tok->type; if (type == SMrbrace) inbrace = 0; else { if ((tok = CONFgettoken(0, f)) == NULL) { SMseterror(SMERR_CONFIG, "Keyword with no value"); syslog(L_ERROR, "SM keyword with no value, line %d", f->lineno); return false; } p = tok->name; switch(type) { case SMgroups: if (pattern) free(pattern); pattern = xstrdup(tok->name); break; case SMsize: minsize = strtoul(p, NULL, 10); if ((p = strchr(p, ',')) != NULL) { p++; maxsize = strtoul(p, NULL, 10); } break; case SMclass: class = atoi(p); if (class > NUM_STORAGE_CLASSES) { SMseterror(SMERR_CONFIG, "Storage class too large"); warn("SM: storage class larger than %d, line %d", NUM_STORAGE_CLASSES, f->lineno); return false; } break; case SMexpire: q = strchr(p, ','); if (q) *q++ = 0; minexpire = ParseTime(p); if (q) maxexpire = ParseTime(q); break; case SMoptions: if (options) free(options); options = xstrdup(p); break; case SMexactmatch: if (strcasecmp(p, "true") == 0 || strcasecmp(p, "yes") == 0 || strcasecmp(p, "on") == 0) exactmatch = true; break; default: SMseterror(SMERR_CONFIG, "Unknown keyword in method declaration"); syslog(L_ERROR, "SM Unknown keyword in method declaration, line %d: %s", f->lineno, tok->name); free(method); return false; break; } } } if (!inbrace) { /* just finished a declaration */ sub = xmalloc(sizeof(STORAGE_SUB)); sub->type = TOKEN_EMPTY; for (i = 0; i < NUM_STORAGE_METHODS; i++) { if (!strcasecmp(method, storage_methods[i].name)) { sub->type = storage_methods[i].type; method_data[i].configured = true; break; } } if (sub->type == TOKEN_EMPTY) { SMseterror(SMERR_CONFIG, "Invalid storage method name"); syslog(L_ERROR, "SM no configured storage methods are named '%s'", method); free(options); free(sub); return false; } if (!pattern) { SMseterror(SMERR_CONFIG, "pattern not defined"); syslog(L_ERROR, "SM no pattern defined"); free(options); free(sub); return false; } sub->pattern = pattern; sub->minsize = minsize; sub->maxsize = maxsize; sub->class = class; sub->options = options; sub->minexpire = minexpire; sub->maxexpire = maxexpire; sub->exactmatch = exactmatch; free(method); method = 0; if (!prev) subscriptions = sub; if (prev) prev->next = sub; prev = sub; sub->next = NULL; } } CONFfclose(f); return true; } /* ** setup storage api environment (open mode etc.) */ bool SMsetup(SMSETUP type, void *value) { if (Initialized) return false; switch (type) { case SM_RDWR: SMopenmode = *(bool *)value; break; case SM_PREOPEN: SMpreopen = *(bool *)value; break; default: return false; } return true; } /* ** Calls the setup function for all of the configured methods and returns ** true if they all initialize ok, false if they don't */ bool SMinit(void) { int i; bool allok = true; static bool once = false; SMATTRIBUTE smattr; if (Initialized) return true; Initialized = true; if (!SMreadconfig()) { SMshutdown(); Initialized = false; return false; } for (i = 0; i < NUM_STORAGE_METHODS; i++) { if (method_data[i].configured) { if (method_data[i].configured && storage_methods[i].init(&smattr)) { method_data[i].initialized = INIT_DONE; method_data[i].selfexpire = smattr.selfexpire; method_data[i].expensivestat = smattr.expensivestat; } else { method_data[i].initialized = INIT_FAIL; method_data[i].selfexpire = false; method_data[i].expensivestat = true; syslog(L_ERROR, "SM storage method '%s' failed initialization", storage_methods[i].name); allok = false; } } typetoindex[storage_methods[i].type] = i; } if (!allok) { SMshutdown(); Initialized = false; SMseterror(SMERR_UNDEFINED, "one or more storage methods failed initialization"); syslog(L_ERROR, "SM one or more storage methods failed initialization"); return false; } if (!once && atexit(SMshutdown) < 0) { SMshutdown(); Initialized = false; SMseterror(SMERR_UNDEFINED, NULL); return false; } once = true; return true; } static bool InitMethod(STORAGETYPE method) { SMATTRIBUTE smattr; if (!Initialized) if (!SMreadconfig()) { Initialized = false; return false; } Initialized = true; if (method_data[method].initialized == INIT_DONE) return true; if (method_data[method].initialized == INIT_FAIL) return false; if (!method_data[method].configured) { method_data[method].initialized = INIT_FAIL; SMseterror(SMERR_UNDEFINED, "storage method is not configured."); return false; } if (!storage_methods[method].init(&smattr)) { method_data[method].initialized = INIT_FAIL; method_data[method].selfexpire = false; method_data[method].expensivestat = true; SMseterror(SMERR_UNDEFINED, "Could not initialize storage method late."); return false; } method_data[method].initialized = INIT_DONE; method_data[method].selfexpire = smattr.selfexpire; method_data[method].expensivestat = smattr.expensivestat; return true; } static bool MatchGroups(const char *g, int len, const char *pattern, bool exactmatch) { char *group, *groups, *q; int i, lastwhite; enum uwildmat matched; bool wanted = false; q = groups = xmalloc(len + 1); for (lastwhite = -1, i = 0 ; i < len ; i++) { /* trim white chars */ if (g[i] == '\r' || g[i] == '\n' || g[i] == ' ' || g[i] == '\t') { if (lastwhite + 1 != i) *q++ = ' '; lastwhite = i; } else *q++ = g[i]; } *q = '\0'; group = strtok(groups, " ,"); while (group != NULL) { q = strchr(group, ':'); if (q != NULL) *q = '\0'; matched = uwildmat_poison(group, pattern); if (matched == UWILDMAT_POISON || (exactmatch && !matched)) { free(groups); return false; } if (matched == UWILDMAT_MATCH) wanted = true; group = strtok(NULL, " ,"); } free(groups); return wanted; } STORAGE_SUB *SMgetsub(const ARTHANDLE article) { STORAGE_SUB *sub; if (article.len == 0) { SMseterror(SMERR_BADHANDLE, NULL); return NULL; } if (article.groups == NULL) return NULL; for (sub = subscriptions; sub != NULL; sub = sub->next) { if (!(method_data[typetoindex[sub->type]].initialized == INIT_FAIL) && (article.len >= sub->minsize) && (!sub->maxsize || (article.len <= sub->maxsize)) && (!sub->minexpire || article.expires >= sub->minexpire) && (!sub->maxexpire || (article.expires <= sub->maxexpire)) && MatchGroups(article.groups, article.groupslen, sub->pattern, sub->exactmatch)) { if (InitMethod(typetoindex[sub->type])) return sub; } } errno = 0; SMseterror(SMERR_NOMATCH, "no matching entry in storage.conf"); return NULL; } TOKEN SMstore(const ARTHANDLE article) { STORAGE_SUB *sub; TOKEN result; if (!SMopenmode) { result.type = TOKEN_EMPTY; SMseterror(SMERR_INTERNAL, "read only storage api"); return result; } result.type = TOKEN_EMPTY; if ((sub = SMgetsub(article)) == NULL) { return result; } return storage_methods[typetoindex[sub->type]].store(article, sub->class); } ARTHANDLE *SMretrieve(const TOKEN token, const RETRTYPE amount) { ARTHANDLE *art; if (method_data[typetoindex[token.type]].initialized == INIT_FAIL) { SMseterror(SMERR_UNINIT, NULL); return NULL; } if (method_data[typetoindex[token.type]].initialized == INIT_NO && !InitMethod(typetoindex[token.type])) { syslog(L_ERROR, "SM could not find token type or method was not initialized (%d)", token.type); SMseterror(SMERR_UNINIT, NULL); return NULL; } art = storage_methods[typetoindex[token.type]].retrieve(token, amount); if (art) art->nextmethod = 0; return art; } ARTHANDLE *SMnext(const ARTHANDLE *article, const RETRTYPE amount) { unsigned char i; int start; ARTHANDLE *newart; if (article == NULL) start = 0; else start= article->nextmethod; if (method_data[start].initialized == INIT_FAIL) { SMseterror(SMERR_UNINIT, NULL); return NULL; } if (method_data[start].initialized == INIT_NO && method_data[start].configured && !InitMethod(start)) { SMseterror(SMERR_UNINIT, NULL); return NULL; } for (i = start, newart = NULL; i < NUM_STORAGE_METHODS; i++) { if (method_data[i].configured && (newart = storage_methods[i].next(article, amount)) != (ARTHANDLE *)NULL) { newart->nextmethod = i; break; } else article = NULL; } return newart; } void SMfreearticle(ARTHANDLE *article) { if (method_data[typetoindex[article->type]].initialized == INIT_FAIL) { return; } if (method_data[typetoindex[article->type]].initialized == INIT_NO && !InitMethod(typetoindex[article->type])) { syslog(L_ERROR, "SM can't free article with uninitialized method"); return; } storage_methods[typetoindex[article->type]].freearticle(article); } bool SMcancel(TOKEN token) { if (!SMopenmode) { SMseterror(SMERR_INTERNAL, "read only storage api"); return false; } if (method_data[typetoindex[token.type]].initialized == INIT_FAIL) { SMseterror(SMERR_UNINIT, NULL); return false; } if (method_data[typetoindex[token.type]].initialized == INIT_NO && !InitMethod(typetoindex[token.type])) { SMseterror(SMERR_UNINIT, NULL); syslog(L_ERROR, "SM can't cancel article with uninitialized method"); return false; } return storage_methods[typetoindex[token.type]].cancel(token); } bool SMprobe(PROBETYPE type, TOKEN *token, void *value) { struct artngnum *ann; ARTHANDLE *art; switch (type) { case SELFEXPIRE: return (method_data[typetoindex[token->type]].selfexpire); case SMARTNGNUM: if (method_data[typetoindex[token->type]].initialized == INIT_FAIL) { SMseterror(SMERR_UNINIT, NULL); return false; } if (method_data[typetoindex[token->type]].initialized == INIT_NO && !InitMethod(typetoindex[token->type])) { SMseterror(SMERR_UNINIT, NULL); syslog(L_ERROR, "SM can't cancel article with uninitialized method"); return false; } if ((ann = (struct artngnum *)value) == NULL) return false; ann->groupname = NULL; if (storage_methods[typetoindex[token->type]].ctl(type, token, value)) { if (ann->artnum != 0) { /* set by storage method */ return true; } else { art = storage_methods[typetoindex[token->type]].retrieve(*token, RETR_HEAD); if (art == NULL) { if (ann->groupname != NULL) free(ann->groupname); storage_methods[typetoindex[token->type]].freearticle(art); return false; } if ((ann->groupname = GetXref(art)) == NULL) { if (ann->groupname != NULL) free(ann->groupname); storage_methods[typetoindex[token->type]].freearticle(art); return false; } storage_methods[typetoindex[token->type]].freearticle(art); if ((ann->artnum = GetGroups(ann->groupname)) == 0) { if (ann->groupname != NULL) free(ann->groupname); return false; } return true; } } else { return false; } case EXPENSIVESTAT: return (method_data[typetoindex[token->type]].expensivestat); default: return false; } } bool SMflushcacheddata(FLUSHTYPE type) { int i; for (i = 0; i < NUM_STORAGE_METHODS; i++) { if (method_data[i].initialized == INIT_DONE && !storage_methods[i].flushcacheddata(type)) syslog(L_ERROR, "SM can't flush cached data method '%s'", storage_methods[i].name); } return true; } void SMprintfiles(FILE *file, TOKEN token, char **xref, int ngroups) { if (method_data[typetoindex[token.type]].initialized == INIT_FAIL) return; if (method_data[typetoindex[token.type]].initialized == INIT_NO && !InitMethod(typetoindex[token.type])) { SMseterror(SMERR_UNINIT, NULL); syslog(L_ERROR, "SM can't print files for article with uninitialized method"); return; } storage_methods[typetoindex[token.type]].printfiles(file, token, xref, ngroups); } void SMshutdown(void) { int i; STORAGE_SUB *old; if (!Initialized) return; for (i = 0; i < NUM_STORAGE_METHODS; i++) if (method_data[i].initialized == INIT_DONE) { storage_methods[i].shutdown(); method_data[i].initialized = INIT_NO; method_data[i].configured = false; } while (subscriptions) { old = subscriptions; subscriptions = subscriptions->next; free(old->pattern); free(old->options); free(old); } Initialized = false; } void SMseterror(int errornum, char *error) { if (ErrorAlloc) free(SMerrorstr); ErrorAlloc = false; if ((errornum == SMERR_UNDEFINED) && (errno == ENOENT)) errornum = SMERR_NOENT; SMerrno = errornum; if (error == NULL) { switch (SMerrno) { case SMERR_UNDEFINED: SMerrorstr = xstrdup(strerror(errno)); ErrorAlloc = true; break; case SMERR_INTERNAL: SMerrorstr = "Internal error"; break; case SMERR_NOENT: SMerrorstr = "Token not found"; break; case SMERR_TOKENSHORT: SMerrorstr = "Configured token size too small"; break; case SMERR_NOBODY: SMerrorstr = "No article body found"; break; case SMERR_UNINIT: SMerrorstr = "Storage manager is not initialized"; break; case SMERR_CONFIG: SMerrorstr = "Error reading config file"; break; case SMERR_BADHANDLE: SMerrorstr = "Bad article handle"; break; case SMERR_BADTOKEN: SMerrorstr = "Bad token"; break; case SMERR_NOMATCH: SMerrorstr = "No matching entry in storage.conf"; break; default: SMerrorstr = "Undefined error"; } } else { SMerrorstr = xstrdup(error); ErrorAlloc = true; } }