Mailing List ecs-isp@2rosenthals.com Archived Message #928

Fra: "Steven Levine" <ecs-isp@2rosenthals.com> Full Headers
Undecoded message
Emne: Re: [eCS-ISP] HTTPS-Misery (for Steven)
Dato: Mon, 07 Oct 2024 11:58:07 -0800
Til: "eCS ISP Mailing List" <ecs-isp@2rosenthals.com>

In <list-11070551@2rosenthals.com>, on 10/07/24
   at 08:22 PM, "Massimo S." <ecs-isp@2rosenthals.com> said:

>i'm trying to catch that challenge value to verify when they accept
>HTTP-01 to improve the script

I have appended a copy of the uacme-hook.cmd that Dan and I use.

The interesting code is above the

  /*==============================================================================*/
  /*=== SkelRexxFunc standards - Delete unused - Move modified above this
mark ===*/
  /*==============================================================================*/

separator.  Everything below is boilerplate code that is maintained by my
tools.

You may notice that the script is almost 100% generic.  Since our mapping
of domain names to document roots is consistent, I saw no need to parse
the httpd conf files or read the mappings from an external file.

The relevant mapping code follows the

  /* Map domain to VirtualHost docroot directory */

comment.

To handle possible uacme.exe failures, we back up the certificates before
the uacme.exe runs and configure httpd to use the certificates as created
by uacme.

Steven

--
----------------------------------------------------------------------
"Steven Levine" <steve53@earthlink.net>  Warp/DIY/BlueLion etc.
www.scoug.com www.arcanoae.com www.warpcave.com
----------------------------------------------------------------------
/* uacme-hook - uacme hook script
   Expects to be install where uacme.exe can find it
   Expects to be able to map the ident passed to hook to challenge directory name
   Ident is typically same as domain name
   Typical usage is
     uacme -v -h uacme-hook.cmd issue example.com

   Copyright (c) 2024 Steven Levine and Associates, Inc.
   All rights reserved.

   This program is free software licensed under the terms of
   the GNU General Public License Version 3 or newer.  The GPL
   Software License can be found in gnugpl3.txt or at
   http://www.gnu.org/licenses/licenses.html#GPL

   2024-08-12 SHL Baseline
   2024-08-19 SHL More generic
   2024-10-07 SHL Comments
*/

signal on Error
signal on Failure name Error
signal on Halt
signal on NotReady name Error
signal on NoValue name Error
signal on Syntax name Error

Globals = 'gVersion gCmdName gEnv gLogDir gLogFile gTesting gTokenFile',
 'gMethod gType gIdent gToken gAuth'

gVersion = '0.1 2024-10-07'

call Initialize

Main:
  parse arg gMethod gType gIdent gToken gAuth
  gAuth = strip(gAuth)

  /* Optionally override default log directory */
  logdir = 'd:\logs'
  if IsDir(logdir) then
    gLogDir = logDir

  call LogWriteVTSC '',,
   gCmdName 'started at' MakeTimeStamp(),,
   'method is' gMethod,,
   'type is' gType,,
   'ident is' gIdent,,
   'token is' gToken,,
   'auth is' gAuth

  /* FIXME to be gone somewhen */
  gTesting = 0
  if GetEnv('HOSTNAME') == 'slamain' then do
    call LogWriteVTSC '', 'Running on slamain in test mode'
    gTesting = 1
  end

  /* Find docs directory for server */
  docsdir = left(directory(), 1) || ':\www\docs\'
  if gTesting then
    docsdir = left(directory(), 1) || ':\Internet\apache24-data\htdocs'

  if \ IsDir(docsdir) then
    call Die 'Cannot access' docsdir 'directory'

  /* Map domain to VirtualHost docroot directory */
  ndx = lastpos('.', gIdent)
  if ndx = 0 then do
    domain = gIdent
    suffix = ''
  end
  else do
    suffix = substr(gIdent, ndx + 1) /* Without dot */
    domain = left(gIdent, ndx - 1)
    ndx = lastpos('.', domain)
    if ndx > 0 then
      domain = substr(domain, ndx + 1)
  end

  docroot = MakePath( docsdir, domain)
  if \ IsDir(docroot) then
    docroot = MakePath( docsdir, domain || suffix) /* cih.bz -> cihbz */

  if \ IsDir(docroot) then
    call Die 'Cannot access' docroot 'directory for' gIdent

  challengeDir = MakePath( docroot, '.well-known\acme-challenge')

  if \ IsDir(challengeDir) then
    call Die 'Cannot access' challengeDir 'directory'

  gTokenFile = MakePath(challengeDir, gToken)

  select
  when gMethod == 'begin' then
    call DoBegin
  when gMethod == 'done' then
    call DoDone
  when gMethod == 'failed' then
    call DoFailed
  otherwise
    call Die 'method' gMethod 'unexpected'
  end

  exit RESULT

