VIM – podstawowe komendy

Ktoś może pomyśleć – „po co mi znajomość VIMa, przecież jest milion przyjaźniejszych dla użytkownika edytorów”. Zgodzę się, po części – jest wiele UNIX-owych edytorów tekstu posiadających interface dużo przyjaźniejszy jednak żaden z nich nie ma takich dużych możliwości jak stary poczciwy VIM, nie znam innego edytora, który z równie sprawnie i szybko edytuje pliki 200 megowe. Zresztą na niektórych serwerach (wierzcie mi, że są takie) VIM jest jedynym dostępnym edytorem tekstu.

Jako, że kiedyś sam szukałem prostego manuala z podstawowymi komedami VIMa to może komuś się przyda:

poruszanie się po dokumencie
+ – przejście do następnej linii
– – przejście do poprzedniej linii
0 – przejście do początku linii
^ – przejście do pierwszego znaku nie będącego znakiem białym w linii
$ – przejście na koniec linii
n| – przejście do kolumny n linii
} – przejście do następnego paragrafu
{ – przejście do poprzedniego paragrafu
% – przejście do otwarcia / zamknięcia nawiasu (),[],<>,{}
G – przejście na koniec dokumentu
nG – przejście do linii <n>

zapisywanie
:x lub :wq – zapisywanie pliku i wyjście
:q! – wyjście bez zapisania zmian
:w plik – zapisanie w nowym pliku „plik”

wyszukiwanie
/ STRING – szukanie do przodu
? STRING – szukanie do tyłu
n – przejście do następnego znalezionego elementu
SHIFT+n przejście do poprzedniego znalezionego elementu

kasowanie tekstu
x – kasowanie bieżącego znaku
nx – kasowanie znaków
dw – kasowanie bieżącego wyrazu
dd – kasowanie bieżącej linii
ndd – kasowanie <n> linii
D – kasowanie znaków od kursora do końca bieżącej lini
dG – kasowanie wszystkiego od kursora do końca dokumentu

edycja tekstu
o – wstawienie nowej linii poniżej bieżącej
O – wstawienie nowej linii powyżej bieżącej
i – rozpoczęcie edycji przed kursorem
I – rozpoczęcie edycji na początku bieżącej linii
a – rozpoczęcie edycji po kursorze
A – rozpoczęcie edycji na końcu bieżącej linii
cw – zastąpienie wyrazu (bieżący wyraz się kasuje, kursor ustawia się na jego początku)
cc – zastąpienie linii (bieżąca linia się kasuje, kursor ustawia się na jej początku)

kopiowanie i wstawianie
yw – kopiuj do schowka bieżący wyraz
yb – kopiuj do schowka poprzedni wyraz
Y – kopiuj do schowka bieżącą linię
nY – kopiuj następne <n> linii
p – wklej skopiowany tekst za kursorem
P – wklej skopiowany tekst przed kursorem

inne
u – cofnięcie ostatniej zmiany
U – cofnięcie wszystkich zmian w bieżącej linii
. – powtórzenie ostatniej komendy
SHIFT+# – wyszukiwanie wyrażeń pasujących do zaznaczonego
SHIFT+% – przejście do kolejnego nawiasu () lub {}
~ – zmiana litery z małej na dużą i na odwrót

zastępowanie
Można używać wyrażeń regularnych PERL- kompatybilnych

zastąpienie stringu OLD stringiem NEW
:s/OLD/NEW – pierwszego wystąpienia w bieżącej linii
:s/OLD/NEW/g – każdego wystąpienia w linii
:#,#s/OLD/NEW/g – pomiędzy liniami # i #
:%s/OLD/NEW/g – w całym dokumencie
:%s/\([0-9]\+\) OLD/\1 NEW/g – w całym dokumencie z użyciem backreferencji (trzeba poprzez „\” wyescapować znaki specjalne, następnie poprzez „\1” wstawić wyciągnięte dane)

praca z danymi zewnętrznymi
Vim umożliwia wywołanie dowolnej komendy z poziomu edytora, można także odczytywać efekt jej działania

:! komenda – wywołanie 'komenda’ w shellu i wyświetlenie jej wyniku, komenda może być każde polecenie unixowe
:r ! komenda – wywołanie 'komenda’ w shellu i wczytanie wyniku jej działania do edytora
:r ! last | grep user – na wywoływanych komendach można robić dowolne operacje przed wczytaniem do edytora
:r textfile – wczytanie źródła pliku 'textfile’ do edytora
:r ! w3m http://en.wikipedia.org/wiki/Vi -dump – wczytanie do edytora zawartości strony internetowej

Praca z zakładkami
:tabs – wyświetlenie informacjie o aktualnie otwartych zakładkach
:tabnew – otwarcie pustej zakładki
:tabnew FILE – otwarcie pliku FILE w nowej zakładce
:tabf FILE – otwarcie pliku w nowej zakładce
:tabn – przejście do następnej zakładki
gt – przejście do następnej zakładki (działa w trybie NORMAL)
gT – przejście do poprzedniej zakładki (działa w trybie NORMAL)
n gt – przejście do zakładki o numerze n (działa w trybie NORMAL)
n gT – cofnięcie się do zakładki znajdującej się w odległości n od aktualnej (działa w trybie NORMAL)
:tabp – przejście do poprzedniej zakładki
:tabl – przejście do ostatniej zakładki
:tabc – zamknięcie aktualnej zakładki, gdy jest jedna karta, to nie będzie zamknięta
:tabo – zamknięcie wszystkich zakładek oprócz tej która jest aktualnie używana
:tabd KOMENDA – wykonuje komendę na wszystkich otwartych zakładkach

Polecam rewelacyjny VIM Tutorial.

Możliwe problemy i ich rozwiązania

  • Jeżeli przy wklejaniu do konsoli kodu z tabulacjami/spacjami tworzą się niechciane wcięcia należy użyć polecenia „:set paste” następnie wkleić kod i ponownie ustawić „:set nopaste”, więcej o wklejaniu na w tym temacie stackoverflow.

Zobacz też A vim Tutorial and Primer.

Proste i skuteczne zabezpiecznie przez spamującym botem.

Tworząc aplikacje internetowe dostępne bez autoryzacji dla wszystkich internautów musimy liczy się z tym, że wszelkie formularze są narażone na setki spamu wstawianego przez różnorakie boty.
Dotyczy to np. formularza kontaktowego lub chociażby formularz komentowania WordPress’a.

Rozwiązań jest wiele: można zaserwować botowi zagadkę logiczną typu „ile to jest 4+4=?” lub wstawić znienawidzony przez wielu kod CAPTCHA, którego odgadnięcie w wielu przypadkach graniczy z cudem.
Ja proponuję inne i dużo prostsze rozwiązane skuteczne w ponad 99%.

Action formularza ustawiamy na # (czyli przeładowanie na bieżącą stronę) a następnie w prostym skrypcie javascriptowym podmieniamy na właściwy adres.

<form action="#" method="post" id="sample_form">
<fieldset>
  <label>Wiadomość:</label>
  <textarea rows="5" cols="5" name="message"></textarea>
  <br />
  <input class="button" type="submit" value="Wyślij" />
  </fieldset>
  <noscript><h3 class="red">Do poprawnego wysłania wiadomości wymagana jest włączona obsługa JavaScript.</h3></noscript>
</form>    

<script type="text/javascript">
<!--
  document.getElementById( "sample_form" ).action = 'http://www.sample.pl/send.php';
//-->
</script>

W ten sposób, jako, że żaden bot jeszcze nie umie (i nie zanosi się na to w najbliższej przyszłości) czytać JSa, parsując stronę HTML nie odnajdzie docelowego skryptu odbierającego dane z formularza a zatem jestesmy pewni, że nie dostaniemy żadnych niechcianych wpisów.
Użytkownikom, nie posiadającym włączonej obsługi javascript ukarze się stosowny napis zawarty w tagu noscript i jeżeli będzie im zależało na wysłaniu formularza to wierzcie mi, że włączą JSa ;-)

