svn merge

Trochę głupio w SVN jest zorganizowane zarządzanie gałęziami. Niby pojęciowo łatwo, bo to tylko kopia katalogu, ale prowadzi to do problemów.

Ponieważ każdą kopię katalogu można traktować jak branch, to z każdego takiego katalogu można merge'ować zmiany. Ale jak zapamiętać, co gdzie było merge'owane? Subversion zapisuje atrybut svn:mergeinfo. Wygląda ładnie i elegancko, do momentu gdy trzeba z tym popracować.

Okazuje się, że można wykonać merge, ale nie zapisać metadanych (commitując zawartość brancha, ale omijając sam katalog). W ten sposób zepsuty branch nie nadaje się już właściwie do niczego. Można próbować uzupełnić kolejnym commitem metadane, żeby z innych branchy (z trunka?) nadal można było ściągać zmiany, ale o merge'owaniu w drugą stronę można zapomnieć. Łatwiej jest usunąć branch i utworzyć go ponownie.

Gdy przychodzi do merge'owania gałęzi, nigdy nie wiem, kiedy użyć svn merge, a kiedy svn merge --reintegrate. SVN Book mówi, że pierwsze jest do wciągania zmian z trunka do gałęzi, a drugie do odwrotnej operacji. Prawda jest taka, że to działa tylko przy modelu pracy, gdy gałąź po reintegracji z trunkiem jest usuwana. Gdy pracuje się na gałęzi dłużej, czasem trzeba z trunka merge'ować z opcją --reintegrate, a czasem bez -- i nie jest oczywiste, kiedy które. Moim zdaniem to porażka programistów od interfejsu i backendu.

Wysłany: 17 kwietnia 2012, 11:07:40; 2 komentarze

Postfix: podświetlanie składni map LDAP dla Vima

Na wzór podświetlania składni dla slapd.conf, napisałem podświetlanie dla postfiksowych map LDAP: http://dozzie.jarowit.net/jogger/pfldap/pfldap.vim.

Podobnie jak w poprzednim wpisie, jeszcze nie opublikowałem skryptu na www.vim.org, bo czegoś mu brakuje (choć niedużo: jedynie obsługi porównań bardziej skomplikowanych niż mail=foo@* w zapytaniu LDAP).

Przykładowa składnia (choć na jarowicie akurat używam Exima, nie Postfiksa):
podświetlanie mapy LDAP Postfiksa

Wysłany: 01 lutego 2012, 19:03:14; Dodaj komentarz

slapd.conf: podświetlanie składni dla Vima

Od dość dawna mnie uwierało, że nie ma podświetlania składni dla slapd.conf (plik konfiguracyjny OpenLDAP). W końcu się wkurzyłem i napisałem własne reguły, a w każdym razie ich większość: http://dozzie.jarowit.net/jogger/slapd/slapd.vim.

Te reguły są całkiem używalne, ale brakuje im paru rzeczy. Do tego czasu nie chcę ich publikować na www.vim.org.

Przede wszystkim chcę jeszcze dorobić specjalne podświetlanie niektórych ważnych parametrów w definicjach database (np. rootdn czy suffix), a dyrektywy limit, ldapsyntax i ditcontentrule nie otrzymują żadnego specjalnego traktowania ponad podświetlenie nazwy dyrektywy. Za to ładnie się wyświetlają definicje uprawnień (access to ...) i schemy (attributetype i objectclass).

Przykłady podświetlania (argument rootpw został zmieniony na losowy):
główny konfig
uprawnienia
schema

Wysłany: 01 lutego 2012, 16:17:13; Dodaj komentarz

niskobudżetowy(?) dyndns.com

Niedawno na techblogu czytałem wpis o logowaniu się przez SSH z komputerem, który ma dynamicznie przydzielany adres IP. Przyszło mi do głowy, że może dynamicznie ustawiany DNS da się załatwić za pomocą bardziej eleganckich i standardowych konstrukcji niż skrypt uruchamiany przez nieinteraktywną sesję SSH?

Mój pomysł jest taki: weźmy dowolny serwer DNS, który obsługuje dynamic DNS updates (RFC 2136), na przykład BIND-a. Aktualizacje będziemy mu wysyłać ze specjalnego skryptu na jakiejś maszynie (żeby łatwiej było napisać zezwolenie dla skryptu, ewnetualnie żeby skrypt miał dostęp do sekretu DNSSEC). Ze skryptem natomiast łączyć się będziemy za pomocą XML-RPC. Słowem: zdalne uruchomienie procedury XML-RPC, której treścią jest zaktualizowanie wpisu w DNS.

Wykonanie opiera się o opisanego przeze mnie jakiś czas temu daemona xmlrpcd. Przedstawię przykład z użyciem BIND-a 9.6.

Najpierw trzeba wygenerować sekret, który będzie używany do uwierzytelniania żądań uaktualnienia rekordu. To uwierzytelnianie to będzie HMAC-MD5, więc wystarczy że sekret będzie dowolną wartością losową zakodowaną w Base64 (wybrałem 256 bitów):

perl -MMIME::Base64 -e 'read STDIN, $_, 256 / 8; print encode_base64 $_' < /dev/random

Output tego polecenia zapiszemy w konfiguracji BIND-a.

# named.conf
key "my-ddns-key" {
  algorithm hmac-md5;
  secret "CU5rRmwPsmKuWeDyKlPk9QGZKTb4aMStK2ul4aIZE1A=";
};

zone "dyn.atled.pl" IN {
  type master;
  file "/var/lib/bind/dyn.atled.pl.zone";
  update-policy { grant my-ddns-key subdomain dyn.atled.pl A; };
};

W ten sposób strefa dyn.atled.pl może mieć modyfikowane rekordy A, o ile żądanie zmiany jest uwierzytelnione odpowiednim kodem MAC. Oczywiście plik zawierający sekret powinien być chroniony (uprawnienia! najlepiej użyć dyrektywy include i wydzielić sekcję key do osobnego pliku).

Teraz pora na procedurę uaktualniającą wpis w DNS. Potrzebne perlowe moduły: Net::DNS i YAML

#!/usr/bin/perl
#
# /var/lib/xmlrpcd/methods/dyndns/update.pm
#
#-----------------------------------------------------------------------------

use warnings;
use strict;

use Net::DNS;
use YAML qw{LoadFile};

#-----------------------------------------------------------------------------

my $rpc_config = '/var/lib/xmlrpcd/etc/dyndns.yaml';

#-----------------------------------------------------------------------------

our $RPC_PARAMS = [qw{address user}];