/* end main */

/*=== DoBegin() Handle begin method ===*/

DoBegin: procedure expose (Globals)

  if gType \== 'http-01' then
     call Die 'DoBegin: type' gType 'unsupported'

  call LogWriteVTSC 'Creating' gTokenFile
  call SysFileDelete gTokenFile
  call lineout gTokenFile, gAuth
  call lineout gTokenFile

  return 0

/* end DoBegin */

/*=== DoDone() Handle done method ===*/

DoDone: procedure expose (Globals)

  call LogWriteVTSC 'DoDone deleting' gTokenFile
  call SysFileDelete gTokenFile
  return 0

/* end DoDone */

/*=== DoFailed() Handle failed method ===*/

DoFailed: procedure expose (Globals)

  call LogWriteVTSC 'DoFailed deleting' gTokenFile
  call SysFileDelete gTokenFile
  return 0

/* end DoFailed */

/*=== Initialize() Initialize globals ===*/

Initialize: procedure expose (Globals)
  call SetCmdName
  call LoadRexxUtil
  gEnv = 'OS2ENVIRONMENT'
  return

/* end Initialize */

/*==============================================================================*/
/*=== SkelRexxFunc standards - Delete unused - Move modified above this mark ===*/
/*==============================================================================*/

/*=== DieVTS(message) Write message to STDOUT and timestampped message to log file and die ===*/

Die:
  /* Requires LogWriteVTS and dependents */
  /* Requires LogWriteVTSC and dependents */
  parse arg msg
  callerSIGL = SIGL
  /* Use say to avoid NotReady in case running detached - FIXME to be sure not ok to write to STDERR */
  say
  call LogWriteVTS msg
  msg = gCmdName 'aborting at line' callerSIGL || '.'
  call LogWriteVTSC msg
  call beep 200, 300
  exit 254

/* end DieVTS */

/*=== GetEnv(var) Return value for environment variable or empty string ===*/

GetEnv: procedure expose (Globals)
  parse arg var
  if var = '' then
    call Die 'GetEnv requires an argument'
  return value(var,, gEnv)

/* end GetEnv */

/*=== IsDir(dirName[, full]) return true if directory is valid, accessible directory ===*/