W przypadku opensourcowych skryptów typu WordPress istnieje możliwość, że boty będą próbować odwoływać się bezpośrednio do domyślnego pliku odbierającego dane, w takim przypadku wystarczy zmienić jego nazwę choćby z wp-comments-post.php na wp-comments-post-2.php i sprawa załatwiona. Nie wierze aby komukolwiek chciało się ręcznie wpisywać do spamiarki adresy.

Odczytywanie informacji EXIF zdjęcia w PHP

Większość aparatów cyfrowych w pliku ze zdjęciem zapisuje nagłówki informujące o parametrach zrobionego zdjęcia, użytego aparatu. Dane te są zapisane w formacie IPTC i zawierają takie dane jak: datę i czas zrobienia zdjęcia, czas naświetlania, przysłonę, ogniskową, ISO, program paratu, nazwę producenta i modelu aparatu etc…

Informacje te można odczytywać i modyfikować w programach graficznych, standard IPTC szczególnie przydatny jest w fotografii stockowej (opis wykorzystania standardu IPTC).

Dane zapisane w pliku JPEG/TIFF możemy w prosty sposób odczytać w skrypcie PHP. Dostępna jest specjalna bibloteka EXIF, która nam to umożliwia. Aby móc korzystać z tej biblioteki PHP musi być skompilowane z opcjami –enable-exif i –enable-mbstring.