sub entry_point {
  my ($addr, $user) = @_;

  $addr =~ s/^::ffff://;
  if ($addr =~ /:/) {
    die "IPv6 addresses are not supported yet.";
  }

  my $cfg = LoadFile($rpc_config);

  my $dns = new Net::DNS::Resolver(
    nameservers => [$cfg->{server}],
  );

  my $should_replace = 0;

  my $old_ip = $dns->query("$user.$cfg->{domain}", "A");
  if (defined $old_ip) {
    my @addresses = $old_ip->answer;
    if (@addresses != 1) {
      $should_replace = 1;
    } elsif ($addresses[0]->address ne $addr) {
      $should_replace = 1;
    }
  } else {
    $should_replace = 1;
  }

  if (not $should_replace) {
    return "Will keep current IP address.";
  }

  my $update = new Net::DNS::Update($cfg->{domain});
  $update->push(
    update => rr_del("$user.$cfg->{domain} A"),
    update => rr_add("$user.$cfg->{domain} $cfg->{record}{ttl} A $addr"),
  );
  $update->sign_tsig($cfg->{key}{name}, $cfg->{key}{secret});

  my $result = $dns->send($update);
  if (not defined $result) {
    die "Some error";
  }

  return "Updated: $user.$cfg->{domain} $cfg->{record}{ttl} A $addr";
}

#-----------------------------------------------------------------------------
1;
# vim:ft=perl

Procedura jest gotowa do użycia z xmlrpcd, ale oczekuje, że wołający się uwierzytelni (login podany podczas uwierzytelniania determinuje który rekord poprawić). Odsyłam do strony narzędzia i do dokumentacji po szczegóły konfiguracyjne daemona.

Powyższa procedura wymaga paru parametrów zapisanych w /var/lib/xmlrpcd/etc/dyndns.yaml. Poniżej przykładowa zawartość tego pliku:

domain: dyn.atled.pl
server: 127.0.0.1
key:
  name: my-ddns-key
  algo: hmac-md5
  secret: CU5rRmwPsmKuWeDyKlPk9QGZKTb4aMStK2ul4aIZE1A=
record:
  ttl: 60

Jak łatwo się domyślić, domain musi odpowiadać domenie, która będzie uaktualniana, server wskazuje adres serwera DNS, któremu aktualizacje będą wysyłane, key.name i key.secret muszą odpowiadać nazwie klucza i jego sekretowi z named.conf.

Od tej pory wystarczy na kliencie wywołać procedurę dyndns.update na odpowiednim serwerze. Przypominam, że procedura przewiduje uwierzytelnianie HTTP (i to na jego podstawie ustali, który rekord przestawiać).

Wysłany: 17 listopada 2011, 16:06:15; 3 komentarze

svn checkout && make && svn commit

W mojej pracy zdarza się potrzeba przebudowywania pewnych plików i zapisywania ich w repozytorium systemu kontroli wersji (Subversion albo git), głównie dla transportu na inną maszynę. Pliki wynikowe z czegoś muszą być wygenerowane, więc mam do czynienia również ze źródłami (a przynajmniej ja je tak nazywam). Źródła warto trzymać w repozytorium systemu kontroli wersji, niekoniecznie tym samym co wyniki.

Przy takiej organizacji danych aż prosi się o zadanie crona, które ściągnie źródła, uruchomi ich kompilację, a wyniki zapisze w repozytorium docelowym.

Powyższe zadanie nie wydaje się specjalnie trudne czy skomplikowane. Prosty skrypt uruchamiany z crona powinien załatwić sprawę. Niestety, przejście do szczegółów ujawnia masę spraw, którymi trzeba się zająć:

  • wybór: jeśli źródła się nie zmieniły, to budować czy nie?
    • czasem wynik dwóch kompilacji jest różny, ale równoważny; wtedy commit jest niepotrzebnym generowaniem ruchu w repozytorium
    • czasem wynik kompilacji zależy od czegoś spoza repozytorium (np. od wpisów w DNS albo bazie SQL)
  • jak stwierdzić że źródła się zmieniły?
  • jeli pliki wynikowe się nie zmieniły -- nie wysyłać e-maila (budowanie będzie się odbywać często, np. co 10 minut; mało interesującym jest czytać 144 razy dziennie że wszystko się udało i nic nie zrobiono)
  • jak uciszyć uruchamiane komendy, gdy jeszcze nie wiadomo czy powiadomienie będzie wysyłane (wtedy pełny log z wykonania będzie potrzebny), czy też nie będzie, bo nie było zmian w wynikach?
  • podsumowanie wykonanych operacji powinno w e-mailu znaleźć się przed logami, czyli w odwrotnej kolejności niż te informacje powstają
  • jeśli svn up na źródłach się nie powiodło, nie ma co budować
  • automat powinien sam wyprodukować commit message, którego treść powinna nieść użyteczne informacje
  • recovery from errors, i dla błędów budowania, i dla błędów systemu kontroli wersji (jeśli coś się nie powiodło, trzeba wyczyścić środowisko pracy, żeby następna próba miała szansę się udać)

Jak widać, to dość żmudna i powtarzalna robota, którą łatwo oddzielić od samego budowania. Uznałem, że najlepiej będzie stworzyć narzędzie, które raz na zawsze odciąży mnie od myślenia o obsłudze błędów i pozwoli zająć się procesem budowania i plikami źródłowymi.

I tak powstał cronbuilder.

Chciałem opisać tu jakiś przykład użycia, ale na stronie narzędzia właściwie jest już opisany, nawet z metodą pisania skryptu budującego.

Wysłany: 23 października 2011, 21:44:41; 7 komentarzy

sleep $RANDOM; push-logs

Dzisiaj dość prosta sprawa.

Mam pod opieką coś koło 350 maszyn. Potrzebuję żeby te maszyny raz na jakiś (nie za długi, tak ze 20 minut) czas wysłały logi z wykonania do centralnego serwera. Ot, proste zadanie cronowe:

*/20 *  * * *  root  /usr/local/sbin/push-logs

Problem, jakiego próbuję uniknąć, to skok obciążenia serwera odbierającego logi: 350 połączeń SSL naraz (o *:00, *:20 i *:40) może łatwo zeżreć zasoby serwera, zwłaszcza gdy ten jest jedynie wirtualką z jednym procesorem.

Podszedłem do sprawy podobnie jak to robi cfengine: zadanie najpierw śpi pewną liczbę sekund, a potem uruchamia skrypt wysyłający. Spokojnie mógłbym zapisać zadanie tak:

*/20 *  * * *  root  sleep $(( $RANDOM % (5 * 60) )); /usr/local/sbin/push-logs

