1 /*@ S-nail - a mail user agent derived from Berkeley Mail.
2 *@ Auxiliary functions that don't fit anywhere else.
4 * Copyright (c) 2000-2004 Gunnar Ritter, Freiburg i. Br., Germany.
5 * Copyright (c) 2012 - 2015 Steffen (Daode) Nurpmeso <sdaoden@users.sf.net>.
8 * Copyright (c) 1980, 1993
9 * The Regents of the University of California. All rights reserved.
11 * Redistribution and use in source and binary forms, with or without
12 * modification, are permitted provided that the following conditions
14 * 1. Redistributions of source code must retain the above copyright
15 * notice, this list of conditions and the following disclaimer.
16 * 2. Redistributions in binary form must reproduce the above copyright
17 * notice, this list of conditions and the following disclaimer in the
18 * documentation and/or other materials provided with the distribution.
19 * 3. Neither the name of the University nor the names of its contributors
20 * may be used to endorse or promote products derived from this software
21 * without specific prior written permission.
23 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
24 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
25 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
26 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
27 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
28 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
29 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
30 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
31 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
32 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
36 #define n_FILE auxlily
38 #ifndef HAVE_AMALGAMATION
42 #include <sys/utsname.h>
45 # ifdef HAVE_GETADDRINFO
46 # include <sys/socket.h>
52 #ifndef HAVE_POSIX_RANDOM
60 ui8_t b8
[sizeof(struct rand_arc4
)];
61 ui32_t b32
[sizeof(struct rand_arc4
) / sizeof(ui32_t
)];
66 struct a_aux_err_node
{
67 struct a_aux_err_node
*ae_next
;
72 #ifndef HAVE_POSIX_RANDOM
73 static union rand_state
*_rand
;
76 /* Error ring, for `errors' */
78 static struct a_aux_err_node
*a_aux_err_head
, *a_aux_err_tail
;
79 static size_t a_aux_err_cnt
, a_aux_err_cnt_noted
;
81 static size_t a_aux_err_dirty
;
83 /* Our ARC4 random generator with its completely unacademical pseudo
84 * initialization (shall /dev/urandom fail) */
85 #ifndef HAVE_POSIX_RANDOM
86 static void _rand_init(void);
87 static ui32_t
_rand_weak(ui32_t seed
);
88 SINLINE ui8_t
_rand_get8(void);
91 #ifndef HAVE_POSIX_RANDOM
95 # ifdef HAVE_CLOCK_GETTIME
100 union {int fd
; size_t i
;} u
;
104 _rand
= smalloc(sizeof *_rand
);
106 if ((u
.fd
= open("/dev/urandom", O_RDONLY
)) != -1) {
107 bool_t ok
= (sizeof *_rand
== (size_t)read(u
.fd
, _rand
, sizeof *_rand
));
114 for (seed
= (uintptr_t)_rand
& UI32_MAX
, rnd
= 21; rnd
!= 0; --rnd
) {
115 for (u
.i
= NELEM(_rand
->b32
); u
.i
-- != 0;) {
118 # ifdef HAVE_CLOCK_GETTIME
119 clock_gettime(CLOCK_REALTIME
, &ts
);
120 t
= (ui32_t
)ts
.tv_nsec
;
122 gettimeofday(&ts
, NULL
);
123 t
= (ui32_t
)ts
.tv_usec
;
126 t
= (t
>> 16) | (t
<< 16);
127 _rand
->b32
[u
.i
] ^= _rand_weak(seed
^ t
);
128 _rand
->b32
[t
% NELEM(_rand
->b32
)] ^= seed
;
129 if (rnd
== 7 || rnd
== 17)
130 _rand
->b32
[u
.i
] ^= _rand_weak(seed
^ (ui32_t
)ts
.tv_sec
);
131 k
= _rand
->b32
[u
.i
] % NELEM(_rand
->b32
);
132 _rand
->b32
[k
] ^= _rand
->b32
[u
.i
];
133 seed
^= _rand_weak(_rand
->b32
[k
]);
135 seed
^= nextprime(seed
);
139 for (u
.i
= 5 * sizeof(_rand
->b8
); u
.i
!= 0; --u
.i
)
146 _rand_weak(ui32_t seed
)
148 /* From "Random number generators: good ones are hard to find",
149 * Park and Miller, Communications of the ACM, vol. 31, no. 10,
150 * October 1988, p. 1195.
151 * (In fact: FreeBSD 4.7, /usr/src/lib/libc/stdlib/random.c.) */
158 seed
= (seed
* 16807) - (hi
* 2836);
159 if ((si32_t
)seed
< 0)
169 si
= _rand
->a
._dat
[++_rand
->a
._i
];
170 sj
= _rand
->a
._dat
[_rand
->a
._j
+= si
];
171 _rand
->a
._dat
[_rand
->a
._i
] = sj
;
172 _rand
->a
._dat
[_rand
->a
._j
] = si
;
173 return _rand
->a
._dat
[(ui8_t
)(si
+ sj
)];
175 #endif /* HAVE_POSIX_RANDOM */
184 if ((cp
= ok_vlook(screen
)) == NULL
|| (s
= atoi(cp
)) <= 0)
185 s
= scrnheight
- 2; /* XXX no magics */
191 n_pager_get(char const **env_addon
){
195 rv
= ok_vlook(PAGER
);
197 if(env_addon
!= NULL
){
199 /* Update the manual upon any changes:
200 * *colour-pager*, $PAGER */
201 if(strstr(rv
, "less") != NULL
){
202 if(getenv("LESS") == NULL
)
205 (pstate
& PS_TERMCAP_CA_MODE
) ? "LESS=Ri"
206 : !(pstate
& PS_TERMCAP_DISABLE
) ? "LESS=FRi" :
209 }else if(strstr(rv
, "lv") != NULL
){
210 if(getenv("LV") == NULL
)
211 *env_addon
= "LV=-c";
219 page_or_print(FILE *fp
, size_t lines
)
227 if (n_source_may_yield_control() && (cp
= ok_vlook(crt
)) != NULL
) {
229 union {sl_i sli
; size_t rows
;} u
;
231 u
.sli
= strtol(cp
, &eptr
, 0);
232 u
.rows
= (*cp
!= '\0' && *eptr
== '\0')
233 ? (size_t)u
.sli
: (size_t)scrnheight
;
235 if (u
.rows
> 0 && lines
== 0) {
236 while ((c
= getc(fp
)) != EOF
)
237 if (c
== '\n' && ++lines
>= u
.rows
)
242 if (lines
>= u
.rows
) {
243 run_command(n_pager_get(NULL
), 0, fileno(fp
), COMMAND_FD_PASS
,
244 NULL
, NULL
, NULL
, NULL
);
249 while ((c
= getc(fp
)) != EOF
)
256 which_protocol(char const *name
) /* XXX (->URL (yet auxlily.c)) */
262 enum protocol rv
= PROTO_UNKNOWN
;
265 temporary_protocol_ext
= NULL
;
267 if (name
[0] == '%' && name
[1] == ':')
269 for (cp
= name
; *cp
&& *cp
!= ':'; cp
++)
273 if (cp
[0] == ':' && cp
[1] == '/' && cp
[2] == '/') {
274 if (!strncmp(name
, "pop3://", 7)) {
278 n_err(_("No POP3 support compiled in\n"));
280 } else if (!strncmp(name
, "pop3s://", 8)) {
281 #if defined HAVE_POP3 && defined HAVE_SSL
285 n_err(_("No POP3 support compiled in\n"));
288 n_err(_("No SSL support compiled in\n"));
295 /* TODO This is the de facto maildir code and thus belongs into there!
296 * TODO and: we should have maildir:// and mbox:// pseudo-protos, instead of
297 * TODO or (more likely) in addition to *newfolders*) */
300 np
= ac_alloc((sz
= strlen(name
)) + 4 +1);
301 memcpy(np
, name
, sz
+ 1);
302 if (!stat(name
, &st
)) {
303 if (S_ISDIR(st
.st_mode
) &&
304 (memcpy(np
+sz
, "/tmp", 5), !stat(np
, &st
) && S_ISDIR(st
.st_mode
)) &&
305 (memcpy(np
+sz
, "/new", 5), !stat(np
, &st
) && S_ISDIR(st
.st_mode
)) &&
306 (memcpy(np
+sz
, "/cur", 5), !stat(np
, &st
) && S_ISDIR(st
.st_mode
)))
309 if ((memcpy(np
+sz
, cp
=".gz", 4), !stat(np
, &st
) && S_ISREG(st
.st_mode
)) ||
310 (memcpy(np
+sz
, cp
=".xz",4), !stat(np
,&st
) && S_ISREG(st
.st_mode
)) ||
311 (memcpy(np
+sz
, cp
=".bz2",5), !stat(np
, &st
) && S_ISREG(st
.st_mode
)))
312 temporary_protocol_ext
= cp
;
313 else if ((cp
= ok_vlook(newfolders
)) != NULL
&& !strcmp(cp
, "maildir"))
323 torek_hash(char const *name
)
325 /* Chris Torek's hash.
326 * NOTE: need to change *at least* mk-okey-map.pl when changing the
331 while (*name
!= '\0') {
340 pjw(char const *cp
) /* TODO obsolete that -> torek_hash */
347 h
= (h
<< 4 & 0xffffffff) + (*cp
&0377);
348 if ((g
= h
& 0xf0000000) != 0) {
360 static ui32_t
const primes
[] = {
361 5, 11, 23, 47, 97, 157, 283,
362 509, 1021, 2039, 4093, 8191, 16381, 32749, 65521,
363 131071, 262139, 524287, 1048573, 2097143, 4194301,
364 8388593, 16777213, 33554393, 67108859, 134217689,
365 268435399, 536870909, 1073741789, 2147483647
371 i
= (n
< primes
[NELEM(primes
) / 4] ? 0
372 : (n
< primes
[NELEM(primes
) / 2] ? NELEM(primes
) / 4
373 : NELEM(primes
) / 2));
375 if ((mprime
= primes
[i
]) > n
)
377 while (++i
< NELEM(primes
));
378 if (i
== NELEM(primes
) && mprime
< n
)
385 getprompt(void) /* TODO evaluate only as necessary (needs a bit) PART OF UI! */
386 { /* FIXME getprompt must mb->wc->mb+reset seq! */
387 static char buf
[PROMPT_BUFFER_SIZE
];
390 char const *ccp_base
, *ccp
;
391 size_t NATCH_CHAR( cclen_base COMMA cclen COMMA
) maxlen
, dfmaxlen
;
392 bool_t trigger
; /* 1.: `errors' ring note? 2.: first loop tick done */
395 /* No other place to place this */
397 if (options
& OPT_INTERACTIVE
) {
398 if (!(pstate
& PS_ERRORS_NOTED
) && a_aux_err_head
!= NULL
) {
399 pstate
|= PS_ERRORS_NOTED
;
400 fprintf(stderr
, _("There are new messages in the error message ring "
401 "(denoted by \"#ERR#\")\n"
402 " The `errors' command manages this message ring\n"));
405 if ((trigger
= (a_aux_err_cnt_noted
!= a_aux_err_cnt
)))
406 a_aux_err_cnt_noted
= a_aux_err_cnt
;
412 if ((ccp_base
= ok_vlook(prompt
)) == NULL
|| *ccp_base
== '\0') {
422 ccp_base
= savecatsep(_("#ERR#"), '\0', ccp_base
);
424 NATCH_CHAR( cclen_base
= strlen(ccp_base
); )
426 dfmaxlen
= 0; /* keep CC happy */
430 NATCH_CHAR( cclen
= cclen_base
; )
431 maxlen
= sizeof(buf
) -1;
439 #ifdef HAVE_NATCH_CHAR
440 c
= mblen(ccp
, cclen
); /* TODO use mbrtowc() */
449 } else if ((l
= c
) > 1) {
459 if ((c
= n_shell_expand_escape(&ccp
, TRU1
)) > 0) {
465 if (c
== 0 || c
== PROMPT_STOP
)
469 char const *a
= (c
== PROMPT_DOLLAR
) ? account_name
: displayname
;
472 if ((l
= field_put_bidi_clip(cp
, dfmaxlen
, a
, strlen(a
))) > 0) {
492 nodename(int mayoverride
)
494 static char *sys_hostname
, *hostname
; /* XXX free-at-exit */
499 # ifdef HAVE_GETADDRINFO
500 struct addrinfo hints
, *res
;
502 struct hostent
*hent
;
507 if (mayoverride
&& (hn
= ok_vlook(hostname
)) != NULL
&& *hn
!= '\0') {
509 } else if ((hn
= sys_hostname
) == NULL
) {
513 # ifdef HAVE_GETADDRINFO
514 memset(&hints
, 0, sizeof hints
);
515 hints
.ai_family
= AF_UNSPEC
;
516 hints
.ai_flags
= AI_CANONNAME
;
517 if (getaddrinfo(hn
, NULL
, &hints
, &res
) == 0) {
518 if (res
->ai_canonname
!= NULL
) {
519 size_t l
= strlen(res
->ai_canonname
) +1;
522 memcpy(hn
, res
->ai_canonname
, l
);
527 hent
= gethostbyname(hn
);
532 sys_hostname
= sstrdup(hn
);
533 #if defined HAVE_SOCKETS && defined HAVE_GETADDRINFO
534 if (hn
!= ut
.nodename
)
540 if (hostname
!= NULL
&& hostname
!= sys_hostname
)
542 hostname
= sstrdup(hn
);
548 getrandstring(size_t length
)
555 #ifndef HAVE_POSIX_RANDOM
560 /* We use our base64 encoder with _NOPAD set, so ensure the encoded result
561 * with PAD stripped is still longer than what the user requests, easy way */
562 data
= ac_alloc(i
= length
+ 3);
564 #ifndef HAVE_POSIX_RANDOM
566 data
[i
] = (char)_rand_get8();
571 union {ui32_t i4
; char c
[4];} r
;
574 r
.i4
= (ui32_t
)arc4random();
575 switch ((j
= i
& 3)) {
576 case 0: cp
[3] = r
.c
[3]; j
= 4;
577 case 3: cp
[2] = r
.c
[2];
578 case 2: cp
[1] = r
.c
[1];
579 default: cp
[0] = r
.c
[0]; break;
587 b64_encode_buf(&b64
, data
, length
+ 3,
588 B64_SALLOC
| B64_RFC4648URL
| B64_NOPAD
);
591 assert(b64
.l
>= length
);
592 b64
.s
[length
] = '\0';
598 boolify(char const *inbuf
, uiz_t inlen
, si8_t emptyrv
)
605 assert(inlen
== 0 || inbuf
!= NULL
);
607 if (inlen
== UIZ_MAX
)
608 inlen
= strlen(inbuf
);
611 rv
= (emptyrv
>= 0) ? (emptyrv
== 0 ? 0 : 1) : -1;
613 if ((inlen
== 1 && *inbuf
== '1') ||
614 !ascncasecmp(inbuf
, "true", inlen
) ||
615 !ascncasecmp(inbuf
, "yes", inlen
) ||
616 !ascncasecmp(inbuf
, "on", inlen
))
618 else if ((inlen
== 1 && *inbuf
== '0') ||
619 !ascncasecmp(inbuf
, "false", inlen
) ||
620 !ascncasecmp(inbuf
, "no", inlen
) ||
621 !ascncasecmp(inbuf
, "off", inlen
))
624 dat
= ac_alloc(inlen
+1);
625 memcpy(dat
, inbuf
, inlen
);
628 sli
= strtol(dat
, &eptr
, 0);
629 if (*dat
!= '\0' && *eptr
== '\0')
642 quadify(char const *inbuf
, uiz_t inlen
, char const *prompt
, si8_t emptyrv
)
647 assert(inlen
== 0 || inbuf
!= NULL
);
649 if (inlen
== UIZ_MAX
)
650 inlen
= strlen(inbuf
);
653 rv
= (emptyrv
>= 0) ? (emptyrv
== 0 ? 0 : 1) : -1;
654 else if ((rv
= boolify(inbuf
, inlen
, -1)) < 0 &&
655 !ascncasecmp(inbuf
, "ask-", 4) &&
656 (rv
= boolify(inbuf
+ 4, inlen
- 4, -1)) >= 0 &&
657 (options
& OPT_INTERACTIVE
))
658 rv
= getapproval(prompt
, rv
);
664 n_is_all_or_aster(char const *name
){
668 rv
= ((name
[0] == '*' && name
[1] == '\0') || !asccasecmp(name
, "all"));
676 #ifdef HAVE_CLOCK_GETTIME
678 #elif defined HAVE_GETTIMEOFDAY
684 #ifdef HAVE_CLOCK_GETTIME
685 clock_gettime(CLOCK_REALTIME
, &ts
);
686 rv
= (time_t)ts
.tv_sec
;
687 #elif defined HAVE_GETTIMEOFDAY
688 gettimeofday(&ts
, NULL
);
689 rv
= (time_t)ts
.tv_sec
;
698 time_current_update(struct time_current
*tc
, bool_t full_update
)
701 tc
->tc_time
= n_time_epoch();
703 memcpy(&tc
->tc_gm
, gmtime(&tc
->tc_time
), sizeof tc
->tc_gm
);
704 memcpy(&tc
->tc_local
, localtime(&tc
->tc_time
), sizeof tc
->tc_local
);
705 sstpcpy(tc
->tc_ctime
, ctime(&tc
->tc_time
));
711 n_msleep(uiz_t millis
, bool_t ignint
){
715 #ifdef HAVE_NANOSLEEP
717 struct timespec ts
, trem
;
720 ts
.tv_sec
= millis
/ 1000;
721 ts
.tv_nsec
= (millis
%= 1000) * 1000 * 1000;
723 while((i
= nanosleep(&ts
, &trem
)) != 0 && ignint
)
725 rv
= (i
== 0) ? 0 : (trem
.tv_sec
* 1000) + (trem
.tv_nsec
/ (1000 * 1000));
728 #elif defined HAVE_SLEEP
729 if((millis
/= 1000) == 0)
731 while((rv
= sleep((unsigned int)millis
)) != 0 && ignint
)
734 # error Configuration should have detected a function for sleeping.
742 n_err(char const *format
, ...){
746 va_start(ap
, format
);
748 if(options
& OPT_INTERACTIVE
)
753 if(a_aux_err_dirty
++ == 0)
754 fputs(UAGENT
": ", stderr
);
755 vfprintf(stderr
, format
, ap
);
756 if(strchr(format
, '\n') != NULL
){ /* TODO */
766 n_verr(char const *format
, va_list ap
){
767 /* Check use cases of PS_ERRORS_NOTED, too! */
769 char buf
[LINESIZE
], *xbuf
;
771 struct a_aux_err_node
*enp
;
773 LCTA(ERRORS_MAX
> 3);
777 if(a_aux_err_dirty
++ == 0)
778 fputs(UAGENT
": ", stderr
);
781 if(!(options
& OPT_INTERACTIVE
))
784 vfprintf(stderr
, format
, ap
);
792 l
= vsnprintf(xbuf
, lmax
, format
, ap
);
795 if (UICMP(z
, l
, >=, lmax
)) {
796 /* FIXME Cannot reuse va_list
798 xbuf = srealloc((xbuf == buf ? NULL : xbuf), lmax);
803 fwrite(xbuf
, 1, l
, stderr
);
805 /* Link it into the `errors' message ring */
806 if((enp
= a_aux_err_tail
) == NULL
){
808 enp
= scalloc(1, sizeof *enp
);
809 if(a_aux_err_tail
!= NULL
)
810 a_aux_err_tail
->ae_next
= enp
;
812 a_aux_err_head
= enp
;
813 a_aux_err_tail
= enp
;
815 }else if(enp
->ae_str
.l
> 0 && enp
->ae_str
.s
[enp
->ae_str
.l
- 1] == '\n'){
816 if(a_aux_err_cnt
< ERRORS_MAX
)
819 a_aux_err_head
= (enp
= a_aux_err_head
)->ae_next
;
820 a_aux_err_tail
->ae_next
= enp
;
821 a_aux_err_tail
= enp
;
823 memset(enp
, 0, sizeof *enp
);
826 n_str_add_buf(&enp
->ae_str
, xbuf
, l
);
830 #endif /* HAVE_ERRORS */
833 /* If the format ends with newline, be clean again */
835 size_t i
= strlen(format
);
837 if(i
> 0 && format
[i
- 1] == '\n'){
846 n_err_sighdl(char const *format
, ...){ /* TODO sigsafe; obsolete! */
850 va_start(ap
, format
);
851 vfprintf(stderr
, format
, ap
);
857 n_perr(char const *msg
, int errval
){
870 n_err(fmt
, msg
, strerror(errval
));
875 n_alert(char const *format
, ...){
879 n_err(a_aux_err_dirty
> 0 ? _("\nAlert: ") : _("Alert: "));
881 va_start(ap
, format
);
890 n_panic(char const *format
, ...){
894 if(a_aux_err_dirty
> 0){
898 fprintf(stderr
, UAGENT
": Panic: ");
900 va_start(ap
, format
);
901 vfprintf(stderr
, format
, ap
);
907 abort(); /* Was exit(EXIT_ERR); for a while, but no */
914 struct a_aux_err_node
*enp
;
921 if(!asccasecmp(*argv
, "show"))
923 if(!asccasecmp(*argv
, "clear"))
926 fprintf(stderr
, _("Synopsis: errors: (<show> or) <clear> the error ring\n"));
930 return v
== NULL
? !STOP
: !OKAY
; /* xxx 1:bad 0:good -- do some */
936 if(a_aux_err_head
== NULL
){
937 fprintf(stderr
, _("The error ring is empty\n"));
941 if((fp
= Ftmp(NULL
, "errors", OF_RDWR
| OF_UNLINK
| OF_REGISTER
)) ==
943 fprintf(stderr
, _("tmpfile"));
948 for(i
= 0, enp
= a_aux_err_head
; enp
!= NULL
; enp
= enp
->ae_next
)
949 fprintf(fp
, "- %4" PRIuZ
". %" PRIuZ
" bytes: %s",
950 ++i
, enp
->ae_str
.l
, enp
->ae_str
.s
);
951 /* We don't know wether last string ended with NL; be simple */
954 page_or_print(fp
, 0);
960 a_aux_err_tail
= NULL
;
961 a_aux_err_cnt
= a_aux_err_cnt_noted
= 0;
962 while((enp
= a_aux_err_head
) != NULL
){
963 a_aux_err_head
= enp
->ae_next
;
969 #endif /* HAVE_ERRORS */