Poniżej przykładowa funkcja, zwracająca informacje o zdjęciu

/**
* Metoda zwraca informacje o zdjęciu
*
* @param string $file - sciezka do zdjecia
* @return array od mixed - informacje o zdjeciu
* @access public
*/
function getImageInfo( $file ) {
	$exif = exif_read_data( $file, "IFD0" );
	if( $exif ) {
		$return exif_read_data( $file, 0, true );
	}
}

Funkcja zwraca tablice z danymi, które możemy zaprezentować na stronie przy zdjęciu.
Poniżej przykładowy wynik powyższej metody.

Array
(
    [FILE] => Array
        (
            [FileName] => IMG_7378.jpg
            [FileDateTime] => 1198952415
            [FileSize] => 1240033
            [FileType] => 2
            [MimeType] => image/jpeg
            [SectionsFound] => ANY_TAG, IFD0, THUMBNAIL, EXIF
        )

    [COMPUTED] => Array
        (
             => width="2664" height="2304"
            [Height] => 2304
            [Width] => 2664
            [IsColor] => 1
            [ByteOrderMotorola] => 0
            [CCDWidth] => 17mm
            [ApertureFNumber] => f/18.0
            [Thumbnail.FileType] => 2
            [Thumbnail.MimeType] => image/jpeg
        )

    [IFD0] => Array
        (
            [ImageDescription] => Red Tomato isolated on white background
            [Make] => Canon
            [Model] => Canon EOS 350D DIGITAL
            [Orientation] => 1
            [XResolution] => 2400000/10000
            [YResolution] => 2400000/10000
            [ResolutionUnit] => 2
            [Software] => Adobe Photoshop CS3 Windows
            [DateTime] => 2007:12:29 19:20:14
            [Artist] => Marcin Mamala
            [Exif_IFD_Pointer] => 296
        )

    [THUMBNAIL] => Array
        (
            [Compression] => 6
            [XResolution] => 72/1
            [YResolution] => 72/1
            [ResolutionUnit] => 2
            [JPEGInterchangeFormat] => 798
            [JPEGInterchangeFormatLength] => 3028
        )

    [EXIF] => Array
        (
            [ExposureTime] => 1/20
            [FNumber] => 18
            [ExposureProgram] => 1
            [ISOSpeedRatings] => 100
            [ExifVersion] => 0221
            [DateTimeOriginal] => 2007:12:29 18:06:50
            [DateTimeDigitized] => 2007:12:29 18:06:50
            [ShutterSpeedValue] => 4321928/1000000
            [ApertureValue] => 833985/100000
            [ExposureBiasValue] => 0/2
            [MaxApertureValue] => 434375/100000
            [MeteringMode] => 5
            [Flash] => 16
            [FocalLength] => 43
            [ColorSpace] => 65535
            [ExifImageWidth] => 2664
            [ExifImageLength] => 2304
            [FocalPlaneXResolution] => 3456000/874
            [FocalPlaneYResolution] => 2304000/582
            [FocalPlaneResolutionUnit] => 2
            [CustomRendered] => 0
            [ExposureMode] => 1
            [WhiteBalance] => 0
            [SceneCaptureType] => 0
        )

)

Działanie response w FireBug

Co to jest Firebug nie będę wyjaśniał, każdy webdeveloper powinien wiedzieć o co chodzi. W dwóch słowach: jest to bardzo przydatny plugin do Firefoxa umożliwiający m.in wyświetlenie wszystkich request-ów wygenerowanych przez wygenerowaną stronę. Dotyczy to zarówno żądań wygenerowanych przez odwołania do obrazków, css-ów jak i żądań wygenerowanych przez skrypty javascript-owe i flash-owe. (inspect->net-all)

Przy każdym requescie można podglądnąć 'response’ – czyli odpowiedź skryptu na wysłane żądanie. Jest to szczególnie przydatne w przypadku debugowania serwerowych skryptów aplikacji, które dostarczają dane skryptom działającym po stronie przeglądarki (np. javascript, flash).

Ale uwaga! Należy pamiętać, że w momencie gdy w Firebugu przy danym requescie klikniemy na response to prezentowany response niestety nie jest tym oryginalnym tylko skrypt, którego dotyczy jest wywoływany ponownie.