Nie jest to jednak moim zdaniem ładne rozwiązanie: ten sam serwer będzie miał w wysyłaniu logów opóźnienie raz rzędu paru sekund, raz trzech minut. Fajnie by było mieć komendę, która zwróci liczbę różną na różnych hostach, ale dla danego hosta zawsze taką samą (albo przynajmniej rzadko się zmieniającą).

Pomysł: MD5 z nazwy hosta i ewentualnie reszty danych zwracanych przez uname(2). Rozwiązanie (skrypty nazwałem hosthash):

#!/usr/bin/perl -l

use warnings;
use strict;

use POSIX qw{uname};
use Digest::MD5 qw{md5};

if (@ARGV && $ARGV[0] eq '--help') {
  print "Usage: $0 [modulus]";
  print "";
  print "This script prints a 32-bit number (0 <= num < modulus).";
  print "The number is based on uname() (including hostname and kernel release).";
  exit;
}

my $hash = md5(join " ", uname());

my $int = unpack "N", $hash;

if (@ARGV) {
  print $int % $ARGV[0];
} else {
  print $int;
}

# vim:ft=perl
#!/usr/bin/python

from hashlib import md5
from os import uname
from sys import argv
from struct import unpack

if len(argv) > 1 and argv[1] == "--help":
  print "Usage: %s [modulus]" % argv[0]
  print ""
  print "This script prints a 32-bit number (0 <= num < modulus)."
  print "The number is based on uname() (including hostname and kernel release)."
  exit()

h = md5(" ".join(uname())).digest()
# XXX: to be compatible with Perl version
i = unpack(">L", h[0:4])[0]

if len(argv) > 1:
  print i % int(argv[1])
else:
  print i

# vim:ft=python:ts=2:sw=2:et

Teraz zadanie cronowe może wyglądać tak:

*/20 *  * * *  root  sleep $(hosthash $((5 * 60))); /usr/local/sbin/push-logs

Wysłany: 16 września 2011, 15:29:17; 34 komentarze

chrootowanie programu nie przewidzianego do tego

Jakiś czas temu napisałem narzędzie robiące za scponly, tylko prostsze w administracji. Główną przewagą był brak konieczności utrzymywania dodatkowych plików, których nie mógł stworzyć użytkownik. Głównym problemem zaś -- brak obsługi czegokolwiek poza sftp.

Wczoraj wymyśliłem jak można naprawić główny problem zachowując główną przewagę. Sztuczka polega na wstrzyknięciu za pomocą LD_PRELOAD kodu do wykonania przed funkcją main() -- ten kod wywoła chroot() do odpowiedniego katalogu.

Jeśli programy, które bym chciał w ten sposób obsłużyć, nie uruchamiają żadnych zewnętrznych poleceń, to takie podejście w zupełności wystarczy. Okazuje się, że oprócz sftp-server uruchamianego przez OpenSSH do obsługi sftp scp i rsync zachowują się podobnie.

Szczegóły techniczne rozwiązania opisałem na stronie narzędzia.

Wysłany: 07 kwietnia 2011, 14:34:19; 5 komentarzy

RHEL 4, yum i SIGSEGV

Nosiłem się z tym wpisem już dość długo. Pora go wystawić.

Mamy w pracy dwie wersje Red Hatów: RHEL 4.x i 5.x (wiem że czwórki są już trochę starawe). Na piątkach używamy yuma, na czwórkach był up2date. Chciałem przestawić wszystko na yuma (pakiety od Red Hata przechodzą przez cache mrepo), ale się nie dało: na jednej maszynie yum segfaultował. Co ciekawe, tylko na jednej, wszystkie pozostałe czwórki działały prawidłowo.

[root@volvo root]# rpm -q cscope
package cscope is not installed
[root@volvo root]# yum install cscope 
Setting up Install Process
Setting up repositories
zsh: 13133 segmentation fault  yum install cscope
[root@volvo root]# yum list cscope      
Setting up repositories
zsh: 13134 segmentation fault  yum list cscope
[root@volvo root]#

Widziałem w sieci podobny wpis, ale nie znalazłem żadnego gotowego rozwiązania. Skończyło się na debuggerze; cóż, nie pierwszy raz -- pewnie i nie ostatni.

Problemem okazała się zbyt nowa wersja zlib zainstalowana w /usr/local. Biblioteka libxml2, używana w pythonowym module do obsługi XML, w wersjach <=2.7.6 nadużywała nieco zliba, co przestało działać z wersją zlib 1.2.4 (za stroną domową zlib). Dla RHEL 4.x właściwymi wersjami są libxml2-2.6.16 i zlib-1.2.1.

Wysłany: 23 marca 2011, 13:36:55; 4 komentarze

pomysł na narzędzie: uniwersalny przetwarzacz repozytoriów

Ostatnio często mi się zdarza zadaniami cronowymi przetwarzać pliki pochodzące z jednego repozytorium i zapisywać je w innym.

Bardzo dobrym przykładem takiego użycia jest mój sposób dystrybucji konfiguracji dla serwerów: źródła (jeden zestaw szablonów konfigów i parametry do tych konfigów, po jednym zestawie dla każdego serwera) znajdują się w jednym repozytorium, a gotowe pliki wygenerowane z tych źródeł mają się znaleźć w drugim repozytorium. Serwery wtedy się łączą z repozytorium i pobierają każdy swoje pliki. Oczywiście generowanie konfiguracji powinno się odbywać regularnie -- stąd cron.

W tej konstrukcji chodzi o to, żeby zmusić administratora do używania repozytorium ze źródłami, a utrudnić mu papranie bezpośrednio w plikach konfiguracyjnych dla maszyn. Zysk z tego jest taki, że zaaplikowanie takich samych poprawek na innej maszynie robi się proste, a i historia zmian jest trzymana w jednym miejscu i w jednolitej formie.

To zadanie cronowe jest dość typowe w zamyśle. Składa się w sumie z trzech etapów: ściągnięcia (uaktualnienia) źródeł (nazwijmy je SRC), kompilacji źródeł do wyniku (skrypt kompilujący to BUILD) oraz zapisania zmian w plikach wynikowych (repozytorium docelowe to DEST).

Po co więc cały ten wpis, skoro to można załatwić prostym skryptem? Otóż, jak się okazuje, nie takim znowu prostym. Prześledźmy co ten skrypt w ogóle wyczynia, kiedy i jakie komunikaty wysyła.

Pierwsza rzecz, którą trzeba wziąć pod uwagę: czy jeśli nie było zmian w SRC, to uruchamiać BUILD? Ano czasem nie ma sensu (bo wynik będzie taki sam jak to co już jest zbudowane), a czasem trzeba (jeśli SRC nie jest jedyną rzeczą wpływającą na wynik kompilacji).

