Programowanie w powłoce - ćwiczenia

Język powłoki SH/BASH, jako dość prosty i powszechnie stosowany, jest wykorzystywany w listingach zawartych w książce.

Wykorzystujemy skrypty powłoki do archiwizowania pracy, automatyzowania żmudnych, powtarzająch się czynności (takich, jak na przykład budowanie oprogramowania). A także do oprogramowywania niektórych procedur w systemie wbudowanym (na przykład reakcji na pojawienie się nowych urządzeń).

Sumy kontrolne MD5 i ich weryfikacja

Napisz program, który dla każdego podanego pliku (ścieżki) obliczy (przy pomocy md5sum) jego sumę kontrolną i zapisze ją w pliku o nazwie takiej jak ścieżka, z / zamienionymi na _ w katalogu SUMY, w katalogu domowym użutkownika. Wywołanie programu:

./kontrola.sh /bin/bash /etc/passw <kolejne ścieżki, nie wiesz ile>

Wywołanie z parametrem -s powinno powodować weryfikację sum kontrolnych.

kontrola.sh
#!/bin/bash
 
SPRAWDZ='nie'
if [ "$1" == '-s' ]; then
  SPRAWDZ='tak'
  shift
fi
 
[ -e ~/SUMY ] || mkdir ~/SUMY
 
while [ "$1" ]; do
  PLIK=$(echo $1 |  tr / _)
  if [ $SPRAWDZ == 'tak' ]; then
    #md5sum z Debiana od razu wypisuje plik:DOBRZE/ŹLE
    #md5sum -c ~/SUMY/$PLIK
    #W większości dystrybucji trzeba zrobić tak:
    md5sum -c ~/SUMY/$PLIK > /dev/null 2>&1 && echo "$1: OK" || echo "$2: ERROR"
  else
    md5sum $1 > ~/SUMY/$PLIK
  fi
  shift
done

Tego typu programy są jednym z elementów systemów wykrwania włamań (IDS). Sumy kontrolne przechowywane są poza systemem i mogą zostać użyte do cylklicznego sprawdzania, czy włamywacz nie podmienił jakichś istotnych plików systemowych.

Biblioteki współdzielone

Dla zadanej listy programów znajdź wszystkie biblioteki współdzielone, z których one korzystają.

Należy skorzystać z polecenia (readelf -d <plik>), aby odczytać wymagane biblioteki (oznaczone jako NEEDED). Lista programów zostanie podana jako parametry wywołania. Znalezione biblioteki i programy powinny zostać skopiowane z zachowaniem względnego położenia do wydzielonego katalogu w /tmp. (program readelf jest instalowany w pakiecie binutils - tym samym, co linker i assembler)

dependency.sh
#!/bin/bash
 
#To są pliki
BINARIES="/bin/bash /bin/ls /bin/cat"
 
#To są ściezki, w których szukamy bibliotek współdzielonych
LIBDIRS="/lib /usr/lib /usr/local/lib"
 
OUTPUT="/tmp/target"
 
[ -e $OUTPUT ] || mkdir $OUTPUT
 
while [ "$BINARIES" ]; do
 
  #lista bibliotek do sprawdzenia - na razie pusta
  LIBS=""
 
  for PLIK in $BINARIES; do
    #Skopiuj plik z zachowaniem ściezki.
    KAT="$OUTPUT$(dirname $PLIK)"
    [ -e $KAT ] || mkdir -p $KAT
 
    cp -a $PLIK $KAT
    #Weź pod uwagę, że plik może być linkiem symbolicznym do właściwego pliku!
    #tak będzie przy bibliotekach. (nie przewidujemy linków do linków ani ścieżek bezwzględnych)
    RPLIK=$(readlink $PLIK)
    if [ "$RPLIK" ]; then
      cp -a $(dirname $PLIK)/$RPLIK $KAT
    fi
 
    #Znajdź biblioteki których wymaga plik i dodaj do listy bibliotek do znalezienia
    L="$(readelf -d $PLIK | grep "(NEEDED)" | sed -e "s/^.*\[\(.*\)\]$/\1/")"
    #Jeżeli chcemy uniknąć wyrażeń regularnych, to można jeszcze:
    #readelf -d $PLIK | grep "(NEEDED)" | tr '[]' ' ' | awk '{print $NF}'
    #albo nawet
    #readelf -d $PLIK | grep "(NEEDED)" | cut -d [ -f 2 | tr ] ' '
 
    #Zamiast readelf można jeszcze skorzystać w wyjścia polecenia ldd, ale działa on tylko
    #na binariach z tej samej platformy.
 
    # W L są ścieżki względne, a my potrzebujemy bezwzględnych
    for i in $L; do
      znalazlem=nie
      for d in $LIBDIRS; do
        if [ -e $d/$i ]; then
          znalazlem=tak
          LIBS="$LIBS $d/$i"
          break
        fi
      done
      [ "$znalazlem" == "nie" ] && echo "Nie znaleziono biblioteki $i w $LIBDIRS"
    done
  done
 
  #Usuń duplikaty
  LIBS=$(echo $LIBS | tr ' ' '\n' | sort -u )
 
  BINARIES=$LIBS
