2 * ========================================================================
3 * Copyright 2013-2022 Eduardo Chappa
4 * Copyright 2006-2007 University of Washington
6 * Licensed under the Apache License, Version 2.0 (the "License");
7 * you may not use this file except in compliance with the License.
8 * You may obtain a copy of the License at
10 * http://www.apache.org/licenses/LICENSE-2.0
12 * ========================================================================
15 #include "../pith/headers.h"
16 #include "../pith/conf.h"
17 #include "../pith/context.h"
18 #include "../pith/state.h"
19 #include "../pith/status.h"
20 #include "../pith/stream.h"
21 #include "../pith/folder.h"
22 #include "../pith/util.h"
23 #include "../pith/tempfile.h"
29 char *context_percent_quote(char *);
32 /* Context Manager context format digester
33 * Accepts: context string and buffers for sprintf-suitable context,
34 * remote host (for display), remote context (for display),
36 * Returns: NULL if successful, else error string
38 * Comments: OK, here's what we expect a fully qualified context to
41 * [*] [{host ["/"<proto>] [:port]}] [<cntxt>] "[" [<view>] "]" [<cntxt>]
43 * 2) It's understood that double "[" or "]" are used to
44 * quote a literal '[' or ']' in a context name.
46 * 3) an empty view in context implies a view of '*', so that's
47 * what gets put in the view string
49 * The 2nd,3rd,4th, and 5th args all have length at least len.
53 context_digest(char *context
, char *scontext
, char *host
, char *rcontext
,
54 char *view
, size_t len
)
56 char *p
, *viewp
= view
;
57 char *scontextp
= scontext
;
60 if((p
= context
) == NULL
|| *p
== '\0'){
61 if(scontext
) /* so the caller can apply */
62 strncpy(scontext
, "%s", len
); /* folder names as is. */
64 scontext
[len
-1] = '\0';
65 return(NULL
); /* no error, just empty context */
68 /* find hostname if requested and exists */
69 if(*p
== '{' || (*p
== '*' && *++p
== '{')){
70 for(p
++; *p
&& *p
!= '}' ; p
++)
71 if(host
&& p
-context
< len
-1)
72 *host
++ = *p
; /* copy host if requested */
74 while(*p
&& *p
!= '}') /* find end of imap host */
78 return("Unbalanced '}'"); /* bogus. */
80 p
++; /* move into next field */
83 for(; *p
; p
++){ /* get thru context */
84 if(rcontext
&& i
< len
-1)
85 rcontext
[i
] = *p
; /* copy if requested */
89 if(*p
== '['){ /* done? */
90 if(*++p
== '\0') /* look ahead */
91 return("Unbalanced '['");
93 if(*p
!= '[') /* not quoted: "[[" */
96 else if(*p
== ']' && *(p
+1) == ']') /* these may be quoted, too */
101 return("No '[' in context");
103 for(; *p
; p
++){ /* possibly in view portion ? */
105 if(*(p
+1) == ']') /* is quoted */
111 if(viewp
&& viewp
-view
< len
-1)
116 return("No ']' in context");
118 for(; *p
; p
++){ /* trailing context ? */
119 if(rcontext
&& i
< len
-1)
124 if(host
) *host
= '\0';
125 if(rcontext
&& i
< len
) rcontext
[i
] = '\0';
127 /* MAIL_LIST: dealt with it in new_context since could be either '%' or '*'
128 if(viewp == view && viewp-view < len-1)
135 if(scontextp
){ /* sprint'able context request ? */
137 if(scontextp
-scontext
< len
-1)
138 *scontextp
++ = *context
;
144 while(*context
&& *context
!= '}'){
145 if(scontextp
-scontext
< len
-1)
146 *scontextp
++ = *context
;
154 for(p
= rcontext
; *p
; p
++){
155 if(*p
== '[' && *(p
+1) == ']'){
156 if(scontextp
-scontext
< len
-2){
157 *scontextp
++ = '%'; /* replace "[]" with "%s" */
163 else if(scontextp
-scontext
< len
-1)
170 return(NULL
); /* no problems to report... */
174 /* Context Manager apply name to context
175 * Accepts: buffer to write, context to apply, ambiguous folder name
176 * Returns: buffer filled with fully qualified name in context
177 * No context applied if error
180 context_apply(char *b
, CONTEXT_S
*c
, char *name
, size_t len
)
182 if(!c
|| IS_REMOTE(name
) ||
183 (!IS_REMOTE(c
->context
) && is_absolute_path(name
))){
184 strncpy(b
, name
, len
-1); /* no context! */
186 else if(name
[0] == '#'){
187 if(IS_REMOTE(c
->context
)){
188 char *p
= strchr(c
->context
, '}'); /* name specifies namespace */
189 snprintf(b
, len
, "%.*s", (int) MIN(p
- c
->context
+ 1, len
-1), c
->context
);
190 b
[MIN(p
- c
->context
+ 1, len
-1)] = '\0';
191 snprintf(b
+strlen(b
), len
-strlen(b
), "%.*s", (int)(len
-1-strlen(b
)), name
);
194 strncpy(b
, name
, len
-1);
197 else if(c
->dir
&& c
->dir
->ref
){ /* has reference string! */
198 snprintf(b
, len
, "%.*s", (int) len
-1, c
->dir
->ref
);
200 snprintf(b
+strlen(b
), len
-strlen(b
), "%.*s", (int) (len
-1-strlen(b
)), name
);
202 else{ /* no ref, apply to context */
206 * Have to quote %s for the sprintf because we're using context
207 * as a format string.
209 pq
= context_percent_quote(c
->context
);
211 if(strlen(c
->context
) + strlen(name
) < len
)
212 snprintf(b
, len
, pq
, name
);
217 l
= strlen(pq
)+strlen(name
);
218 t
= (char *) fs_get((l
+1) * sizeof(char));
219 snprintf(t
, l
+1, pq
, name
);
220 strncpy(b
, t
, len
-1);
221 fs_give((void **)&t
);
225 fs_give((void **) &pq
);
234 * Insert % before existing %'s so printf will print a real %.
235 * This is a special routine just for contexts. It only does the % stuffing
236 * for %'s inside of the braces of the context name, not for %'s to
237 * the right of the braces, which we will be using for printf format strings.
238 * Returns a malloced string which the caller is responsible for.
241 context_percent_quote(char *context
)
245 if(!context
|| !*context
)
248 if(IS_REMOTE(context
)){
251 /* don't worry about size efficiency, just allocate double */
252 pq
= (char *) fs_get((2*strlen(context
) + 1) * sizeof(char));
254 end
= strchr(context
, '}');
258 if(*p
== '%' && p
< end
)
267 pq
= cpystr(context
);
274 /* Context Manager check if name is ambiguous
275 * Accepts: candidate string
276 * Returns: T if ambiguous, NIL if fully-qualified
280 context_isambig (char *s
)
282 return(!(*s
== '{' || *s
== '#'));
286 /*----------------------------------------------------------------------
287 Check to see if user is allowed to read or write this folder.
289 Args: s -- the name to check
291 Result: Returns 1 if OK
292 Returns 0 and posts an error message if access is denied
295 context_allowed(char *s
)
297 struct variable
*vars
= ps_global
? ps_global
->vars
: NULL
;
299 MAILSTREAM stream
; /* fake stream for error message in mm_notify */
302 && ps_global
->restricted
303 && (strindex("./~", s
[0]) || srchstr(s
, "/../"))){
305 mm_notify(&stream
, "Restricted mode doesn't allow operation", WARN
);
308 else if(vars
&& VAR_OPER_DIR
309 && s
[0] != '{' && !(s
[0] == '*' && s
[1] == '{')
310 && strucmp(s
,ps_global
->inbox_name
) != 0
311 && strcmp(s
, ps_global
->VAR_INBOX_PATH
) != 0){
312 char *p
, *free_this
= NULL
;
315 if(strindex(s
, '~')){
316 p
= strindex(s
, '~');
317 free_this
= (char *)fs_get(strlen(p
) + 200);
318 strncpy(free_this
, p
, strlen(p
)+200);
319 fnexpand(free_this
, strlen(p
)+200);
322 else if(p
[0] != '/'){ /* add home dir to relative paths */
323 free_this
= p
= (char *)fs_get(strlen(s
)
324 + strlen(ps_global
->home_dir
) + 2);
325 build_path(p
, ps_global
->home_dir
, s
,
326 strlen(s
)+strlen(ps_global
->home_dir
)+2);
329 if(!in_dir(VAR_OPER_DIR
, p
)){
332 /* TRANSLATORS: User is restricted to operating within a certain directory */
333 snprintf(err
, sizeof(err
), _("Not allowed outside of %.150s"), VAR_OPER_DIR
);
335 mm_notify(&stream
, err
, WARN
);
338 else if(srchstr(p
, "/../")){ /* check for .. in path */
340 mm_notify(&stream
, "\"..\" not allowed in name", WARN
);
345 fs_give((void **)&free_this
);
353 /* Context Manager create mailbox
356 * mailbox name to create
357 * Returns: T on success, NIL on failure
361 context_create (CONTEXT_S
*context
, MAILSTREAM
*stream
, char *mailbox
)
363 char tmp
[MAILTMPLEN
]; /* must be within context */
365 return(context_allowed(context_apply(tmp
, context
, mailbox
, sizeof(tmp
)))
366 ? pine_mail_create(stream
,tmp
) : 0L);
370 /* Context Manager open
372 * candidate stream for recycling
375 * Returns: stream to use on success, NIL on failure
379 context_open (CONTEXT_S
*context
, MAILSTREAM
*old
, char *name
, long int opt
, long int *retflags
)
381 char tmp
[MAILTMPLEN
]; /* build FQN from ambiguous name */
383 if(!context_allowed(context_apply(tmp
, context
, name
, sizeof(tmp
)))){
385 * If a stream is passed to context_open, we will either re-use
389 pine_mail_close(old
);
391 return((MAILSTREAM
*)NULL
);
394 return(pine_mail_open(old
, tmp
, opt
, retflags
));
398 /* Context Manager status
400 * candidate stream for recycling
403 * Returns: T if call succeeds, NIL on failure
407 context_status(CONTEXT_S
*context
, MAILSTREAM
*stream
, char *name
, long int opt
)
409 return(context_status_full(context
, stream
, name
, opt
, NULL
, NULL
));
414 context_status_full(CONTEXT_S
*context
, MAILSTREAM
*stream
, char *name
,
415 long int opt
, imapuid_t
*uidvalidity
, imapuid_t
*uidnext
)
417 char tmp
[MAILTMPLEN
]; /* build FQN from ambiguous name */
420 return(context_allowed(context_apply(tmp
, context
, name
, sizeof(tmp
)))
421 ? pine_mail_status_full(stream
,tmp
,flags
,uidvalidity
,uidnext
) : 0L);
425 /* Context Manager status
427 * This is very similar to context_status. Instead of a stream pointer we
428 * receive a pointer to a pointer so that we can return a stream that we
429 * opened for further use by the caller.
432 * candidate stream for recycling
435 * Returns: T if call succeeds, NIL on failure
439 context_status_streamp(CONTEXT_S
*context
, MAILSTREAM
**streamp
, char *name
, long int opt
)
441 return(context_status_streamp_full(context
,streamp
,name
,opt
,NULL
,NULL
));
446 context_status_streamp_full(CONTEXT_S
*context
, MAILSTREAM
**streamp
, char *name
,
447 long int opt
, imapuid_t
*uidvalidity
, imapuid_t
*uidnext
)
450 char tmp
[MAILTMPLEN
]; /* build FQN from ambiguous name */
452 if(!context_allowed(context_apply(tmp
, context
, name
, sizeof(tmp
))))
458 if(!*streamp
&& IS_REMOTE(tmp
)){
459 *streamp
= pine_mail_open(NULL
, tmp
,
460 OP_SILENT
|OP_HALFOPEN
|SP_USEPOOL
, NULL
);
466 return(pine_mail_status_full(stream
, tmp
, opt
, uidvalidity
, uidnext
));
470 /* Context Manager rename
475 * Returns: T on success, NIL on failure
479 context_rename (CONTEXT_S
*context
, MAILSTREAM
*stream
, char *old
, char *new)
481 char tmp
[MAILTMPLEN
],tmp2
[MAILTMPLEN
];
483 return((context_allowed(context_apply(tmp
, context
, old
, sizeof(tmp
)))
484 && context_allowed(context_apply(tmp2
, context
, new, sizeof(tmp2
))))
485 ? pine_mail_rename(stream
,tmp
,tmp2
) : 0L);
490 context_already_open_stream(CONTEXT_S
*context
, char *name
, int flags
)
492 char tmp
[MAILTMPLEN
];
494 return(already_open_stream(context_apply(tmp
, context
, name
, sizeof(tmp
)),
499 /* Context Manager delete mailbox
502 * mailbox name to delete
503 * Returns: T on success, NIL on failure
507 context_delete (CONTEXT_S
*context
, MAILSTREAM
*stream
, char *name
)
509 char tmp
[MAILTMPLEN
]; /* build FQN from ambiguous name */
511 return(context_allowed(context_apply(tmp
, context
, name
, sizeof(tmp
)))
512 ? pine_mail_delete(stream
, tmp
) : 0L);
516 /* Context Manager append message string
519 * destination mailbox
520 * stringstruct of message to append
521 * Returns: T on success, NIL on failure
525 context_append (CONTEXT_S
*context
, MAILSTREAM
*stream
, char *name
, STRING
*msg
)
527 char tmp
[MAILTMPLEN
]; /* build FQN from ambiguous name */
529 return(context_allowed(context_apply(tmp
, context
, name
, sizeof(tmp
)))
530 ? pine_mail_append(stream
, tmp
, msg
) : 0L);
534 /* Context Manager append message string with flags
537 * destination mailbox
538 * flags to assign message being appended
539 * date of message being appended
540 * stringstruct of message to append
541 * Returns: T on success, NIL on failure
545 context_append_full(CONTEXT_S
*context
, MAILSTREAM
*stream
, char *name
,
546 char *flags
, char *date
, STRING
*msg
)
548 char tmp
[MAILTMPLEN
]; /* build FQN from ambiguous name */
550 return(context_allowed(context_apply(tmp
, context
, name
, sizeof(tmp
)))
551 ? pine_mail_append_full(stream
, tmp
, flags
, date
, msg
) : 0L);
555 /* Context Manager append multiple message
558 * destination mailbox
559 * append data callback
560 * arbitrary ata for callback use
561 * Returns: T on success, NIL on failure
565 context_append_multiple(CONTEXT_S
*context
, MAILSTREAM
*stream
, char *name
,
566 append_t af
, APPENDPACKAGE
*data
, MAILSTREAM
*not_this_stream
)
568 char tmp
[MAILTMPLEN
]; /* build FQN from ambiguous name */
570 return(context_allowed(context_apply(tmp
, context
, name
, sizeof(tmp
)))
571 ? pine_mail_append_multiple(stream
, tmp
, af
, data
, not_this_stream
) : 0L);
575 /* Mail copy message(s)
579 * destination mailbox
583 context_copy (CONTEXT_S
*context
, MAILSTREAM
*stream
, char *sequence
, char *name
)
585 char *s
, tmp
[MAILTMPLEN
]; /* build FQN from ambiguous name */
589 if(context_apply(tmp
, context
, name
, sizeof(tmp
))[0] == '{'){
590 if((s
= strindex(tmp
, '}')) != NULL
)
598 tmp
[sizeof(tmp
)-1] = '\0';
601 strncpy(s
= tmp
, "INBOX", sizeof(tmp
)); /* presume "inbox" ala c-client */
603 tmp
[sizeof(tmp
)-1] = '\0';
605 return(context_allowed(s
) ? pine_mail_copy(stream
, sequence
, s
) : 0L);
610 * Context manager stream usefulness test
614 * mail stream to test against mailbox name
615 * Returns: stream if useful, else NIL
618 context_same_stream(CONTEXT_S
*context
, char *name
, MAILSTREAM
*stream
)
620 extern MAILSTREAM
*same_stream(char *name
, MAILSTREAM
*stream
);
621 char tmp
[MAILTMPLEN
]; /* build FQN from ambiguous name */
623 return(same_stream(context_apply(tmp
, context
, name
, sizeof(tmp
)), stream
));
628 * new_context - creates and fills in a new context structure, leaving
629 * blank the actual folder list (to be filled in later as
630 * needed). Also, parses the context string provided
631 * picking out any user defined label. Context lines are
634 * [ ["] <string> ["] <white-space>] <context>
638 new_context(char *cntxt_string
, int *prime
)
641 char host
[MAXPATH
], rcontext
[MAXPATH
],
642 view
[MAXPATH
], dcontext
[MAXPATH
],
643 *nickname
= NULL
, *c_string
= NULL
, *p
;
646 * do any context string parsing (like splitting user-supplied
647 * label from actual context)...
649 get_pair(cntxt_string
, &nickname
, &c_string
, 0, 0);
651 if(update_bboard_spec(c_string
, tmp_20k_buf
, SIZEOF_20KBUF
)){
652 fs_give((void **) &c_string
);
653 c_string
= cpystr(tmp_20k_buf
);
656 if(c_string
&& *c_string
== '\"')
657 (void)removing_double_quotes(c_string
);
659 view
[0] = rcontext
[0] = host
[0] = dcontext
[0] = '\0';
660 if((p
= context_digest(c_string
, dcontext
, host
, rcontext
, view
, MAXPATH
)) != NULL
){
661 q_status_message2(SM_ORDER
| SM_DING
, 3, 4,
662 "Bad context, %.200s : %.200s", p
, c_string
);
663 fs_give((void **) &c_string
);
665 fs_give((void **)&nickname
);
670 fs_give((void **) &c_string
);
672 c
= (CONTEXT_S
*) fs_get(sizeof(CONTEXT_S
)); /* get new context */
673 memset((void *) c
, 0, sizeof(CONTEXT_S
)); /* and initialize it */
675 c
->server
= cpystr(host
); /* server + params */
677 c
->context
= cpystr(dcontext
);
679 if(strstr(c
->context
, "#news."))
680 c
->use
|= CNTXT_NEWS
;
682 c
->dir
= new_fdir(NULL
, view
, (c
->use
& CNTXT_NEWS
) ? '*' : '%');
684 /* fix up nickname */
685 if(!(c
->nickname
= nickname
)){ /* make one up! */
686 snprintf(tmp_20k_buf
, SIZEOF_20KBUF
, "%s%s%.100s",
687 (c
->use
& CNTXT_NEWS
)
690 ? (c
->dir
->ref
) :"Mail",
691 (c
->server
) ? " on " : "",
692 (c
->server
) ? c
->server
: "");
693 c
->nickname
= cpystr(tmp_20k_buf
);
696 if(prime
&& !*prime
){
698 c
->use
|= CNTXT_SAVEDFLT
;
703 snprintf(tmp_20k_buf
, SIZEOF_20KBUF
, "%sews groups%s%.100s",
704 (*host
) ? "N" : "Local n", (*host
) ? " on " : "",
705 (*host
) ? host
: "");
708 p
= srchstr(rcontext
, "[]");
709 snprintf(tmp_20k_buf
, SIZEOF_20KBUF
, "%solders%s%.100s in %.*s%s",
710 (*host
) ? "F" : "Local f", (*host
) ? " on " : "",
712 p
? (int) MIN(p
- rcontext
, 100) : 0,
713 rcontext
, (p
&& (p
- rcontext
) > 0) ? "" : "home directory");
716 c
->label
= cpystr(tmp_20k_buf
);
718 dprint((5, "Context: %s serv:%s ref: %s view: %s\n",
719 c
->context
? c
->context
: "?",
720 (c
->server
) ? c
->server
: "\"\"",
721 (c
->dir
->ref
) ? c
->dir
->ref
: "\"\"",
722 (c
->dir
->view
.user
) ? c
->dir
->view
.user
: "\"\""));
729 * Release resources associated with global context list
732 free_contexts(CONTEXT_S
**ctxt
)
735 free_contexts(&(*ctxt
)->next
);
742 * Release resources associated with the given context structure
745 free_context(CONTEXT_S
**cntxt
)
748 if(*cntxt
== ps_global
->context_current
)
749 ps_global
->context_current
= NULL
;
751 if(*cntxt
== ps_global
->context_last
)
752 ps_global
->context_last
= NULL
;
754 if(*cntxt
== ps_global
->last_save_context
)
755 ps_global
->last_save_context
= NULL
;
757 if((*cntxt
)->context
)
758 fs_give((void **) &(*cntxt
)->context
);
761 fs_give((void **) &(*cntxt
)->server
);
763 if((*cntxt
)->nickname
)
764 fs_give((void **)&(*cntxt
)->nickname
);
767 fs_give((void **) &(*cntxt
)->label
);
769 if((*cntxt
)->comment
)
770 fs_give((void **) &(*cntxt
)->comment
);
772 if((*cntxt
)->selected
.reference
)
773 fs_give((void **) &(*cntxt
)->selected
.reference
);
775 if((*cntxt
)->selected
.sub
)
776 free_selected(&(*cntxt
)->selected
.sub
);
778 free_strlist(&(*cntxt
)->selected
.folders
);
780 free_fdir(&(*cntxt
)->dir
, 1);
782 fs_give((void **)cntxt
);