IsDir: procedure
  /* If arg(2) not omitted, return full directory name or empty string */
  parse arg dir, full
  newdir = ''

  do 1
    if dir == '' then do
      cwd = '' /* No restore needed */
      leave
    end
    dir = translate(dir, '\', '/') /* Convert to OS/2 slashes */
    s = strip(dir, 'T', '\') /* Chop trailing slashes unless root */
    if s \== '' & right(s, 1) \== ":" then
      dir = s /* Chop */
    drv = filespec('D', dir)
    cwd = directory() /* Remember */
    /* If have drive letter and requested directory on some other drive */
    if drv \== '' & translate(drv) \== translate(left(cwd, 2)) then do
      /* Avoid slow failures and unwanted directory changes */
      drvs = SysDriveMap('A:')
      if pos(translate(drv), drvs) = 0 then
leave /* Unknown drive */
      if SysDriveInfo(drv) == '' then
leave /* Drive not ready */
      cwd2 = directory(drv) /* Remember current directory on other drive */
      newdir = directory(dir) /* Try to change and get full path name */
      call directory cwd2 /* Restore current directory on other drive */
      leave
    end

    /* If no drive letter or same drive and not UNC name */
    if left(dir, 2) \== '\\' then do
      newdir = directory(dir) /* Try to change and get full path name */
      leave
    end

    /* UNC name - hopefully server is accessible or this will be slow
       Accept
\\server
\\server\
\\server\dir\
\\server\dir
     */
    cwd = '' /* No restore needed */
    wc = dir
    if right(wc, 1) \== '\' then
      wc = wc || '\'
    i = lastpos('\', wc)
    if substr(wc, 3, 1) == '\' then
      leave /* Malformed UNC - no server name */
    if pos('*', wc) > 0 | pos('?', wc) > 0 then
      leave /* No wildcards allowed */
    call SysFileTree wc, 'files', 'O'
    if files.0 > 0 then do
      s = files.1 /* Exists and is not empty */
      i = lastpos('\', s)
      newdir = left(s, i - 1) /* Extract directory name from full path name */
      leave
    end
    /* Try wildcarded directory name */
    wc = strip(wc, 'T', '\')
    i = lastpos('\', wc)
    base = substr(wc, i + 1)
    if base == '' then
      leave /* Should have matched above */
    wc = substr(wc, 1, i) || '*' || base || '*'
    call SysFileTree wc, 'files', 'DO'
    do fileNum = 1 to files.0
      /* Find directory name is list */
      s = files.fileNum
      i = lastpos('\', s)
      s2 = substr(s, i + 1)
      if translate(base) == translate(s2) then do
newdir = left(s, i - 1)
leave
      end
    end /* i */
  end /* 1 */

  if cwd \== '' then
    call directory cwd /* Restore original directory and drive */

  if full \== '' then
    ret = newdir /* Return full directory name or empty string */
  else
    ret = newdir \== '' /* Return true if valid and accessible */
  return ret

/* end IsDir */

/*=== LogOpen() Open log file for append ===*/

LogOpen: procedure expose (Globals)
  /* Requires LogSetName unless gLogFile defined */
  /* Sets gLogFile if not defined */
  /* Overrides gLogFile if open fails */
  if symbol('gLogFile') \== 'VAR' then
    call LogSetName
  /* Assume closed */
  call stream gLogFile, 'C', 'OPEN WRITE'
  if stream(gLogFile) \== 'READY' then do
    gLogFile = '\' || gCmdName || '.log' /* Try root */
    call stream gLogFile, 'C', 'OPEN WRITE'
  end
  return

/* end LogOpen */

/*=== LogSetName() Set log file name ===*/

/**
 * Sets gLogFile if not defined
 * Sets gLogDir if not defined
 */

LogSetName: procedure expose (Globals)
  /* Requires LogSetDir unless gLogDir defined */
  /* Requires gCmdName */
  if symbol('gLogFile') \== 'VAR' then do
    if symbol('gLogDir') \== 'VAR' then
      call LogSetDir
    /* Ensure trailing backslash unless using current directory */
    dir = gLogDir
    if dir \== '' & right(dir, 1) \== ':' & right(dir, 1) \== '\' then
      dir = dir || '\' /* Ensure trailing backslash */
    gLogFile = dir || gCmdName'.log'
  end
  return

/* end LogSetName */

/*=== LogSetDir() Set gLogDir and provide trailing backslash if needed ===*/

/**
 * Set gLogDir if gLogDir not defined
 * Tries %LOGFILES gTmpDir %TMP
 * Falls back to current directory and returns null string
 */

LogSetDir: procedure expose (Globals)
  if symbol('gLogDir') \== 'VAR' then do
    /* Try gLogDir %LOGFILES gTmpDir %TMP */
    do 1
      /* Try %LOGFILES */
      gLogDir = value('LOGFILES',, gEnv)
      if gLogDir \== '' then leave
      /* Try gTmpDir */
      if symbol('gTmpDir') == 'VAR' then do
gLogDir = gTmpDir
leave
      end
      /* Try %TMP - return empty string if TMP not defined */
      gLogDir = value('TMP',, gEnv)
    end
  end
  return

/* end LogSetDir */

/*=== LogWriteVTS(message,...) Write multi-line message to STDOUT and timestamped message to log file ===*/

LogWriteVTS: procedure expose (Globals)
  /* Requires LogOpen */
  /* Requires MakeTimestamp */
  if symbol('gLogFile') \== 'VAR' then
    call LogOpen
  do i = 1 to arg()
    say arg(i)
    call lineout gLogFile, MakeTimestamp() arg(i)
    if symbol('gLogWrites') == 'VAR' then
      gLogWrites = gLogWrites + 1
  end
  return

/* end LogWriteVTS */

/*=== LogWriteVTSC(message,...) Write multi-line message to STDOUT and timestamped message to log file and close log ===*/

LogWriteVTSC: procedure expose (Globals)
  /* Requires LogOpen */
  /* Requires MakeTimestamp */
  if symbol('gLogFile') \== 'VAR' then
    call LogOpen
  ts = MakeTimestamp()
  do i = 1 to arg()
    say ts arg(i)
    call lineout gLogFile, ts arg(i)
    if symbol('gLogWrites') == 'VAR' then
      gLogWrites = gLogWrites + 1
  end
  call stream gLogFile, 'C', 'CLOSE'
  return

/* end LogWriteVTSC */

/*=== MakePath(pathparts,...) Make path name from parts ===*/

MakePath: procedure

  /* All parts optional - code guesses what caller means.
     If last arg begins with a dot and is not .. and does not
     contain a slash, it is assumed to be a file extension.
     To avoid this behavior, pass empty arg as last arg.
     Empty args are ignored.
     Automatically converts unix slashes to dos slashes.
     If 1st arg is drive letter, it must have trailing colon.
   */

  argCnt = arg()

  path = ''

  do argNum = 1 to argCnt
    s = arg(argNum)
    s = translate(s, '\', '/') /* Ensure DOS */
    if s == '' & argNum = argCnt then
      iterate /* Ignore nul last arg */
    if argNum = 1 then
      path = s
    else do
      lead = left(s, 1)
      tail = right(path, 1)
      if tail == ':' & argNum = 2 then
path = path || s /* Append path part to drive spec */
      else if lead == '.' & argNum = argCnt & s \== '..' & pos('\', s) = 0 then
path = path || s /* Assume extension unless .. or contains \ */
      else if tail == '\' & lead == '\' then
path = path || substr(s, 2) /* Drop extra backslash */
      else if path \== '' & tail \== '\' & lead \== '\' then
path = path || '\' || s /* Ensure have backslash */
      else
path = path || s
    end
  end /* for */

  return path

/* end MakePath */

/*=== MakeTimestamp() Convert current date/time to sorted, delimited timestamp - yyyy/mm/dd-hh:mm:ss ===*/

MakeTimestamp: procedure
  /* Return yyyy/mm/dd-hh:mm:ss */
  return translate('ABCD/EF/GH',date('S'),'ABCDEFGH')'-'time()

/* end MakeTimestamp */

/*==========================================================================*/
/*=== SkelRexx standards - Delete unused - Move modified above this mark ===*/
/*==========================================================================*/

/*=== Error() Set gErrCondition; report to STDOUT; trace and exit or return if called ===*/

Error:
  say
  parse source . . cmd
  gErrCondition = condition('C')
  say gErrCondition 'signaled at line' SIGL 'of' cmd || '.'
  if condition('D') \== '' then
    say 'REXX reason =' condition('D') || '.'
  if gErrCondition == 'SYNTAX' & symbol('RC') == 'VAR' then
    say 'REXX error =' RC '-' errortext(RC) || '.'
  else if symbol('RC') == 'VAR' then
    say 'RC =' RC || '.'
  say 'Source =' sourceline(SIGL)

  if condition('I') \== 'CALL' | gErrCondition == 'NOVALUE' | gErrCondition == 'SYNTAX' then do
    trace '?A'
    say 'Enter REXX commands to debug failure.  Press enter to exit script.'
    nop
    if symbol('RC') \== 'VAR' then
      RC = 255
    exit RC
  end

  return

/* end Error */

/*=== Halt() Report HALT condition to STDOUT and exit ===*/

Halt:
  say
  parse source . . cmd
  say condition('C') 'signaled at' cmd 'line' SIGL || '.'
  say 'Source =' sourceline(SIGL)
  say 'Sleeping for 2 seconds...'
  call SysSleep 2
  exit 253

/* end Halt */

/*=== LoadRexxUtil() Load RexxUtil functions ===*/

LoadRexxUtil:
  if RxFuncQuery('SysLoadFuncs') then do
    call RxFuncAdd 'SysLoadFuncs', 'REXXUTIL', 'SysLoadFuncs'
    if RESULT then
      call Die 'Cannot load SysLoadFuncs.'
    call SysLoadFuncs
  end
  return

/* end LoadRexxUtil */

/*=== SetCmdName() Set gCmdName to short script name ===*/

SetCmdName: procedure expose (Globals)
  parse source . . cmd
  cmd = filespec('N', cmd) /* Chop path */
  c = lastpos('.', cmd)
  if c > 1 then
    cmd = left(cmd, c - 1) /* Chop extension */
  gCmdName = translate(cmd, xrange('a', 'z'), xrange('A', 'Z')) /* Lowercase */
  return

/* end SetCmdName */

/* eof */


Abboner: Feed, Digest, Index.
Stopp abbonement
E-post til ListMaster