Po drugie, trzeba sprawdzić czy coś się zmieniło w DEST i ewentualnie te zmiany odnotować w repozytorium:

  • nowe pliki dodać do kontroli wersji
  • zmienionym plikom odnotować zmiany (think of: git)
  • usunięte pliki usunąć spod kontroli wersji
  • usunąć puste katalogi, biorąc pod uwagę artefakty systemu kontroli wersji (think of: Subversion i katalogi .svn)

Rzecz jasna jeśli nie ma żadnych zmian, nie ma co commitować.

Po trzecie, przy commitowaniu do DEST potrzebny jest jakiś komentarz. Oczywiście możemy sobie pozwolić na puste komentarze, ale później wyśledzenie o co chodziło i co było zmieniane będzie trudne. Więc -- jakieś komentarze automatyczne. Kilka pomysłów na dane, jakie mogłyby się w takim autokomentarzu znaleźć:

  • lista zmienionych (dodanych, usuniętych, zmodyfikowanych) plików
  • lista rewizji z SRC, które wchodzą w skład tego commita (tzn. od ostatniej SRC-rewizji, która była wcześniej zapisana w DEST do aktualnej SRC-rewizji)
  • komentarze dołączone do rewizji z poprzedniego punktu

Po czwarte, obsługa błędów. Jeśli się nie udało ściągnąć SRC, to prawdopodobnie nie ma sensu uruchamiać BUILD (chociaż może i jest...). Ale gdy BUILD zawiedzie, to nie ma co się kłopotać commitowaniem DEST.

Po piąte, powiadomienia:

  • zawsze gdy pojawi się problem przy którymś z trzech kroków, powiadomienie ma być wysłane
  • gdy nie ma co zapisać w DEST, powiadomienie ma nie być wysyłane (bo i po co? zbędny ruch)
  • gdy jest co zapisać w DEST, w niektórych instalacjach jest potrzeba powiadamiać admina że coś było zrobione, a w innych nie ma; ma być wybór

W powiadomieniu mogą się znaleźć różne informacje:

  • pełny output wykonywanych komend (jeśli wystąpił błąd, to to jest obowiązkowe)
  • rewizja w DEST, pod jaką zmiany zostały zapisane
  • komentarz użyty przy zapisywaniu zmian w DEST
  • każda z informacji dostępnych przy tworzeniu komentarza

Decyzja czy wysyłać komunikat, czy też nie, powinna należeć całkowicie do opisanego właśnie narzędzia. Użytkownik tego narzędzia ma się nie martwić że skrypt budujący czy ściągający SRC coś wypisuje na STDOUT czy STDERR i kiedy to robi (narzędzie jest uruchamiane z crona; jeśli zostanie wypisane cokolwiek, zostanie wysłany mail, być może niepotrzebnie).

Jak widać, w szczegółach trochę się jednak skrypt komplikuje. Pomyślałem sobie że warto sporządzić takie coś jeden jedyny raz, a potem używać w różnych sytuacjach i przy różnych okazjach. I łatwiej będzie dokładać kolejne środowiska przetwarzające jedne pliki w inne, i bardziej elegancko to będzie wyglądać, i będzie lepsza kontrola nad tym jakie komunikaty plączą się po sieci. Skrypt mógłby nawet nie tyle wysyłać powiadomienia mailem (przez pisanie na STDOUT w crontabie), co wręcz wysyłać raporty z wykonania do centralnego systemu zbierającego takie informacje!

Prawdopodobnie zastosuję tu technikę na dozziego, to znaczy zamiast szukać pasującego mi gotowca (bo niespecjalnie nawet wiem o co pytać w wyszukiwarce), napiszę ustrojstwo samodzielnie. Pewnie będzie szybciej.

Wysłany: 03 marca 2011, 16:41:15; 3 komentarze

libwww-perl fail

Miałem parę dni temu pewien zgryz: daemon, którego napisałem w Perlu, nie chciał obsługiwać klientów. Wszędzie działa, ale na jednej maszynie nie. Daemon jest oparty o moduł HTTP::Daemon::SSL, który korzysta z kolei z HTTP::Daemon (pakiet libwww-perl).

Trochę boli, bo mi ten daemon potrzebny, a przepisywanie niespecjalnie mi się uśmiecha. No to zacząłem śledzić problem, zaczynając od komunikatu błędu (odtwarzam z pamięci i stanu na Debianie).

Bad arg length for Socket::inet_ntoa, length is 16, should be 4 at /usr/share/perl5/HTTP/Daemon.pm line 48.

Linijka z komunikatu to konwersja upakowanego adresu (32 bity, czyli 4 bajty) na stringa czytelnego dla człowieka. Ale jakim cudem upakowany adres może mieć 16 bajtów?... czyli 128 bitów... czyli adres IPv6? Niby tak. Szesnaście zer.

Trochę kopania dalej (już ukierunkowanego) i się okazało, że to faktycznie IPv6, a konkretnie stała INADDR_ANY6. A HTTP::Daemon spodziewał się stałej INADDR_ANY, czyli 0.0.0.0. No to mamy winowajcę. Faktycznie na maszynie sprawiającej problemy daemon się bindował do ::, a na wszystkich pozostałych do 0.0.0.0.

Zmieniłem adres bindowania i działa jak ta lala.

Wysłany: 25 listopada 2010, 23:44:36; Dodaj komentarz

podróże w czasie pod Linuksem

Wyobraźmy sobie sytuację, gdy program przetwarza jakieś dane przychodzące od klientów, a dane historyczne kasuje. To, jakie dane są aktualnie w pamięci, wpływa na odpowiedzi zwracane klientom. Przychodzi w końcu pora przetestować ten program.

Najprostszym rozwiązaniem jest zmienić czas na maszynie i patrzeć jak to się zachowuje. Takie rozwiązanie w końcu jednak prowadzi do problemów. Jakiekolwiek systemy do zbierania statystyk oparte o bazy RRD momentalnie się posypią. W przypadku collectd może to prowadzić do zapchania /var/log komunikatami "illegal attempt to update using time 1234567890 when last update time is 1300000000 (minimum one second step)". cfengine również źle reaguje na takie cyrki. Poza tym, może być tak że na maszynie się nie da zmienić czasu, czy to dlatego że to maszyna wirtualna (np. OpenVZ), czy przez zwyczajny brak praw roota.

Rozwiązanie które wymyśliłem jest dość proste: wystarczy podmienić wszystkie wywołania biblioteczne zwracające czas na zwracające ten sam czas, tylko przesunięty.

Skonstruowany przeze mnie system składa się z trzech elementów:

  • ładowanej dynamicznie biblioteki ze zmienionymi funkcjami odczytu czasu
  • daemona zwracającego bibliotece przez socket uniksowy aktualnego przesunięcia w sekundach
  • polecenia ustawiającego w daemonie aktualne przesunięcie