done

Otrzymaliśmy minimalne środowisko do uruchomienia zadanych programów. Wystarczy teraz wykonać:

ln bin/bash /tmp/<kat>/init
 
cd /tmp/<kat>
find . | cpio -o -H newc | gzip > /tmp/rd.gz

Otrzymany w ten sposób plik rd.gz jest uruchomieniowym ramdyskiem, który może być wczytany wraz z jądrem przez program uruchomieniowy - i mamy system typu kiosk, jednozadaniowy. Zamiast init-a uruchomiliśmy bash-a. (Po wyjściu z powłoki - będzie kernel-panic).

Moduły jądra

Szukamy sterowników, pasujących do sprzętu wykrytego przez lspci lub lsusb.

Polecenie:

modinfo plik.ko

Wypisuje linie alias, w którcyh znajduje się lista kompatybilnych urządzeń. Dla PCI:

  • vXXXXXXXX - określa producenta,
  • dXXXXXXXX - urządzenie

Te same numery wyświetla polecenie lspci -n.

Przykład:

lpci -n  | cut -d ' ' -f 3
...
109e:036e

/sbin/modinfo bttv
alias:          pci:v0000109Ed0000036Esv*sd*bc*sc*i*

(uwaga na wielkie litery i inny format zapisu).

A skąd wiedziałem, że to bttv?

$ cat /lib/modules/`uname -r`/modules.alias | grep 109E.*036E
alias pci:v0000109Ed0000036Esv*sd*bc*sc*i* bttv

Napisz skrypt, który wyświetli nazwę urządzenia, nazwę sterownika (pliku) i jego wersję.

sterowniki.sh
#!/bin/bash
for urzadzenie in  $(lspci -n | cut -d ' ' -f 1,3 | tr ' ' ':'); do
  #05:01.0:109e:036e
 
  #Wyciągam informacje o sprzęcie
  numer_na_szynie=$(echo $urzadzenie | cut -d : -f 1,2)
 
  producent=$(echo $urzadzenie | cut -d : -f 3 | tr abcdef ABCDEF)
  model=$(echo $urzadzenie | cut -d : -f 4 | tr abcdef ABCDEF)
 
  #Szukam sterownika
  modul=$(grep "v.*${producent}d.*${model}" /lib/modules/$(uname -r)/modules.alias | cut -d ' ' -f 3 | head -n 1)
 
  #Wypisuję urządzenie
  echo "$numer_na_szynie $producent $model"
  #Jego nazwę
  lspci -s "$numer_na_szynie"
  #Oraz informacje ze sterownika
  if [ "$modul" ]; then
    /sbin/modinfo $modul | grep "^version\|^filename"
  fi
  echo
 
done

Statystyki użycia systemu

Zbieranie danych z systemu: CPU, pamięć, dysk, ilość procesów, obciążenie systemu i generowanie z nich pliku w formacie csv-a.

Na przykład dla ruchu sieciowego - zbieranie i wyświetlanie ile urosło.

netload.sh
#!/bin/bash
INTERFEJS=eth0
 
#Aktualne dane
CZAS=$(date +%s)
rx=$(/sbin/ifconfig $INTERFEJS | grep "RX bytes" | cut -d : -f 2 | cut -d ' ' -f 1)
tx=$(/sbin/ifconfig $INTERFEJS | grep "RX bytes" | cut -d : -f 3 | cut -d ' ' -f 1)
 
#Dane z poprzedniego odczytu
POPRZEDNI=/tmp/net.$INTERFEJS.log
 
