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. Greeter’ın EBAQR altyapısı aşağıdaki görseldeki gibi.

“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.
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.
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ış.
"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.
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:
# lsof -i :7777
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:
# ps -p 771 -o pid,ppid,args
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:
#!/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.
# echo 'ebaqronline:ali-candan:parola' | /usr/bin/pardus-lightdm-greeter-listener
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.
/usr/bin/sshlogin $1 $2 &
# ls -la /usr/bin/sshlogin
lrwxrwxrwx 1 root root 45 Eyl 24 12:55 /usr/bin/sshlogin -> ../share/pardus/pardus-lightdm-greeter/cli.py
# ls -la /usr/bin/pardus-login
lrwxrwxrwx 1 root root 45 Eyl 24 12:55 /usr/bin/pardus-login -> ../share/pardus/pardus-lightdm-greeter/cli.py
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.
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.
#****************************************************************
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.
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ış.
root@etahta:/var/lib/lightdm# ls -la
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.
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:
#!/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.
