Bu yazıda QR Kod (EBAQR) özelinde LightDM altyapısı hakkında yaptığım incelemeleri paylaşıyor olacağım. İncelediğim kısmı kadarıyla altyapının büyük kısmında kod emeği olan Bayram Karahan hocamızın emeğine sağlık.
“Sabit kullanıcı (ebaqr) giriş” ve “Kişiye Özel Giriş” giriş seçeneklerinin yer aldığı ETAP sistemlerde lightdm-greeter adlı grafik giriş yöneticisi kullanılıyor. Karşılama ekranını (login) oluşturan python kaynak kodları /usr/share/pardus/pardus-lightdm-greeter dizininde bulunabilir. Aşağıda ekran görüntüsü yer alan QR giriş seçenekleri main.py tarafından yüklenen module/ebaonline.py tarafından sağlanıyor.
Kaynak kod incelendiğinde bir WebKit.WebView() nesnesi içinde “https://giris.eba.gov.tr/EBA_GIRIS/studentQrcode.jsp” sayfasının yüklendiği görülebilir. Bu webview nesnesinin load-changed olayına bağlanan response_dataonline yordamı sayfa tazelendiğinde çalışacak şekilde ayarlanmış. Sayfanın tazelenmesi için EBA uygulaması üzerinden kamera ile QR kodu okutulması yeterli. Okutma gerçekleştikten sonra webview’de yüklü sayfaya taramayı yapan kullanıcıya ait JSON verisi EBA sunucusu tarafından geri döndürülüyor (Response). Sayfayı bir tarayıcıda açarak network hareketlerinden ilgili JSON verisini inceleyebilirsiniz.

