Smokes your problems, coughs fresh air.

Tag: bash (Page 1 of 2)

Making a shell-script run with setuid root

If you want to run a process with root privileges that you can invoke as a less unprivileged user, you can make the program setuid root. This can be very useful, for example, when you want a PHP or CGI script to call a backup process, or to create a new site or irrevocably delete you whole system. The latter example points to a serious security problem: if anyone can figure out a way to make your program do something you don’t want, you’re screwed, because you just gave them root privileges to wreak maximum havoc. That’s why, normally, scripts (anything executed by an interpreter by the kernel because of a shebang) won’t get elevated privileges when you set their setuid bit.

To understand the setuid bit, let’s first see what happens when I try to cat a file that belongs to root:

su -
# I am now root; fear me
touch no-one-can-touch-me
chmod 600 no-one-can-touch-me
cat no-one-can-touch-me
# cat: Permission denied 

Next, I’ll create a shell script that cats the file:

#!/bin/bash
 
cat no-one-can-touch-me

And make the script setuid root:

su -
chown root:root script.sh
chmod +xs script.sh

If I now execute the script, I still get the permission denied. What I need to make this work is a wrapper program. For that, I refer to Wiebe’s post about the same subject. (Yeah, I know: why bother publishing this if Wiebe already did an excellent job explaining? Well, I just hate to throw away an otherwise fine draft.)

Remove appending slash from a path using Sed

Here’s how you can remove the appending slash from a path using sed, the stream editor:

/just/a/path/ | sed -e 's#/$##'
# Output: /just/a/path
 
# And, if there isn't an appending slash, nothing happens:
 /just/another/path | sed -e 's#/$##'
# Output: /just/another/path 

It works quite simple. Sed executes expression (-e) on its standard input. The expression is a substitution using regular expressions. The #-sign is the delimiter. The part (#/) between the first two hash signs is the matching expression and the (empty) part between the second and the third hash sign is the replacement expression. This expression (“s#/$##”) basically says: replace all occurrences of “/” at the end of the line (the dollar sign is the end-of-line anchor) with nothing.

To use this in a script is easy-peasy. Suppose $1 is a system path that may or may not include an appending slash:

#!/bin/bash
 
sanitized_path= "$1" | sed -e 's#/$##'`
 $sanitized_path

This script outputs its first parameter with the appending slash removed.

Bash script template

#!/bin/bash
#
# Author: Wiebe Cazemier (wiebe@halfgaar.net)
#
# Template bash script, for when you need something overengineerd :)
 
# Hack prevention
PATH="/sbin:/usr/sbin:/bin:/usr/bin"
 
# Error codes
wrong_params=5
interrupted=99
default_error=1
 
# Function to echo in color. Don't supply color for normal color.
echo_color()
{
  message="$1"
  color="$2"
 
  red_begin="\033[01;31m"
  green_begin="\033[01;32m"
  yellow_begin="\033[01;33m"
  color_end="\033[00m"
 
  # Set color to normal when there is no color
  [ ! "$color" ] && color_begin="$color_end"
 
  if [ "$color" == "red" ]; then
    color_begin="$red_begin"
  fi
 
  if [ "$color" == "green" ]; then
    color_begin="$green_begin"
  fi
 
  if [ "$color" == "yellow" ]; then
    color_begin="$yellow_begin"
  fi
 
  echo -e "${color_begin}${message}${color_end}"
}
 
end()
{
  message="$1"
  exit_status="$2"
 
  if [ -z "$exit_status" ]; then
    exit_status="0"
  fi
 
  if [ ! "$exit_status" -eq "0" ]; then
    echo_color "$message" "red"
  else
    echo_color "$message" "green"
  fi
 
  if [ "$exit_status" -eq "$wrong_params" ]; then
    dohelp
  fi
 
  exit $exit_status
}
 
# Define function to call when SIGTERM is received
trap "end 'Interrupted' $interrupted" 1 2 3 15
 
dohelp()
{
  echo ""
  echo "Example script"
  echo ""
  echo "help = todo"
 
  # Exit because you don't want the script to do anything after displaying help
  exit 
}
 
 
while [ -n "$*" ]; do
  flag=$1
  value=$2
 
  case "$flag" in
    "--option1")
      option1=$value
      shift
    ;;
    "--help")
      dohelp
    ;;
    "--")
      break
    ;;
    *)
      end "unknown option $flag. Type --help" "$wrong_params"
    ;;
  esac
 
  shift
done
 
if [ -z "$option1" ]; then
  end "option1 not given" $wrong_params
fi

Bash parameter parsing

Here is a code snippet I use for parameter parsing:

dohelp()
{
  "Example script"
  ""
 
  # Exit because you don't want the script to do anything after
  # displaying help, and do so with error, so that calling scripts won't think it succeeded
  1
}
 
  [ -n "$*" ];
  flag="$1"
  value="$2"
 
  "$flag"
    "--one")
      one="$value"
     
    ;;
    "--two")
      two="$value"
     
    ;;
    "--pretend")
      pretend=true
    ;;
    "--help")
      dohelp
    ;;
    "--")
     
    ;;
    *)
      -e "unknown option $flag\n"
      dohelp
    ;;
 
 

Preventing syntax errors with old shell scripts

I was trying to install Unreal Tournament GOTY on one of my Linux machines. I downloaded and ran the script ut-install-436-GOTY.run but I got this error:

cannot open `+6' for reading: No such file or directory 

