/****************************************************************/ /* */ /* Program to sort a host list in WEASEL.INI */ /* */ /* */ /* Author: Peter Moylan (peter@pmoylan.org) */ /* Last revised: 16 November 2020 */ /* Status: Working */ /* */ /* Usage: */ /* sorthostlist arg */ /* where arg is one of */ /* Banned Local MayRelay RelayDest Whitelisted */ /* (but Local doesn't work in the multidomain case) */ /* */ /* Installation: */ /* Put this file in the directory containing WEASEL.INI */ /* or WEASEL.TNI */ /* */ /* Remark: apologies for the not-very-clean code. Rexx is */ /* not really a suitable language for a job like this, but */ /* I wanted people to be able to change the sorting criterion */ /* if they wished. The sorting criterion is defined in */ /* procedure Compare, see below. */ /* */ /****************************************************************/ CALL RxFuncAdd SysLoadFuncs, rexxutil, sysloadfuncs CALL SysLoadFuncs CALL CheckPrerequisites SelectTNI INI_get INI_put IF SelectTNI("Weasel") > 0 THEN INIFile = "Weasel.TNI" ELSE INIFile = "Weasel.INI" SAY "Using "INIfile /* START OF TESTING CODE */ /*CALL TestCode EXIT 0*/ /* END OF TESTING CODE */ PARSE ARG option IF option = "" THEN option = "Banned" ELSE IF option = "banned" THEN option = "Banned" ELSE IF option = "local" THEN option = "Local" ELSE IF option = "mayrelay" THEN option = "MayRelay" ELSE IF option = "relaydest" THEN option = "RelayDest" ELSE IF option = "whitelisted" THEN option = "Whitelisted" IF (option \= "Banned") & (option \= "Local") & (option \= "MayRelay") & (option \= "RelayDest") & (option \= "Whitelisted") THEN DO SAY "ERROR: invalid option "option EXIT 1 END /*IF*/ CALL SortList option SAY "Finished. The "||option||" list should now be sorted." EXIT 0 /****************************************************************/ /* COMPARING TWO STRINGS */ /****************************************************************/ CrudeCompare: PROCEDURE /* NOT USED EXCEPT FOR TESTING */ /* Takes two arguments a and b. Returns -1 if a < b, */ /* 0 if a = b, and +1 if a > b. By altering the code in */ /* this procedure, you can redefine what is meant by '<' */ /* and '>', and therefore control the sort order. */ PARSE ARG a,b IF a < b THEN RETURN -1 ELSE IF a = b THEN RETURN 0 ELSE RETURN +1 /****************************************************************/ Display: PROCEDURE /* Only used while testing/debugging. */ PARSE ARG a b cmpresult IF cmpresult < 0 THEN SAY a '<' b ELSE IF cmpresult = 0 THEN SAY a '=' b ELSE IF cmpresult > 0 THEN SAY a '>' b RETURN /****************************************************************/ Compare: PROCEDURE /* Takes two arguments a and b. Returns -1 if a < b, */ /* 0 if a = b, and +1 if a > b. By altering the code in */ /* this procedure, you can redefine what is meant by '<' */ /* and '>', and therefore control the sort order. */ /* In this version, textual data sorts ahead of numeric */ /* IP addresses, and IP addresses are sorted in the obvious */ /* way. We use case-independent comparison for text data. */ PARSE ARG a,b IF IsNumeric(a) THEN DO IF IsNumeric(b) THEN DO ans = CmpIP(a,b) END ELSE DO /* a numeric, b not numeric */ ans = +1 END END ELSE DO /* a not numeric */ IF IsNumeric(b) THEN ans = -1 ELSE DO /* Remaining cases: a and b both non-numeric */ ua = TRANSLATE(a) ub = TRANSLATE(b) IF ua < ub THEN ans = -1 ELSE IF ua = ub THEN ans = 0 ELSE ans = +1 END END /*CALL Display a b ans */ RETURN ans /****************************************************************/ IsNumeric: PROCEDURE /* Returns >0 iff its argument is a string containing */ /* nothing but digits and '.' and ']'. */ PARSE ARG str str = TRANSLATE(str, '00', '.]') RETURN DATATYPE(str,'Number') > 0 /****************************************************************/ CmpIP: PROCEDURE /* Compares two IP addresses. */ /* Returns >0 if a>b, 0 if a=b, <0 if a < b. */ PARSE ARG a,b IF (a = '') | (a = ']') THEN DO IF (b = '') | (b = ']') THEN RETURN 0 ELSE RETURN -1 END IF (b = '') | (b = ']') THEN RETURN +1 PARSE VAR a head1 '.' tail1 PARSE VAR b head2 '.' tail2 IF head1 > head2 THEN RETURN +1 ELSE IF head1 < head2 THEN RETURN -1 ELSE RETURN CmpIP(tail1,tail2) /****************************************************************/ /* SORTING ONE LIST IN THE INI FILE */ /****************************************************************/ SortList: PROCEDURE EXPOSE INIFile arr. /* Reads a list of names and addresses from the INI file, */ /* sorts that list, and writes the result back to the file. */ /* Note that the sorting criterion is defined by the */ /* procedure Compare, earlier in this file. */ PARSE ARG option str = INI_get(INIFile, '$SYS', option); IF str = '' THEN DO SAY "Nothing to sort." RETURN END /* Separate out the components into an array. */ Nul = '00'X count = 0 DO WHILE str \= '' PARSE VAR str part1 '00'X str IF part1 \= '' THEN DO count = count + 1 /* Special trick: remove any leading '[' */ IF LEFT(part1,1) = '[' THEN PARSE VAR part1 '[' part1 arr.count = part1 END END arr.0 = count CALL SortArray 1 count /* Put the sorted array back into "string of strings" format */ ans = '' DO j = 1 TO arr.0 IF RIGHT(arr.j,1) = ']' THEN ans = ans||'[' ans = ans||arr.j||'00'X END ans = ans||'00'X /* Write the result back to the INI file. */ CALL INI_put INIFile, '$SYS', option, ans RETURN /****************************************************************/ /* SORTING AN ARRAY */ /****************************************************************/ SortArray: PROCEDURE EXPOSE arr. /* Sorts the subarray arr.j1 to arr.j2, where j1 and j2 */ /* are the two arguments to the procedure. */ /* Note that the sorting criterion is defined by the */ /* procedure Compare, elsewhere in this file. */ /* This version uses quicksort. */ DROP low high PARSE ARG low high /* Quicksort. */ DO WHILE high > low mid = Partition(low,high) newhigh = mid-1 IF newhigh > low THEN CALL SortArray low newhigh low = mid + 1 END RETURN arr /****************************************************************/ Partition: PROCEDURE EXPOSE arr. /* By shuffling elements of arr as necessary, ensures the */ /* property */ /* arr.j <= v for low <= j < mid */ /* arr.mid = v */ /* arr.j >= v for mid < j <= high */ /* where v is some unspecified value chosen by this */ /* procedure. Input assumption: high > low, i.e. more than */ /* one element. Returns mid. Remark: for an array of <=3 */ /* elements, this procedure completely sorts the array. */ PARSE ARG low,high down = low up = high N = up - down + 1 mid = down + (N % 2) /* Pre-sort: first we put the first, middle and last */ /* elements in their correct relative order. */ /* To begin with, ensure that arr.up >= arr.down. */ IF Compare(arr.up,arr.down) < 0 THEN DO temp = arr.down arr.down = arr.up arr.up = temp END IF N <= 2 THEN RETURN mid /* Load the middle element into temp. By swapping elements */ /* as necessary, ensure that arr.down <= temp <= arr.up. */ /* Note that we already have arr.down <= arr.up. */ temp = arr.mid IF Compare(temp,arr.down) < 0 THEN DO temp = arr.down arr.down = arr.mid END ELSE IF Compare(temp,arr.up) > 0 THEN DO temp = arr.up arr.up = arr.mid END /* For an array of <=3 elements, the above pre-sort is */ /* actually a complete sort. */ IF N <= 3 THEN DO arr.mid = temp RETURN mid END /* Throughout the following loop, temp is the value that */ /* should be stored in arr.mid. For the sake of */ /* efficiency, we do not store that value in arr.mid until */ /* the end of the calculation, because mid keeps changing. */ /* Instead, we leave a floating "hole" at arr.mid, a hole */ /* that will be finally filled when we exit the loop. */ DO FOREVER DO WHILE (down < mid) & Compare(arr.down, temp) <= 0 down = down + 1 END /* All elements below arr.down <= temp */ /* down >= mid OR ((down < mid) & (arr.down > temp) */ IF down < mid THEN DO /* All elements below arr.down <= temp */ /* (arr.down > temp) AND (down < mid) */ arr.mid = arr.down mid = down down = down + 1 /* The hole is now at mid < down */ END /* Note that down >= mid at this point. */ DO WHILE (up > mid) & Compare(arr.up, temp) >= 0 up = up - 1 END /* All elements above arr.up >= temp */ /* ((arr.up < temp) AND (up > mid)) OR up <= mid */ IF up <= mid THEN LEAVE arr.mid = arr.up mid = up up = up - 1 /* Now the hole is at mid > up */ END arr.mid = temp RETURN mid /****************************************************************/ /* OBSOLETE SORTING METHOD */ /****************************************************************/ SortSubarrayRipple: PROCEDURE EXPOSE arr. /* Sorts the subarray arr.j1 to arr.j2, where j1 and j2 */ /* are the two arguments to the procedure. */ /* Note that the sorting criterion is defined by the */ /* procedure Compare, elsewhere in this file. */ /* This version uses a ripple sort. It is in fact never */ /* called, but I've left it in case anyone wants to alter */ /* the code. */ PARSE ARG j1 j2 IF j2 > j1 THEN DO /* Ripple sort. */ changed = 1 DO WHILE changed DO j = j1 TO j2-1 changed = 0 next = j+1 IF Compare(arr.j,arr.next) > 0 THEN DO temp = arr.j arr.j = arr.next arr.next = temp changed = 1 END END END END RETURN /****************************************************************/ /* CHECKING PREREQUISITES */ /****************************************************************/ CheckPrerequisites: PROCEDURE /* The argument is a space-separated list of prerequisite */ /* functions, for example */ /* CALL CheckPrerequisites rxu SelectTNI INI_get */ /* where (at least in this version) each list item is */ /* either 'rxu' or a function from my TNItools package. */ /* If any is missing then we exit with an error message. */ PARSE UPPER ARG funclist funclist = STRIP(funclist) needrxu = 0 needtools = 0 DO WHILE funclist \= '' PARSE VAR funclist func funclist funclist = STRIP(funclist) IF func = 'RXU' THEN DO /* Initialise RXU if not already available, fail if */ /* the RxFuncAdd operation fails. We must */ /* RxFuncQuery RxuTerm because RxuTerm does not */ /* deregister RxuInit. The RxFuncDrop is needed */ /* because RxFuncAdd seems to report failure if the */ /* function is already registered. */ IF RxFuncQuery('RxuTerm') THEN DO CALL RxFuncDrop('RxuInit') CALL RxFuncAdd 'RxuInit','RXU','RxuInit' IF result THEN DO SAY 'Cannot load RXU' needrxu = 1 END ELSE CALL RxuInit END END ELSE DO func = func||'.CMD' IF SysSearchPath('PATH', func) = '' THEN DO SAY 'ERROR: 'func' must be in your PATH' needtools = 1 END END END IF needrxu THEN SAY 'You can find RXU1a.zip at Hobbes' IF needtools THEN SAY 'Please install the GenINI package' IF needrxu | needtools THEN EXIT 1 RETURN /****************************************************************/ /* TEST CODE */ /* Not actually used in the final version */ /****************************************************************/ TestCode: PROCEDURE EXPOSE arr. /* For testing, we set up a dummy array to sort. */ CALL FillArray '*.biz 1.2.34.45 timesbestseller.com 22.33.44.55 *.us 22.33.44.54 example.org alpha.beta' CALL SortArray RETURN /****************************************************************/ FillArray: PROCEDURE EXPOSE arr. /* For testing, we set up a dummy array to sort. */ PARSE ARG str count = 0 DO WHILE str \= '' PARSE VAR str part1 part2 IF val <> '' THEN DO count = count + 1; arr.count = part1 END str = part2 END arr.0 = count RETURN /****************************************************************/