ebaonline.py modülü JSON biçiminde gelen response verisi içerisinden gerekli alanları parsellenerek bir python listesine dönüşütürüyor. ebaonine.py modülünün python listesini oluşturmak için kullandığı alanların listesi aşağıdan görülebilir.
1 2 3 4 5 |
role=str(persondata["userInfoData"]["role"]) userid=persondata["userInfoData"]["userId"] name=persondata["userInfoData"]["name"] surname=persondata["userInfoData"]["surname"] username = self.username_prepare(name+"-"+surname) |
modül kodunun ilerleyen satırları incelendiğinde; oturumun açılması için bu veri yapısı TCP 7777’yi dinleyen bir prosese gönderiliyor. Veri yapısının gönderilmesi için aşağıdaki format kullanılmış.
Eğer “Sabit kullanıcı (ebaqr) giriş” seçilmiş ise sokete gönderilen veri şuna benziyor ebaqrebaqr:ozgur-koca:209823d20ad93948434
Eğer “Kişiye özel giriş” seçilmiş ise sokete gönderilen veri şunun gibi oluyor: ebaonline:ozgur-koca:209823d20ad93948434
İlk alandaki değerler ebaqrebaqr veya ebaqronline hangi kullanıcı ile oturum açılacağını söylüyor. Yani sabit kullanıcı olan ebaqr ile mi yoksa kullanıcının ad-soyad ile oluşturulan kişisel hesabı ile mi. Diğer alan ad (name) ve soyad (surname) alanlarından oluşturulan kullanıcı adını ifade ediyor. Son alan ise kullanıcının EBA üzerindeki kullanıcı ID’si ve bu kullanıcının geçici parolasını tanımlamak için kullanılıyor.
1 2 |
if role == "2" or role == "300" or role == "301": os.system("echo '"+self.loginType+":"+username+":"+userid+"' | netcat localhost 7777 &") |
Öğretmen kullanıcılarının rol tanımı EBA sisteminde 2 ile ifade edilmiş. 39’un veli olduğu görülebiliyor. 300 ve 301 ile ilgili bir dökümantasyon paylaşılmamış.
1 2 3 4 5 6 7 8 9 10 11 12 |
"userRoleList": [ { "id": 39, "name": "Veli", "userAuthorities": null }, { "id": 2, "name": "Öğretmen", "userAuthorities": null } ], |
TCP 7777’ye gönderilen bu veri ilgili kullanıcı hesabının oluşturulması ve oturum açılması için yeterli. Komut satırında aşağıdaki komutu vererek ali-candan isimli kullanıcının oluşturulmasını ve otomatik olarak oturum açılmasını sağlayabilirsiniz.
1 |
echo 'ebaqronline:ali-candan:parola' | netcat localhost 7777 |
Burada netcat aracı echo ile pipe edilen verinin TCP 7777. porta yazılmasını sağlamak için kullanılmış. TCP 7777’yi hangi prosesin dinlediğini görmek için lsof komutundan faydalandığımızda:
1 2 3 |
<strong># lsof -i :7777</strong> COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME tcpserver 771 root 3u IPv4 18602 0t0 TCP *:7777 (LISTEN) |
PID numarası 771 olan tcpserver adından bir binary olduğunu görüyoruz. Bu binary’iyi hangi prosesin çalıştırdığını ya da dinlendiğini görmek için ise ps aracını aşağıdaki gibi çalıştırabiliriz:
1 2 3 |
<strong># ps -p 771 -o pid,ppid,args</strong> PID PPID COMMAND 771 1 tcpserver -v -P -R -H -l 0 0.0.0.0 7777 /usr/bin/pardus-lightdm-greeter-listener |
TCP soketine gelen verilerin /usr/bin/pardus-lightdm-greeter-listener programına yönlendirildiğini görüyorum. Bu yapı bir systemd servisi ile (pardus-lightdm-greeter-listener.service) ayakta tutuluyor. Yani 7777. porta bir veri akışı olduğunda bu veri pardus-lightdm-greeter-listener programının başlatılmasında kullanılıyor. Bu program aslında bir bash betiği. Betiğin içinde kullanıcı hesabını oluşturan add_user() alt programının kodları ise aşağıdaki gibi:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
#!/bin/bash add_user() { user="$1" defpass="$user" pass="$2" if [ ! -d /home/$user ] ; then useradd -m $user -s /bin/bash -p $(openssl passwd "$pass") -U -d /home/$user useradd $user-qr -s /bin/bash -p $(openssl passwd "$defpass") -d /home/$user mkdir -p /home/$user chown $user -R /home/$user chmod 755 /home/$user uida=$(grep "^$user:" /etc/passwd | cut -f 3 -d ":") uidb=$(grep "^$user-qr:" /etc/passwd | cut -f 3 -d ":") sed -i "s/:$uidb:/:$uida:/g" /etc/passwd for g in floppy audio video plugdev netdev $user; do usermod -aG $g $user-qr || true;usermod -aG $g $user || true;done #usermod $user-qr -p $(openssl passwd -6 "$pass") fi } |
Bu alt program incelendiğinde ali-candan ve ali-candan-qr isimli olmak üzere iki tane kullanıcı hesabı oluşturuyor ve gereki hesap ayarlarını (ev dizini, erişim izinleri, grup ayarları vb) yapıyor.
İkinci useradd komutuna baktığımızda ilkinden farklı olarak parola tanımının $defpass ile yapıldığını görebiliriz. Bu diğerinden farklı olarak ali-candan-qr kullanıcı adlı hesaba kullanıcı adı ile aynı ($defpass=”$user”) olan bir parolanın tanımlanmasını sağlıyor.
İstenilen hesabın oluşturulması ve oturumunun açılması için tcpserver’a veri akıtmak yerine veriyi dorudan bu betiğe göndermek de mümkün. Aynı sonucu yaratıyor. Buradaki tcp soket yaklaşımının nedeni uzaktan (ağ tabanlı) kullanıcı oluşturma/oturum açma isteği olabilir.
1 2 3 4 5 6 7 8 |
<strong># echo 'ebaqronline:ali-candan:parola' | /usr/bin/pardus-lightdm-greeter-listener</strong> PID: 116322 ebaqronline:ali-candan:parola Type: ebaqronline User: ali-candan Password: parola {"username": "ali-candan-qr", "password": "parola"} Failed to connect pardus lightdm greeter |
Betiğin tamamını incelediğimizde oturum açma işlemi için greeter_login() adındaki alt programa kullanıcı adı ve parolanın parametre geçildiğini görüyoruz. Aslında bu alt program /usr/share/pardus/pardus-lightdm-greeter/cli.py ile sembolik olarak bağlı /usr/bin/sshlogin‘in kendisi. cli.py’a sembolik link içeren bir diğer dosya da /usr/bin/pardus-login konumunda bulunuyor.
1 |
/usr/bin/sshlogin $1 $2 & |
1 2 |
<strong># ls -la /usr/bin/sshlogin</strong> lrwxrwxrwx 1 root root 45 Eyl 24 12:55 /usr/bin/sshlogin -> ../share/pardus/pardus-lightdm-greeter/cli.py |
1 2 |
<strong># ls -la /usr/bin/pardus-login</strong> lrwxrwxrwx 1 root root 45 Eyl 24 12:55 /usr/bin/pardus-login -> ../share/pardus/pardus-lightdm-greeter/cli.py |
1 2 |
sshlogin ali-candan parola {"username": "ali-candan", "password": "parola"} |
Sonuç olarak; yalnızca var olan bir kullanıcının oturumunu açmak için sshlogin ve diğer sembolik aracıları devreden çıkartarak aşağıdaki komut da çalıştırılabilir. Buradan yola çıkarak cli.py modülünün lightdm aracılığıyla oturum açmaktan sorumlu olduğunu söyleyebiliriz.
1 |
python3 /usr/share/pardus/pardus-lightdm-greeter/cli.py ali-candan parola |
Bu incelemeleri yaptığım sırada şunu farkettim; betik kullanıcı hesaplarını oluşturduktan sonra bazen oturum açmada başarısız oluyor ve tahta siyah ve boş bir ekranda kalıyor. Bu durum systemctl restart lightdm ile greeter’ın yeniden başlatarak çözülüyor fakat “/usr/bin/pardus-lightdm-greeter-listener: satır 90: Masaüstü: komut yok” dan da anlaşılacağı üzere betikde bir hata var (Ocak 2024 itibariyle güncellenmiş bir sürümdeyiz). 90’ı satıra baktığımızda yorum satırının # ile kapatmasının unutulduğunu görüyorum. Ancak bu betiğin çalışmasını etkilemeyecek önemsiz bir hata.
Oturumun açılmasını sağlayan betik parçası aşağıdaki gibi, burada 4 saniye aralıkla iki kez giriş işlemi yaptırılmış. Eğer grafik ekranda “Sabit Kullanıcı (ebaqr) Giriş” seçilmiş ise “$type” == “ebaqrebaqr” şartlı if bloğu, “Kişiye Özel Giriş” seçilmişse “$type” == “ebaqronline” şartlı if blok çalışıyor.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
#**************************************************************** greeter_login() { /usr/bin/sshlogin $1 $2 & sleep 4 /usr/bin/sshlogin $1 $2 & } read MESSAGE echo "PID: $$" echo "$MESSAGE" type=$(echo $MESSAGE|cut -d":" -f1) #**************************************************************** if [ "$type" == "ebaqronline" ] then user=$(echo $MESSAGE|cut -d":" -f2) psw=$(echo $MESSAGE|cut -d":" -f3) echo "Type: $type" echo "User: $user" echo "Password: $psw" add_user $user $psw greeter_login "$user-qr" $psw fi #**************************************************************** if [ "$type" == "ebaqrebaqr" ] then user=$(echo $MESSAGE|cut -d":" -f2) #psw=$(echo $MESSAGE|cut -d":" -f3) psw="rastgele-parola" user="ebaqr" echo "Type: $type" echo "User: $user" echo "Password: $psw" greeter_login $user $psw fi |
ebaqrebaqr if bloğuna bakacak olursak kullanıcı adı olarak ebaqr, parola olarak ise rastgele üretilen bir parola kullanılıyor.
lightdm tarafında oturum açma sürecinin nasıl işlediğini görmek için cli.py incelediğimizde aşağıdaki gibi kullanıcı adı ve parolanın json biçimine dönüştürülerek /var/lib/lightdm/pardus-greeter dosyasına append yani ekleme modunda yazdığını görüyoruz.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
def login(username=None, password=None, session=None): if not os.path.exists("/var/lib/lightdm/pardus-greeter"): print("Failed to connect pardus lightdm greeter") exit(2) data = {} data["username"] = str(username) data["password"] = str(password) if session != None: data["session"] = str(session) with open("/var/lib/lightdm/pardus-greeter", "a") as f: print(json.dumps(data)) f.write(json.dumps(data)) f.flush() |
/var/lib/lightdm/pardus-greeter konumuna gittiğimizde ise bu dosyanın aslında bir named pipe (FIFO) olduğu anlaşılıyor. Bu pipe yukarıdakine benzer (tcpserver) şekilde prosesler arasında iletişim kurmak için kullanılan bir linux yöntemi. ls -la çıktısındaki p (pipe) takısından bunu anlayabiliriz. lightdm-greeter yani oturum açma ekranı aktif olduğunuda bu pipe’da mevcut oluyor. Kullanıcı oturum açtığında ise DELETE_SELF işlemi ile kendini siliyor (root@etahta:/var/lib/lightdm# inotifywait -m pardus-greeter ile görülebilir). Bu aynı zamanda önceki kod parçasında görüleceği üzere os.path.exists işlevi ile lightdm-greeter’ın etkin olup olmadığını sınamak için kullanılmış.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
root@etahta:/var/lib/lightdm# <strong>ls -la</strong> toplam 40 drwxr-x--- 7 lightdm lightdm 4096 Oca 18 00:09 . drwxr-xr-x 44 root root 4096 Oca 17 00:14 .. drwxr-xr-x 6 lightdm lightdm 4096 Oca 16 21:39 .cache drwx------ 4 lightdm lightdm 4096 Oca 16 21:39 .config drwxr-xr-x 7 root root 4096 Oca 18 00:09 data drwx------ 3 lightdm lightdm 4096 Oca 16 21:39 .dbus drwx------ 3 lightdm lightdm 4096 Oca 16 21:39 .local prw------- 1 lightdm lightdm 0 Oca 18 00:09 pardus-greeter -rw-r--r-- 1 lightdm lightdm 822 Oca 18 00:09 pardus-lightdm-greeter.log -rw------- 1 lightdm lightdm 215 Oca 18 00:09 .wget-hsts -rw------- 1 lightdm lightdm 51 Oca 18 00:09 .Xauthority |
Görebildiğimiz gibi lighdm oturum açacak kullanıcıyı buradan kabul ediyor. Bu FIFO pipe’ı ise module/daemon.py tarafından dinleniyor ve aşağıda kod parçasından görüleceği üzer ekrandaki kullanıcı parola kutularını doldurarak lightdm.login() işlevini çalıştırıyor.
1 2 3 4 |
GLib.idle_add(loginwindow.o("ui_entry_username").set_text, username) GLib.idle_add(loginwindow.o("ui_entry_password").set_text, password) GLib.idle_add(loginwindow.event_login_button,loginwindow.o("ui_button_login")) lightdm.login() |
module/gtkwindow.py içinde tanımlanan login_handler() işlevi de son iş olarak self.kill_windowmanager() metodunu çalıştırarak Greeter’ı yok ediyor.
Açılışta başlatılan betikler
Grafik oturumun başlangıcında yürütelen yapılandırmalardan birisi olan /etc/xdg/autostart/newpassword.desktop içeriği incelendiğinde /usr/bin/newpassword.sh betiğini çalıştırdığı görülebilir. Betik $HOME/.config/np konumundaki dosyanın varlığını kontrol ederek kullanıcıya sifredegistir diyalog penceresini görüntülüyor. Bu betiğin içeriği aşağıdaki gibi:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
#!/bin/bash passwordstatus="$HOME/.config/np" if [ -f $passwordstatus ]; then echo "dosya var" else echo "dosya yok" #touch $passwordstatus notify-send -i password "İlk Defa Oturum Açıyorsunuz.." -u critical notify-send -i password "Şifrenizi Mutlaka Değiştirin.." -u critical /usr/bin/sifredegistir fi |
Eğer kullanıcı sifredegistir binary’isini kullanarak bir parola tanımlarsa $HOME/.config/np (muhtemelen no password) konumunda boş bir dosya oluşturuluyor.