This line caused it:

sum1=`tail +6 $0 | cksum | sed -e 's/ /Z/' -e 's/   /Z/' | cut -dZ -f1`

To fix it, I set this environment variable:

export _POSIX2_VERSION=199209

Apparently, this makes programs behave differently. Research is required to find out exactly what it does…

Replacing the full contents of a Subversion working (sub)dir

The annoyances that I suffered earlier today during the upgrade of a WordPress plugin made me turn to my favorite text-editor to create a simple script, svn-replace-dir:

#!/bin/bash
 
usage() {
    cat <<"EOF"
$0 [--dry-run] <svn_dir> <replacement_dir>
 
This script replaces the contents of <svn_dir> with the contents of <replacement_dir>,
where <replacement_dir> is not an svn directory.
 
Copyleft 2010, Rowan Rodrik van der Molen <rowan@bigsmoke.us>
EOF
}
 
fatal_error() {
    message=$1
 
    -e "\e[1;31m$message\e[0m"
    1
}
 
usage_error() {
    error="Wrong usage."
 
    [ -n "$1" ];
        error=$1
   
 
    -e "\e[1;31m$error\e[0m"
    1
}
 
run_command() {
    -e "\e[1;34m$1\e[0m"
 
    [ $dry_run == 1 ] || $1
}
 
dry_run=0 [ $1 == '--dry-run' ];
  dry_run=1
 
 
 
