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 /*======================================================================
17 Routines for pine start up and initialization
21 #include "../pith/headers.h"
22 #include "../pith/init.h"
23 #include "../pith/conf.h"
24 #include "../pith/status.h"
25 #include "../pith/folder.h"
31 int compare_sm_files(const qsort_t
*, const qsort_t
*);
35 /*----------------------------------------------------------------------
36 Sets login, full_username and home_dir
38 Args: ps -- The Pine structure to put the user name, etc in
40 Result: sets the fullname, login and home_dir field of the pine structure
41 returns 0 on success, -1 if not.
45 init_username(struct pine
*ps
)
52 #if defined(DOS) || defined(OS2)
54 expanded
= expand_variables(tmp_20k_buf
, SIZEOF_20KBUF
,
57 if(!expanded
&& ps
->vars
[V_USER_ID
].post_user_val
.p
)
58 expanded
= expand_variables(tmp_20k_buf
, SIZEOF_20KBUF
,
59 ps
->vars
[V_USER_ID
].post_user_val
.p
, 0);
61 if(!expanded
&& ps
->vars
[V_USER_ID
].main_user_val
.p
)
62 expanded
= expand_variables(tmp_20k_buf
, SIZEOF_20KBUF
,
63 ps
->vars
[V_USER_ID
].main_user_val
.p
, 0);
66 ps
->blank_user_id
= 1;
68 ps
->VAR_USER_ID
= cpystr(expanded
? expanded
: "");
70 ps
->VAR_USER_ID
= cpystr(ps
->ui
.login
);
71 if(!ps
->VAR_USER_ID
[0]){
72 fprintf(stderr
, "Who are you? (Unable to look up login name)\n");
78 if(ps
->vars
[V_PERSONAL_NAME
].is_fixed
){
79 if(ps
->FIX_PERSONAL_NAME
){
80 expanded
= expand_variables(tmp_20k_buf
, SIZEOF_20KBUF
,
81 ps
->FIX_PERSONAL_NAME
, 0);
83 if(ps
->vars
[V_PERSONAL_NAME
].main_user_val
.p
||
84 ps
->vars
[V_PERSONAL_NAME
].post_user_val
.p
){
85 ps_global
->give_fixed_warning
= 1;
86 ps_global
->fix_fixed_warning
= 1;
88 else if(ps
->COM_PERSONAL_NAME
)
89 ps_global
->give_fixed_warning
= 1;
92 if(ps
->COM_PERSONAL_NAME
)
93 expanded
= expand_variables(tmp_20k_buf
, SIZEOF_20KBUF
,
94 ps
->COM_PERSONAL_NAME
, 0);
96 if(!expanded
&& ps
->vars
[V_PERSONAL_NAME
].post_user_val
.p
)
97 expanded
= expand_variables(tmp_20k_buf
, SIZEOF_20KBUF
,
98 ps
->vars
[V_PERSONAL_NAME
].post_user_val
.p
, 0);
100 if(!expanded
&& ps
->vars
[V_PERSONAL_NAME
].main_user_val
.p
)
101 expanded
= expand_variables(tmp_20k_buf
, SIZEOF_20KBUF
,
102 ps
->vars
[V_PERSONAL_NAME
].main_user_val
.p
, 0);
106 expanded
= ps
->ui
.fullname
;
107 #if defined(DOS) || defined(OS2)
108 ps
->blank_personal_name
= 1;
112 ps
->VAR_PERSONAL_NAME
= cpystr(expanded
? expanded
: "");
114 dprint((1, "Userid: %s\nFullname: \"%s\"\n",
115 ps
->VAR_USER_ID
, ps
->VAR_PERSONAL_NAME
));
120 /*----------------------------------------------------------------------
123 Args: ps -- The Pine structure to put the user name, etc in
125 Result: sets the home_dir field of the pine structure
126 returns 0 on success, -1 if not.
130 init_userdir(struct pine
*ps
)
132 char fld_dir
[MAXPATH
+1];
134 if(strlen(ps
->home_dir
) + strlen(ps
->VAR_MAIL_DIRECTORY
)+2 > MAXPATH
){
135 printf(_("Folders directory name is longer than %d\n"), MAXPATH
);
136 printf(_("Directory name: \"%s/%s\"\n"),ps
->home_dir
,
137 ps
->VAR_MAIL_DIRECTORY
);
140 #if defined(DOS) || defined(OS2)
141 if(ps
->VAR_MAIL_DIRECTORY
[1] == ':'){
142 strncpy(fld_dir
, ps
->VAR_MAIL_DIRECTORY
, sizeof(fld_dir
)-1);
143 fld_dir
[sizeof(fld_dir
)-1] = '\0';
147 build_path(fld_dir
, ps
->home_dir
, ps
->VAR_MAIL_DIRECTORY
, sizeof(fld_dir
));
148 ps
->folders_dir
= cpystr(fld_dir
);
154 /*----------------------------------------------------------------------
155 Fetch the hostname of the current system and put it in pine struct
157 Args: ps -- The pine structure to put the hostname, etc in
159 Result: hostname, localdomain, userdomain and maildomain are set
162 ** Pine uses the following set of names:
163 hostname - The fully-qualified hostname. Obtained with
164 gethostbyname() which reads /etc/hosts or does a DNS
165 lookup. This may be blank.
166 localdomain - The domain name without the host. Obtained from the
167 above hostname if it has a "." in it. Removes first
168 segment. If hostname has no "." in it then the hostname
169 is used. This may be blank.
170 userdomain - Explicitly configured domainname. This is read out of the
171 global pine.conf or user's .pinerc. The user's entry in the
174 ** Pine has the following uses for such names:
176 1. On outgoing messages in the From: line
177 Uses userdomain if there is one. If not uses, uses
178 hostname unless Pine has been configured to use localdomain.
180 2. When expanding/fully-qualifying unqualified addresses during
184 3. When expanding/fully-qualifying unqualified addresses during
185 composition when a local entry in the password file exists for
187 If no userdomain is given, then this lookup is always done
188 and the hostname is used unless Pine's been configured to
189 use the localdomain. If userdomain is defined, it is used,
190 but no local lookup is done. We can't assume users on the
191 local host are valid in the given domain (and, for simplicity,
192 have chosen to ignore the cases userdomain matches localdomain
193 or localhost). Setting user-lookup-even-if-domain-mismatch
194 feature will tell pine to override this behavior and perform
195 the local lookup anyway. The problem of a global "even-if"
196 set and a .pinerc-defined user-domain of something odd causing
197 the local lookup, but this will only effect the personal name,
198 and is not judged to be a significant problem.
200 4. In determining if an address is that of the current pine user for
201 formatting index and filtering addresses when replying
202 If a userdomain is specified the address must match the
203 userdomain exactly. If a userdomain is not specified or the
204 userdomain is the same as the hostname or domainname, then
205 an address will be considered the users if it matches either
206 the domainname or the hostname. Of course, the userid must
210 The fully-qualified hostname is always users here.
213 ** Setting the domain names
214 To set the domain name for all Pine users on the system to be
215 different from what Pine figures out from DNS, set the domain name in
216 the "user-domain" variable in pine.conf. To set the domain name for an
217 individual user, set the "user-domain" variable in his .pinerc.
218 The .pinerc setting overrides any other setting.
221 init_hostname(struct pine
*ps
)
223 char hostname
[MAX_ADDRESS
+1], domainname
[MAX_ADDRESS
+1];
225 getdomainnames(hostname
, sizeof(hostname
)-1,
226 domainname
, sizeof(domainname
)-1);
229 fs_give((void **)&ps
->hostname
);
231 ps
->hostname
= cpystr(hostname
);
234 fs_give((void **)&ps
->localdomain
);
236 ps
->localdomain
= cpystr(domainname
);
237 ps
->userdomain
= NULL
;
239 if(ps
->VAR_USER_DOMAIN
&& ps
->VAR_USER_DOMAIN
[0]){
240 ps
->maildomain
= ps
->userdomain
= ps
->VAR_USER_DOMAIN
;
242 #if defined(DOS) || defined(OS2)
243 if(ps
->VAR_USER_DOMAIN
)
244 ps
->blank_user_domain
= 1; /* user domain set to null string! */
246 ps
->maildomain
= ps
->localdomain
[0] ? ps
->localdomain
: ps
->hostname
;
248 ps
->maildomain
= strucmp(ps
->VAR_USE_ONLY_DOMAIN_NAME
, "yes")
249 ? ps
->hostname
: ps
->localdomain
;
254 * Tell c-client what domain to use when completing unqualified
255 * addresses it finds in local mailboxes. Remember, it won't
256 * affect what's to the right of '@' for unqualified addresses in
259 mail_parameters(NULL
, SET_LOCALHOST
, (void *) ps
->maildomain
);
260 if(F_OFF(F_QUELL_MAILDOMAIN_WARNING
, ps
) && !strchr(ps
->maildomain
, '.')){
261 snprintf(tmp_20k_buf
, SIZEOF_20KBUF
, _("Incomplete maildomain \"%s\"."),
263 init_error(ps
, SM_ORDER
| SM_DING
, 3, 5, tmp_20k_buf
);
265 _("Return address in mail you send may be incorrect."), SIZEOF_20KBUF
);
266 tmp_20k_buf
[SIZEOF_20KBUF
-1] = '\0';
267 init_error(ps
, SM_ORDER
| SM_DING
, 3, 5, tmp_20k_buf
);
270 dprint((1,"User domain name being used \"%s\"\n",
271 ps
->userdomain
== NULL
? "" : ps
->userdomain
));
272 dprint((1,"Local Domain name being used \"%s\"\n",
273 ps
->localdomain
? ps
->localdomain
: "?"));
274 dprint((1,"Host name being used \"%s\"\n",
275 ps
->hostname
? ps
->hostname
: "?"));
277 "Mail Domain name being used (by c-client too) \"%s\"\n",
278 ps
->maildomain
? ps
->maildomain
: "?"));
280 if(!ps
->maildomain
|| !ps
->maildomain
[0]){
281 #if defined(DOS) || defined(OS2)
282 if(ps
->blank_user_domain
)
283 return(0); /* prompt for this in send.c:dos_valid_from */
285 fprintf(stderr
, _("No host name or domain name set\n"));
293 /*----------------------------------------------------------------------
294 Make sure the default save folders exist in the default
298 init_save_defaults(void)
300 CONTEXT_S
*save_cntxt
;
302 if(!ps_global
->VAR_DEFAULT_FCC
||
303 !*ps_global
->VAR_DEFAULT_FCC
||
304 !ps_global
->VAR_DEFAULT_SAVE_FOLDER
||
305 !*ps_global
->VAR_DEFAULT_SAVE_FOLDER
)
308 if(!(save_cntxt
= default_save_context(ps_global
->context_list
)))
309 save_cntxt
= ps_global
->context_list
;
311 if(!(folder_exists(save_cntxt
, ps_global
->VAR_DEFAULT_FCC
) & FEX_ISFILE
))
312 context_create(save_cntxt
, NULL
, ps_global
->VAR_DEFAULT_FCC
);
314 if(!(folder_exists(save_cntxt
, ps_global
->VAR_DEFAULT_SAVE_FOLDER
) &
316 context_create(save_cntxt
, NULL
, ps_global
->VAR_DEFAULT_SAVE_FOLDER
);
318 free_folder_list(save_cntxt
);
322 /*----------------------------------------------------------------------
323 Put sent-mail files in date order
325 Args: a, b -- The names of two files. Expects names to be sent-mail-mmm-yy
326 Other names will sort in order and come before those
330 compare_sm_files(const qsort_t
*aa
, const qsort_t
*bb
)
332 struct sm_folder
*a
= (struct sm_folder
*)aa
,
333 *b
= (struct sm_folder
*)bb
;
335 if(a
->month_num
== -1 && b
->month_num
== -1 && a
->name
&& b
->name
)
336 return(strucmp(a
->name
, b
->name
));
337 if(a
->month_num
== -1) return(-1);
338 if(b
->month_num
== -1) return(1);
340 return(a
->month_num
- b
->month_num
);
345 /*----------------------------------------------------------------------
346 Create an ordered list of sent-mail folders and their month numbers
348 Args: dir -- The directory to find the list of files in
350 Result: Pointer to list of files is returned.
352 This list includes all files that start with "sent-mail", but not "sent-mail"
356 get_mail_list(CONTEXT_S
*list_cntxt
, char *folder_base
)
358 register struct sm_folder
*sm
= NULL
;
359 struct sm_folder
*sml
= NULL
;
361 int i
, folder_base_len
;
363 char searchname
[MAXPATH
+1];
365 if((folder_base_len
= strlen(folder_base
)) == 0 || !list_cntxt
){
366 sml
= (struct sm_folder
*) fs_get(sizeof(struct sm_folder
));
367 memset((void *)sml
, 0, sizeof(struct sm_folder
));
372 if(*list_cntxt
->context
!= '{'){ /* NOT an IMAP collection! */
373 snprintf(searchname
, sizeof(searchname
), "%4.4s*", folder_base
);
374 folder_base_len
= strlen(searchname
) - 1;
377 #endif /* MAXPATH + 1 = sizeof(searchmane) */
378 snprintf(searchname
, sizeof(searchname
), "%.*s*", MAXPATH
+1-2, folder_base
);
380 build_folder_list(NULL
, list_cntxt
, searchname
, NULL
, BFL_FLDRONLY
);
382 max_files
= MIN(MAX(0, folder_total(FOLDERS(list_cntxt
))), 5000);
383 sml
= sm
= (struct sm_folder
*) fs_get(sizeof(struct sm_folder
)*(max_files
+1));
384 memset((void *)sml
, 0, sizeof(struct sm_folder
) * (max_files
+1));
386 for(i
= 0; i
< folder_total(FOLDERS(list_cntxt
)); i
++){
387 filename
= folder_entry(i
, FOLDERS(list_cntxt
))->name
;
389 if(struncmp(filename
, folder_base
, folder_base_len
) == 0
390 && strucmp(filename
, folder_base
)){
392 if(*list_cntxt
->context
!= '{'){
394 for(j
= 0; j
< 4; j
++)
395 if(!isdigit((unsigned char)filename
[folder_base_len
+ j
]))
398 if(j
< 4) /* not proper date format! */
399 continue; /* keep trying */
403 if(strnicmp(filename
, folder_base
, folder_base_len
) == 0
404 && stricmp(filename
, folder_base
)){
406 if(strncmp(filename
, folder_base
, folder_base_len
) == 0
407 && strcmp(filename
, folder_base
)){
410 sm
->name
= cpystr(filename
);
412 if(*list_cntxt
->context
!= '{'){ /* NOT an IMAP collection! */
413 sm
->month_num
= (sm
->name
[folder_base_len
] - '0') * 10;
414 sm
->month_num
+= sm
->name
[folder_base_len
+ 1] - '0';
418 sm
->month_num
= month_num(sm
->name
+ (size_t)folder_base_len
+ 1);
420 if(sm
>= &sml
[max_files
])
421 break; /* Too many files, ignore the rest ; shouldn't occur */
425 /* anything to sort?? */
426 if(sml
->name
&& *(sml
->name
) && (sml
+1)->name
&& *((sml
+1)->name
)){
429 sizeof(struct sm_folder
),
439 check_prune_time(time_t *now
, struct tm
**tm_now
)
443 *now
= time((time_t *) 0);
444 *tm_now
= localtime(now
);
447 * If the last time we did this is blank (as if pine's run for
448 * first time), don't go thru list asking, but just note it for
451 if(ps_global
->VAR_LAST_TIME_PRUNE_QUESTION
== NULL
){
452 ps_global
->last_expire_year
= (*tm_now
)->tm_year
;
453 ps_global
->last_expire_month
= (*tm_now
)->tm_mon
;
454 snprintf(tmp
, sizeof(tmp
), "%d.%d", ps_global
->last_expire_year
,
455 ps_global
->last_expire_month
+ 1);
456 set_variable(V_LAST_TIME_PRUNE_QUESTION
, tmp
, 1, 1, Main
);
460 if(ps_global
->last_expire_year
!= -1 &&
461 ((*tm_now
)->tm_year
< ps_global
->last_expire_year
||
462 ((*tm_now
)->tm_year
== ps_global
->last_expire_year
&&
463 (*tm_now
)->tm_mon
<= ps_global
->last_expire_month
)))
471 first_run_of_month(void)
476 now
= time((time_t *) 0);
477 tm_now
= localtime(&now
);
479 if(ps_global
->last_expire_year
== -1 ||
480 (tm_now
->tm_year
< ps_global
->last_expire_year
||
481 (tm_now
->tm_year
== ps_global
->last_expire_year
&&
482 tm_now
->tm_mon
<= ps_global
->last_expire_month
)))
490 first_run_of_year(void)
495 now
= time((time_t *) 0);
496 tm_now
= localtime(&now
);
498 if(ps_global
->last_expire_year
== -1 ||
499 (tm_now
->tm_year
<= ps_global
->last_expire_year
))
507 * prune_move_folder - rename folder in context and delete old copy
508 * Returns -1 if unsuccessful.
511 prune_move_folder(char *oldpath
, char *newpath
, CONTEXT_S
*prune_cntxt
)
513 char spath
[MAXPATH
+1];
515 strncpy(spath
, oldpath
, sizeof(spath
)-1);
516 spath
[sizeof(spath
)-1] = '\0';
518 /*--- User says OK to rename ---*/
519 dprint((5, "rename \"%s\" to \"%s\"\n",
520 spath
? spath
: "?", newpath
? newpath
: "?"));
521 q_status_message1(SM_ORDER
, 1, 3,
522 /* TRANSLATORS: arg is a filename */
523 _("Renaming \"%s\" at start of month"),
524 pretty_fn(spath
? spath
: "?"));
526 if(!context_rename(prune_cntxt
, NULL
, spath
, newpath
)){
527 q_status_message2(SM_ORDER
| SM_DING
, 3, 4,
528 /* TRANSLATORS: 1st arg is filename, 2nd is error message */
529 _("Error renaming \"%s\": %s"),
530 pretty_fn(spath
? spath
: "?"),
531 error_description(errno
));
532 dprint((1, "Error renaming %s to %s: %s\n",
533 spath
? spath
: "?", newpath
? newpath
: "?",
534 error_description(errno
)));
535 display_message('x');
539 context_create(prune_cntxt
, NULL
, spath
);
545 html_directory_path(char *sname
, char *sbuf
, size_t len
)
549 size_t snl
= strlen(sname
);
550 if(is_absolute_path(sname
)){
551 strncpy(sbuf
, sname
, len
-1);
555 else if(ps_global
->VAR_OPER_DIR
){
556 if(strlen(ps_global
->VAR_OPER_DIR
) + snl
< len
- 1)
557 build_path(sbuf
, ps_global
->VAR_OPER_DIR
, sname
, len
);
559 else if(strlen(ps_global
->home_dir
) + snl
< len
- 1)
560 build_path(sbuf
, ps_global
->home_dir
, sname
, len
);
562 return(*sbuf
? sbuf
: NULL
);
566 init_html_directory(char *path
)
569 switch(is_writable_dir(path
)){
570 case 0: rv
= 0; break;
571 case 3: rv
= create_mail_dir(path
) < 0 ? -1 : 0; break;
572 default: rv
= -1; break;
578 create_html_log(void)
581 rv
= fs_get(sizeof(HTML_LOG_S
));
582 memset((void *) rv
, 0, sizeof(HTML_LOG_S
));
588 add_html_log(HTML_LOG_S
**htmlp
, char *d
)
592 for(h
= *htmlp
; h
&& h
->next
; h
= h
->next
);
595 h
= create_html_log();
599 h
->next
= create_html_log();
603 h
->to_delete
= time(0) + 10*60; /* 10 minutes life */
607 free_html_log(HTML_LOG_S
**htmlp
)
609 if (htmlp
== NULL
|| *htmlp
== NULL
)
612 if((*htmlp
)->dir
) fs_give((void **) &(*htmlp
)->dir
);
613 if((*htmlp
)->next
) free_html_log(&(*htmlp
)->next
);
614 fs_give((void **)htmlp
);
618 html_dir_clean(int force
)
620 HTML_LOG_S
*h
, prev
, *j
;
621 time_t now
= time(0);
626 struct _finddata_t dbuf
;
627 char buf
[_MAX_PATH
+ 4];
629 #endif /* _WINDOWS */
631 char fpath
[MAXPATH
], *fname
;
632 int delete_happened
= 0;
634 /* these are barriers to try to not to enter here */
635 if(ps_global
->html_dir
== NULL
636 || ps_global
->html_dir_list
== NULL
637 || our_stat(ps_global
->html_dir
, &sbuf
) < 0) return;
639 for(h
= ps_global
->html_dir_list
; h
; h
= h
->next
){
640 if(force
|| now
>= h
->to_delete
){ /* the time to delete has come */
641 if(strlen(h
->dir
) + strlen(ps_global
->html_dir
) + 3 < MAXPATH
){
642 if(our_stat(h
->dir
, &sbuf
) < 0)
644 if((sbuf
.st_mode
& S_IFMT
) == S_IFDIR
){
646 if((dirp
= opendir(h
->dir
)) != NULL
){
647 while ((d
= readdir(dirp
)) != NULL
){
650 snprintf(buf
, sizeof(buf
), "%s%s*.*", ps_global
->html_dir
, (ps_global
->html_dir
[strlen(ps_global
->html_dir
)-1] == '\\') ? "" : "\\");
651 buf
[sizeof(buf
)-1] = '\0';
652 if((findrv
= _findfirst(buf
, &dbuf
)) < 0)
655 fname
= fname_to_utf8(dbuf
.name
);
656 #endif /* _WINDOWS */
657 if(!strcmp(fname
, ".") || !strcmp(fname
, ".."))
659 if(strlen(h
->dir
) + strlen(fname
) + 3 < MAXPATH
){
660 snprintf(fpath
, sizeof(fpath
) - 1, "%s%s%s", h
->dir
, S_FILESEP
, fname
);
661 fpath
[sizeof(fpath
)-1] = '\0';
669 } while(_findnext(findrv
, &dbuf
) == 0);
670 #endif /* _WINDOWS */
671 if(!our_rmdir(h
->dir
)){
672 h
->to_delete
= 0; /* mark it deleted */
680 /* remove all deleted directories from the list */
682 prev
.to_delete
= 1; /* do not free this entry */
683 (&prev
)->next
= ps_global
->html_dir_list
;
684 for (j
= &prev
; j
&& j
->next
; ){
685 if(j
->next
->to_delete
== 0){
694 ps_global
->html_dir_list
= (&prev
)->next
;
697 /* this is wrong, but we do not care if this fails. What this
698 * is trying to do is to the delete the .alpine-html directory
699 * if it is empty. We could test if it is empty by reading the
700 * directory. Instead we will use a weaker test checking if we
701 * have anything else to erase. If we don't, then we try to
702 * erase ps_global->html_dir, and we do not worry if we fail.
704 if(ps_global
->html_dir_list
== NULL
)
705 our_rmdir(ps_global
->html_dir
);