/*****************************************************************************/ /* VMS.c Eclectic collection of stubs, glue and miscellaneous functionality, primarily supporting acme-client but also LibTLS and WCME in general. The file-system wrapper functions rely on the VMS.H file using a macro to modify the standard function name into an equivalent "wrap_" prefixed one. For example, a use of fdopen() in code becomes an actual use of wrap_fdopen(). In this code module those are undefined below and functions beginning "rtl_" (e.g. rtl_fdopen()) are redefined to the C-RTL function name (e.g. fdopen()). The multi-pass macro preprocessor permits this. The "rtl_" is used merely to improve clarity in the macro preprocessing. This module is a mix of home-grown and code segments/functions purloined from OpenBSD sources and used and redistibuted under the OpenBSD Copyright Policy https://www.openbsd.org/policy.html VERSION HISTORY --------------- 26-DEC-2018 MGD modifications for OpenBSD acme-client 29-MAR-2018 MGD replaced timegm() code 15-OCT-2017 MGD pairofsockets() for building on VMS < V8.2 23-APR-2017 MGD initial */ /*****************************************************************************/ #include "wcme_config.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "vms.h" #include "../util.h" static ulong SysPrvMask [2] = { PRV$M_SYSPRV, 0 }; static ulong PrevPrvMask [2]; static char* subps[10] = { "netproc", /* COMP_NET */ "keyproc", /* COMP_KEY */ "certproc", /* COMP_CERT */ "acctproc", /* COMP_ACCOUNT */ "chngproc", /* COMP_CHALLENGE */ "fileproc", /* COMP_FILE */ "dnsproc", /* COMP_DNS */ "revokeproc" /* COMP_REVOKE */ }; /*****************************************************************************/ /* Stubs. */ int chroot (const char *path) { printf("chroot() |%s|\n",path);return 0; } char* getprogname() { return "WCME"; } int setgroups (int size, void *gid_t) { return 0; } int setegid (unsigned int gid_t) { return 0; } char *strsignal (int signal) { return "no-signal"; } /*****************************************************************************/ /* After zeroing free sensitive memory. */ void freezero (void* buf, int size) { memset (buf, 0, size); free (buf); } /*****************************************************************************/ /* * This is sqrt(SIZE_MAX+1), as s1*s2 <= SIZE_MAX * if both s1 < MUL_NO_OVERFLOW and s2 < MUL_NO_OVERFLOW */ #define MUL_NO_OVERFLOW (1UL << (sizeof(size_t) * 4)) void * reallocarray (void *optr, int nmemb, int size) { if ((nmemb >= MUL_NO_OVERFLOW || size >= MUL_NO_OVERFLOW) && nmemb > 0 && SIZE_MAX / nmemb < size) { errno = ENOMEM; return NULL; } return realloc(optr, size * nmemb); } /*****************************************************************************/ /* * This is sqrt(SIZE_MAX+1), as s1*s2 <= SIZE_MAX * if both s1 < MUL_NO_OVERFLOW and s2 < MUL_NO_OVERFLOW */ void * recallocarray(void *ptr, size_t oldnmemb, size_t newnmemb, size_t size) { size_t oldsize, newsize; void *newptr; if (ptr == NULL) return calloc(newnmemb, size); if ((newnmemb >= MUL_NO_OVERFLOW || size >= MUL_NO_OVERFLOW) && newnmemb > 0 && SIZE_MAX / newnmemb < size) { errno = ENOMEM; return NULL; } newsize = newnmemb * size; if ((oldnmemb >= MUL_NO_OVERFLOW || size >= MUL_NO_OVERFLOW) && oldnmemb > 0 && SIZE_MAX / oldnmemb < size) { errno = EINVAL; return NULL; } oldsize = oldnmemb * size; /* * Don't bother too much if we're shrinking just a bit, * we do not shrink for series of small steps, oh well. */ if (newsize <= oldsize) { size_t d = oldsize - newsize; if (d < oldsize / 2 && d < (size_t)getpagesize()) { memset((char *)ptr + newsize, 0, d); return ptr; } } newptr = malloc(newsize); if (newptr == NULL) return NULL; if (newsize > oldsize) { memcpy(newptr, ptr, oldsize); memset((char *)newptr + oldsize, 0, newsize - oldsize); } else memcpy(newptr, ptr, newsize); #ifndef __VMS explicit_bzero(ptr, oldsize); #endif free(ptr); return newptr; } /*****************************************************************************/ /* Error message. */ void verr (const char *format, va_list ap) { char *cptr; char fbuf [256]; if (cptr = UtilSetPrn(NULL)) sprintf (fbuf, "%s [%s] %s\n", tstamp(NULL), cptr, format); else sprintf (fbuf, "%s\n", format); vfprintf (stderr, fbuf, ap); } /*****************************************************************************/ /* Error message. */ void verrx (const char *format, va_list ap) { char *cptr; char fbuf [256]; if (cptr = UtilSetPrn(NULL)) sprintf (fbuf, "%s [%s] %s\n", tstamp(NULL), cptr, format); else sprintf (fbuf, "%s\n", format); vfprintf (stderr, fbuf, ap); } /*****************************************************************************/ /* Error message. */ void err (int eval, const char *fmt, ...) { va_list ap; va_start(ap, fmt); verr(fmt, ap); va_end(ap); } /*****************************************************************************/ /* Error message. */ void errx (int eval, const char *fmt, ...) { va_list ap; va_start(ap, fmt); verrx(fmt, ap); va_end(ap); } /*****************************************************************************/ /* Warning message. */ void vwarn (const char *format, va_list ap) { char *cptr; char fbuf [256]; if (cptr = UtilSetPrn(NULL)) sprintf (fbuf, "%s [%s] %s\n", tstamp(NULL), cptr, format); else sprintf (fbuf, "%s\n", format); vfprintf (stderr, fbuf, ap); } /*****************************************************************************/ /* Warning message. */ void vwarnx (const char *format, va_list ap) { char *cptr; char fbuf [256]; if (cptr = UtilSetPrn(NULL)) sprintf (fbuf, "%s [%s] %s\n", tstamp(NULL), cptr, format); else sprintf (fbuf, "%s\n", format); vfprintf (stderr, fbuf, ap); } /*****************************************************************************/ /* Warning message. */ void warn (const char *fmt, ...) { va_list ap; va_start(ap, fmt); vwarn(fmt, ap); va_end(ap); } /*****************************************************************************/ /* Warning message. */ void warnx (const char *fmt, ...) { va_list ap; va_start(ap, fmt); vwarnx(fmt, ap); va_end(ap); } /*****************************************************************************/ /* * Copy src to string dst of size siz. At most siz-1 characters * will be copied. Always NUL terminates (unless siz == 0). * Returns strlen(src); if retval >= siz, truncation occurred. */ size_t strlcpy (char *dst, const char *src, size_t siz) { char *d = dst; const char *s = src; size_t n = siz; /*********/ /* begin */ /*********/ /* Copy as many bytes as will fit */ if (n != 0) { while (--n != 0) { if ((*d++ = *s++) == '\0') break; } } /* Not enough room in dst, add NUL and traverse rest of src */ if (n == 0) { if (siz != 0) *d = '\0'; /* NUL-terminate dst */ while (*s++) ; } return(s - src - 1); /* count does not include NUL */ } /*****************************************************************************/ /* * Appends src to string dst of size siz (unlike strncat, siz is the * full size of dst, not space left). At most siz-1 characters * will be copied. Always NUL terminates (unless siz <= strlen(dst)). * Returns strlen(src) + MIN(siz, strlen(initial dst)). * If retval >= siz, truncation occurred. */ size_t strlcat (char *dst, const char *src, size_t siz) { char *d = dst; const char *s = src; size_t n = siz; size_t dlen; /*********/ /* begin */ /*********/ /* Find the end of dst and adjust bytes left but don't go past end */ while (n-- != 0 && *d != '\0') d++; dlen = d - dst; n = siz - dlen; if (n == 0) #ifndef __VMS return(dlen + strlen(s)); #else { const char *sptr = s; while (*sptr) sptr++; return(dlen + (sptr - s)); } #endif while (*s != '\0') { if (n != 1) { *d++ = *s; n--; } s++; } *d = '\0'; return(dlen + (s - src)); /* count does not include NUL */ } /*****************************************************************************/ /* Duplicate the string up to |n| characters. */ char* strndup(const char *s, size_t n) { char* new = malloc(n+1); if (new) { strncpy(new, s, n); new[n] = '\0'; } return new; } /*****************************************************************************/ /* The timingsafe_bcmp() and timingsafe_memcmp() functions lexicographically compare the first len bytes (each interpreted as an unsigned char) pointed to by b1 and b2. Additionally, their running times are independent of the byte sequences compared, making them safe to use for comparing secret values such as cryptographic MACs. In contrast, bcmp(3) and memcmp(3) may short-circuit after finding the first differing byte. */ int timingsafe_memcmp (const void *b1, const void *b2, size_t len) { size_t i; int res = 0, done = 0; const unsigned char *p1 = b1, *p2 = b2; /*********/ /* begin */ /*********/ for (i = 0; i < len; i++) { /* lt is -1 if p1[i] < p2[i]; else 0. */ int lt = (p1[i] - p2[i]) >> CHAR_BIT; /* gt is -1 if p1[i] > p2[i]; else 0. */ int gt = (p2[i] - p1[i]) >> CHAR_BIT; /* cmp is 1 if p1[i] > p2[i]; -1 if p1[i] < p2[i]; else 0. */ int cmp = lt - gt; /* set res = cmp if !done. */ res |= cmp & ~done; /* set done if p1[i] != p2[i]. */ done |= lt | gt; } return (res); } /*****************************************************************************/ /* Find the first occurrence of the byte string s in byte string l. */ void* memmem (const void *l, size_t l_len, const void *s, size_t s_len) { const char *cur, *last; const char *cl = l; const char *cs = s; /* a zero length needle should just return the haystack */ if (s_len == 0) return (void *)cl; /* "s" must be smaller or equal to "l" */ if (l_len < s_len) return NULL; /* special case where s_len == 1 */ if (s_len == 1) return memchr (l, *cs, l_len); /* the last position where its possible to find "s" in "l" */ last = cl + l_len - s_len; for (cur = cl; cur <= last; cur++) if (cur[0] == cs[0] && memcmp (cur, cs, s_len) == 0) return (void *)cur; return NULL; } /*****************************************************************************/ /* Takes a broken-down time and convert it to calendar time (seconds since the Epoch, 1970-01-01 00:00:00 +0000, UTC). */ static int timegm_is_leap_year(int year) { return (year % 4 == 0 && (year % 100 != 0 || year % 400 == 0)); } static int timegm_leap_days(int y1, int y2) { --y1; --y2; return (y2/4 - y1/4) - (y2/100 - y1/100) + (y2/400 - y1/400); } time_t timegm(const struct tm *tm) { /* number of days per month (except for February in leap years) */ const int monoff[] = { 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334 }; int year; time_t days; time_t hours; time_t minutes; time_t seconds; year = 1900 + tm->tm_year; days = 365 * (year - 1970) + timegm_leap_days(1970, year); days += monoff[tm->tm_mon]; if (tm->tm_mon > 1 && timegm_is_leap_year(year)) ++days; days += tm->tm_mday - 1; hours = days * 24 + tm->tm_hour; minutes = hours * 60 + tm->tm_min; seconds = minutes * 60 + tm->tm_sec; return (seconds); } /*****************************************************************************/ /* Implementation of fork()/exec..() for this application. SYSPRV is enabled so the subprocess inherits as an authorised privilege. The scripting process account (unprivileged) requires this. */ int furk ( char* process, char* args[] ) { int argc, pid, retval; /*********/ /* begin */ /*********/ sys$setprv (1, &SysPrvMask, 0, &PrevPrvMask); pid = vfork (); if (pid == 0) { for (argc = 0; args[argc]; argc++) if (!strncmp (args[argc], "/process=", 9)) { strcpy (args[argc]+9, process); break; } retval = execvp (args[0], (char**)args); if (retval == -1) { if (!(PrevPrvMask[0] & PRV$M_SYSPRV)) sys$setprv (0, &SysPrvMask, 0, 0); warn("|%s|%s|%s|", args[0], args[1], args[2]); return (-1); } } if (!(PrevPrvMask[0] & PRV$M_SYSPRV)) sys$setprv (0, &SysPrvMask, 0, 0); #ifdef WCME_DBG_VMS doddbg("furk() %08.08X |%s|%s|%s|", pid, args[0], args[1], args[2]); #endif return (pid); } /*****************************************************************************/ /* Roll our own pair-of-sockets (socketpair()) function. Oringinally intended to work-around C-RTLs less than VMS V8.2. From WCME v2 also ensures the file descriptors for the sockets have a known and predictable sequence. */ /** #include #include #include **/ #include #include int pairofsockets (int sd[2]) { #define FIRST_SOCK_FD_NUM 30 static int SockNum; int sock0, sock1, sock2; unsigned int len; struct sockaddr_in sock_in, sock_out; if (sd == NULL) { /* reset the socket bas number and return its value */ return (SockNum = FIRST_SOCK_FD_NUM); } sock0 = socket (AF_INET, SOCK_STREAM, 0); if (sock0 <= 0) return (-1); /* localhost dynamic port */ sock_in.sin_family = AF_INET; sock_in.sin_port = 0; sock_in.sin_addr.s_addr = inet_addr("127.0.0.1"); if (bind (sock0, (struct sockaddr *)&sock_in, sizeof(sock_in)) < 0) { close (sock0); return (-1); } /* get that dynamic port */ len = sizeof(sock_in); if (getsockname (sock0, (struct sockaddr*)&sock_in, &len) < 0) { close (sock0); return (-1); } /* make this socket a listener */ listen (sock0, 2); sock2 = socket (AF_INET, SOCK_STREAM, 0); if (sock2 <= 0) { close (sock0); return (-1); } /* connect to the listener */ if (connect (sock2, (struct sockaddr*)&sock_in, sizeof(sock_in)) < 0) { close (sock0); close (sock2); return (-1); } /* accept the connection and close the listener */ len = sizeof (sock_in); if ((sock1 = accept (sock0, (struct sockaddr*) &sock_in, &len)) <= 0) { close (sock0); close (sock2); return (-1); } close (sock0); /* establish a descriptor number in the descriptor sequence */ if (dup2 (sock1, SockNum) == -1) err (EXIT_FAILURE, "dup2.1"); close (sock1); sd[0] = SockNum++; if (dup2 (sock2, SockNum) == -1) err (EXIT_FAILURE, "dup2.2"); close (sock2); sd[1] = SockNum++; return (0); } /*****************************************************************************/ /* Don't throw the terminal for a six. Excise non-printables a la buf_dump(). */ char* exctrl (char* sptr, int slen) { static char *bptr; char *zptr; /*********/ /* begin */ /*********/ if (bptr) free (bptr); if (slen <= 0) { for (bptr = sptr; *bptr; bptr++); slen = bptr - sptr; } if (!slen) return (""); bptr = calloc(1, slen); memcpy (bptr, sptr, slen); for (zptr = (sptr = bptr) + slen; sptr < zptr; sptr++) if (!isprint(*sptr) && *sptr != '\r' && *sptr != '\n') *sptr = '?'; return (bptr); } /*****************************************************************************/ /* Return a pointer to string time-stamp "yyyy-mm-dd-hh:mm:ss.hh". Not reentrant. */ char* tstamp (ulong *binptr) { static char stamp [32]; ulong BinTime [2]; ushort NumTime [7]; /*********/ /* begin */ /*********/ if (!binptr) sys$gettim (binptr = (ulong*)&BinTime); sys$numtim (&NumTime, binptr); sprintf (stamp, "%04.04d-%02.02d-%02.02d %02.02d:%02.02d:%02.02d.%02.02d", NumTime[0], NumTime[1], NumTime[2], NumTime[3], NumTime[4], NumTime[5], NumTime[6]); return (stamp); } /*****************************************************************************/ /* Always succeeds. */ int pledge (const char* promises, const char* execpromises) { #ifdef WCME_DBG_VMS doddbg("pledge() %s %s", promises, execpromises); #endif return (0); } /*****************************************************************************/ /* Always succeeds. */ int unveil (const char* path, const char* permissions) { #ifdef WCME_DBG_VMS doddbg("unveil() %s %s", path, permissions); #endif return (0); } /*****************************************************************************/ /* Same as doddbg() but without the -v[v] test. */ void vmsdbg (char *fmt, ...) { va_list ap; va_start (ap, fmt); vwarnx (fmt, ap); va_end (ap); } /*****************************************************************************/ /* Debug tool to list the allocated file descriptors. */ void fdlist (int which, const char* what) { int ret; /*********/ /* begin */ /*********/ fprintf (stderr, "fdlist() %d %s\n", which, (char*)what); for (int cnt = 0; cnt <= 20; cnt++) if (-1 != (ret = fcntl(cnt, F_GETFL))) fprintf (stderr, "%d %08.08x %d\n", cnt, ret, decc$get_sdc(cnt)); for (int cnt = 50; cnt <= 60; cnt++) if (-1 != (ret = fcntl(cnt, F_GETFL))) fprintf (stderr, "%d %s %08.08x %d\n", cnt, subps[cnt-50], ret, decc$get_sdc(cnt)); } /*****************************************************************************/ /* Debug tool providing which process the file descriptor belongs to. */ char* fdid (int fd) { static char buf [256]; sprintf (buf, "%d %s", fd, subps[fd-50]); return buf; } /*****************************************************************************/ /* Debug tool to list the arguments. */ void arglist (char* where, char *argv[]) { fprintf (stdout, "%s:\n", where); for (int cnt = 0; argv[cnt]; cnt++) fprintf (stdout, "argv[%d] |%s|\n", cnt, argv[cnt]); } /*****************************************************************************/ /* see description in module prologue */ #undef access #define rtl_access access #undef chdir #define rtl_chdir chdir #undef creat #define rtl_creat creat #undef fdopen #define rtl_fdopen fdopen #undef fopen #define rtl_fopen fopen #undef fstat #define rtl_fstat fstat #undef open #define rtl_open open #undef rename #define rtl_rename rename #undef unlink #define rtl_unlink unlink /*****************************************************************************/ /* Ensure SYSPRV before file-system access. */ int wrap_access (const char *fspec, int mode) { int retval; /*********/ /* begin */ /*********/ sys$setprv (1, &SysPrvMask, 0, &PrevPrvMask); retval = rtl_access (fspec, mode); if (!(PrevPrvMask[0] & PRV$M_SYSPRV)) sys$setprv (0, &SysPrvMask, 0, 0); return (retval); } /*****************************************************************************/ /* Ensure SYSPRV before file-system access. */ int wrap_chdir (const char *dspec, ...) { int retval; /*********/ /* begin */ /*********/ sys$setprv (1, &SysPrvMask, 0, &PrevPrvMask); retval = rtl_chdir (dspec); if (!(PrevPrvMask[0] & PRV$M_SYSPRV)) sys$setprv (0, &SysPrvMask, 0, 0); return (retval); } /*****************************************************************************/ /* Ensure SYSPRV before file-system access. */ int wrap_creat (const char *fspec, mode_t mode, ...) { int fd; /*********/ /* begin */ /*********/ sys$setprv (1, &SysPrvMask, 0, &PrevPrvMask); fd = rtl_creat (fspec, mode); if (!(PrevPrvMask[0] & PRV$M_SYSPRV)) sys$setprv (0, &SysPrvMask, 0, 0); return (fd); } /*****************************************************************************/ /* Ensure SYSPRV before file-system access. */ FILE* wrap_fdopen (int fd, const char *amode) { FILE *fp; /*********/ /* begin */ /*********/ sys$setprv (1, &SysPrvMask, 0, &PrevPrvMask); fp = rtl_fdopen (fd, amode); if (!(PrevPrvMask[0] & PRV$M_SYSPRV)) sys$setprv (0, &SysPrvMask, 0, 0); return (fp); } /*****************************************************************************/ /* Ensure SYSPRV before file-system access. */ FILE* wrap_fopen (const char *fspec, const char *amode) { FILE *fp; /*********/ /* begin */ /*********/ sys$setprv (1, &SysPrvMask, 0, &PrevPrvMask); fp = rtl_fopen (fspec, amode); if (!(PrevPrvMask[0] & PRV$M_SYSPRV)) sys$setprv (0, &SysPrvMask, 0, 0); return (fp); } /*****************************************************************************/ /* Ensure SYSPRV before file-system access. */ int wrap_fstat (int fd, struct stat *buf) { int retval; /*********/ /* begin */ /*********/ sys$setprv (1, &SysPrvMask, 0, &PrevPrvMask); retval = rtl_fstat (fd, buf); if (!(PrevPrvMask[0] & PRV$M_SYSPRV)) sys$setprv (0, &SysPrvMask, 0, 0); return (retval); } /*****************************************************************************/ /* Ensure SYSPRV before file-system access. */ int wrap_open (const char *fspec, int flags, ...) { int fd; mode_t mode; va_list argptr; /*********/ /* begin */ /*********/ va_start (argptr, flags); mode = (mode_t)va_arg (argptr, mode_t); sys$setprv (1, &SysPrvMask, 0, &PrevPrvMask); fd = rtl_open (fspec, flags, mode); if (!(PrevPrvMask[0] & PRV$M_SYSPRV)) sys$setprv (0, &SysPrvMask, 0, 0); return (fd); } /*****************************************************************************/ /* Ensure SYSPRV before file-system access. */ int wrap_rename (const char* old, const char* new) { int retval; /*********/ /* begin */ /*********/ sys$setprv (1, &SysPrvMask, 0, &PrevPrvMask); retval = rtl_rename (old, new); if (!(PrevPrvMask[0] & PRV$M_SYSPRV)) sys$setprv (0, &SysPrvMask, 0, 0); return (retval); } /*****************************************************************************/ /* Ensure SYSPRV before file-system access. */ int wrap_unlink (const char* fspec) { int retval; /*********/ /* begin */ /*********/ sys$setprv (1, &SysPrvMask, 0, &PrevPrvMask); /* remove all versions */ if (!(retval = remove(fspec)) || errno != ENOENT) while (!remove (fspec)); if (!(PrevPrvMask[0] & PRV$M_SYSPRV)) sys$setprv (0, &SysPrvMask, 0, 0); return (retval); } /*****************************************************************************/