[ $# == 2 ] || usage_error "Wrong number of arguments."
 
svn_dir= "$1"|sed -e 's#/$##'`
replacement_dir= "$2"|sed -e 's#/$##'`
begin_path=$PWD
 
#if [ "${svn_dir:0:1}" != "/" ]; then svn_dir="$PWD/$svn_dir"; fi
#if [ "${replacement_dir:0:1}" != "/" ]; then replacement_dir="$PWD/$replacement_dir"; fi
 
[ -d "$svn_dir" ] || usage_error "$svn_dir is not a directory."
[ -d "$replacement_dir" ] || usage_error "$replacement_dir is not a directory."
 
 
# Create all subdirectories in $svn_dir that do not yet exist
$replacement_dir
find . -mindepth 1  d -print | sed -e 's#^./##' | d;
    $begin_path/$svn_dir
    # Doesn't the destination directory already exist?
    [ ! -d "$d" ];
        run_command "svn mkdir '$d'"
   
 
# Copy all files from $replacement_dir to $svn_dir
$begin_path/$replacement_dir
find .  f -print | sed -e 's#^./##' | f;
    $begin_path
    run_command "cp '$replacement_dir/$f' '$svn_dir/$f'" # FIXME: Quoting problem
 
# Remove all files that do no longer exist in $replacement dir
$begin_path/$svn_dir
find .  f -print | grep -v '.svn' | f;
    [ ! -f "$begin_path/$replacement_dir/$f" ];
        run_command "svn rm '$f'"
   
 
# Remove all subdirs that do no longer exist in $replacement dir
$begin_path/$svn_dir
find . -mindepth 1  d -print | grep -v '.svn' | d;
    [ ! -d "$begin_path/$replacement_dir/$d" ];
        run_command "svn rm '$d'"
   
 0

Using the script is simple:

svn-replace-dir simple-tags new-simple-tags|less -R

It replaces all the contents of the first directory (simple-tags in the example) with those of the second directory and it deletes everything that is no longer present in the second dir. In the process, it does all the necessary calls to svn mkdir, svn rm and (in the next version) svn add.

diff tells me that the script has done its work correctly:

diff -x .svn -ruN simple-tags new-simple-tags
# Emptiness is bliss :-) 

This is another one of these occasions when Git would have made life so much easier. Luckily, at least there’s GitHub to host this script as a Gist. Check there if you want to fetch the newest version of this script.

My custom Linux environment

On every machine that I install, I need a custom environment. At the very basic, I need screen and bash customizations. I will attempt to keep this blog post up-to-date with my most recent config.

/etc/bash.bashrc_halfgaar (naming scheme depends on distro):

prompt_command {
  XTERM_TITLE="\e]2;\u@\H:\w\a"
 
  BGJOBS_COLOR="\[\e[1;30m\]"
  BGJOBS=""
  [ "$(jobs | head -c1)" ]; BGJOBS=" $BGJOBS_COLOR(bg:\j)";
 
  DOLLAR_COLOR="\[\e[1;32m\]"
  [[ ${EUID} == 0 ]] ; DOLLAR_COLOR="\[\e[1;31m\]";
  DOLLAR="$DOLLAR_COLOR\\\$"
 
  USER_COLOR="\[\e[1;32m\]"
  [[ ${EUID} == 0 ]]; USER_COLOR="\[\e[41;1;32m\]";
 
  PS1="$XTERM_TITLE$USER_COLOR\u\[\e[1;32m\]@\H:\[\e[m\] \[\e[1;34m\]\w\[\e[m\]\n\
$DOLLAR$BGJOBS \[\e[m\]"
} PROMPT_COMMAND=prompt_command
 EDITOR=vim
 ls='ls --color=auto' ll='ls -l' lh='ls -lh' grep='grep --color=auto'

Don’t forget to source the file in ~/.bashrc

~/.screenrc:

caption always "%{= kB}%-Lw%{=s kB}%50>%n%f* %t %{-}%+Lw%<"
vbell off
startup_message off
term linux

Bash script for sending SMS using Mollie

I signed up for a Mollie account so that I can send SMS’s from my machines. To do that, I needed a bash script, so I wrote one:

#! /bin/bash
#
# Script to send an SMS email notifcation to Mollie's HTTP gateways
# Based on the SMS2Email script which can also be found on nagiosexchange.org
# Reworked by Dennis Storm - Brainstorm ICT
# Again reworked by Wiebe Cazemier (wiebe@halfgaar.net), to include proper failure checks
#
#################################################################################
 
config_file="/etc/send-sms.conf"
log_file="/var/log/send-sms.log"
  [ -L "$log_file" ];
  "cannot continue, $log_file is a symlink."
  2
 
touch "$log_file" > /dev/null 2>&1
 
sender="SMSScript"
  [ -f "$config_file" ];
  $config_file
 
curl_location=`which curl 2> /dev/null`
  [ -z "$curl_location" ];
  "Curl command not found. Install that." >&2
  1
 
logfail()
{
  message="$1"
  message="Failure: [`date`]: $message" 
 
  $message
 
  [ -w "$log_file" ];
    "$message" >> "$log_file"
 
 
  1
}
 
# Show usage if necessary
[ $# -eq 0 ]; then
    "Usage: $0 -s [sender] -n [numbers] -m [message] -u [username] -p [password]";
    "";
    "[numbers]  = SMS numbers (if multiple, enclose in quotes) to send message to.";
    "[message]  = Text of message you want to send";
    "[username] = Username assocated with Mollie  account";
    "[sender]   = Sender"
    "[password] = MD5 HTTP API Password assocated with Mollie account";
    ""
    "-d         = Dry run, pretend success."
    ""
    "The numbers, sender, username and password options are optional and";
    "override the account credentials defined in $config_file.";
    ""
    "A log file $log_file will be kept if it is writable to the user."
    "";
    1;
 
# Get command line arguments
[ "$1" != "" ] ;
    $1
   
  -n)
      # Get the SMS numbers that we should send message to
      numbers="$2";
      2;
      ;;
  -m)
      # Get the message we should send
      message="$2";
      2;
      ;;
  -s)
      # Get the sender to show in the SMS
      sender="$2";
      2;
      ;;
  -u)
      # Get the username
      username="$2";
      2;
      ;;
  -p)
      # Get the password
      password="$2";
      2;
      ;;
  -d)
      dry_run="dry_run";
      1;
      ;;
  *)
      "Unknown option: $1"
      1;
      ;;
   
  [ -z "$username" ];
  logfail "No username specified or found in $config_file."
  [ -z "$password" ];
  logfail "No password specified or found in $config_file."
  [ -z "$numbers" ];
  logfail "No numbers specified or found in $config_file."
 