Użycie:

# uruchomienie jednego programu z przesunieciem czasowym
LD_PRELOAD=libtime-shifter.so program

# uruchomienie kilku programow z przesunieciem czasowym
export LD_PRELOAD=libtime-shifter.so
program1
program2

# SUID-owane programy ignoruja LD_PRELOAD; w przypadku sudo mozna to obejsc
# programem /usr/bin/env
sudo env LD_PRELOAD=libtime-shifter.so program

Do przesuwania czasu służy komenda time-shift:

# do przodu o 3 dni 10 godzin
time-shift --set --offset "3 days 10 hours"
# wstecz o 5 dni 10 minut
time-shift --set --offset "-5 days -5 minutes"
# do konkretnego czasu
time-shift --date 2010/05/20 20:10
# zerowanie przesuniecia
time-shift --reset
# odpytanie o aktualne przesuniecie
time-shift --get

Procedura budowy pakietu dla Debiana:

git clone http://dozzie.jarowit.net/code/time-shifter.git
cd time-shifter
dpkg-buildpackage -b -uc

Wysłany: 06 maja 2010, 16:28:58; 7 komentarzy

MySQL -- what a sh&*(

Zadziwiające że takie badziewie jak MySQL zdobyło taką popularność, mimo ewidentnych baboli.

Przykład pierwszy: domyślnym backendem składowania danych jest MyISAM. Nadal. Dopiero w wersji 5.6 domyślne będzie InnoDB.

Co złego jest w MyISAM? Przede wszystkim brak transakcji i kluczy obcych. Nie możesz wykonać ROLLBACK. Tego silnika w ogóle nie interesuje spójność danych, które wkładasz. Pewnie, po co to komu? Przecież programista nie potrzebuje mieć pewności że cały obiekt rozsmarowany po kilku tabelach jest obecny. Niech ma niespodziankę z braku części danych!

Przykład drugi: ALTER TABLE powoduje implicit COMMIT, również na silniku InnoDB. Bosko. Nie możesz sobie opakować w transakcję zmiany schemy, bo MySQL i tak to oleje cienkim sikiem. A jeśli zmiana (będąca częścią większego bloku zmian w różnych tabelach) się nie powiedzie, bo na przykład był zakładany klucz UNIQUE na tabelę z powtarzającymi się rekordami? Dostajesz poprawkę częściowo nałożoną, której stanu właściwie nie masz jak ustalić.

Naturalnie w PostgreSQL zapytania ALTER TABLE są transakcyjne.

Przykład trzeci: jeśli z jakiegokolwiek powodu silnik InnoDB jest niedostępny, MySQL będzie usłużny i utworzy tabelę MyISAM. Widać programista jest nieuważny i zamiast InnoDB miał na myśli zje^Wzepsuty silnik, mimo że o co innego prosił. Owszem, można powiedzieć MySQLowi że ma nie być mądrzejszy od programisty -- ale to nie jest zachowanie domyślne. Ba, programista zatrudniony w MySQL w ogóle nie widzi problemu w domyślnym zachowaniu, bo przecież można sobie włączyć inne!

Przykład czwarty: jeden z dwóch sensownych uniwersalnych silników (drugi, BerkeleyDB, został porzucony i usunięty z wersji 5.1) ma tragiczny czas przywracania zrzutów. Przywracałem bazę, której mysqldump ważył około 1.7GB w tabelach InnoDB. Czego bym nie robił, czas przywracania był liczony w godzinach (od 3.5 godziny). Przyrządzanie zrzutu trwało parę minut (około dwóch). Przy tamtej konfiguracji jest to akceptowalny czas. No i oczywiście PostgreSQL sobie z tymi samymi danymi radzi znacznie, znacznie lepiej: oba czasy, sporządzania zrzutu i przywracania, wynoszą po kilka minut dla takiej samej tabeli.

Przykład piąty: obsługiwane mechanizmy uwierzytelniania. Właściwie każda baza danych obsługuje jakiś sposób zrzucenia tego zadania na kogoś innego. W Microsoft SQL Server jest to domena AD. W Oracle Database jest external authentication. W PostgreSQL można użyć PAM, co daje dostęp do prawie wszystkich mechanizmów (w tym LDAP).

Wprawdzie konta i tak trzeba zakładać, ale przynajmniej użytkownicy nie mają kilku setek haseł, osobnego do każdej usługi. Że niby to podnosi bezpieczeństwo, osobne hasło do każdej usługi? Nie, nie podnosi. Taki użytkownik musi sobie hasła zapisać (security fail!) albo i tak ustawia zawsze to samo (security fail!). Za to trudniej się zmienia hasła, trzeba pamiętać o każdej jednej usłudze.

MySQL oczywiście nie obsługuje niczego poza swoją własną biedną metodą. Hasła zapisane w tabeli z użytkownikami jako niesolony hash domowej roboty... Chcesz dać użytkownikowi możliwość zalogowania się, to musi dostać nowe hasło. Nie da się skłonić go do użycia LDAP, SASL ani PAM. Wprawdzie jest plan na dodanie takiej możliwości, ale pojawi się dopiero w niewydanej jeszcze wersji 5.6 -- o ile dobrze pójdzie. Oczywiście dostał niski priorytet, bo kto niby by chcial używać centralnego uwierzytelniania?...

Częste używanie MySQL najprawdopodobniej również rzuca się na mózg.

Primo: kursory w pythonowym MySQLdb nie obsługują wielu zapytań w jednym stringu. Nie żeby się nie dało tego osiągnąć: tworzysz kursor, wysyłasz jeden zestaw zapytań i zastępujesz kursor kolejnym.

Potrzebowałem wczytać i wykonać zapytania z pliku *.sql. Miałem do wyboru albo taki dziki hack, albo parsowanie SQL-a, ewentualnie uruchomienie zewnętrznego klienta /usr/bin/mysql. Chyba nie muszę wyjaśniać co było najbardziej eleganckim rozwiązaniem?

Secundo: dane zwracane przez perlowy moduł DBD::mysql nie mają włączonej flagi utf8. Mogą być w bazie w kodowaniu CP1250, a skrypt uruchomiony pod ISO-8859-2. Dane z kolumn tekstowych zostaną poprawnie przekodowane i zwrócone do skryptu w UTF-8 (perlowy internal encoding dla stringów). Ale dlaczego moduł nie włączy pieprzonej flagi utf8? Nie, trzeba ręcznie w pętli dla każdego zwróconego stringa mozolnie ją włączać. Oczywiście to samo w DBD::PgSQL działa jak trzeba bez hocków-klocków.

Tertio: różnice kodowań terminala pod commandline'owym klientem i w bazie. /usr/bin/mysql dysponuje pełnią informacji jak przekodowywać dane: ma i kodowanie terminala ($LC_CTYPE), i kodowanie bazy/tabeli. I oczywiście wyświetla dane źle, jeśli się nie poda default-character-set. Dla porównania, klient /usr/bin/psql konwertuje kodowanie out-of-the-box.

Quattro: debianowe pakiety. Postgresowy initskrypt z Debiana przewiduje uruchamianie fafnastu baz na silniku w wersji 8.3, a na pierwszy rzut oka również w każdej innej. MySQL-owy nie tylko nie pozwala na różne wersje, ale też utrudnia uruchamianie kilku instancji jednej wersji silnika! Bardzo trudno jest dostosować ten initskrypt do uruchamiania wielu baz.

Bosh, jak ja nie cierpię MySQL...

Wysłany: 24 kwietnia 2010, 23:54:10; Dodaj komentarz

TODO: znaleźć tutorial Mercurial for git users

Złożyło się że potrzebuję trochę popracować na Mercurialu, a że twardziel ze mnie, to zabrałem się za manual zamiast po ludzku wziąć jakiś quick start guide czy inny tutorial. Po co, skoro całkiem nieźle sobie radzę z gitem? Przecież to nie może być bardzo różne.

Otóż okazuje się że może być różne. Taki hg merge na przykład, zgodnie z manem, ściąga tylko jeden commit (obserwacja niby mówi inaczej...), a po sprawdzeniu mi wyszło że ten merge jeszcze trzeba commitować. Bosko. A jeszcze lepiej że nie widzę, jak miałbym ściągać zmiany z dwóch-trzech różnych repozytoriów, w których występują tak samo nazwane branche (w gicie można pchnąć branche do jakiegoś podkatalogu, np. refs/heads/pulled-from-rudolf/*, wtedy master z rudolfa jest dostępny pod pulled-from-rudolf/master).

Zabieram się jednak za tutorial.

Wysłany: 10 grudnia 2009, 12:54:53; Dodaj komentarz

python-hildon na Debianie stable

Wpis ma charakter notatki na kiedyś w przyszłości, więc nie będę specjalnie dbał ani o jego przejrzystość, ani o czystość i elegancję zastosowanej metody działania w nim opisanej.

Plan: na igorze (laptop z Debianem stable, znaczy z 5.0.1, Lennym) zainstalować pakiet python-hildon, który mi pozwoli uruchamiać i oglądać w mniej więcej niezmienionej formie aplikacje pisane przeze mnie pod N800 (Maemo).

Pakiet wezmę z Ubuntu. Wymagania w nazwach pakietów wszystkie mam spełnione bez odwoływania się do repozytoriów spoza oficjalnej listy Debiana. Niespełnione mogą być (i są) wymagania wersji niektórych pakietów, a konkretnie:

dpkg: problemy z zależnościami uniemożliwiają skonfigurowanie python-hildon:
 python-hildon zależy od libdbus-glib-1-2 (>= 0.78); jednakże:
  Wersją libdbus-glib-1-2 w systemie jest 0.76-1.
 python-hildon zależy od libgtk2.0-0 (>= 2.16.0); jednakże:
  Wersją libgtk2.0-0 w systemie jest 2.12.12-1~lenny1.
 python-hildon zależy od libhildon-1-0 (>= 2.0.1); jednakże:
  Wersją libhildon-1-0 w systemie jest 1.99.1.debian.1-1.

Rozwiązanie:

mkdir nowypakiet
cd nowypakiet
dpkg-deb -x ../python-hildon_0.8.8-1ubuntu6_i386.deb .      
dpkg-deb -e ../python-hildon_0.8.8-1ubuntu6_i386.deb DEBIAN
vim DEBIAN/control
dpkg-deb --build . ../python-hildon_0.8.8-1dzz6_i386.deb

Krótki opis (bo nie lubię zostawiać w sieci suchych poleceń bez wyjaśnienia co się dzieje):

  • rozpakowuję pliki z pakietu
  • rozpakowuję metadane z pakietu (od razu do katalogu, w którym dpkg-deb --build ich się spodziewa)
  • poprawiam wymagane wersje w Depends na moje
  • poprawiam wersję pakietu w Version (s/ubuntu/dzz/)
  • buduję nowy pakiet

Uwaga! Skrypt używający modułów z tego pakietu ma pełne prawo się wywrócić z segfaultem albo nie działać w jakikolwiek inny sposób. Większość pakietów nie na darmo zależy od biblioteki XYZ w wersji nie mniejszej niż. Mogło po drodze się zmienić API, ABI albo oba. To rozwiązanie traktuję wyłącznie jako quickfix dla czegoś, co mi niezbędne nie jest, ale nie pogardzę jeśli będzie mi działać.

Wysłany: 14 sierpnia 2009, 01:49:18; Dodaj komentarz

Perl: %symbol::{table}

Do zapamiętania: jeśli przyjdzie mi kiedyś jeszcze w Perlu dobierać się do funkcji znając jej nazwę, to najrozsądniej jest zdobyć referencję do niej za pomocą tablicy symboli.

Wyobraź sobie że masz stado funkcji, które odpowiadają jakimś parametrom z wiersza poleceń podawanym skryptowi. Wygodnie jest jeśli te funkcje mają nazwy powiązane z argumentami (na przykład dla przełącznika --foo wywoływana jest funkcja action_foo()), bo to i przejrzyste, i... nakłania do zautomatyzowania powiązania funkcji i przełącznika.

Niezłym pomysłem jest stworzenie %hasza handlerów, czegoś w tym stylu:

my %handlers = (
  foo      => \&action_foo,
  bar      => \&action_bar,
  rabarbar => \&action_rabarbar,
);

for my $arg (@ARGV) {
  if ($arg =~ /^--(.*)(?:=(.*))/ && exists $handlers{$1}) {
    $handlers{$1}($2);
  }
}

Fajna sprawa, ale trzeba jeszcze utrzymywać %handlers. Tu pojawia się pomysł użycia tablicy symboli.

W Perlu do każdego zdefiniowanego pakietu przysługuje zmienna %pakiet::. Przypomina to dostęp do %hasza ze specyfikacją pakietu, tylko że nazwa zmiennej jest pusta. Kluczami, jak się można domyślać, są nazwy symboli (zmiennych globalnych, I/O handles, funkcji i tak dalej). A kluczami? *GLOBy, bo tylko one dają dostęp do wszystkich obiektów.

Zobaczmy to w akcji:

sub a_jeden { print 1; }
sub a_dwa   { print 2; }
sub a_trzy  { print 3; }

for my $a (sort grep { /^a_/ } %{main::}) {
  printf "%s():\n", $a;
  ${main::}{$a}();
}

To już daje jakiś punkt zaczepienia w dalszych działaniach. Możemy teraz sporządzić sobie %hasz z referencjami do funkcji:

use Data::Dumper;

sub a_jeden { print 1; }
sub a_dwa   { print 2; }
sub a_trzy  { print 3; }

my %funcs;
for my $a (grep { /^a_/ } %{main::}) {
  $funcs{$a} = \&{ ${main::}{$a} };
}

print Dumper \%funcs;

Ja osobiście wolę ciut inne podejście. Wadą powyższego jest tworzenie referencji również do nieistniejących funkcji, jeśli tylko zadeklarowana została zmienna globalna o odpowiedniej nazwie. Używam składni *foo{THING} (perldoc perlref):

use Data::Dumper;

sub a_jeden { print 1; }
sub a_dwa   { print 2; }
sub a_trzy  { print 3; }

our $a_cztery = 4;

my %funcs;
for my $a (grep { /^a_/ } %{main::}) {
  $funcs{$a} = *{ ${main::}{$a} }{CODE};
}

print Dumper \%funcs;

Jak widać, dla klucza a_cztery wartością jest undef, nie referencja do kodu. To już można efektywnie wykrywać (albo wręcz, co zresztą rozsądniejsze, wyeliminować na etapie wypełniania %hasza).

Co w tej metodzie jest najfajniejsze, to brak funkcji eval() i działanie pod use warnings; use strict; (w odróżnieniu od referencji symbolicznych, ${ "nazwazmiennej" }).

Sztuczkę tę poznałem dobre parę lat temu, ale do niedawna nie miałem okazji z niej skorzystać i zupełnie o niej zapomniałem.

Wysłany: 23 czerwca 2009, 19:33:46; 4 komentarze

root@vserver# ping localhost

Osoby budujące pakiety we wnętrzu VServera czasem mogą napotkać przy niektórych programach problem z autotools: proces kompilacji zatrzymuje się na sprawdzaniu składni polecenia ping. Trick polega na tym, że autotools uruchamia ping 127.0.0.1, co można zobaczyć w liście procesów (ps -C ping -f), a VServer nie zwielokrotnia interfejsu lo i w systemie-gościu brak routingu do 127.0.0.0/8.

Gdyby w poleceniu nie był użyty adres IP, tylko nazwa, dało by się obejść problem umieszczając w /etc/hosts wpis kierujący localhost na któryś dostępny adres.

A co zrobić z adresem IP? Można napisać bibliotekę podmieniającą funkcję sendmsg() i przepisującą w miarę potrzeby adres docelowy pakietu. Taka biblioteka umieszczona w zmiennej $LD_PRELOAD pozwoli pingowi zadziałać jak trzeba (wymaga to praw roota, bo /bin/ping na ogół jest SUID-owany; man ld.so). Władowanie mojej wersji biblioteki do /etc/ld.so.preload skończyło się problemami z daemonem SSH, których nie chciało mi się debuggować. Prościej mi było zmienić nazwę /bin/ping, a w jego stare miejsce wstawić skrypt-wrapper ustawiający $LD_PRELOAD.

Samą bibliotekę wystawiam tutaj. Skorzystałem z dwóch rozszerzeń GCC, pod czymś innym może się nie skompilować.

Notatka: ping z iputils do określenia adresu źródłowego dla pakietu tworzy sobie gniazdo datagramowe skierowane na adres docelowy i odczytuje jego lokalny adres (getsockname()). Łatwo to sprawdzić używając strace.

Notatka 2: połączenia sieciowe nawiązywane są domyślnie z adresu 127.0.0.1, jeśli adres docelowy jest lokalną maszyną. Wewnątrz vservera nie miałyby do czego się przywiązać, dlatego warto mieć zaznaczoną opcję Remap Source IP Address (VSERVER_REMAP_SADDR), która nada takiemu połączeniu pierwszy z brzegu adres przeznaczony dla vservera. Tak jest u mnie zrobione.

Wysłany: 23 września 2008, 22:24:52; 18 komentarzy

DDNS w ISC DHCP

Dla pełnego obrazu problemu i rozwiązania muszę naszkicować sytuację w naszej sieci firmowej.

Mamy kilkanaście oddziałów rozrzuconych po Polsce i mamy kilkuset użytkowników laptopów, z czego wielu przemieszcza się między różnymi oddziałami.

Oddziały mają rózne klasy adresowe /16, adresy są przydzielane przez DHCP na podstawie adresu MAC.

Wszystkie komputery zapisane są w bazie danych jako (nazwa, adres-IP, adres-MAC). Pole adres-IP definiuje młodsze szesnaście bitów 32-bitowego adresu IP, czyli to, co dostał każdy oddział. Z tej właśnie bazy mamy mapowanie adresów MAC-IP.

Dla przykładu załóżmy, że mamy komputer rudolf o adresie IP x.x.10.20, to w oddziałach z sieciami 172.16.0.0/16 i 172.18.0.0/16 rudolf dostanie adresy, odpowiednio, 172.16.10.20 i 172.18.10.20.

Każdy z komputerów ma zainstalowany serwer UltraVNC, żeby informatycy znajdujący się w centrali mogli bez problemów pomagać użytkownikom znajdującym się w innym mieście. Dla jeszcze większego ułatwienia został napisany frontend dla VNC viewera. Ten frontend pobiera listę komputerów z bazy i prezentuje ją operatorowi pozwalając wybrać, z którym komputerem ma się połączyć.

Przydałoby się mieć jakiś stały element, który pozwoli dostać się do komputera niezależnie od aktualnego miejsca pracy użytkownika. Użytkownicy przemieszczają się między sieciami, więc nie można wklepać do bazy pełnego adresu IP. Tu z pomocą przychodzą dynamiczne aktualizacje wybranej strefy DNS wykonywane przez serwer DHCP (schemat interim, jako ten zalecany przez dokumentację ISC DHCP i jedyny obsługiwany przy statycznych dzierżawach).

Tłumaczyć jak skonfigurować do tego serwer DHCP nie mam zamiaru, choćby wspomniana dokumentacja przyzwoicie to wyjaśnia. Wspomnę tylko, że nazwy pakowane do DDNS są wybierane przez serwer, nie przez klienta (opcja ddns-hostname w definicji każdego hosta i update-static-leases on w sekcji globalnej).

Ciekawy jest natomiast problem, który się dla tej konfiguracji pojawia. Przy statycznie skonfigurowanych przydziałach adresów IP dhcpd nie kasuje wpisów DNS (man dhcpd.conf, opcja update-static-leases). Gdy wymieniany jest cały komputer, czyli gdy zupełnie inna maszyna dostaje wcześniej używaną nazwę, aktualizacja DDNS nie powodzi się. Okazuje się, że identyfikator maszyny (tytułowy DHCID), który jest zapisywany w rekordzie TXT, dla jednego i drugiego komputera jest różny, a dhcpd zamienia wpisy w DNS tylko gdy maszyna, która dostała dzierżawę, ma taki sam identyfikator jak ten zapisany w DNS albo gdy rekord A o tej samej nazwie w DNS nie istnieje. Gdy rekord A jest obecny, a w rekordzie TXT znajduje się coś innego albo tego rekordu w ogóle nie ma, DNS nie jest uaktualniany.

Najlepsze rozwiązanie, na jakie wpadłem, polega na regularnym sprawdzaniu, czy DHCID w DNS zgadza się z tym, który dostanie komputer o odpowiedniej nazwie, i ewentualnym kasowaniu par rekordów A i TXT, jeśli DHCID z TXT się nie zgadza.

Łatwiej powiedzieć niż zrobić, bo rzeczony DHCID trzeba najpierw umieć generować. W sieci nijak nie mogłem znaleźć algorytmu, a draft RFC i sam RFC 4701 o rekordach dla DHCP nie przystają do tego co robi ISC DHCP. Trzeba było zastosować metodę dozziego: tar zxf dhcp-*.tar.gz; cd dhcp-*; vim *.c.

Na efekt kopania w kodzie źródłowym trzeba było czekać cztery godziny. Okazało się, że w rekordach TXT zapisywana jest szesnastkowo zapisana suma MD5 pewnych danych poprzedzona bajtem 0x00 albo 0x31 (nie wnikałem od czego ten bajt zależy). Danymi, którymi jest nakarmiony algorytm MD5, jest bajt 0x01 (raz się zdarzył 0x00, znowu nie wnikałem dlaczego akurat tak) i doklejony do niego adres MAC komputera, zapisany jako sześciobajtowy string. A obrazowo, dane z rekordu TXT można porównać tak:

#!/usr/bin/perl -l

use Digest::MD5 qw/md5_hex/;

my $mac = '00:0C:6B:7A:E8:70';
print "expected: 31b717509e296b7b34587f80910a641021";

my $data = "\x01";
#my $data = "\x00"; # tylko raz mi sie zdarzylo ze bylo trzeba uzyc tego
$data .= chr hex $_ for split /:/, $mac;
print "computed: 31" . md5_hex($data);
# drugi wariant:
#print "computed: 00" . md5_hex($data);

Jeśli się odetnie dwa pierwsze heksy, zostają tylko dwie możliwości: $data zaczęta od 0x01 (częstsza sytuacja) albo od 0x00 (znalazłem tylko jedną taką). Dzięki temu co godzinę można uruchomić sprawdzanie, czy któryś z rekordów TXT nie jest czasem nieaktualny.

Dla kompletności wpisu dodam, że usuwanie wpisów DDNS można spokojnie załatwić narzędziem nsupdate z dystrybucji BIND-a.

Wysłany: 09 lutego 2008, 17:51:21; 4 komentarze

transport dla jabberd@x86_64

Przez parę dni (rozciągniętych w czasie na tygodnie) szarpałem się z konfiguracją transportu GG dla serwera Jabbera jabberd2, wszystko stawiane na 64-bitowym Gentoo (x86_64). Póki jabberd stał na starej maszynie (x86), transport działał poprawnie. Przeniesienie serwera Jabbera (uwaga: nie transportu) na nową spowodowało, że transport się wywracał przy starcie.

Opcje debuggowe podpowiedziały, że skrót SHA1(connection_id || secret) przesyłany serwerowi był odrzucany. connection_id to ID połączenia, przesyłany przez serwer przy handshake'u, a secret to współdzielony sekret do uwierzytelnienia transportu. Prymitywna metoda echo -n id_i_sekret | sha1sum powiedziała, że transport ma rację co do hasza. No to kopiemy w samym jabberd.

Użyłem kodu w wersji 2.0s11. Wstawiając debugowe fprintfy i operując na żywym organizmie ustaliłem, że do funkcji shahash_r() (router/router.c, _router_process_handshake() w wierszu 108) pchane są właściwe dane. Zszedłem głębiej, do samej funkcji shahash_r(). Tu jest w zasadzie samo wywołanie sha1_hash(), własnoręcznej implementacji SHA-1. Zamieniłem domorosły kod na #include <openssl/sha.h> i funkcję SHA1() z biblioteki OpenSSL, skompilowałem router i uruchomiłem (na 64 bitach). Transport się połączył i działa poprawnie.

Morał? Nie reimplementuj koła, tylko użyj gotowego kodu. OpenSSL-a i tak praktycznie każdy ma zainstalowanego.

Wysłany: 21 sierpnia 2007, 20:13:27; 8 komentarzy

gorion gadu

"Siedzę i siedzę, myślę i myślę, czego naprawdę ci brak"... Pewnie bezpiecznego komunikatora nie wymagającego serwera i szyfrującego przesyłane komunikaty! Na taki pomysł wpadł niejaki gorion. I skubany przez dwa dni napisał coś, co już działa jako para (no, trójka) programów, serwer i klient (dwaj klienci). Na razie nie szyfruje transmisji, ale pozwala "łączyć się z adresami internetowymi jak również sieciowymi" (whatever that means). Na razie szyfrowanie algorytmem AES jeszcze nie jest zaimplementowane. Program najwyraźniej został napisany w C++Builderze.

Niezły wynik jak na dwa dni.

Zobaczmy, ile mnie zajmie napisanie czegoś podobnego (a nie jestem specjalnie szybkim programistą). Tcl/Tk do GUI, OpenSSL jako warstwa transportowa, własny protokół tekstowy, całość klejona kodem w C.
Wed, 10 Jan 2007 18:49:19 +0100. Czas start.

Wysłany: 10 stycznia 2007, 18:53:33; 16 komentarzy

Subversion master/slave

Aaaaaaaaaaaaa!!!

Już zdążyłem się nauczyć używać gita jako narzędzia do offline'owej pracy na subversionowym kodzie. Chciałem początkowo zrobić drugie repozytorium Subversion, które by robiło za slave'a i tylko co jakiś czas by było synchronizowane z masterem, ale nie miałem do tego narzędzia (skończyło się na gicie). I co ja dzisiaj widzę? Pushmi, które służy dokładnie do tego!

Wysłany: 29 grudnia 2006, 11:54:25; Dodaj komentarz