/var/lib/sorcery/modules/libunpack

     1	#!/bin/bash
     2	#---------------------------------------------------------------------
     3	##
     4	## @Libgrimoire
     5	##
     6	## @Synopsis Set of functions containing the spell writing API.
     7	##
     8	##
     9	## These functions can be used in the PRE_BUILD, BUILD, POST_BUILD
    10	## and POST_INSTALL sections of spells.
    11	##
    12	## @Copyright
    13	## Original version Copyright 2001 by Kyle Sallee
    14	## Additions/Corrections Copyright 2002 by the Source Mage Team
    15	## New World libunpack Additions/Corrections by Seth Woolley (2005)
    16	##
    17	#---------------------------------------------------------------------
    18	
    19	#===================== libunpack common ==============================
    20	
    21	#---------------------------------------------------------------------
    22	## @Type API
    23	## @param SOURCE suffix
    24	##
    25	## unpack_file takes the SOURCE suffix and figures out if it is supposed
    26	## to hash or gpg check it -- then it does its dirty work and runs unpack_hash
    27	## or unpack_gpg depending upon the circumstances.  That's the only argument it
    28	## takes and needs: '' '2' '3', etc.  It is run in default_pre_build for the
    29	## null argument only.  Custom unpacking still requires a custom PRE_BUILD.
    30	##
    31	## valid formats: vendor-provided gpg, guru-provided gpg, any
    32	## hash-algorithm provided by gpg (currently md5, sha1, sha256, sha384,
    33	## sha512, ripemd160
    34	##
    35	##           SOURCE=blah
    36	##          SOURCE2=blah.asc
    37	##       SOURCE_URL=http://blah.com/$SOURCE
    38	##      SOURCE2_URL=http://blah.com/$SOURCE2
    39	##       SOURCE_GPG=blah.gpg:$SOURCE2:UPSTREAM_KEY
    40	##   SOURCE2_IGNORE=signature # for auditing purposes
    41	##
    42	##           SOURCE=blah
    43	##       SOURCE_URL=http://blah.com/$SOURCE
    44	##       SOURCE_GPG=swoolley.gpg:$SOURCE.asc:WORKS_FOR_ME
    45	##
    46	##           SOURCE=blah
    47	##       SOURCE_URL=http://blah.com/$SOURCE
    48	##           MD5[0]=d41d8cd98f00b204e9800998ecf8427e
    49	##
    50	##           SOURCE=blah
    51	##       SOURCE_URL=http://blah.com/$SOURCE
    52	##       SOURCE_HASH=md5:d41d8cd98f00b204e9800998ecf8427e:WORKS_FOR_ME
    53	##
    54	## In GPG mode:
    55	##   Validates the verification level (the third parameter) and the
    56	##   hash algorithm against user defined lists.
    57	##   It finds the public key and signature using locate_spell_file,
    58	##   Then it validates it at the beginning.
    59	##   see unpack_gpg()
    60	##
    61	## In HASH mode:
    62	##   Validates the verification level (the third parameter) and the
    63	##   hash algorithm against user defined lists.
    64	##   It uses gpg to calculate the hash value except for md5 and sha1, which
    65	##   coreutils provides.
    66	##   see unpack_hash()
    67	##
    68	## In IGNORE mode:
    69	##   It checks for the following text:
    70	##     volatile (for cvs/svn/any-other-scm)
    71	##     unversioned (the source file changes frequently, but not a direct scm)
    72	##     signature (for gnupg signatures)
    73	##   as reasons for ignoring the source code validation.  Signatures
    74	##   are silently ignored.  Everything else respects MD5SUM_DL.
    75	##   see unpack_ignore
    76	##
    77	## Otherwise, it falls back to MISSING mode, see unpack_missing
    78	## (or for now)
    79	## Otherwise, it falls back to old uncompressed md5sum check with MD5[n].
    80	##   see real_unpack()
    81	##
    82	## The default verification level is "WORKS_FOR_ME"
    83	##
    84	## Verification levels are, these indicate how much effort was put into
    85	## validating the integrity of the source from the upstream vendor.
    86	##   WORKS_FOR_ME No verification was done.
    87	##   UPSTREAM_HASH Checked the upstream hash file
    88	##   UPSTREAM_KEY Checked upstream (gpg) key, signature matched, but the
    89	##                key was not validated
    90	##   ESTABLISHED_UPSTREAM_KEY Upstream key was not validated against
    91	##                            multiple independent sources, but has been
    92	##                            in use for several years
    93	##   VERIFIED_UPSTREAM_KEY Upstream key id was verified against multiple
    94	##                         independent sources.
    95	##   ID_CHECK_UPSTREAM_KEY Key was verified in person with a photo id check.
    96	##
    97	## Also if you want to include more than one signature, hash, etc, just put
    98	## a 2, 3, 4, etc on the end of the variable like so:
    99	##   SOURCE2_HASH2=...
   100	##
   101	## For cascading, currently it will still ask abort questions: a no abort
   102	## will make it fail over all cascades; a yes abort will have it skip to
   103	## the next cascades.  Missing binaries or other failures like that (error 200
   104	## below) will silently fail over to the next check.  The cascade order is:
   105	##  GPG, HASH, IGNORE, MISSING
   106	##
   107	## The cascade setup allows you to place a higher bit checksum earlier
   108	## in the cascade and even if the binary doesn't work it will just print
   109	## out an abort query which can be said no to and it will continue to
   110	## fail over to the lower bit checksum that should be available in
   111	## coreutils (like sha1/md5).  That's if you're not using gpg, which is
   112	## preferred.  If multiple hashes are included of different ciphers, the
   113	## user can abort on either that go bad, so it can be considered a
   114	## security increase to have more than one, but only if the harder cipher
   115	## is first in the cascade order, as the first successful hash match will
   116	## go ahead and prompt an untarball.  I may change it later, but for now I
   117	## think first successful match skipping the rest is least intrusive, and
   118	## I'd need to add an interface element to let the user choose to run all
   119	## checks on a single source.
   120	##
   121	#---------------------------------------------------------------------
   122	function real_unpack_file() {
   123	  debug "libgrimoire" "real_unpack_file - $*"
   124	
   125	  local FILENUM="$1"
   126	  local SVAR="SOURCE${FILENUM}"
   127	
   128	  real_verify_file "$@"
   129	  rc=$?
   130	  case "$rc" in
   131	    200) debug "libunpack" "unable to verify $SVAR ${!SVAR}" ;;
   132	      1) return 1 ;; # verification failed
   133	      0) uncompress_unpack ${!SVAR}; return $? ;;
   134	  esac
   135	
   136	  if false; then # <------ here's the switch to disable oldworld -------
   137	    debug "libgrimoire" "falling back to missing verification"
   138	    unpack_missing "${!SVAR}"
   139	    rc="$?"
   140	    case "$rc" in
   141	        0) uncompress_unpack "${!SVAR}"; return "$?"             ;;
   142	        *) return "$rc"                                          ;;
   143	    esac
   144	  else
   145	    debug "libgrimoire" "falling back to regular MD5[]"
   146	    local MD5NUM="$([ -z "$FILENUM" ] && echo 0 || echo "$(($FILENUM - 1))")"
   147	    real_unpack "${!SVAR}" "${MD5[$MD5NUM]}"
   148	  fi
   149	}
   150	
   151	
   152	#---------------------------------------------------------------------
   153	## @Type API
   154	## @param SOURCE suffix
   155	##
   156	## Does the work of verifying a file with the new-world verification
   157	## system.
   158	#---------------------------------------------------------------------
   159	function real_verify_file() {
   160	  debug "libunpack" "real_verify_file - $*"
   161	
   162	  local FILENUM="$1"
   163	  local SVAR="SOURCE${FILENUM}"
   164	
   165	  local crypto_func
   166	  for crypto_func in GPG HASH IGNORE; do
   167	    debug "libgrimoire" "checking $crypto_func verification"
   168	
   169	    local AVAR="SOURCE${FILENUM}_${crypto_func}"
   170	    [[ -n ${!AVAR} ]] || continue
   171	
   172	    local rc=""
   173	    local lcase_crypto_func="$(echo $crypto_func | tr 'A-Z' 'a-z')"
   174	    unpack_$lcase_crypto_func "${!SVAR}" "${!AVAR}"
   175	    rc="$?"
   176	
   177	    case "$rc" in
   178	      200) debug "libgrimoire" "unable to verify $AVAR with $crypto_func" ;;
   179	        *) return $rc ;;
   180	    esac
   181	
   182	  done
   183	  return 200
   184	}
   185	
   186	
   187	#---------------------------------------------------------------------
   188	## @param filename
   189	## @param compressor
   190	## @Stdout uncompressed
   191	##
   192	## Just uncompresses the file, but does not expand it. i.e. bunzip
   193	## it, but don't untar it. It dumps the expanded file to stdout.
   194	## Note: zip is a special case because it doesn't work with streams.
   195	##
   196	#---------------------------------------------------------------------
   197	function uncompress_core() {
   198	  debug "libgrimoire" "uncompress_core - $*"
   199	
   200	  case  "$2"  in
   201	          bzip2)  bzip2  -cdf   "$1"  ;;
   202	           gzip)  gzip   -cdf   "$1"  ;;
   203	      compress*)  gzip   -cdf   "$1"  ;;
   204	            Zip)  cat           "$1"  ;;
   205	            RPM)  rpmunpack  <  "$1" | gzip  -cd    ;;
   206	            tar)  cat           "$1"  ;;
   207	          xz|XZ)  xz -cdf "$1" ;;
   208	           LZMA)  xz -cdf "$1" ;;
   209	          7-zip)  cat "$1" ;; # 7z supports stdout, but it unpacks at the same time
   210	              *)  cat           "$1"  ;;
   211	  esac
   212	
   213	}
   214	
   215	
   216	#---------------------------------------------------------------------
   217	## @param filename
   218	## @param compressor
   219	## @Stdout uncompressed
   220	##
   221	## unpack_core takes the uncompressed stream and turns it into the
   222	## fully unarchived form.
   223	## Note: zip is a special case because it doesn't work with streams.
   224	##
   225	#---------------------------------------------------------------------
   226	function unpack_core() {
   227	  debug "libgrimoire" "unpack_core - $*"
   228	
   229	  case  "$2"  in
   230	            bzip2|gzip|compress*|tar|XZ|xz|LZMA)
   231	                    if real_list_find "$3" same-permissions; then
   232	                      tar -xf /dev/stdin 2> /dev/null
   233	                    else
   234	                      tar --no-same-owner --no-same-permissions -xf \
   235	                      /dev/stdin 2> /dev/null
   236	                    fi || cat > /dev/null ;;
   237	              Zip)  cat /dev/stdin >/dev/null   #get rid of unused output
   238	                    unzip  -q  "$1"                                    ;;
   239	            7-zip)  cat /dev/stdin >/dev/null   #get rid of unused output
   240	                    7z e "$1"                                          ;;
   241	              RPM)  cpio  -idm < /dev/stdin                            ;;
   242	                *)  cat > /dev/null                                    ;;
   243	  esac
   244	
   245	}
   246	
   247	
   248	#---------------------------------------------------------------------
   249	## @Type API
   250	## @param filename
   251	## @Stdout compressor
   252	##
   253	## Guesses what program was used to compress a file
   254	## Return value is always success due to `file' workings
   255	##
   256	#---------------------------------------------------------------------
   257	function real_guess_compressor()  {
   258	  # NOTE: if the file doesn't exist, `file' still completes successfully
   259	  #       the COMPRESSOR value in this case will be "can't"
   260	
   261	  local OUTPUT="$($FILEPROG -L -b "$1")"
   262	  local COMPRESSOR="$(echo "$OUTPUT" | cut -d ' ' -f1)"
   263	  [ "$COMPRESSOR" = "GNU" -o "$COMPRESSOR" = "POSIX" ] &&
   264	    COMPRESSOR="$(echo "$OUTPUT" | cut -d ' ' -f2)"
   265	  debug "libgrimoire" "guess_compressor() - guessed $1 compressor <$COMPRESSOR>"
   266	  echo "$COMPRESSOR"
   267	}
   268	
   269	
   270	#---------------------------------------------------------------------
   271	## @Type API
   272	##
   273	## Used to be uncompress_md5(), now it is uncompress_core()
   274	##
   275	#---------------------------------------------------------------------
   276	function real_uncompress() { uncompress_core "$@"; }
   277	
   278	
   279	#---------------------------------------------------------------------
   280	## @param required spell
   281	##
   282	## Returns 200 if the user says not to Abort in the face, otherwise
   283	##
   284	#---------------------------------------------------------------------
   285	function unpack_spell_required() {
   286	  debug "libgrimoire" "Running unpack_spell_required -- $1"
   287	
   288	  local x
   289	  if ! spell_ok "$1" && ! smgl_which $2 x &> /dev/null; then
   290	    query "This spell has an option to check its integrity via spell "\
   291	"${SPELL_COLOR}${1}${DEFAULT_COLOR}${QUERY_COLOR} with the $2 command for $3, you might consider installing it. "\
   292	"Abort?" n &&
   293	      return 1 ||
   294	        return 200
   295	  else
   296	    return 0
   297	  fi
   298	
   299	}
   300	
   301	
   302	#===================== libunpack newworld ============================
   303	
   304	#--------------------------------------------------------------------
   305	## @param the verification level
   306	##
   307	## returns 0 if the specified verification level is in the user's
   308	## list of allowed verification levels, or if they allow unknown
   309	## verification levels, 1 otherwise
   310	##
   311	#--------------------------------------------------------------------
   312	function is_allowed_verf_level() {
   313	  local rc=0
   314	  local VRFLEVEL=$1
   315	  message "${MESSAGE_COLOR}Checking spell level ${VRFLEVEL}${DEFAULT_COLOR}"
   316	  if list_find "${VRF_ALLOWED_LEVELS}" "${VRFLEVEL}:on"
   317	  then
   318	    message "${MESSAGE_COLOR}Spell level is an allowed level${DEFAULT_COLOR}"
   319	  elif list_find "${VRF_ALLOWED_LEVELS}" "${VRFLEVEL}:off"
   320	  then
   321	      message "${PROBLEM_COLOR}Spell level is not an allowed level${DEFAULT_COLOR}"
   322	      rc=1
   323	  else
   324	    if [[ "${VRF_ALLOW_NEW_LEVELS}" == "on" ]]
   325	    then
   326	      message "${MESSAGE_COLOR}Spell level is a new allowed level${DEFAULT_COLOR}"
   327	    else
   328	      message "${PROBLEM_COLOR}Spell level is not an allowed level${DEFAULT_COLOR}"
   329	      rc=1
   330	    fi
   331	  fi
   332	  return $rc
   333	}
   334	
   335	#--------------------------------------------------------------------
   336	## @param hash used
   337	## @param spells verification level
   338	##
   339	## first checks if the hash is in the user specified list in an on state then
   340	## checks if the hash is there in an off state, if it can't find either then
   341	## it checks the state of VRF_ALLOW_NEW_HASHES to see if we should succeed or
   342	## not
   343	## Returns 0 if the hash is allowed or (VRF_ALLOW_NEW_HASHES is on and the hash
   344	## is not present in the hash list)
   345	##
   346	#--------------------------------------------------------------------
   347	function is_allowed_hash() {
   348	  local rc=0
   349	  local hash=$1
   350	  local HASHLEVEL=$2
   351	  message "${MESSAGE_COLOR}Algorithm used: ${hash}${DEFAULT_COLOR}" &&
   352	  if list_find "$VRF_ALLOWED_HASHES" "${hash}:on"
   353	  then
   354	    message "${MESSAGE_COLOR}Algorithm checks out${DEFAULT_COLOR}"
   355	    if is_allowed_verf_level $HASHLEVEL ; then rc=0 ; else rc=1 ; fi
   356	  elif list_find "$VRF_ALLOWED_HASHES" "${hash}:off"
   357	  then
   358	    message "${PROBLEM_COLOR}Algorithm is not in user selected list${DEFAULT_COLOR}"
   359	    rc=1
   360	  elif [[ "$VRF_ALLOW_NEW_HASHES" == "on" ]]
   361	  then
   362	    message "${MESSAGE_COLOR}Allowing new hash ${hash}${DEFAULT_COLOR}"
   363	    if is_allowed_verf_level $HASHLEVEL ; then rc=0 ; else rc=1; fi
   364	  else
   365	    message "${PROBLEM_COLOR}Disallowing new hash ${hash}${DEFAULT_COLOR}"
   366	    rc=1
   367	  fi
   368	  return $rc
   369	}
   370	
   371	#---------------------------------------------------------------------
   372	## @param file to unpack
   373	## @param gpg public key file (.gpg) ":" gpg signature file  (.asc)
   374	##
   375	## Given a file, unpack checks the gpg signature for that file, and, if
   376	## appropriate, runs the decompression program for that file, as well as
   377	## untar'ing the file. Note: zip is a special case because it doesn't
   378	## work with streams.
   379	##
   380	#---------------------------------------------------------------------
   381	function unpack_gpg() {
   382	  debug "libgrimoire" "Running unpack_gpg -- $*"
   383	
   384	  local FILENAME="$( guess_filename   "$SOURCE_CACHE/$1" )"
   385	  local PFNAME="$( echo "$2" | cut -d: -f1  )"
   386	  local SFNAME="$( echo "$2" | cut -d: -f2  )"
   387	  local GPGLEVEL="$( echo "$2" | cut -d: -f3 )"
   388	  if [[ -z $GPGLEVEL ]]
   389	  then
   390	    GPGLEVEL=$DEFAULT_SPELL_VRF_LEVEL
   391	  elif ! list_find "${VERIFY_SPELL_LEVELS}" "${GPGLEVEL}"
   392	  then
   393	    message "${PROBLEM_COLOR}This is probably a spell bug ${GPGLEVEL} is not in ${VERIFY_SPELL_LEVELS}${DEFAULT_COLOR}"
   394	    return 1
   395	  fi
   396	  local GPGALGO_USED=""
   397	  local message_file=""
   398	
   399	  message "${MESSAGE_COLOR}GPG checking source file $1...${DEFAULT_COLOR}"
   400	
   401	  unpack_spell_required gnupg gpg || return "$?"
   402	
   403	  gpg_verify_signature "$( locate_spell_file "$SFNAME" )" \
   404	                       "$FILENAME" \
   405	                       "$( locate_spell_file "$PFNAME" securely)" GPGALGO_USED
   406	  rc="$?"
   407	  case "$rc" in
   408	    0)
   409	      local algo
   410	      rc=1
   411	      for algo in $GPGALGO_USED; do
   412	        if is_allowed_hash "$algo" "$GPGLEVEL"; then
   413	          rc=0
   414	          break
   415	        fi
   416	      done
   417	      ;;
   418	    3) message_file="Signature" ;;
   419	    4) message_file="Source" ;;
   420	    5) message_file="Keyring" ;;
   421	  esac
   422	  if [[ $message_file ]]
   423	  then
   424	      message "${PROBLEM_COLOR}AHHH!!! ${message_file} file not found${DEFAULT_COLOR}"
   425	  fi
   426	  if [ "$rc" -eq 200 ]; then
   427	    return 200
   428	  fi
   429	
   430	  gpg_user_query $rc $SPELL spell || return 1
   431	  return 0
   432	
   433	}
   434	
   435	
   436	#---------------------------------------------------------------------
   437	## @param file to unpack
   438	## @param algorithm ":" hashsum
   439	##
   440	## Given a file, unpack checks the hash for that file, and, if
   441	## appropriate, runs the decompression program for that file, as well as
   442	## untar'ing the file. Note: zip is a special case because it doesn't
   443	## work with streams.
   444	##
   445	#---------------------------------------------------------------------
   446	function unpack_hash() {
   447	  debug "libgrimoire" "Running unpack_hash() on $1"
   448	
   449	  local FILENAME="$( guess_filename   "$SOURCE_CACHE/$1" )"
   450	  local ALGORITHM="$( echo "$2" | cut -d: -f1  )"
   451	  local HASHSUM="$(   echo "$2" | cut -d: -f2  )"
   452	  local HLEVEL="$(    echo "$2" | cut -d: -f3  )"
   453	  local rc=0
   454	  if [[ -z "$HLEVEL" ]]
   455	  then
   456	    HLEVEL=$DEFAULT_SPELL_VRF_LEVEL
   457	  fi
   458	
   459	  message "${MESSAGE_COLOR}Hash checking source file $1...${DEFAULT_COLOR}"
   460	  local HASH
   461	  if [ "$MD5SUM_DL" != "off" ]; then
   462	
   463	    if [[ "$ALGORITHM" == md5 ]] || [[ "$ALGORITHM" == sha1 ]] ; then
   464	      unpack_spell_required coreutils "${ALGORITHM}sum" "$ALGORITHM" ||
   465	        return "$?"
   466	      HASH="$(${ALGORITHM}sum "$FILENAME" | cut -d' ' -f1)"
   467	    else
   468	      unpack_spell_required gnupg gpg "$ALGORITHM" || return "$?"
   469	      if list_find "$(gpg_get_hashes)" $ALGORITHM; then
   470	        HASH="$(gpg_hashsum "${ALGORITHM}" "$FILENAME" | cut -d' ' -f1)"
   471	      else
   472	        message "${PROBLEM_COLOR}Algorithm $ALGORITHM is not"\
   473	                "known!${DEFAULT_COLOR}"
   474	        return 200
   475	      fi
   476	    fi
   477	    local rc=$?
   478	
   479	    if [[ "$HASH" != "$HASHSUM" ]] || [[ $rc != 0 ]]
   480	    then
   481	      error_message "${PROBLEM_COLOR}$ALGORITHM check failed!" &&
   482	      error_message "$HASH (computed) != $HASHSUM (expected)!$DEFAULT_COLOR" &&
   483	      hash_user_query 1 "$SPELL" spell || return 1
   484	    else
   485	      is_allowed_hash "$ALGORITHM" "$HLEVEL"
   486	      rc=$?
   487	      hash_user_query $rc "$SPELL" spell || return 1
   488	    fi
   489	  else
   490	    message "${PROBLEM_COLOR}Continuing!${DEFAULT_COLOR}"
   491	  fi
   492	  return 0
   493	}
   494	
   495	#--------------------------------------------------------------------
   496	## @param return code from unpack_hash
   497	## @param spell name
   498	##
   499	## Does some basic output to tell the user what failed and how then calls
   500	## unpack_file_user_query
   501	## Returns 0 if hash succeeded otherwise returns 1 if unpack_file_user_query
   502	## fails
   503	##
   504	#--------------------------------------------------------------------
   505	function hash_user_query() {
   506	  local rc=$1
   507	  local spell=$2
   508	  case "$rc" in
   509	    0)
   510	      message "${MESSAGE_COLOR}Hash verification succeeded${DEFAULT_COLOR}"
   511	      ;;
   512	    *)
   513	      message "${PROBLEM_COLOR}Hash verification failure${DEFAULT_COLOR}"
   514	      unpack_file_user_query $rc || return 1
   515	      ;;
   516	  esac
   517	  return 0
   518	}
   519	
   520	#--------------------------------------------------------------------
   521	## @param return code from the unpack_gpg or unpack_hash
   522	##
   523	## checks MD5SUM_DL to abort or not
   524	## Returns what query returns if it's called
   525	##
   526	#--------------------------------------------------------------------
   527	function unpack_file_user_query() {
   528	  local rc=$1
   529	  case "$rc" in
   530	    0)
   531	      ;;
   532	    *)
   533	      case "$MD5SUM_DL" in
   534	        ask_ignore)  query "Abort?" "n" && return 1  ;;
   535	        ask_risky|ask_abort)  query "Abort?" "y" && return 1 ;;
   536	        on|abort_all|*) message "${RED}Aborting.${DEFAULT_COLOR}" ; return 1 ;;
   537	      esac
   538	      ;;
   539	  esac
   540	  return 0
   541	}
   542	
   543	#---------------------------------------------------------------------
   544	## @param file to unpack
   545	## @param reason to ignore it, one of: volatile unversioned signature
   546	##
   547	## Given a file, unpack checks the ignore rules for that file, and, if
   548	## appropriate, runs the decompression program for that file, as well as
   549	## untar'ing the file. Note: zip is a special case because it doesn't
   550	## work with streams.
   551	##
   552	#---------------------------------------------------------------------
   553	function unpack_ignore() {
   554	  debug "libgrimoire" "Running unpack_ignore() on $1"
   555	
   556	  REASON="$2"
   557	
   558	  message "${MESSAGE_COLOR}Not checking ${2} source file $1...${DEFAULT_COLOR}"
   559	
   560	  if [ "$MD5SUM_DL" != "off" ]; then
   561	
   562	    [ "$REASON" == "signature" ]  ||
   563	      case "$MD5SUM_DL" in
   564	ask_risky|ask_ignore)  query "Abort?" "n"         && return 1   ||  return 0 ;;
   565	           abort_all)  message "${RED}Aborting.${DEFAULT_COLOR}"  ; return 1 ;;
   566	      ask_abort|on|*)  query "Abort?" "y"         && return 1   ||  return 0 ;;
   567	      esac
   568	
   569	  else
   570	    message "${RED}Continuing!${DEFAULT_COLOR}"
   571	    return 0
   572	  fi
   573	
   574	}
   575	
   576	
   577	#---------------------------------------------------------------------
   578	## @param file to unpack
   579	## @param reason to ignore it, one of: volatile unversioned signature
   580	##
   581	## Given a file, unpack checks the ignore rules for that file, and, if
   582	## appropriate, runs the decompression program for that file, as well as
   583	## untar'ing the file. Note: zip is a special case because it doesn't
   584	## work with streams.
   585	##
   586	#---------------------------------------------------------------------
   587	function unpack_missing() {
   588	  debug "libgrimoire" "Running unpack_missing() on $1"
   589	
   590	  message "${PROBLEM_COLOR}Missing check for source file $1!${DEFAULT_COLOR}"
   591	
   592	  if [ "$MD5SUM_DL" != "off" ]; then
   593	
   594	    case "$MD5SUM_DL" in
   595	          ask_ignore)  query "Abort?" "n"         && return 1   ||  return 0 ;;
   596	 ask_risky|ask_abort)  query "Abort?" "y"         && return 1   ||  return 0 ;;
   597	      on|abort_all|*)  message "${RED}Aborting.${DEFAULT_COLOR}"  ; return 1 ;;
   598	    esac
   599	
   600	  else
   601	    message "${RED}Continuing!${DEFAULT_COLOR}"
   602	    return 0
   603	  fi
   604	
   605	}
   606	
   607	#---------------------------------------------------------------------
   608	## @param file to unpack
   609	##
   610	## Given a cache file, runs the decompression and the unarchival
   611	## program on it. A wrapper around uncompress_unpack
   612	##
   613	#---------------------------------------------------------------------
   614	function uncompress_unpack_cache() {
   615	  uncompress_unpack "$1" root cache same-permissions
   616	}
   617	
   618	#---------------------------------------------------------------------
   619	## @param file to unpack
   620	## @param (optional) dirname of the file (default: $SOURCE_CACHE)
   621	##                   'root' if you already have an absolute path
   622	## @param (optional) type (default: source)
   623	## @param (optional) hints - used for passing hints to unpack/uncompress
   624	##
   625	## Given a file, runs the decompression program for that file, as well as
   626	## untar'ing the file.
   627	##
   628	#---------------------------------------------------------------------
   629	function uncompress_unpack() {
   630	  debug "libgrimoire" "Running uncompress_unpack() on $@"
   631	
   632	  local basedir
   633	  [[ $2 == root ]] || basedir=$SOURCE_CACHE
   634	  local filename=$(guess_filename "$basedir/$1") &&
   635	  local compressor=$(guess_compressor "$filename")
   636	  local type=${3:-source}
   637	  local hints="$4"
   638	
   639	  if [[ $type != cache ]]; then
   640	    message "${MESSAGE_COLOR}Unpacking $type file ${SPELL_COLOR}${1}${DEFAULT_COLOR}"  \
   641	            "${MESSAGE_COLOR}for spell"  \
   642	            "${SPELL_COLOR}${SPELL}${DEFAULT_COLOR}${MESSAGE_COLOR}.${DEFAULT_COLOR}"
   643	  fi
   644	
   645	  if [[ ! -f $filename ]]; then
   646	    error_message "$PROBLEM_COLOR$type file not found!$DEFAULT_COLOR"
   647	    return 1
   648	  fi
   649	
   650	  uncompress_core "$filename" "$compressor" "$hints" |
   651	      unpack_core "$filename" "$compressor" "$hints"
   652	}
   653	
   654	#---------------------------------------------------------------------
   655	## @param file to unpack
   656	##
   657	## Interface to unpack a file without any verification.
   658	##
   659	#---------------------------------------------------------------------
   660	function real_unpack_file_simple() { uncompress_unpack "$@"; }
   661	
   662	
   663	#---------------------------------------------------------------------
   664	## @param absolute or relative file path
   665	## @param empty or 'securely', which would skip SOURCE_CACHE
   666	## @Stdout the real path of the file (sometimes relative to CWD)
   667	##
   668	## Given a file, locate_spell_file finds out where it really is within
   669	## the spell hierarchy down to the grimoire root, and then tries cwd and
   670	## then the source cache.
   671	##
   672	#---------------------------------------------------------------------
   673	function locate_spell_file() {
   674	  debug "libgrimoire" "Running locate_spell_file() $2 on $1"
   675	
   676	  # checks in any case
   677	  [ -f    "$SPELL_DIRECTORY/$1" ] && echo    "$SPELL_DIRECTORY/$1" && return 0
   678	  [ -f  "$SECTION_DIRECTORY/$1" ] && echo  "$SECTION_DIRECTORY/$1" && return 0
   679	  [ -f           "$GRIMOIRE/$1" ] && echo           "$GRIMOIRE/$1" && return 0
   680	  [ -f                     "$1" ] && echo                     "$1" && return 0
   681	
   682	  [ "$2" != "securely" ] &&  # checks in "secure" mode
   683	  [ -f       "$SOURCE_CACHE/$1" ] && echo       "$SOURCE_CACHE/$1" && return 0
   684	
   685	  message "${MESSAGE_COLOR}"                                 \
   686	          "Problem: $1: file not found in spell hierarchy.${DEFAULT_COLOR}"  \
   687	          > /dev/stderr
   688	  echo  "$1"
   689	  return 1
   690	
   691	}
   692	
   693	
   694	#===================== libunpack oldworld ============================
   695	
   696	#---------------------------------------------------------------------
   697	## @Type API
   698	## @param file to unpack
   699	## @param md5sum
   700	##
   701	## Given a file, unpack runs the decompression program for that file,
   702	## as well as untar'ing the file if appropriate and if the MD5
   703	## matches.
   704	## Note: zip is a special case because it doesn't work with streams.
   705	##
   706	#---------------------------------------------------------------------
   707	function real_unpack() {
   708	  debug "libgrimoire" "Running unpack -- $*"
   709	
   710	  message "${MESSAGE_COLOR}Unpacking source file ${SPELL_COLOR}${1}${DEFAULT_COLOR}"  \
   711	          "${MESSAGE_COLOR}for spell"  \
   712	          "${SPELL_COLOR}${SPELL}${DEFAULT_COLOR}${MESSAGE_COLOR}.${DEFAULT_COLOR}"
   713	
   714	  FILENAME="$(guess_filename  "$SOURCE_CACHE/$1")" &&
   715	  COMPRESSOR="$(guess_compressor "$FILENAME")"
   716	
   717	  if [[ ! $FILENAME ]] || ! test -f "$FILENAME" ; then
   718	    message "${PROBLEM_COLOR}Source file not found.${DEFAULT_COLOR}"
   719	    return 1
   720	  fi
   721	
   722	  uncompress_md5 "$FILENAME" "$COMPRESSOR" "$2" |
   723	     unpack_core "$FILENAME" "$COMPRESSOR"      &&
   724	  {
   725	
   726	    # This section takes care of what happens if the md5sum doesn't match.
   727	    # $TMP_DIR/libgrimoire.uncompress.$$ is set in uncompress. It's the only
   728	    # way to get the return value since it's in a pipe.
   729	    if ! [[ $2 ]] ; then
   730	
   731	      rm "$TMP_DIR/libgrimoire.uncompress.$$"
   732	
   733	      message "${SPELL_COLOR}${SPELL}${DEFAULT_COLOR}:" \
   734	              "${QUERY_COLOR}doesn't have an MD5 sum for the uncompressed $1."
   735	
   736	      case "$MD5SUM_DL" in
   737	                 off)  message "${RED}Continuing!${DEFAULT_COLOR}"; return 0 ;;
   738	          ask_ignore)  query "Abort?" "n"         && return 1   ||  return 0 ;;
   739	 ask_risky|ask_abort)  query "Abort?" "y"         && return 1   ||  return 0 ;;
   740	      on|abort_all|*)  message "${RED}Aborting.${DEFAULT_COLOR}"  ; return 1 ;;
   741	      esac
   742	
   743	    elif [[ $2 == "IGNORE" ]] ; then
   744	
   745	      rm "$TMP_DIR/libgrimoire.uncompress.$$"
   746	
   747	      message "${SPELL_COLOR}${SPELL}${DEFAULT_COLOR}: ${QUERY_COLOR}MD5 sum was"  \
   748	              "purposefully left out for the uncompressed $1."
   749	      message "${QUERY_COLOR}Would you like to abort so you can validate"  \
   750	              "the source yourself via some alternate method?"
   751	
   752	      case "$MD5SUM_DL" in
   753	                 off)  message "${RED}Continuing!${DEFAULT_COLOR}"; return 0 ;;
   754	ask_risky|ask_ignore)  query "Abort?" "n"         && return 1   ||  return 0 ;;
   755	           abort_all)  message "${RED}Aborting.${DEFAULT_COLOR}"  ; return 1 ;;
   756	      ask_abort|on|*)  query "Abort?" "y"         && return 1   ||  return 0 ;;
   757	      esac
   758	
   759	    elif [[  "$(cat $TMP_DIR/libgrimoire.uncompress.$$)" != 0  ]]  ; then
   760	
   761	      rm "$TMP_DIR/libgrimoire.uncompress.$$"
   762	
   763	      message "${SPELL_COLOR}${SPELL}${DEFAULT_COLOR}:" \
   764	              "${QUERY_COLOR}MD5 sum is different for uncompressed $1."
   765	
   766	      case "$MD5SUM_DL" in
   767	                 off)  message "${RED}Continuing!${DEFAULT_COLOR}"; return 0 ;;
   768	          ask_ignore)  query "Abort?" "n"         && return 1   ||  return 0 ;;
   769	 ask_risky|ask_abort)  query "Abort?" "y"         && return 1   ||  return 0 ;;
   770	      on|abort_all|*)  message "${RED}Aborting.${DEFAULT_COLOR}"  ; return 1 ;;
   771	      esac
   772	
   773	    fi
   774	
   775	    rm "$TMP_DIR/libgrimoire.uncompress.$$"
   776	
   777	  }
   778	
   779	  #By this point, the archive is unarchived, and we know the MD5 check was good.
   780	  return 0
   781	
   782	}
   783	
   784	
   785	#---------------------------------------------------------------------
   786	## @param filename
   787	## @param compressor
   788	## @param md5
   789	## @Stdout uncompressed
   790	##
   791	## Uncompress_md5 dumps the expanded file via tee to md5_tar_check where it
   792	## is gobbled up by the bitbucket.  It also dumps the main">main">main">main">main stream out to
   793	## stdout.
   794	##
   795	#---------------------------------------------------------------------
   796	function uncompress_md5() {
   797	  debug "libgrimoire" "uncompress_md5 - $*"
   798	
   799	  # This is here so Duff's super debugging info doesn't screw the next step up
   800	  set +x
   801	
   802	  # Outer subshell is necessary to redirect stderr to stdout
   803	  (
   804	    uncompress_core "$1" "$2" |
   805	      tee /dev/stderr |
   806	      md5_tar_check "$3" 2>&1 1>/dev/null #we must avoid this printing
   807	  ) 2>&1
   808	
   809	  # This temp file is here because this function MUST NOT send
   810	  # anything to stdout or stderr, and upack needs a way to get the success or
   811	  # failure of this function.
   812	
   813	  local a="$?"
   814	  [[ $SUPER_DEBUG ]] && set -x  #turn this back on as soon as possible
   815	  echo "$a"  > "$TMP_DIR/libgrimoire.uncompress.$$"
   816	  return "$a"
   817	
   818	}
   819	
   820	
   821	#---------------------------------------------------------------------
   822	## @param md5
   823	##
   824	## Checks that the stdin matches the argument.
   825	## Note that DEBUG output may dissapear if it's /dev/stderr due to
   826	## uncompress' 2>/dev/null.
   827	##
   828	#---------------------------------------------------------------------
   829	function md5_tar_check()  {
   830	  debug "libgrimoire" "md5_tar_check() - Checking MD5 sum"
   831	
   832	  local md5
   833	
   834	  #Do the md5
   835	  md5="$(md5sum /dev/stdin | awk '{print $1}')"
   836	  debug "libgrimoire" "md5_tar_check() - MD5 of tarball is $md5."
   837	  debug "libgrimoire" "md5_tar_check() - argument received is $1."
   838	
   839	  #See if they match
   840	  if [[ $1 == $md5 ]] ; then
   841	    debug "libgrimoire" "md5_tar_check() - MD5 Sum Success ( $1 == $md5 )"
   842	    return 0
   843	  fi
   844	
   845	  #See of we need to md5sum it at all
   846	  if [[ ${MD5SUM_DL:-on} == off ]] || ! [[  $1  ]] ; then
   847	    debug "libgrimoire" "md5_tar_check() - Skipping check"
   848	    return 0
   849	  fi
   850	
   851	  #If we get here, the md5's don't match, but should.
   852	  debug "libgrimoire" "md5_tar_check() - bad md5"
   853	  return 1
   854	
   855	}
   856	
   857	
   858	#---------------------------------------------------------------------
   859	## @License
   860	##
   861	## This software is free software; you can redistribute it and/or modify
   862	## it under the terms of the GNU General Public License as published by
   863	## the Free Software Foundation; either version 2 of the License, or
   864	## (at your option) any later version.
   865	##
   866	## This software is distributed in the hope that it will be useful,
   867	## but WITHOUT ANY WARRANTY; without even the implied warranty of
   868	## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   869	## GNU General Public License for more details.
   870	##
   871	## You should have received a copy of the GNU General Public License
   872	## along with this software; if not, write to the Free Software
   873	## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
   874	##
   875	#---------------------------------------------------------------------