if [ -e $POPRZEDNI ]; then
  ODCZYT=$(tail -n 1 $POPRZEDNI)
  old_CZAS=$(echo $ODCZYT | cut -d , -f 2)
  old_rx=$(echo $ODCZYT | cut -d , -f 3)
  old_tx=$(echo $ODCZYT | cut -d , -f 4)
  echo "W ciągu $(($CZAS - $old_CZAS))s odebrano: $(($rx - $old_rx)) bajtow, wyslano: $(($tx - $old_tx)) bajtow."
else
  echo "Nie znaleziono poprzedniego odczytu."
fi
 
echo "$INTERFEJS,$CZAS,$rx,$tx" >> $POPRZEDNI

Można jeszcze sprawdzać, czy różne liczniki mieszczą się w normie. ilość procesów w odpowiednich widełkach, ilość procesów per użytkownik ilość zalogowanych użytkowników itp.

Tego typu narzędzia wykorzystywane są w systemach monitorowania (na przykład Nagios, Zabbix).

Konta użytkowników

Generowanie poleceń potrzebnych do założenia kont użytkowników (useradd, passwd, chage) na podstawie pliku csv.

To jest problem z hasłami. Można je albo generować przy pomocy

pwgen -1

i podawać na standardowe wejście programu

passwd --stdin <opcje> <login>

albo generować z

/dev/urandom

na przykład:

head -c 8 /dev/urandom | md5sum | head -c 8

Drugi problem to zmiana wielkości znaków: Jan Kowalski → JKowalski → jkowalski

Zależności pakietów

Wydrukowanie drzewa zależności zadanych pakietów dla konkretnej dystrybucji (opcje rpm-a lub dpkg, generowanie html-a). Przykład dla Debian-a:

PAKIETY=$(dpkg -s bash | grep "^Depends\|^Pre-Depends" | cut -d : -f 2- | sed -e "s/([^()]*)//g" | tr ',' ' ')

itd rekurenyjnie aż dojdziemy do jakieoś pakietu, który nie ma zależności, na przykład libc-bin. dla rpm-a można wyjść od

rpm -q --provides/rpm -q --requirename

Watchdog

Watchdog - program, króry uruchomi proces rezydentny, a następnie będzie monitorował jego działanie (co 10s sprawdza czy proces działa i jeśli nie - restartuje go). Skrypt po uruchomieniu powinien przejść w tło i nie blokować konsoli.

Galeria

Skrypt, który w podanym jako argument katalogu znajdzie wszystkie pliki z obrazkami (JPG i PNG) i przygotuje z nich galerię. To znaczy stronę WWW (prosty HTML) z miniaturkami. Do zmiany rozmiaru można wykorzystać na przykład convert z pakietu ImageMagic.

gallery.sh
#!/bin/bash
if [ -z "$1" ]; then
	echo "Użycie: $0 <katalog z plikami jpg lub png>"
	exit 1
fi
 
#Spacje w nazwach plików _nie są_ dozwolone.
FILES=''
if ls $1/*.jpg > /dev/null 2>&1; then
	FILES="$1/*.jpg"
fi
 
if ls $1/*.png > /dev/null 2>&1; then
	FILES="$FILES $1/*.png"
fi
 
if [ "$FILES" == '' ]; then
	echo "Nie znaleziono plików."
	exit 1
else
	echo "Znaleziono: $(echo $FILES | wc -l) pliki(ów)."
fi
 
OUT=$(mktemp -d /tmp/galeria.XXXXXX)
mkdir -p $OUT/min
mkdir -p $OUT/img
 
cat << EOF > $OUT/index.html
<html>
<head><title>Galeria: $1</title></head>
<body>
EOF
 
COUNT=1
for i in $FILES; do
	cp -v $i $OUT/img/
	convert $i -resize 150 $OUT/min/$COUNT.jpg
 
cat << EOF >>$OUT/index.html
<div style="float: left; margin: 10px;">
  <a href="img/$img/$(basename $i)">
    <img src="min/$COUNT.jpg"/><br/>$(basename $i)
  </a>
</div>
EOF
 
	((COUNT++))
done
 
cat << EOF >> $OUT/index.html
</body>
</html>
EOF
 
echo "Galeria w $OUT/index.html"
ostatnio zmienione: 2011/07/18 13:45