1 #if !defined(lint) && !defined(DOS)
2 static char rcsid
[] = "$Id: context.c 1144 2008-08-14 16:53:34Z hubert@u.washington.edu $";
6 * ========================================================================
7 * Copyright 2006-2007 University of Washington
8 * Copyright 2013-2017 Eduardo Chappa
10 * Licensed under the Apache License, Version 2.0 (the "License");
11 * you may not use this file except in compliance with the License.
12 * You may obtain a copy of the License at
14 * http://www.apache.org/licenses/LICENSE-2.0
16 * ========================================================================
19 #include "../pith/headers.h"
20 #include "../pith/conf.h"
21 #include "../pith/context.h"
22 #include "../pith/state.h"
23 #include "../pith/status.h"
24 #include "../pith/stream.h"
25 #include "../pith/folder.h"
26 #include "../pith/util.h"
27 #include "../pith/tempfile.h"
33 char *context_percent_quote(char *);
36 /* Context Manager context format digester
37 * Accepts: context string and buffers for sprintf-suitable context,
38 * remote host (for display), remote context (for display),
40 * Returns: NULL if successful, else error string
42 * Comments: OK, here's what we expect a fully qualified context to
45 * [*] [{host ["/"<proto>] [:port]}] [<cntxt>] "[" [<view>] "]" [<cntxt>]
47 * 2) It's understood that double "[" or "]" are used to
48 * quote a literal '[' or ']' in a context name.
50 * 3) an empty view in context implies a view of '*', so that's
51 * what get's put in the view string
53 * The 2nd,3rd,4th, and 5th args all have length at least len.
57 context_digest(char *context
, char *scontext
, char *host
, char *rcontext
,
58 char *view
, size_t len
)
60 char *p
, *viewp
= view
;
61 char *scontextp
= scontext
;
64 if((p
= context
) == NULL
|| *p
== '\0'){
65 if(scontext
) /* so the caller can apply */
66 strncpy(scontext
, "%s", len
); /* folder names as is. */
68 scontext
[len
-1] = '\0';
69 return(NULL
); /* no error, just empty context */
72 /* find hostname if requested and exists */
73 if(*p
== '{' || (*p
== '*' && *++p
== '{')){
74 for(p
++; *p
&& *p
!= '}' ; p
++)
75 if(host
&& p
-context
< len
-1)
76 *host
++ = *p
; /* copy host if requested */
78 while(*p
&& *p
!= '}') /* find end of imap host */
82 return("Unbalanced '}'"); /* bogus. */
84 p
++; /* move into next field */
87 for(; *p
; p
++){ /* get thru context */
88 if(rcontext
&& i
< len
-1)
89 rcontext
[i
] = *p
; /* copy if requested */
93 if(*p
== '['){ /* done? */
94 if(*++p
== '\0') /* look ahead */
95 return("Unbalanced '['");
97 if(*p
!= '[') /* not quoted: "[[" */
100 else if(*p
== ']' && *(p
+1) == ']') /* these may be quoted, too */
105 return("No '[' in context");
107 for(; *p
; p
++){ /* possibly in view portion ? */
109 if(*(p
+1) == ']') /* is quoted */
115 if(viewp
&& viewp
-view
< len
-1)
120 return("No ']' in context");
122 for(; *p
; p
++){ /* trailing context ? */
123 if(rcontext
&& i
< len
-1)
128 if(host
) *host
= '\0';
129 if(rcontext
&& i
< len
) rcontext
[i
] = '\0';
131 /* MAIL_LIST: dealt with it in new_context since could be either '%' or '*'
132 if(viewp == view && viewp-view < len-1)
139 if(scontextp
){ /* sprint'able context request ? */
141 if(scontextp
-scontext
< len
-1)
142 *scontextp
++ = *context
;
148 while(*context
&& *context
!= '}'){
149 if(scontextp
-scontext
< len
-1)
150 *scontextp
++ = *context
;
158 for(p
= rcontext
; *p
; p
++){
159 if(*p
== '[' && *(p
+1) == ']'){
160 if(scontextp
-scontext
< len
-2){
161 *scontextp
++ = '%'; /* replace "[]" with "%s" */
167 else if(scontextp
-scontext
< len
-1)
174 return(NULL
); /* no problems to report... */
178 /* Context Manager apply name to context
179 * Accepts: buffer to write, context to apply, ambiguous folder name
180 * Returns: buffer filled with fully qualified name in context
181 * No context applied if error
184 context_apply(char *b
, CONTEXT_S
*c
, char *name
, size_t len
)
186 if(!c
|| IS_REMOTE(name
) ||
187 (!IS_REMOTE(c
->context
) && is_absolute_path(name
))){
188 strncpy(b
, name
, len
-1); /* no context! */
190 else if(name
[0] == '#'){
191 if(IS_REMOTE(c
->context
)){
192 char *p
= strchr(c
->context
, '}'); /* name specifies namespace */
193 snprintf(b
, len
, "%.*s", (int) MIN(p
- c
->context
+ 1, len
-1), c
->context
);
194 b
[MIN(p
- c
->context
+ 1, len
-1)] = '\0';
195 snprintf(b
+strlen(b
), len
-strlen(b
), "%.*s", (int)(len
-1-strlen(b
)), name
);
198 strncpy(b
, name
, len
-1);
201 else if(c
->dir
&& c
->dir
->ref
){ /* has reference string! */
202 snprintf(b
, len
, "%.*s", (int) len
-1, c
->dir
->ref
);
204 snprintf(b
+strlen(b
), len
-strlen(b
), "%.*s", (int) (len
-1-strlen(b
)), name
);
206 else{ /* no ref, apply to context */
210 * Have to quote %s for the sprintf because we're using context
211 * as a format string.
213 pq
= context_percent_quote(c
->context
);
215 if(strlen(c
->context
) + strlen(name
) < len
)
216 snprintf(b
, len
, pq
, name
);
221 l
= strlen(pq
)+strlen(name
);
222 t
= (char *) fs_get((l
+1) * sizeof(char));
223 snprintf(t
, l
+1, pq
, name
);
224 strncpy(b
, t
, len
-1);
225 fs_give((void **)&t
);
229 fs_give((void **) &pq
);
238 * Insert % before existing %'s so printf will print a real %.
239 * This is a special routine just for contexts. It only does the % stuffing
240 * for %'s inside of the braces of the context name, not for %'s to
241 * the right of the braces, which we will be using for printf format strings.
242 * Returns a malloced string which the caller is responsible for.
245 context_percent_quote(char *context
)
249 if(!context
|| !*context
)
252 if(IS_REMOTE(context
)){
255 /* don't worry about size efficiency, just allocate double */
256 pq
= (char *) fs_get((2*strlen(context
) + 1) * sizeof(char));
258 end
= strchr(context
, '}');
262 if(*p
== '%' && p
< end
)
271 pq
= cpystr(context
);
278 /* Context Manager check if name is ambiguous
279 * Accepts: candidate string
280 * Returns: T if ambiguous, NIL if fully-qualified
284 context_isambig (char *s
)
286 return(!(*s
== '{' || *s
== '#'));
290 /*----------------------------------------------------------------------
291 Check to see if user is allowed to read or write this folder.
293 Args: s -- the name to check
295 Result: Returns 1 if OK
296 Returns 0 and posts an error message if access is denied
299 context_allowed(char *s
)
301 struct variable
*vars
= ps_global
? ps_global
->vars
: NULL
;
303 MAILSTREAM stream
; /* fake stream for error message in mm_notify */
306 && ps_global
->restricted
307 && (strindex("./~", s
[0]) || srchstr(s
, "/../"))){
309 mm_notify(&stream
, "Restricted mode doesn't allow operation", WARN
);
312 else if(vars
&& VAR_OPER_DIR
313 && s
[0] != '{' && !(s
[0] == '*' && s
[1] == '{')
314 && strucmp(s
,ps_global
->inbox_name
) != 0
315 && strcmp(s
, ps_global
->VAR_INBOX_PATH
) != 0){
316 char *p
, *free_this
= NULL
;
319 if(strindex(s
, '~')){
320 p
= strindex(s
, '~');
321 free_this
= (char *)fs_get(strlen(p
) + 200);
322 strncpy(free_this
, p
, strlen(p
)+200);
323 fnexpand(free_this
, strlen(p
)+200);
326 else if(p
[0] != '/'){ /* add home dir to relative paths */
327 free_this
= p
= (char *)fs_get(strlen(s
)
328 + strlen(ps_global
->home_dir
) + 2);
329 build_path(p
, ps_global
->home_dir
, s
,
330 strlen(s
)+strlen(ps_global
->home_dir
)+2);
333 if(!in_dir(VAR_OPER_DIR
, p
)){
336 /* TRANSLATORS: User is restricted to operating within a certain directory */
337 snprintf(err
, sizeof(err
), _("Not allowed outside of %.150s"), VAR_OPER_DIR
);
339 mm_notify(&stream
, err
, WARN
);
342 else if(srchstr(p
, "/../")){ /* check for .. in path */
344 mm_notify(&stream
, "\"..\" not allowed in name", WARN
);
349 fs_give((void **)&free_this
);
357 /* Context Manager create mailbox
360 * mailbox name to create
361 * Returns: T on success, NIL on failure
365 context_create (CONTEXT_S
*context
, MAILSTREAM
*stream
, char *mailbox
)
367 char tmp
[MAILTMPLEN
]; /* must be within context */
369 return(context_allowed(context_apply(tmp
, context
, mailbox
, sizeof(tmp
)))
370 ? pine_mail_create(stream
,tmp
) : 0L);
374 /* Context Manager open
376 * candidate stream for recycling
379 * Returns: stream to use on success, NIL on failure
383 context_open (CONTEXT_S
*context
, MAILSTREAM
*old
, char *name
, long int opt
, long int *retflags
)
385 char tmp
[MAILTMPLEN
]; /* build FQN from ambiguous name */
387 if(!context_allowed(context_apply(tmp
, context
, name
, sizeof(tmp
)))){
389 * If a stream is passed to context_open, we will either re-use
393 pine_mail_close(old
);
395 return((MAILSTREAM
*)NULL
);
398 return(pine_mail_open(old
, tmp
, opt
, retflags
));
402 /* Context Manager status
404 * candidate stream for recycling
407 * Returns: T if call succeeds, NIL on failure
411 context_status(CONTEXT_S
*context
, MAILSTREAM
*stream
, char *name
, long int opt
)
413 return(context_status_full(context
, stream
, name
, opt
, NULL
, NULL
));
418 context_status_full(CONTEXT_S
*context
, MAILSTREAM
*stream
, char *name
,
419 long int opt
, imapuid_t
*uidvalidity
, imapuid_t
*uidnext
)
421 char tmp
[MAILTMPLEN
]; /* build FQN from ambiguous name */
424 return(context_allowed(context_apply(tmp
, context
, name
, sizeof(tmp
)))
425 ? pine_mail_status_full(stream
,tmp
,flags
,uidvalidity
,uidnext
) : 0L);
429 /* Context Manager status
431 * This is very similar to context_status. Instead of a stream pointer we
432 * receive a pointer to a pointer so that we can return a stream that we
433 * opened for further use by the caller.
436 * candidate stream for recycling
439 * Returns: T if call succeeds, NIL on failure
443 context_status_streamp(CONTEXT_S
*context
, MAILSTREAM
**streamp
, char *name
, long int opt
)
445 return(context_status_streamp_full(context
,streamp
,name
,opt
,NULL
,NULL
));
450 context_status_streamp_full(CONTEXT_S
*context
, MAILSTREAM
**streamp
, char *name
,
451 long int opt
, imapuid_t
*uidvalidity
, imapuid_t
*uidnext
)
454 char tmp
[MAILTMPLEN
]; /* build FQN from ambiguous name */
456 if(!context_allowed(context_apply(tmp
, context
, name
, sizeof(tmp
))))
462 if(!*streamp
&& IS_REMOTE(tmp
)){
463 *streamp
= pine_mail_open(NULL
, tmp
,
464 OP_SILENT
|OP_HALFOPEN
|SP_USEPOOL
, NULL
);
470 return(pine_mail_status_full(stream
, tmp
, opt
, uidvalidity
, uidnext
));
474 /* Context Manager rename
479 * Returns: T on success, NIL on failure
483 context_rename (CONTEXT_S
*context
, MAILSTREAM
*stream
, char *old
, char *new)
485 char tmp
[MAILTMPLEN
],tmp2
[MAILTMPLEN
];
487 return((context_allowed(context_apply(tmp
, context
, old
, sizeof(tmp
)))
488 && context_allowed(context_apply(tmp2
, context
, new, sizeof(tmp2
))))
489 ? pine_mail_rename(stream
,tmp
,tmp2
) : 0L);
494 context_already_open_stream(CONTEXT_S
*context
, char *name
, int flags
)
496 char tmp
[MAILTMPLEN
];
498 return(already_open_stream(context_apply(tmp
, context
, name
, sizeof(tmp
)),
503 /* Context Manager delete mailbox
506 * mailbox name to delete
507 * Returns: T on success, NIL on failure
511 context_delete (CONTEXT_S
*context
, MAILSTREAM
*stream
, char *name
)
513 char tmp
[MAILTMPLEN
]; /* build FQN from ambiguous name */
515 return(context_allowed(context_apply(tmp
, context
, name
, sizeof(tmp
)))
516 ? pine_mail_delete(stream
, tmp
) : 0L);
520 /* Context Manager append message string
523 * destination mailbox
524 * stringstruct of message to append
525 * Returns: T on success, NIL on failure
529 context_append (CONTEXT_S
*context
, MAILSTREAM
*stream
, char *name
, STRING
*msg
)
531 char tmp
[MAILTMPLEN
]; /* build FQN from ambiguous name */
533 return(context_allowed(context_apply(tmp
, context
, name
, sizeof(tmp
)))
534 ? pine_mail_append(stream
, tmp
, msg
) : 0L);
538 /* Context Manager append message string with flags
541 * destination mailbox
542 * flags to assign message being appended
543 * date of message being appended
544 * stringstruct of message to append
545 * Returns: T on success, NIL on failure
549 context_append_full(CONTEXT_S
*context
, MAILSTREAM
*stream
, char *name
,
550 char *flags
, char *date
, STRING
*msg
)
552 char tmp
[MAILTMPLEN
]; /* build FQN from ambiguous name */
554 return(context_allowed(context_apply(tmp
, context
, name
, sizeof(tmp
)))
555 ? pine_mail_append_full(stream
, tmp
, flags
, date
, msg
) : 0L);
559 /* Context Manager append multiple message
562 * destination mailbox
563 * append data callback
564 * arbitrary ata for callback use
565 * Returns: T on success, NIL on failure
569 context_append_multiple(CONTEXT_S
*context
, MAILSTREAM
*stream
, char *name
,
570 append_t af
, APPENDPACKAGE
*data
, MAILSTREAM
*not_this_stream
)
572 char tmp
[MAILTMPLEN
]; /* build FQN from ambiguous name */
574 return(context_allowed(context_apply(tmp
, context
, name
, sizeof(tmp
)))
575 ? pine_mail_append_multiple(stream
, tmp
, af
, data
, not_this_stream
) : 0L);
579 /* Mail copy message(s)
583 * destination mailbox
587 context_copy (CONTEXT_S
*context
, MAILSTREAM
*stream
, char *sequence
, char *name
)
589 char *s
, tmp
[MAILTMPLEN
]; /* build FQN from ambiguous name */
593 if(context_apply(tmp
, context
, name
, sizeof(tmp
))[0] == '{'){
594 if((s
= strindex(tmp
, '}')) != NULL
)
602 tmp
[sizeof(tmp
)-1] = '\0';
605 strncpy(s
= tmp
, "INBOX", sizeof(tmp
)); /* presume "inbox" ala c-client */
607 tmp
[sizeof(tmp
)-1] = '\0';
609 return(context_allowed(s
) ? pine_mail_copy(stream
, sequence
, s
) : 0L);
614 * Context manager stream usefulness test
618 * mail stream to test against mailbox name
619 * Returns: stream if useful, else NIL
622 context_same_stream(CONTEXT_S
*context
, char *name
, MAILSTREAM
*stream
)
624 extern MAILSTREAM
*same_stream(char *name
, MAILSTREAM
*stream
);
625 char tmp
[MAILTMPLEN
]; /* build FQN from ambiguous name */
627 return(same_stream(context_apply(tmp
, context
, name
, sizeof(tmp
)), stream
));
632 * new_context - creates and fills in a new context structure, leaving
633 * blank the actual folder list (to be filled in later as
634 * needed). Also, parses the context string provided
635 * picking out any user defined label. Context lines are
638 * [ ["] <string> ["] <white-space>] <context>
642 new_context(char *cntxt_string
, int *prime
)
645 char host
[MAXPATH
], rcontext
[MAXPATH
],
646 view
[MAXPATH
], dcontext
[MAXPATH
],
647 *nickname
= NULL
, *c_string
= NULL
, *p
;
650 * do any context string parsing (like splitting user-supplied
651 * label from actual context)...
653 get_pair(cntxt_string
, &nickname
, &c_string
, 0, 0);
655 if(update_bboard_spec(c_string
, tmp_20k_buf
, SIZEOF_20KBUF
)){
656 fs_give((void **) &c_string
);
657 c_string
= cpystr(tmp_20k_buf
);
660 if(c_string
&& *c_string
== '\"')
661 (void)removing_double_quotes(c_string
);
663 view
[0] = rcontext
[0] = host
[0] = dcontext
[0] = '\0';
664 if((p
= context_digest(c_string
, dcontext
, host
, rcontext
, view
, MAXPATH
)) != NULL
){
665 q_status_message2(SM_ORDER
| SM_DING
, 3, 4,
666 "Bad context, %.200s : %.200s", p
, c_string
);
667 fs_give((void **) &c_string
);
669 fs_give((void **)&nickname
);
674 fs_give((void **) &c_string
);
676 c
= (CONTEXT_S
*) fs_get(sizeof(CONTEXT_S
)); /* get new context */
677 memset((void *) c
, 0, sizeof(CONTEXT_S
)); /* and initialize it */
679 c
->server
= cpystr(host
); /* server + params */
681 c
->context
= cpystr(dcontext
);
683 if(strstr(c
->context
, "#news."))
684 c
->use
|= CNTXT_NEWS
;
686 c
->dir
= new_fdir(NULL
, view
, (c
->use
& CNTXT_NEWS
) ? '*' : '%');
688 /* fix up nickname */
689 if(!(c
->nickname
= nickname
)){ /* make one up! */
690 snprintf(tmp_20k_buf
, SIZEOF_20KBUF
, "%s%s%.100s",
691 (c
->use
& CNTXT_NEWS
)
694 ? (c
->dir
->ref
) :"Mail",
695 (c
->server
) ? " on " : "",
696 (c
->server
) ? c
->server
: "");
697 c
->nickname
= cpystr(tmp_20k_buf
);
700 if(prime
&& !*prime
){
702 c
->use
|= CNTXT_SAVEDFLT
;
707 snprintf(tmp_20k_buf
, SIZEOF_20KBUF
, "%sews groups%s%.100s",
708 (*host
) ? "N" : "Local n", (*host
) ? " on " : "",
709 (*host
) ? host
: "");
712 p
= srchstr(rcontext
, "[]");
713 snprintf(tmp_20k_buf
, SIZEOF_20KBUF
, "%solders%s%.100s in %.*s%s",
714 (*host
) ? "F" : "Local f", (*host
) ? " on " : "",
716 p
? (int) MIN(p
- rcontext
, 100) : 0,
717 rcontext
, (p
&& (p
- rcontext
) > 0) ? "" : "home directory");
720 c
->label
= cpystr(tmp_20k_buf
);
722 dprint((5, "Context: %s serv:%s ref: %s view: %s\n",
723 c
->context
? c
->context
: "?",
724 (c
->server
) ? c
->server
: "\"\"",
725 (c
->dir
->ref
) ? c
->dir
->ref
: "\"\"",
726 (c
->dir
->view
.user
) ? c
->dir
->view
.user
: "\"\""));
733 * Release resources associated with global context list
736 free_contexts(CONTEXT_S
**ctxt
)
739 free_contexts(&(*ctxt
)->next
);
746 * Release resources associated with the given context structure
749 free_context(CONTEXT_S
**cntxt
)
752 if(*cntxt
== ps_global
->context_current
)
753 ps_global
->context_current
= NULL
;
755 if(*cntxt
== ps_global
->context_last
)
756 ps_global
->context_last
= NULL
;
758 if(*cntxt
== ps_global
->last_save_context
)
759 ps_global
->last_save_context
= NULL
;
761 if((*cntxt
)->context
)
762 fs_give((void **) &(*cntxt
)->context
);
765 fs_give((void **) &(*cntxt
)->server
);
767 if((*cntxt
)->nickname
)
768 fs_give((void **)&(*cntxt
)->nickname
);
771 fs_give((void **) &(*cntxt
)->label
);
773 if((*cntxt
)->comment
)
774 fs_give((void **) &(*cntxt
)->comment
);
776 if((*cntxt
)->selected
.reference
)
777 fs_give((void **) &(*cntxt
)->selected
.reference
);
779 if((*cntxt
)->selected
.sub
)
780 free_selected(&(*cntxt
)->selected
.sub
);
782 free_strlist(&(*cntxt
)->selected
.folders
);
784 free_fdir(&(*cntxt
)->dir
, 1);
786 fs_give((void **)cntxt
);