diff options
Diffstat (limited to 'pith/context.c')
-rw-r--r-- | pith/context.c | 788 |
1 files changed, 788 insertions, 0 deletions
diff --git a/pith/context.c b/pith/context.c new file mode 100644 index 00000000..05f2d301 --- /dev/null +++ b/pith/context.c @@ -0,0 +1,788 @@ +#if !defined(lint) && !defined(DOS) +static char rcsid[] = "$Id: context.c 1144 2008-08-14 16:53:34Z hubert@u.washington.edu $"; +#endif + +/* + * ======================================================================== + * Copyright 2006-2007 University of Washington + * Copyright 2013 Eduardo Chappa + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * ======================================================================== + */ + +#include "../pith/headers.h" +#include "../pith/conf.h" +#include "../pith/context.h" +#include "../pith/state.h" +#include "../pith/status.h" +#include "../pith/stream.h" +#include "../pith/folder.h" +#include "../pith/util.h" +#include "../pith/tempfile.h" + + +/* + * Internal prototypes + */ +char *context_percent_quote(char *); + + +/* Context Manager context format digester + * Accepts: context string and buffers for sprintf-suitable context, + * remote host (for display), remote context (for display), + * and view string + * Returns: NULL if successful, else error string + * + * Comments: OK, here's what we expect a fully qualified context to + * look like: + * + * [*] [{host ["/"<proto>] [:port]}] [<cntxt>] "[" [<view>] "]" [<cntxt>] + * + * 2) It's understood that double "[" or "]" are used to + * quote a literal '[' or ']' in a context name. + * + * 3) an empty view in context implies a view of '*', so that's + * what get's put in the view string + * + * The 2nd,3rd,4th, and 5th args all have length at least len. + * + */ +char * +context_digest(char *context, char *scontext, char *host, char *rcontext, + char *view, size_t len) +{ + char *p, *viewp = view; + char *scontextp = scontext; + int i = 0; + + if((p = context) == NULL || *p == '\0'){ + if(scontext) /* so the caller can apply */ + strncpy(scontext, "%s", len); /* folder names as is. */ + + scontext[len-1] = '\0'; + return(NULL); /* no error, just empty context */ + } + + /* find hostname if requested and exists */ + if(*p == '{' || (*p == '*' && *++p == '{')){ + for(p++; *p && *p != '}' ; p++) + if(host && p-context < len-1) + *host++ = *p; /* copy host if requested */ + + while(*p && *p != '}') /* find end of imap host */ + p++; + + if(*p == '\0') + return("Unbalanced '}'"); /* bogus. */ + else + p++; /* move into next field */ + } + + for(; *p ; p++){ /* get thru context */ + if(rcontext && i < len-1) + rcontext[i] = *p; /* copy if requested */ + + i++; + + if(*p == '['){ /* done? */ + if(*++p == '\0') /* look ahead */ + return("Unbalanced '['"); + + if(*p != '[') /* not quoted: "[[" */ + break; + } + else if(*p == ']' && *(p+1) == ']') /* these may be quoted, too */ + p++; + } + + if(*p == '\0') + return("No '[' in context"); + + for(; *p ; p++){ /* possibly in view portion ? */ + if(*p == ']'){ + if(*(p+1) == ']') /* is quoted */ + p++; + else + break; + } + + if(viewp && viewp-view < len-1) + *viewp++ = *p; + } + + if(*p != ']') + return("No ']' in context"); + + for(; *p ; p++){ /* trailing context ? */ + if(rcontext && i < len-1) + rcontext[i] = *p; + i++; + } + + if(host) *host = '\0'; + if(rcontext && i < len) rcontext[i] = '\0'; + if(viewp) { +/* MAIL_LIST: dealt with it in new_context since could be either '%' or '*' + if(viewp == view && viewp-view < len-1) + *viewp++ = '*'; +*/ + + *viewp = '\0'; + } + + if(scontextp){ /* sprint'able context request ? */ + if(*context == '*'){ + if(scontextp-scontext < len-1) + *scontextp++ = *context; + + context++; + } + + if(*context == '{'){ + while(*context && *context != '}'){ + if(scontextp-scontext < len-1) + *scontextp++ = *context; + + context++; + } + + *scontextp++ = '}'; + } + + for(p = rcontext; *p ; p++){ + if(*p == '[' && *(p+1) == ']'){ + if(scontextp-scontext < len-2){ + *scontextp++ = '%'; /* replace "[]" with "%s" */ + *scontextp++ = 's'; + } + + p++; /* skip ']' */ + } + else if(scontextp-scontext < len-1) + *scontextp++ = *p; + } + + *scontextp = '\0'; + } + + return(NULL); /* no problems to report... */ +} + + +/* Context Manager apply name to context + * Accepts: buffer to write, context to apply, ambiguous folder name + * Returns: buffer filled with fully qualified name in context + * No context applied if error + */ +char * +context_apply(char *b, CONTEXT_S *c, char *name, size_t len) +{ + if(!c || IS_REMOTE(name) || + (!IS_REMOTE(c->context) && is_absolute_path(name))){ + strncpy(b, name, len-1); /* no context! */ + } + else if(name[0] == '#'){ + if(IS_REMOTE(c->context)){ + char *p = strchr(c->context, '}'); /* name specifies namespace */ + snprintf(b, len, "%.*s", MIN(p - c->context + 1, len-1), c->context); + b[MIN(p - c->context + 1, len-1)] = '\0'; + snprintf(b+strlen(b), len-strlen(b), "%.*s", len-1-strlen(b), name); + } + else{ + strncpy(b, name, len-1); + } + } + else if(c->dir && c->dir->ref){ /* has reference string! */ + snprintf(b, len, "%.*s", len-1, c->dir->ref); + b[len-1] = '\0'; + snprintf(b+strlen(b), len-strlen(b), "%.*s", len-1-strlen(b), name); + } + else{ /* no ref, apply to context */ + char *pq = NULL; + + /* + * Have to quote %s for the sprintf because we're using context + * as a format string. + */ + pq = context_percent_quote(c->context); + + if(strlen(c->context) + strlen(name) < len) + snprintf(b, len, pq, name); + else{ + char *t; + size_t l; + + l = strlen(pq)+strlen(name); + t = (char *) fs_get((l+1) * sizeof(char)); + snprintf(t, l+1, pq, name); + strncpy(b, t, len-1); + fs_give((void **)&t); + } + + if(pq) + fs_give((void **) &pq); + } + + b[len-1] = '\0'; + return(b); +} + + +/* + * Insert % before existing %'s so printf will print a real %. + * This is a special routine just for contexts. It only does the % stuffing + * for %'s inside of the braces of the context name, not for %'s to + * the right of the braces, which we will be using for printf format strings. + * Returns a malloced string which the caller is responsible for. + */ +char * +context_percent_quote(char *context) +{ + char *pq = NULL; + + if(!context || !*context) + pq = cpystr(""); + else{ + if(IS_REMOTE(context)){ + char *end, *p, *q; + + /* don't worry about size efficiency, just allocate double */ + pq = (char *) fs_get((2*strlen(context) + 1) * sizeof(char)); + + end = strchr(context, '}'); + p = context; + q = pq; + while(*p){ + if(*p == '%' && p < end) + *q++ = '%'; + + *q++ = *p++; + } + + *q = '\0'; + } + else + pq = cpystr(context); + } + + return(pq); +} + + +/* Context Manager check if name is ambiguous + * Accepts: candidate string + * Returns: T if ambiguous, NIL if fully-qualified + */ + +int +context_isambig (char *s) +{ + return(!(*s == '{' || *s == '#')); +} + + +/*---------------------------------------------------------------------- + Check to see if user is allowed to read or write this folder. + + Args: s -- the name to check + + Result: Returns 1 if OK + Returns 0 and posts an error message if access is denied + ----*/ +int +context_allowed(char *s) +{ + struct variable *vars = ps_global ? ps_global->vars : NULL; + int retval = 1; + MAILSTREAM stream; /* fake stream for error message in mm_notify */ + + if(ps_global + && ps_global->restricted + && (strindex("./~", s[0]) || srchstr(s, "/../"))){ + stream.mailbox = s; + mm_notify(&stream, "Restricted mode doesn't allow operation", WARN); + retval = 0; + } + else if(vars && VAR_OPER_DIR + && s[0] != '{' && !(s[0] == '*' && s[1] == '{') + && strucmp(s,ps_global->inbox_name) != 0 + && strcmp(s, ps_global->VAR_INBOX_PATH) != 0){ + char *p, *free_this = NULL; + + p = s; + if(strindex(s, '~')){ + p = strindex(s, '~'); + free_this = (char *)fs_get(strlen(p) + 200); + strncpy(free_this, p, strlen(p)+200); + fnexpand(free_this, strlen(p)+200); + p = free_this; + } + else if(p[0] != '/'){ /* add home dir to relative paths */ + free_this = p = (char *)fs_get(strlen(s) + + strlen(ps_global->home_dir) + 2); + build_path(p, ps_global->home_dir, s, + strlen(s)+strlen(ps_global->home_dir)+2); + } + + if(!in_dir(VAR_OPER_DIR, p)){ + char err[200]; + + /* TRANSLATORS: User is restricted to operating within a certain directory */ + snprintf(err, sizeof(err), _("Not allowed outside of %.150s"), VAR_OPER_DIR); + stream.mailbox = p; + mm_notify(&stream, err, WARN); + retval = 0; + } + else if(srchstr(p, "/../")){ /* check for .. in path */ + stream.mailbox = p; + mm_notify(&stream, "\"..\" not allowed in name", WARN); + retval = 0; + } + + if(free_this) + fs_give((void **)&free_this); + } + + return retval; +} + + + +/* Context Manager create mailbox + * Accepts: context + * mail stream + * mailbox name to create + * Returns: T on success, NIL on failure + */ + +long +context_create (CONTEXT_S *context, MAILSTREAM *stream, char *mailbox) +{ + char tmp[MAILTMPLEN]; /* must be within context */ + + return(context_allowed(context_apply(tmp, context, mailbox, sizeof(tmp))) + ? pine_mail_create(stream,tmp) : 0L); +} + + +/* Context Manager open + * Accepts: context + * candidate stream for recycling + * mailbox name + * open options + * Returns: stream to use on success, NIL on failure + */ + +MAILSTREAM * +context_open (CONTEXT_S *context, MAILSTREAM *old, char *name, long int opt, long int *retflags) +{ + char tmp[MAILTMPLEN]; /* build FQN from ambiguous name */ + + if(!context_allowed(context_apply(tmp, context, name, sizeof(tmp)))){ + /* + * If a stream is passed to context_open, we will either re-use + * it or close it. + */ + if(old) + pine_mail_close(old); + + return((MAILSTREAM *)NULL); + } + + return(pine_mail_open(old, tmp, opt, retflags)); +} + + +/* Context Manager status + * Accepts: context + * candidate stream for recycling + * mailbox name + * open options + * Returns: T if call succeeds, NIL on failure + */ + +long +context_status(CONTEXT_S *context, MAILSTREAM *stream, char *name, long int opt) +{ + return(context_status_full(context, stream, name, opt, NULL, NULL)); +} + + +long +context_status_full(CONTEXT_S *context, MAILSTREAM *stream, char *name, + long int opt, imapuid_t *uidvalidity, imapuid_t *uidnext) +{ + char tmp[MAILTMPLEN]; /* build FQN from ambiguous name */ + long flags = opt; + + return(context_allowed(context_apply(tmp, context, name, sizeof(tmp))) + ? pine_mail_status_full(stream,tmp,flags,uidvalidity,uidnext) : 0L); +} + + +/* Context Manager status + * + * This is very similar to context_status. Instead of a stream pointer we + * receive a pointer to a pointer so that we can return a stream that we + * opened for further use by the caller. + * + * Accepts: context + * candidate stream for recycling + * mailbox name + * open options + * Returns: T if call succeeds, NIL on failure + */ + +long +context_status_streamp(CONTEXT_S *context, MAILSTREAM **streamp, char *name, long int opt) +{ + return(context_status_streamp_full(context,streamp,name,opt,NULL,NULL)); +} + + +long +context_status_streamp_full(CONTEXT_S *context, MAILSTREAM **streamp, char *name, + long int opt, imapuid_t *uidvalidity, imapuid_t *uidnext) +{ + MAILSTREAM *stream; + char tmp[MAILTMPLEN]; /* build FQN from ambiguous name */ + + if(!context_allowed(context_apply(tmp, context, name, sizeof(tmp)))) + return(0L); + + if(!streamp) + stream = NULL; + else{ + if(!*streamp && IS_REMOTE(tmp)){ + *streamp = pine_mail_open(NULL, tmp, + OP_SILENT|OP_HALFOPEN|SP_USEPOOL, NULL); + } + + stream = *streamp; + } + + return(pine_mail_status_full(stream, tmp, opt, uidvalidity, uidnext)); +} + + +/* Context Manager rename + * Accepts: context + * mail stream + * old mailbox name + * new mailbox name + * Returns: T on success, NIL on failure + */ + +long +context_rename (CONTEXT_S *context, MAILSTREAM *stream, char *old, char *new) +{ + char tmp[MAILTMPLEN],tmp2[MAILTMPLEN]; + + return((context_allowed(context_apply(tmp, context, old, sizeof(tmp))) + && context_allowed(context_apply(tmp2, context, new, sizeof(tmp2)))) + ? pine_mail_rename(stream,tmp,tmp2) : 0L); +} + + +MAILSTREAM * +context_already_open_stream(CONTEXT_S *context, char *name, int flags) +{ + char tmp[MAILTMPLEN]; + + return(already_open_stream(context_apply(tmp, context, name, sizeof(tmp)), + flags)); +} + + +/* Context Manager delete mailbox + * Accepts: context + * mail stream + * mailbox name to delete + * Returns: T on success, NIL on failure + */ + +long +context_delete (CONTEXT_S *context, MAILSTREAM *stream, char *name) +{ + char tmp[MAILTMPLEN]; /* build FQN from ambiguous name */ + + return(context_allowed(context_apply(tmp, context, name, sizeof(tmp))) + ? pine_mail_delete(stream, tmp) : 0L); +} + + +/* Context Manager append message string + * Accepts: context + * mail stream + * destination mailbox + * stringstruct of message to append + * Returns: T on success, NIL on failure + */ + +long +context_append (CONTEXT_S *context, MAILSTREAM *stream, char *name, STRING *msg) +{ + char tmp[MAILTMPLEN]; /* build FQN from ambiguous name */ + + return(context_allowed(context_apply(tmp, context, name, sizeof(tmp))) + ? pine_mail_append(stream, tmp, msg) : 0L); +} + + +/* Context Manager append message string with flags + * Accepts: context + * mail stream + * destination mailbox + * flags to assign message being appended + * date of message being appended + * stringstruct of message to append + * Returns: T on success, NIL on failure + */ + +long +context_append_full(CONTEXT_S *context, MAILSTREAM *stream, char *name, + char *flags, char *date, STRING *msg) +{ + char tmp[MAILTMPLEN]; /* build FQN from ambiguous name */ + + return(context_allowed(context_apply(tmp, context, name, sizeof(tmp))) + ? pine_mail_append_full(stream, tmp, flags, date, msg) : 0L); +} + + +/* Context Manager append multiple message + * Accepts: context + * mail stream + * destination mailbox + * append data callback + * arbitrary ata for callback use + * Returns: T on success, NIL on failure + */ + +long +context_append_multiple(CONTEXT_S *context, MAILSTREAM *stream, char *name, + append_t af, APPENDPACKAGE *data, MAILSTREAM *not_this_stream) +{ + char tmp[MAILTMPLEN]; /* build FQN from ambiguous name */ + + return(context_allowed(context_apply(tmp, context, name, sizeof(tmp))) + ? pine_mail_append_multiple(stream, tmp, af, data, not_this_stream) : 0L); +} + + +/* Mail copy message(s) + * Accepts: context + * mail stream + * sequence + * destination mailbox + */ + +long +context_copy (CONTEXT_S *context, MAILSTREAM *stream, char *sequence, char *name) +{ + char *s, tmp[MAILTMPLEN]; /* build FQN from ambiguous name */ + + tmp[0] = '\0'; + + if(context_apply(tmp, context, name, sizeof(tmp))[0] == '{'){ + if((s = strindex(tmp, '}')) != NULL) + s++; + else + return(0L); + } + else + s = tmp; + + tmp[sizeof(tmp)-1] = '\0'; + + if(!*s) + strncpy(s = tmp, "INBOX", sizeof(tmp)); /* presume "inbox" ala c-client */ + + tmp[sizeof(tmp)-1] = '\0'; + + return(context_allowed(s) ? pine_mail_copy(stream, sequence, s) : 0L); +} + + +/* + * Context manager stream usefulness test + * Accepts: context + * mail name + * mailbox name + * mail stream to test against mailbox name + * Returns: stream if useful, else NIL + */ +MAILSTREAM * +context_same_stream(CONTEXT_S *context, char *name, MAILSTREAM *stream) +{ + extern MAILSTREAM *same_stream(char *name, MAILSTREAM *stream); + char tmp[MAILTMPLEN]; /* build FQN from ambiguous name */ + + return(same_stream(context_apply(tmp, context, name, sizeof(tmp)), stream)); +} + + +/* + * new_context - creates and fills in a new context structure, leaving + * blank the actual folder list (to be filled in later as + * needed). Also, parses the context string provided + * picking out any user defined label. Context lines are + * of the form: + * + * [ ["] <string> ["] <white-space>] <context> + * + */ +CONTEXT_S * +new_context(char *cntxt_string, int *prime) +{ + CONTEXT_S *c; + char host[MAXPATH], rcontext[MAXPATH], + view[MAXPATH], dcontext[MAXPATH], + *nickname = NULL, *c_string = NULL, *p; + + /* + * do any context string parsing (like splitting user-supplied + * label from actual context)... + */ + get_pair(cntxt_string, &nickname, &c_string, 0, 0); + + if(update_bboard_spec(c_string, tmp_20k_buf, SIZEOF_20KBUF)){ + fs_give((void **) &c_string); + c_string = cpystr(tmp_20k_buf); + } + + if(c_string && *c_string == '\"') + (void)removing_double_quotes(c_string); + + view[0] = rcontext[0] = host[0] = dcontext[0] = '\0'; + if((p = context_digest(c_string, dcontext, host, rcontext, view, MAXPATH)) != NULL){ + q_status_message2(SM_ORDER | SM_DING, 3, 4, + "Bad context, %.200s : %.200s", p, c_string); + fs_give((void **) &c_string); + if(nickname) + fs_give((void **)&nickname); + + return(NULL); + } + else + fs_give((void **) &c_string); + + c = (CONTEXT_S *) fs_get(sizeof(CONTEXT_S)); /* get new context */ + memset((void *) c, 0, sizeof(CONTEXT_S)); /* and initialize it */ + if(*host) + c->server = cpystr(host); /* server + params */ + + c->context = cpystr(dcontext); + + if(strstr(c->context, "#news.")) + c->use |= CNTXT_NEWS; + + c->dir = new_fdir(NULL, view, (c->use & CNTXT_NEWS) ? '*' : '%'); + + /* fix up nickname */ + if(!(c->nickname = nickname)){ /* make one up! */ + snprintf(tmp_20k_buf, SIZEOF_20KBUF, "%s%s%.100s", + (c->use & CNTXT_NEWS) + ? "News" + : (c->dir->ref) + ? (c->dir->ref) :"Mail", + (c->server) ? " on " : "", + (c->server) ? c->server : ""); + c->nickname = cpystr(tmp_20k_buf); + } + + if(prime && !*prime){ + *prime = 1; + c->use |= CNTXT_SAVEDFLT; + } + + /* fix up label */ + if(NEWS_TEST(c)){ + snprintf(tmp_20k_buf, SIZEOF_20KBUF, "%sews groups%s%.100s", + (*host) ? "N" : "Local n", (*host) ? " on " : "", + (*host) ? host : ""); + } + else{ + p = srchstr(rcontext, "[]"); + snprintf(tmp_20k_buf, SIZEOF_20KBUF, "%solders%s%.100s in %.*s%s", + (*host) ? "F" : "Local f", (*host) ? " on " : "", + (*host) ? host : "", + p ? MIN(p - rcontext, 100) : 0, + rcontext, (p && (p - rcontext) > 0) ? "" : "home directory"); + } + + c->label = cpystr(tmp_20k_buf); + + dprint((5, "Context: %s serv:%s ref: %s view: %s\n", + c->context ? c->context : "?", + (c->server) ? c->server : "\"\"", + (c->dir->ref) ? c->dir->ref : "\"\"", + (c->dir->view.user) ? c->dir->view.user : "\"\"")); + + return(c); +} + + +/* + * Release resources associated with global context list + */ +void +free_contexts(CONTEXT_S **ctxt) +{ + if(ctxt && *ctxt){ + free_contexts(&(*ctxt)->next); + free_context(ctxt); + } +} + + +/* + * Release resources associated with the given context structure + */ +void +free_context(CONTEXT_S **cntxt) +{ + if(cntxt && *cntxt){ + if(*cntxt == ps_global->context_current) + ps_global->context_current = NULL; + + if(*cntxt == ps_global->context_last) + ps_global->context_last = NULL; + + if(*cntxt == ps_global->last_save_context) + ps_global->last_save_context = NULL; + + if((*cntxt)->context) + fs_give((void **) &(*cntxt)->context); + + if((*cntxt)->server) + fs_give((void **) &(*cntxt)->server); + + if((*cntxt)->nickname) + fs_give((void **)&(*cntxt)->nickname); + + if((*cntxt)->label) + fs_give((void **) &(*cntxt)->label); + + if((*cntxt)->comment) + fs_give((void **) &(*cntxt)->comment); + + if((*cntxt)->selected.reference) + fs_give((void **) &(*cntxt)->selected.reference); + + if((*cntxt)->selected.sub) + free_selected(&(*cntxt)->selected.sub); + + free_strlist(&(*cntxt)->selected.folders); + + free_fdir(&(*cntxt)->dir, 1); + + fs_give((void **)cntxt); + } +} |