Wszystko byłoby fajnie gdyby nie to, że niektóre skrypty serwerowe wykonują operacje, które nie można powtórzyć (np. INSERTy, DELETy w bazie) i w takim przypadku kolejne wywołania zwrócą już błędne dane. W takim wypadku jedyna sensowną opcją debugowania pozostaje zapis do pliku. Nie jest to niestety zbyt przyjazna metoda, ale cóż…jedyna skuteczna.

Bot alexa.com i znikające dane w serwisach.

Dostałem ostatnio zgłoszenie, że w aplikacji, którą jakis czas temu napisałem zniknęło część kluczowych danych. Mniejsza z tym, jakie to były dane, powiedzmy ze chodzi o dane kont użytkowników, których usunięcie pociągnęło za sobą kaskadowe skasowanie się wielu innych danych z tabel zależnych.Sytuacja wydała się dosyć dziwna i podejżana. Pierwsza myśl, która zawitała w mej głowie to „pewnie, któryś z adminów niechcąco skasował” było to prawdopodobne tym bardziej, że kiedyś miało juz miejsce podobne zdarzenie. Tym razem jednak żaden z administratorów nie przyznał się do omyłkowego skasowania danych.

Kolejny powód zniknięcia danych o jakim pomyślałem to włam jakiegoś pseudo-hakera i celowe zniszczenie danych. Zaglądam do access-logów serwera z tego feralnego dnia, grepuje po pliku logowania i widze, że logowania pochdziły jedynie ze sprawdzonych IP. Hmm…gerpuje więc po pliku zawierającym sktypt kasujący głowne dane i oczom moim ukazuje się kilka lini, każda postaci:

user 64.208.172.180 - - [29/Oct/2007:17:28:46 +0100] "GET /skrypt_kasujacy.php?userID=15 HTTP/1.0" 302 - "-" "ia_archiver"

Jakim cudem ktoś wywołał (z powodzeniem) skrypt kasujący dane bez wcześniejszej poprawnej autoryzacji logowania? Od razu sprawdzam źródła aplikacji, czy aby jest dobrze zabezpieczona i… jest zabezpieczona poprawnie. wtf?

Totalnie zdziwiony stwierdzilem, że trzeba troche poguglać na temat 64.208.172.180 i „ia_archiver”. Po krótkiej chwili okazało się, że nazwa user agenta „ia_archiver” odpowiada nazwie z jaką się przedstawia bot alexa.com, aby mieć pewność, że ktoś się pod niego nie podszywa weryfikuje także IP, które potwierdza, że była to wizyta z serwera crawlera alexa.

Dobra to juz wiem, że dane nie skasował żaden haker tylko bot, ale to wcalenie jest pocieszająca myśl, to jest tragedia gdyż wynka, że moja aplikacja jest tak dziurawa, że nawet boty potrafią ją zaindeksować! Masakra…

Drążąc temat dalej dowiedziałem się, że bot alexa nie jest zwykłym botem jakich na pęczki w internecie gdyż oprócz latania jak głupek po wszystkich stronach www bieże informacje o stronach do odwiedzenia z Alexa Toolbar, który jaki dodatek można zainstalować w przeglądarce internetowej, także w ten sposób może zaindeksować strony, do których nie prowadzi żaden link a także te, które wymagają autoryzacji.
I tak właśnie było w tym wypadku, jeden z administratorów aplikacji miał zainstalowany alexa toolbar w swojej przeglądarce i przez to nawet gdy poruszał się w sekcji wymagającej autoryzacji to cały czas alexa była poprzez toolbar informowana o URLach przez niego odwiedzanych. W ten sposób w pewnym momencie bot zaindeksował stronę z listą użytkowników, przeparsował ją i wyciągnął linki prowadzące do usunięcia kont a następnie je wywołał i tym samym usunął konta.

Jak się okazało wywołanie linków kasujących dane zawsze miało miejsce w momencie gdy administrator był zalogowany w systemie. Domyślam się, że jakimś cudem bot poprzez toolbar przechwytywał zautoryzowaną sesje użytkownika i dzięki temu uzyskiwał dostęp do stron, do których dostępu mieć nie powinien!

Jak się zabezpieczyć?
Najlepiej stworzyć plik robots.txt w katalogu glównym aplikacji i wpisać w nim instrukcje nie pozwalającą botom na odwiedzani plików administracyjnych.

user-agent: *
Disallow: /admin_pages*

Ukrycie nazwy ściąganego pliku

Czasami chcemy udostępnić ściąganie plików jednak nie chcemy udostępniać bezpośredniego linków do pliku.

Możemy zrobić plik przechodni download.php, który będzie przyjmował np. identyfikator pliku do ściągnięcia, na jego podstawie odczytamy lokalizacje pliku, odczytamy go i prześlemy do przeglądarki.

