/* * ======================================================================== * Copyright 2013-2021 Eduardo Chappa * Copyright 2006-2007 University of Washington * * 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 ["/"] [:port]}] [] "[" [] "]" [] * * 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 gets 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", (int) 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", (int)(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", (int) len-1, c->dir->ref); b[len-1] = '\0'; snprintf(b+strlen(b), len-strlen(b), "%.*s", (int) (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: * * [ ["] ["] ] * */ 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 ? (int) 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); } }