message_length= -n "$message"|wc -c`
sender_length= -n "$sender"|wc -c`
  [ "$message_length" -gt "160" ];
  logfail "SMS message is longer than 160 chars ($message_length). That is not allowed." >&2
  [ "$sender_length" -gt "11" ];
  logfail "Sender is longer than 11 chars ($sender_length). That is not allowed." >&2
 
# We haven't sent the message yet
message_sent_ok=0;
 
# The API supports sending to a comma seperated list, but that doesn't seem
# to work well. Therefore, I space seperate them and just call the API for
# each number.
number $numbers; 
  [ ! "$dry_run" ];
    RESPONSE=`curl -s -d username="$username" -d md5_password="$password" -d originator="$sender" -d recipients="$number" -d message="$message" http://www.mollie.nl/xml/sms/`
 
    RESPONSE="<success>true</success>"
 
 
  # Curl was able to post okay...
  [ "$?" -eq "0" ];
    success_line_true= "$RESPONSE"|grep -i "<success>true</success>"`
 
    [ -z "$success_line_true" ];
      logfail "$message to $number. Response was: $RESPONSE"
   
 
    logfail "curl return an error while trying to contact mollie to send an SMS." >&2
 
 
  [ -w "$log_file" ];
    "Success: [`date`]: $dry_run Sent '$message' to $number" >> $log_file
 
  "Success sending sms(es)."

It has the following config file in /etc/notify_sms.conf:

# Default values. Can be overriden with command line arguments
username="halfgaar"
password="" # MD5sum of HTTP API password.
sender="Melk"
numbers="+316xxxxxxxx" # You can set multiple numbers by space seperating them. 

Bash quoting

I’m always confused by bash’s quoting. I hope to put all my quote wisdom in this post and invoke other’s quote wisdom in the comments. I’ll give some examples of what I mean.

Let’s say you have a file with a space: “bla bla.txt”. If I were to ls that file, I would do:

ls 'bla bla.txt'

This works. However, when I want to do this from a variable (in a script) and do:

command="ls 'bla bla.txt'"
$command

The result is:

ls: cannot access 'bla: No such file or directory
ls: cannot access bla.txt': No such file or directory

You can solve this by using eval:

command="ls 'bla bla.txt'"
eval $command

This gives:

bla bla.txt

Some time ago, I suggested this as answer on somebodies question at userfriendly, to which somebody else said that using eval actually makes things worse:

That’s actually worse. . . as the quoting gets re-parsed (remember, ‘eval’ means “take arguments as shell input”), which means that single quotes in the name break it, horribly, and names with spaces get even _worse_.

Another example: let’s say you have two files:

-rw-r----- 1 halfgaar halfgaar 0 2009-10-18 16:51 bla's bla"s.txt
-rw-r----- 1 halfgaar halfgaar 0 2009-10-18 16:52 normal.txt

I’m gonna run this command on it: find . -mindepth 1 -exec ls ‘{}’ \;. When executed without eval, it says this:

find: missing argument to `-exec' 

With eval, it says:

./normal.txt
./bla's bla"s.txt

Eval seems to be what I need, so what is wrong with using it? Also, shouldn’t that double quote be a problem? If someone can give a situation where that poses problems, I’m all ears.

« Older posts

© 2024 BigSmoke

Theme by Anders NorenUp ↑