$file = $className->getFileByID( $_GET["id] );
header( "Cache-control: private" );
header( "Content-Type: application/octet-stream" );
header( "Content-Length: ".filesize( $file ) );
header( "Content-Disposition: attachment; filename=".basename( $file ) );
header( "Connection: Close" );
readfile( $file )

post_max_size a tablice $_POST i $_FILES

W przypadku gdy mamy formularz umożliwiający przesłanie danych (do bazy lub gdziekolwiek), który ma możliwość załączenia pliku przy walidacji powinniśmy uwzględnić sytuację gdy uploadowany plik(i) nie przekracza maksymalnego rozmiaru tablicy $_POST

Można sumować wartość poszczególnych plików $_FILES[„name”][„size”] i porownywać jej wielkość z wartością ustawioną w post_max_size w php.ini ale jest to rozwiązanie działające tylko na serwerze na którym się aktualnie pisze kod (inne serwer przecież mogą mieć inną konfigurację)

Ale jest lepsze rozwiązanie: Sprawdzamy czy tablica $_POST, którą otrzymaliśmy jest pusta i czy tablica $_FILES nie jest pusta. Jeżeli okaże się to prawdą to oznacza, że nastąpiła próba wgrania co najmniej 1 pliku i suma przekroczyła aktualną wartość post_max_size przez co zresetowała tabelę $_POST.
Zapisujemy błąd do sesji i przekierowywujemy na formularz.

if( ( is_array( $_POST ) && count( $_POST ) == 0 ) && ( is_array( $_FILES ) && count( $_FILES ) != 0 ) ) {
  $errors["MAX_FILE_SIZE"] = "Suma wielkości wgrywanych plików jest zbyt duża";
  $session->setVar( "errors", $errors );
  header( "Location: plik_z_formularzem.php" );
  exit;
}

Autmatyczny backup

Kopia zapasowa – przydatna rzecz, warto ją mieć, jednak człowiek niezbyt często o tym pamięta gdy wszystko działa poprawnie. W momencie gdy pada system myśl o dacie ostatniego backupa jest pierwszą myślą świtającą w głowie.
Po co się tak stresować, po co polegać na zawodnej ludzkiej pamięci, lepiej napisac skrypt i niech wszystko się samo wykonuje.
Synchronizujemy katalogi zawierające dane do backupa z aktualnym stanem danych zbackupowanych

rsync -avrz $HOME/workspace/* $HOME/backup/source/workspace/
rsync -avrz $HOME/thunderbird/* $HOME/backup/source/mail/
rsync -avrz $HOME/gg/* $HOME/backup/source/gg

Następnie tworzymy archiwum zawierające bieżącą date

currDate=$(date +%Y-%m-%d);
tar -cjf $HOME/backup/archive/backup-$currDate.tar.bz2 $HOME/backup/source/*

Tak przygotowane archiwum zgrywamy w bezpieczne miejsce (np. na inna maszynę), a następnie kasujemy tymczasowe archiwum.

rsync -avrz -e ssh $HOME/backup/archive/ bezpieczny@dysk:/home/users/xxx/backup/
rm $HOME/backup/archive/backup-$currDate.tar.bz2

Następnie należy jeszcze tylko zautomatyzować wywoływanie skryptu odpowiednim wpisem w cronie:

0 15 * * fri $HOME/prog/backup.sh

W tym przypadku skrypt będzie wywoływany co tydzień w piątek o godzinie 15, tak więc od tej pory co tydzień mogę spokojnie opuszczać stanowisko pracy na weekend będąc pewnym, że moje dane są bezpieczne.

****dodane****

Aby backup mógł wykonywać się automatycznie to połączenie z bezpieczny@dysk musi być trusted (łączenie z nim bez hasła). Jest wiele sposobów aby to wykonać np:
Na komputerze, z którego robisz backup wpisz:

ssh-keygen -t dsa

Naciśnij enter kilka razy.

W twoim katalogu domowym zostanie stworzony katalog .ssh zawierające potrzebne pliki. Następnie w konsoli wpisz polecenie

ssh bezpieczny@dysk ‘test -d .ssh || mkdir -m 0700 .ssh ; cat >> .ssh/authorized_keys && chmod 0600 .ssh/*’ < ~/.ssh/id_dsa.pub

Zostaniesz poproszony o wprowadzenie hasła na bezpieczny@dysk, wprowadź je. I to wszystko. Hasło zostanie zapamiętane.

Możesz teraz przetestować, powinieneś łączyć się przez ssh z bezpieczny@dysk bez podawania hasła.

ssh bezpieczny@dysk