> Proses kavramı ve proseslerin kontrol blokları: >> İşletim sistemlerindeki en önemli kavramlardan bir tanesi de "process" kavramıdır ve o an çalışmakta olan programlara verilen isimdir. Pek çok işletim sistemi "process" ile "task" sözcüklerini eş değer kullanmaktadır. >> İşletim sistemi bir programı çalıştırdığında onu sürekli olarak izlemektedir. Dolayısıyla işletim sistemi, o programın ne yaptığı hakkında sürekli olarak fikir sahibi olmaktadır. >> İşletim sistemleri, bir program çalıştırıldığında, iş bu programın bilgilerini "Process Control Block" adı verilen bir veri yapısında tutmaktadır. Bu blok "kernel" tarafından oluşturulmaktadır. Linux kaynak kodlarında bu yapı, "task_struct" adında, C dilindeki "struct" ile temsil edilmiştir. >>> İş bu kontrol blokları bünyesinde prosesin yetki derecesi, proesesin "Current Working Directory" bilgisi, prosesin o an bellekteki yeri, prosesler arası geçiş için bir takım bilgiler, prosesin o anki durum bilgisi, prosesin açmış olduğu dosyalar, prosesin akışları(thread) vb. bilgileri barındırır. "task_struct" bünyesinde bazen bilgi içeren veri elemanları, bazen başka veri yapısını gösteren göstericiler bulunmaktadır. Dolayısıyla dallı budaklı bir ağaç şeklindedir. Bağlı Listeler şeklinde örneklendirilebilir. İşte bu ağacın genelini "task_struct" olarak görebiliriz. > Proseslerin ID değerleri: >> UNIX/Linux sistemlerinde her proses eşsiz bir ID değerine sahiptir. Bu ID değerleri tam sayısal değerdir. İş bu ID değeri o anlıktır. Dolayısıyla belli bir zaman sonra, sona eren proseslerin ID'leri başka proseslere de atanabilir. >> Bu ID değerleri "kernel" tarafından o prosesin kontrol bloğuna erişmede kullanılır. UNIX/Linux sistemlerde bu ID değerleri bir "hash-table" veri yapısında tutmaktadır. Bu ID değerleri "pid_t" türü olarak "typedef" edilmiştir. Bazı sistemlerde bu "signed int", bazı sistemlerde "long" türüne tekabül etmektedir. İşletim sistemini yazanlar bunun kararını vermektedirler. Bütün sistemlerde ortak arayüz "pid_t" türünün kullanılmasıdır. >> "ps" komutu ile proseslerin ID değerlerini görüntüleyebiliriz. >> Proseslerin alabileceği maksimum ID değerine "shell" programına aşağıdaki kodu yazarak ulaşabiliriz; "cat /proc/sys/kernel/pid_max" İşletim sistemi bu değere ulaştıktan sonra tekrardan en başa geçiyor ve tamamlanan proseslerin ID değerlerini yeni oluşturulan proseslere atıyor. Böylelikle sistem genelinde tıkanma olasılığı bir hayli düşük. > Prosesler arasındaki "altlık/üstlük" ilişkisi: >> Unutulmamalıdır ki bir programı her zaman bir başka program çalıştırır. Örneğin, komut satırından bir program çalıştırdığımız zaman bu program aslında "shell" programı tarafından çalıştırılmaktadır. >> Proseslerin kontrol bloklarında da kimin çağrılan kimin çağıran olduğu bilgisi de tutulmaktadır. Çağıran program üst("parent"), çağrılan ise alt("child") programdır. "parent" olan bir proses yeni bir proses oluşturduğunda, kendi "Process Control Block" içerisindeki bazı bilgileri "child" olan prosese kopyalamaktadır ya da bu bilgiler kopyalanmaktadır. > Proseslerin Kullanıcı ve Grup ID değerleri: >> Proseslerin Kullanıcı ve Grup ID değerleri yetki derecesini belirtmekte kullanılmaktadır ve her proses Kullanıcı ID ve Grup ID değerini bünyesinde barındırır. >> Kullanıcı ve Grup ID değerleri de şu şekilde gruplanmıştır; >>> Kullanıcı ID Değerleri: >>>> Gerçek Kullanıcı ID Değerleri ("Real User ID"): >>>> Etkin Kullanıcı ID Değerleri ("Effective User ID"): >>> Grup ID Değerleri: >>>> Gerçek Grup ID Değerleri ("Real Group ID"): >>>> Etkin Grup ID Değerleri ("Effective Group ID"): Fakat genel olarak yukarıdaki ID değerleri sayısal olarak birbirinin aynısıdır(kendi içerisinde). İleride işlenecek konularda da göreceğimiz üzere, her ne kadar nadiren de olsa, proseslerin Etkin Kullanıcı ID değeri ile gerçek kullanıcı ID değeri, aynı şekilde Etkin Grup ID değeri ile gerçek Grup ID değeri birbirinden ayrılabiliyor. İleride anlatılacak bir takım test işlemlerinde proseslerin etkin ID değerleri işleme sokulmaktadır. >> Proseslerin iş bu ID değerleri tam sayı şeklindedir ve bu değerler de "typedef" edilmişlerdir ki böylelikle sistemler arasında taşınabilirlik korunsun. İşte bu nedenden dolayı, >>> Gerçek Kullanıcı ID ve Etkin Kullanıcı ID değerlerini "uid_t" isimli tür cinsinden yapmışlar. >>> Gerçek Grup ID ve Etkin Grup ID değerlerlerini de "gid_t" isimli tür cinsinden yapmışlardır. >> Prosesler bu dört ID değerini de üst prosesten alıyor, yani kendisini çalıştıran prosesten. Örneğin, bizler "Bash" isimli programı çalıştırıyor olalım. Çalıştığı için kendisi artık bir proses halindedir, dolayısıyla kendisinin de kullanıcı ve grup ID değeri mevcuttur. İş bu programın Kullanıcı ID değerlerinin 100 olduğunu varsayalım. Bizler ilgili "Bash" programından "./sample" komutu ile "sample" isimli bir programı çalıştırdığımız zaman, yeni oluşturulan bu "sample" programının kullanıcı ID değerleri de 100 olacaktır. Bu yaklaşım proseslerin Grup ID değerleri için de geçerlidir. * Örnek 1: "Bash" programının ID değerleri, #include "stdio.h" int main(int argc, char** argv) { /* # INPUT # id */ /* # OUTPUT # uid=1000(ahmopasa) gid=1000(ahmopasa) groups=1000(ahmopasa),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),120(lpadmin),131(lxd),132(sambashare) */ /** * İş bu programın Kullanıcı ID değerleri, 1000. * İş bu programın Grup ID değerleri, 1000. */ } >> Proseslerin ID değerlerine karşılık bir isim de atanmıştır. Böylelikle konuşma dilindeki anlaşılırlığı arttırmayı hedeflemişlerdir. * Örnek 1, #include "stdio.h" int main(int argc, char** argv) { /* # INPUT # id */ /* # OUTPUT # uid=1000(ahmopasa) gid=1000(ahmopasa) groups=1000(ahmopasa),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),120(lpadmin),131(lxd),132(sambashare) */ /** * İş bu programın Kullanıcı ID değerleri olan 1000 sayısına karşılık "ahmopasha" ismi verilmiştir. * İş bu programın Grup ID değerleri olan 1000 sayısına karşılık "ahmopasha" ismi verilmiştir. */ } >> Proseslerin ID değeri prosesi betimlerken, Kullanıcı ID'ler ise biz kullanıcıları betimlemektedir. >> İşletim sisteminin kendisi bir proses statüsünde olmadığından kullanıcı ve Grup ID değerleri mevcut DEĞİLDİR!. >> İşletim sistemi proseslere ID verirken bazı sayıları bünyesinde rezerv ediyor. Böylelikle, en kötü senaryoda sistemin admini proses çalıştırabilsin. Örneğin, sistemimizde en fazla 32.000 adet ID değeri atanabilir olsun. İşletim sistemi bunun 100 tanesini bize vermiyor, kendisine saklıyor. Çünkü bütün ID'lerin dolu olması durumunda YENİ BİR PROSES HAYATA GETİREMEYİZ. >> Proseslerin Kullanıcı ID ve Grup ID Değerleri ile Kullanıcıların Kullanıcı ID ve Grup ID değerleri arasındaki ilişki şu şekildedir: -> "login" isimli program bizden, oturum açma sırasında kullanıcı adı ve şifreyi aldıktan sonra, "etc/passwd" dosyasında aramaya başlıyor. -> Dışarıdan aldığı kullanıcı adı ve şifre ile iş bu dosyadaki kullanıcı adı ve şifre kombinasyonunu doğrulandığında, "login" fonksiyonu "etc/passwd" içerisinde belirtilen programı çalıştırıyor. Haliyle oradaki program prosese dönüşüyor. -> Prosese dönüşürken Prosesin Kullanıcı ID Değerleri ve Prosesin Grup ID değerlerini, "etc/passwd" içerisinde belirtilen Kullanıcının Kullanıcı ID Değeri ve Kullanıcının Grup ID Değerinden alıyor. Artık bizim prosesimizin User ID ve Group ID değerleri, "login" programı vasıtasıyla, "etc/passwd" içerisinde belirtilen değerlerden alınıyor. -> Daha sonra "login" prosesinin işi bittiği için sonlanıyor. Özetle bu ID bilgilerinin ilk çıkış noktası "etc/passwd" isimli dosya. ID değerleri de kalıtım yoluyla aktarıldığından, ilgili "Bash" üzerinden çalıştırılan bütün proseslerin Kullanıcı ID ve Grup ID değerleri yine "etc/passwd" içerisindekiler. > Prosesler bir dosya üzerinde işlem yapmak istediklerinde, işletim sisteminin aşağıdaki yönergeleri SIRASIYLA takip etmesi gerekmektedir; >> İşlem yapmak isteyen prosesin Etkin Kullanıcı ID bilgisi sıfır ise, işletim sistemine göre bu proses bir "root" proses veya "super-user" proses veya "priviledged" proses olarak değerlendirilirler ve başka herhangi bir sınamaya tabii tutulmazlar. Prosesin yapacak olduğu işe bakmaksızın direkt olarak işleme onay verir. Bunun tek istisnası, "x" hakkı içindir. Şöyle ki; eğer ilgili dosya en az bir kimseye ("user", "group", "other"), "x" hakkı tanımamış ise artık iş bu "root" proses de ilgili dosyayı ÇALIŞTIRAMAZLAR. Örneğin, aşağıdaki özelliklere haiz bir dosyamız olsun; -rw-rw-r-- 1 ahmopasa ahmopasa 17080 Kas 19 04:01 wd Dosyayı "sudo ./wd" ile çalıştırmak istediğimiz zaman aşağıdaki hata mesajını alacağız; sudo: ./wd: command not found Eğer ilgili dosyamızın özellikleri şu şekilde olsaydı; -rwxrw-r-- 1 ahmopasa ahmopasa 17080 Kas 19 04:01 wd Dosyayı "sudo ./wd" ile çalıştırmak istediğimiz zaman aşağıdaki hata mesajını alacaktık; ******************************** Buradan hareketle diyebiliriz ki "root" prosesin de bir dosyayı çalıştırabilmesi için ilgili dosyanın en az bir kimseye "x" hakkı tanıması gerekiyor. Bu kimse ama "user" ama "group" ama "other" olsun. >> Yukarıdaki adım başarısız olmuş ise bu adım izlenir; eğer işlem yapmak isteyen prosesin Etkin Kullanıcı ID bilgisi ile ilgili dosyanın Kullanıcı ID bilgisi birbirinin aynısıysa, işletim sistemi bu prosesi ilgili dosyanın "sahibi" olarak yorumlar. Bu durumda dosyanın yetkilerinden soldan ikinci, üçüncü ve dördüncü yetkiler ile yapılmak istenen iş karşılaştırılır. Eğer yetkiler, bu işlemi destekliyor ise işleme onay verilir. DESTEKLEMİYOR İSE İŞLEM BAŞARISIZLIKLA SONUÇLANIR. >> Yukarıdaki adım da başarısız olmuş ise bu adım izlenir; eğer işlem yapmak isteyen prosesin Etkin Grup ID bilgisi veya ek gruplarının Etkin Grup ID bilgileri, dosyanın Grup ID bilgisiyle aynıysa, işletim sistemi bu prosesi, ilgili dosya ile aynı grupta olan biri olarak yorumlar. Bu durumda dosyanın yetkilerinden soldan beşinci, altıncı ve yedinci yetkiler ile yapılmak istenen iş karşılaştırılır. Eğer yetkiler bu işlemi destekliyor ise işleme onay verilir. DESTEKLEMİYOR İSE İŞLEM BAŞARISIZLIKLA SONUÇLANIR. >> Yukarıdaki adım başarısız olmuş ise, işletim sistemi ilgili prosesi herhangi bir proses olarak görür ve dosyanın sekizinci, dokuzuncu ve onuncu yetkileri ile yapılacak olan işi karşılaştırılır. Eğer ilgili yetkiler, bu işlemi destekliyorsa işleme onay verilir. DEĞİLSE İŞLEM BAŞARISIZLIKLA SONUÇLANIR. Örneğin, aşağıdaki özelliklere haiz bir dosyamız olsun; -rw-r--r-- 1 kaan study 20 Kas 13 13:54 test.txt Dosyaya erişim yapmak isteyen isteyen proses ise "okuma ve yazma" amacı taşısın. Bu durumda, -> Eğer prosesin Etkin Kullanıcı ID bilgisi sıfır ise BU İŞLEM ONAYLANACAKTIR. -> Eğer prosesin Etkin Kullanıcı ID bilgisi "kaan" ise BU İŞLEM YİNE ONAYLANACAKTIR. -> Eğer prosesin Etkin Kullanıcı ID bilgisi veya Etkin Grup ID bilgisi ya da ek gruplarının Etkin Grup ID bilgileri, yukarıdakilerden farklı ise, işlem yine ONAYLANMAYACAKTIR. -> Eğer prosesin Etkin Grup ID bilgisi veya ek gruplarının Etkin Grup ID bilgileri "study" ise BU İŞLEM YİNE ONAYLANMAYACAKTIR. Çünkü dosya ile aynı Grup ID bilgilerine sahip olanlar için sadece "okuma" hakkı tanınmıştır. Öte yandan dosyanın sahibine verilmeyen bir hakkın, grup üyelerine veya diğerlerine verilmesi uygun bir davranış değildir. Örneğin, aşağıdaki özelliklere haiz bir dosyamız olsun; -r--rw-r-- 1 kaan study 20 Kas 13 13:54 test.txt Böylesi bir durumda dosyanın sahibi olan bir kişi sadece okuma yapabilecekken, dosya ile aynı grupta olan veya diğerleri dosyadan okuma ve yazma yapabilecektir. >> Proseslerin "saved-set-user-id" ve "saved-set-group-id" değerleri: Bu güne kadar görmüş olduğumuz proseslere ilişkin ID değerleri şu şekildeydi; Real User ID & Real Group ID Effective User ID & Effective Group ID Normal şartlarda, bu ID değerlerinden "Real User ID" ve "Effective User ID" değerleri birbirinin aynısıdır. Abnormal durumda bu iki ID değeri birbirinden ayrılır ki bu abnormal durum da geçen gördüğümüz konudur; "set-user-id"/"set-group-id" bayrak(ları) "set" edilmiş çalıştırılabilir bir program çalışmışsa prosesin "Real User/Group ID" değerimiz sabit kalırken "Effective User/Group ID" değeri çalıştırılan bu dosyanın "Kullanıcı/Group ID" değeri olarak değiştiriliyor. Şimdi ise torbaya başlıkta belirtilen iki ID değeri daha ekleniyor. Böylelikle proseslere ilişkin ID değerleri aşağıdaki gibidir; Real User ID & Real Group ID & Saved Set User ID Effective User ID & Effective Group ID & Saved Set Group ID Bir proses, "set-user-id" ve/veya "set-group-id" bayrakları "set" edilmiş ya da edilmemiş bir dosyayı çalıştırdığında, bu prosesin "saved-set-user-id" ve/veya "saved-set-group-id" bayrakları, prosesin yeni "Effective User ID" ve/veya "Effective Group ID" değerlerini alırlar. Örneğin, aşağıdaki ID değerlerine sahip bir prosesimiz olsun; Real User ID: kaan Effective User ID: kaan Real Group ID: study Effective Group ID: study ... Şimdi de "set-user-id" bayrağı "set" edilmiş aşağıdaki dosyayı bu proses ile çalıştıralım. -rwsr-xr-x 1 root root 59976 Nov 24 12:05 /bin/passwd Artık prosesimizin ID değerleri aşağıdaki gibi olacaktır; Real User ID: kaan Effective User ID: root Real Group ID: study Effective Group ID: study ... Bu işlem sonucunda prosesimizin "saved-set-user-id" değeri de "root" olacaktır. Dolayısıyla proseslerin ID değerleri aşağıdaki gibidir: Real User ID: kaan Effective User ID: root Saved User ID: root Real Group ID: study Effective Group ID: study Saved Group ID: root Burada prosesin "Effective Group ID" değerinin aynı kalmasının yegane sebebi, "/bin/passwd" dosyasının sadece "set-user-id" bayrağının "set" edilmiş olmasından kaynaklanmaktadır. Özetle şunu diyebiliriz ki bir proses "exec" işlemi uyguladığında, "saved-set-user-id" ve "saved-set-group-id" bayrakları, ilgili dosyanın Kullanıcı ID ve Group ID değerlerini almaktadır. Bu işlemler sırasında ilgili dosyanın "set-user-id" ve "set-group-id" bayraklarının bir önemi yoktur. Tabii bu iki yeni ID değeri de yine prosesin kontrol bloğu içerisinde saklanmaktadır. Pekiyi bu iki yeni ID değerinin işlevi nedir? Bu ID değerleri, proseslerin ID değerlerinde oynama yapan bir takım POSIX fonksiyonlarında kullanılmaktadır. >> Proseslerin ID değerleriyle ilişkili POSIX fonksiyonları: O an çalışmakta olan bir prosesin "Real User ID" ve "Effective User ID" değerlerini bize veren iki fonksiyon vardır. Bunlar sırasıyla "getuid" ve "geteuid" isimli fonksiyonlardır. Fakat POSIX dünyasında "saved-set-user-id" ve "saved-set-group-id" değerlerini bize veren bir fonksiyon yoktur. Fakat Linux dünyasında bu bayrakları bize veren fonksiyonlar mevcuttur. >>> "getuid" ve "geteuid" fonksiyonları, aşağıdaki imzaya sahiptir: #include uid_t getuid(void); uid_t geteuid(void); Bu fonksiyonların başarısız olması mümkün değildir. Geri dönüş değeri bir "typedef" biçimindedir ve karşılık geldiği tür sistemden sisteme değişmektedir. Fakat bu türün bir tam sayı olması şarttır. * Örnek 1, #include #include #include #include #include void exit_sys(const char* msg); int main(void) { /* # OUTPUT # Real User ID: 14012(runner12) Effective User ID: 14012(runner12) */ struct passwd* pass; uid_t r_uid; r_uid = getuid(); if((pass = getpwuid(r_uid)) == NULL) exit_sys("getpwuid"); printf("Real User ID: %ju(%s)\n", (uintmax_t)r_uid, pass->pw_name); uid_t e_uid; e_uid = geteuid(); if((pass = getpwuid(e_uid)) == NULL) exit_sys("getpwuid"); printf("Effective User ID: %ju(%s)\n", (uintmax_t)e_uid, pass->pw_name); return 0; } void exit_sys(const char* msg) { perror(msg); exit(EXIT_FAILURE); } Pekiyi o anda çalışmakta olan bir prosesin "Real Group ID" ve "Effective Group ID" değerlerini nasıl temin edebiliriz? Bunun için de sırasıyla "getgid" ve "getegid" fonksiyonlarını kullanacağız. >>> "getgid" ve "getegid" fonksiyonları, aşağıdaki imzaya sahiptir: #include gid_t getgid(void); gid_t getegid(void); Bu fonksiyonlar da yine başarısız olamamaktadır. Yine geri dönüş değerinin türü de "typedef" edilmiştir ve hangi türe karşılık geldiği sistemden sisteme değişmektedir. Fakat tam sayı bir tür olması zorunluluktur. * Örnek 1, #include #include #include #include #include void exit_sys(const char* msg); int main(void) { /* # OUTPUT # Real Group ID: 14024(runner24) Effective Group ID: 14024(runner24) */ struct group* grp; gid_t r_gid; r_gid = getgid(); if((grp = getgrgid(r_gid)) == NULL) exit_sys("getgrgid"); printf("Real Group ID: %ju(%s)\n", (uintmax_t)r_gid, grp->gr_name); gid_t e_gid; e_gid = getegid(); if((grp = getgrgid(e_gid)) == NULL) exit_sys("getgrgid"); printf("Effective Group ID: %ju(%s)\n", (uintmax_t)e_gid, grp->gr_name); return 0; } void exit_sys(const char* msg) { perror(msg); exit(EXIT_FAILURE); } Aşağıda ise yukarıda işlenen dört fonksiyonun tek bir programdaki kullanımı gösterilmiştir: * Örnek 1, #include #include #include #include #include #include void exit_sys(const char* msg); int main(void) { /* # OUTPUT # Real User ID: 14011(runner11) Real Group ID: 14011(runner11) Effective User ID: 14011(runner11) Effective Group ID: 14011(runner11) */ struct passwd* pass; struct group* grp; uid_t r_uid; r_uid = getuid(); if((pass = getpwuid(r_uid)) == NULL) exit_sys("getpwuid"); printf("Real User ID: %ju(%s)\n", (uintmax_t)r_uid, pass->pw_name); gid_t r_gid; r_gid = getgid(); if((grp = getgrgid(r_gid)) == NULL) exit_sys("getgrgid"); printf("Real Group ID: %ju(%s)\n", (uintmax_t)r_gid, grp->gr_name); uid_t e_uid; e_uid = geteuid(); if((pass = getpwuid(e_uid)) == NULL) exit_sys("getpwuid"); printf("Effective User ID: %ju(%s)\n", (uintmax_t)e_uid, pass->pw_name); gid_t e_gid; e_gid = getegid(); if((grp = getgrgid(e_gid)) == NULL) exit_sys("getgrgid"); printf("Effective Group ID: %ju(%s)\n", (uintmax_t)e_gid, grp->gr_name); return 0; } void exit_sys(const char* msg) { perror(msg); exit(EXIT_FAILURE); } Pekiyi bizler proseslerin ID değerlerini nasıl değiştirebiliriz? İşte bu durumda devreye şu dört fonksiyon girmektedir; "setuid", "seteuid", "setgid" ve "setegid". Bu dört fonksiyon da başarı durumunda "0" ile başarısızlık durumunda "-1" ile geri döner ve "errno" değişkenini uygun bir değerle değiştirir. >>> "setuid" fonksiyonu, aşağıdaki gibi bir parametrik yapıya sahiptir: #include int setuid(uid_t uid); Bu fonksiyon, eğer kendisini çağıran proses "priviledged-user" ise, kendisinin "Real User ID", "Effective User ID" ve "Saved User ID" değerlerini argüman olarak aldığı ID değeri ile değiştirmektedir. Ayrı ayrı ID değerlerini değiştirmemekte, üçünü birden değiştirmektedir. Bu noktada "getuid" fonksiyonundan ayrılmaktadır çünkü o fonksiyon sadece "Real User ID" değerini bize geri döndürmektedir. Pekiyi bu fonksiyonu çağıran proses "priviledged-user" değilse, nasıl işlev görecek? Eğer argüman olarak aldığı ID değeri bu fonksiyonu çağıran prosesin "Real User ID" ya da "Saved User ID" değerine eşitse, artık "Effective User ID" değerini iş bu argüman olarak alınan ID değerine eşit olacak. Özetle; -> Eğer prosesimiz "priviledged-user" ise bu fonksiyon ile prosesimizin "Real User ID", "Effective User ID" ve "Saved User ID" değerlerinin üçü birden değiştirilir. -> Eğer prosesimiz "priviledged-user" DEĞİL İSE ve bu fonksiyona geçilen ID değeri prosesimizin "Real User ID" ya da "Saved User ID" değerine eşitse, sadece "Effective User ID" değeri değiştirilir. Şu örneği inceleyelim; ID değerleri aşağıdaki gibi olan bir prosesimiz olsun. Real User ID: kaan Effective User ID: kaan Saved User ID: kaan ... Bu proses "set-user-id" bayrağı "set" edilmiş, Kullanıcı ID ve Group ID değeri "root" olan bir dosyayı çalıştırsın. Artık prosesimiz aşağıdaki ID değerlerine sahip olacaktır. Real User ID: kaan Effective User ID: root Saved User ID: root ... Şimdi de bir sonraki paragrafta detaylarını göreceğimiz "seteuid" fonksiyonu ile prosesimizin sadece "Effective User ID" değerini "kaan" olarak değiştirelim. Artık prosesimiz aşağıdaki ID değerlerine sahip olacaktır; Real User ID: kaan Effective User ID: kaan Saved User ID: root ... Şimdi ise "setuid" fonksiyonuna çağrı yaparak yeniden "root" olabiliriz. Çünkü bizler "priviledged-user" değiliz. Bu durumda argüman olarak geçilen ID değeri ya "Real User ID" değerine ya da "Saved User ID" değerine eşit olmalıdır. Artık prosesimiz aşağıdaki ID değerlerine sahip olacaktır; Real User ID: root Effective User ID: root Saved User ID: root ... İşte "Saved User ID" değerinin esas var oluş amacı da "root" a yükseltilen ID değerini "kaan" a düşürmek ve tekrardan "root" haline getirmektir. >>> "seteuid" fonksiyonu, aşağıdaki gibi bir parametrik yapıya sahiptir: #include int seteuid(uid_t uid); Bu fonksiyon, eğer kendisini çağıran proses "priviledged-user" ise sadece "Effective User ID" değerini argüman olarak aldığı ID değeri ile değiştirmektedir. Bu fonksiyon, "setuid" fonksiyonundan daha sık kullanılmaktadır. Pekiyi bu fonksiyonu çağıran proses "priviledged-user" değilse, nasıl işlev görecek? Tıpkı "setuid" gibi işlev görecektir. Yani, kendisini çağıran prosesin "Real User ID" değeri ya da "Saved User ID" değeri argüman olarak alınan ID değerine eşitse, prosesimizin sadece "Effective User ID" değerini değiştirecektir. Şimdi de yukarıda anlatılan senaryonun üzerinden tekrar geçelim: Prosimizin işin başında aşağıdaki ID değerlerine sahip olsun; Real User ID: kaan Effective User ID: kaan Saved User ID: kaan ... Şimdi de "set-user-id" bayrağı "set" edilmiş, Kullanıcı ID ve Group ID değeri "root" olan bir programı prosesimiz ile "exec" yapalım. Artık yeni ID değerleri aşağıdaki gibi olacaktır; Real User ID: kaan Effective User ID: root Saved User ID: root ... Şimdi iş bu programımız da bünyesinde aşağıdaki gibi bir fonksiyon çağrısı barındırsın; ... seteuid(getuid()); ... Artık bizim esas prosesimiz aşağıdaki ID değerlerine sahip olacaktır; Real User ID: kaan Effective User ID: kaan Saved User ID: root ... Bu noktada prosesimiz "root" durumundan "kaan" durumuna düşmüş oldu. Artık "root" gibi işlem yapamayız. "kaan" olarak bir takım işlemler yaptıktan sonra tekrardan "root" olmak isteyelim. Bu durumda aşağıdaki kod parçacıkları işimizi görecektir; ... seteuid(0); Yukarıdaki fonksiyon çağrısı yerine aşağıdaki fonksiyon çağrısı da aynı işlevi görecektir çünkü bizim prosesimiz artık "priviledged-user" değil ve argüman olarak geçilen ID değeri de bizim "Saved User ID" değerine eşit. Artık prosesimiz aşağıdaki ID değerlerine sahip olacaktır. Real User ID: kaan Effective User ID: root Saved User ID: root ... Bu noktada prosesimiz "kaan" durumundan "root" durumuna yükselmiş oldu. Artık "root" olarak hayatımıza devam edebiliriz. Buradan da görüleceğiz üzere saklı ID değerleri kavramı hiç olmasaydı, bu tip geri dönüşler mümkün olmayacaktı. İşte bu nedenden dolayı saklı ID değerleri vardır. Eğer yapacaklarımız arasında bu tip kıdem düşüşü yoksa/yükselişi yoksa, saklı ID ile uğraşmamıza gerek yoktur. >>> "setgid" ve "setegid" fonksiyonları da sırasıyla "setuid" ve "seteuid" fonksiyonlarıyla aynı semantiğe sahiptir. Kullanıcı yerine Group değişmektedir. İş bu fonksiyonlar aşağıdaki parametrik yapıya sahiptir; #include int setgid(gid_t gid); int setegid(gid_t gid); Bu fonksiyonlar, Group ID değerleri çok önemli olmadığından, pek fazla kullanılmamaktadırlar. Yine "setuid" ve "seteuid" fonksiyonlarındaki aynı senaryo, bu fonksiyonlarda da geçerlidir. Şöyleki; İşin başında prosesimiz aşağıdaki ID değerlerine sahip olsun: Real User ID: kaan Effective User ID: kaan Saved User ID: kaan Real Group ID: study Effective Group ID: study Saved Group ID: study Şimdi biz "set-group-id" bayrağı "set" edilmiş, Kullanıcı ID değeri "ali" ve Group ID değeri "test" olan bir programa "exec" uygulayalım. Artık bizim prosesimizin yeni ID değerleri aşağıdaki gibi olacaktır. Real User ID: kaan Effective User ID: kaan Saved User ID: kaan Real Group ID: study Effective Group ID: test Saved Group ID: test Şimdi hayata gelen yeni program ise bünyesinde şöyle kodlar barındırsın; ... setegid(getgid()); ... Artık bizim esas prosesimizin ID değerleri şöyle olacaktır: Real User ID: kaan Effective User ID: kaan Saved User ID: kaan Real Group ID: study Effective Group ID: study Saved Group ID: test Artık bu noktada prosesimizin Group ID değeri "test" den "study" olmuş oldu. Bir takım işlemler yaptıktan sonra tekrardan geri dönmek isteyelim; ... setegid(test_gid); ... Artık prosesimiz aşağıdaki ID değerlerine sahip olacaktır; Real User ID: kaan Effective User ID: kaan Saved User ID: kaan Real Group ID: study Effective Group ID: test Saved Group ID: test Bu dört fonksiyonu da kısaca özetlemek gerekirse; -> Eğer "priviledged-user" isek, -> "setuid" ve "setgid" fonksiyonları ile prosesimizin bütün ID değerlerini değiştiririz. -> "seteuid" ve "setegid" fonksiyonları ise sadece "Effective User ID" ve "Effective Group ID" değerlerini değiştirir. -> Eğer "priviledged-user" değilsek ve prosesimizin "Real User ID"/"Real Group ID" ya da "Saved User ID"/"Saved Group ID" değerleri argüman olarak alınan ID değerine eşitse, "setuid"/"setgid" ve "seteuid"/"setegid" fonksiyonları sadece prosesimizin "Effective User ID"/"Effective Group ID" değerini değiştirir. Ayrıca, eğer bizler bu tip geri dönmeler ile işimiz yoksa, saklı ID değerleriyle bir işimiz yoktur. Son olarak, "Advanced Programming In The Linux Env." ya da "The Linux Programming Interface" isimli kitaplardan saklı ID değerlerine ilişkin kısımlara göz atılması tavsiye edilir. Şimdi yukarıda anlatılan dört fonksiyona ek olarak iki fonksiyon daha mevcuttur. Bunlar sırasıyla "setreuid" ve "setregid" isimli fonksiyonlardır. Aslında bu fonksiyonlara mutlak anlamda gerek yoktur ancak bazı işlemleri hızlandırmaktadır. Aslında "setreuid"/"setregid" fonksiyonları bir nevi "switch" yapmak için yazılmış bir fonksiyondur. Yani "Real User ID"/"Real Group ID" ile "Effective User ID"/"Effective Group ID" değerlerini birbiriyle değiştirmek için kullanılır. Fonksiyonlar aşağıdaki parametrik yapıya sahiptir: #include int setreuid(uid_t ruid, uid_t euid); int setregid(gid_t rgid, gid_t egid); Eğer değiştirilmek istenmeyen bir ID varsa, o parametre için "-1" değeri argüman olarak geçilir. Artık argüman geçilen diğer ID değeri değiştirilecektir. Fonksiyon aşağıdaki senaryoya göre çalışmaktadır: -> Eğer "priviledged-user" isek, her iki ID de değiştirilir. -> Eğer "priviledged-user" değil isek ve argüman olarak geçilen ID değeri prosesimizin "Real", "Effective" ya da "Saved" ID değerlerinden birine eşitse, "setreuid" fonksiyonu sadece "Effective" olan ID değeri değiştirilir. "setregid" fonksiyonunun aynı eşitlemeyi yapabilmesi için argüman olan ID ile ya "Real" ya da "Saved" ID değerinin eşit olması gerekmektedir. Son olarak, "setreuid" fonksiyonu bazında, "Real" ID değerinin "Effective" ya da "Saved" ID değerine eşitlenmesi POSIX standartlarınca tanımlı değildir. Fakat Linux sistemlerinde bu davranış tanımlıdır fakat o da sadece "Effective" ID üzerinde işlem yapmaktadır. Ama "setregid" fonksiyonu için bahsi geçen bu son davranış TANIMLIDIR. Ek olarak eğer prosesin "Real" ID değeri değiştirilmişse ya da "Effective" ID değeri "Real" ID değerinden farklı bir değere eşitlenmişse, bu durumda "Saved" ID değeri yeni "Effective" ID değerine eşitlenecektir. Öte yandan bu fonksiyona geçilen argümanların bir tanesi bile uygun değilse, fonksiyon tümüyle başarısız olmaktadır. Tabii başarı durumunda "0", başarısızluk durumunda "-1" ile geri döner ve "errno" uygun bir şekilde "set" edilir. Örneğin, aşağıdaki gibi bir fonksiyon çağrısı yapmış olalım; ... setreuid(geteuid(), getuid()); Burada yapılan şey "Effective User ID" değerini "Real User ID" değerine atamak, "Real User ID" değerini de "Effective User ID" değerine atamak. Bu fonksiyonu çağıran prosesin İLK HALİ DE AŞAĞIDAKİ GİBİ OLSUN; Real User ID: kaan Effective User ID: root Saved User ID: root ... Fakat yukarıdaki fonksiyon çağrısı sonrasında ID değerleri aşağıdaki gibi olacaktır; Real User ID: root Effective User ID: kaan Saved User ID: root ... Burada "Saved User ID" değiştirilmedi çünkü "Effective User ID" değeri "Real User ID" değerine atandı. Başka bir ID değerine atansaydı, o da değiştirilecekti. Eğer tekrar eski haline dönmek istiyorsak, yine yukarıdaki fonksiyon çağrısını yapmalıyız. Eğer bu fonksiyonu çağıran proses "priviledged-user" OLMASAYDI, "Real User ID" değerinin değiştirilmesi POSIX standartlarınca ucu açık bırakılmıştır. Buradaki "switch" ile kastedilen işte bu şekildeki fonksiyon çağrısıdır. Aksi halde "Real User ID" ve "Effective User ID" değerlerini başka değerlere de "set" edebiliriz eğer yeni ID değerleri uygun ise. Tabii bütün bu altı fonksiyon için en doğru kaynak yine POSIX dökümantasyonudur. Fonksiyonu kullanmadan evvel ilgili dökümantasyondan son kontrol yapılması tavsiye edilir. Linux türevi sistemlerde ise bu üç ID değerini alabileceğimiz bir fonksiyon vardır. Bu fonksiyonlar "getresuid" ve "getresgid" isimli fonksiyonlardır. "uid_t"/"gid_t" türünden adres alırlar ve ID değerlerini bu adresin içine yazarlar. Bu fonksiyonlar için de "#define _GNU_SOURCE" tanımlamasını yapmalıyız. Aksi halde bu fonksiyonları kullanamayız. * Örnek 1, #define _GNU_SOURCE #include #include #include #include #include #include void exit_sys(const char* msg); int main(void) { /* # OUTPUT # Real User ID: 14027(runner27) Effective User ID: 14027(runner27) Saved User ID: 14027(runner27) Real Group ID: 0(root) Effective Group ID: 0(root) Saved Group ID: 0(root) */ struct passwd* pass; struct group* grp; uid_t r_uid, e_uid, s_uid; gid_t r_gid, e_gid, s_gid; if(getresuid(&r_uid, &e_uid, &s_uid) == -1) exit_sys("getresuid"); if((pass = getpwuid(r_uid)) == NULL) exit_sys("getpwuid"); printf("Real User ID: %ju(%s)\n", (uintmax_t)r_uid, pass->pw_name); if((pass = getpwuid(e_uid)) == NULL) exit_sys("getpwuid"); printf("Effective User ID: %ju(%s)\n", (uintmax_t)e_uid, pass->pw_name); if((pass = getpwuid(s_uid)) == NULL) exit_sys("getpwuid"); printf("Saved User ID: %ju(%s)\n", (uintmax_t)s_uid, pass->pw_name); if((grp = getgrgid(r_gid)) == NULL) exit_sys("getgrgid"); printf("Real Group ID: %ju(%s)\n", (uintmax_t)r_gid, grp->gr_name); if((grp = getgrgid(e_gid)) == NULL) exit_sys("getgrgid"); printf("Effective Group ID: %ju(%s)\n", (uintmax_t)e_gid, grp->gr_name); if((grp = getgrgid(s_gid)) == NULL) exit_sys("getgrgid"); printf("Saved Group ID: %ju(%s)\n", (uintmax_t)s_gid, grp->gr_name); return 0; } void exit_sys(const char* msg) { perror(msg); exit(EXIT_FAILURE); } ÖZETLE "set-user-id"/"set-group-id" bayrakları "set" edilmiş bir programı çalıştırırken prosesimizin ID değerini kısa süreliğine değiştirip tekrar eski haline getirmek için iş bu saklı ID ve burada bahsedilen 6-7 fonksiyonu kullanabiliriz. Linux sistemlerinde mevcut olan "getresuid" ve "getresgid" fonksiyonlarının "set" eden versiyonları da vardır. İsimleri "setresuid" ve "setresgid" biçimindedir. Bu fonksiyonların imzaları aşağıdaki gibidir; #define _GNU_SOURCE /* See feature_test_macros(7) */ #include int setresuid(uid_t ruid, uid_t euid, uid_t suid); int setresgid(gid_t rgid, gid_t egid, gid_t sgid); Uygun önceliğe sahip olmayan prosesler "Real", "Effective" ve "Saved" ID değerlerini ancak ve ancak o anki "Real", "Effective" ve "Saved" ID değerlerinden birisiyle değiştirebilirler. Başka bir ID değeriyle değiştiremezler. Fakat prosesimiz uygun önceliklere sahipse, yani "priviledged-user" ise, iş bu ID değerlerini herhangi bir ID değeriyle değiştirebilirler. Yine bu fonksiyona geçilen uygunsuz bir argüman durumunda fonksiyon tümüyle başarısız olur. Yine bu fonksiyon için de "#define _GNU_SOURCE" tanımlamasını yapmalıyız. Aksi halde bu fonksiyonları kullanamayız. >> Proseslerin Ek Grupları("Supplementary Groups") : Anımsanacağı üzere bir prosesin bir dosyaya erişim hakları sorgulanırken ilk önce prosesin "root" olup olmadığına bakılıyor. "root" olduğu anlaşıldığında bu sorgulama sonlanıyor ve bütün haklar ilgili prosese veriliyordu. Eğer "root" olmadığı anlaşılırsa, ilgili prosesin sırasıyla o dosyanın sahibi mi yoksa aynı grupta mı olup olmadığı sorgulanmaktaydı. En son da ilgili prosesin diğer kullanıcı olduğu varsayılıyordu. İşte sorgulama grup aşamasına geldiğinde sadece prosesin grup ID değerlerine değil, aynı zamanda prosesin sahip olduğu ek grup ID değerlerine de bakılıyor. Eğer bunlardan birisi uyuşuyorsa, ilgili dosya ile prosesin aynı grupta olduğu anlaşılmaktadır. Buradaki önemli nokta bir prosesin grup ID değerleri ile ek gruplarının ID değerleri aynı kıdem seviyesinde olmasıdır. Burada amaçlanan şey bir prosesin biden fazla grup ID değerine sahip olmasıdır. İşte bu ek gruplara da "supplementary group" denmektedir. Pekiyi bir prosesin ek grup ID değerleri nasıl tespit ediliyor? Hatırlayacağınız üzere "/etc/passwd" dosyasdından bakarak bir prosesin "Real User ID" ve "Real Group ID" değerleri temin edilmektedir. Bu işlem tabii "login" programı tarafından yürütülmektedir. >>> "login" programı şematik olarak şu şekilde işlev görmektedir; -> "/etc/passwd" dosyasına başvurarak ilk önce o kullanıcıya için parola doğrulaması yapmaktadır. Tabii bunun için "/etc/shadow" dosyasına da başvurması gerekebilir eğer şifre bu dosyada ise. -> Eğer doğrulama işlemi başarılı ise yine "/etc/passwd" dosyasına başvurarak burada belirtilen Kullanıcı ID ve Group ID değerlerini ilgili prosesin "Real", "Effective" ve "Saved" ID değerleri olarak atıyor. Bu işlemi ise "setuid" ve "setgid" fonksiyonları ile yapmaktadır. -> "chdir" fonksiyonuyla prosesin "Current Working Directory" konumunu, "/etc/passwd" dosyasında belirtilen konum olarak güncelle. -> Daha sonra "exec" fonksiyonu ile "/etc/passwd" dosyasında belirtilen "shell" programı çalıştırılır. Burada "login" programının "fork" yapmadan direkt olarak "exec" yaptığına dikkat ediniz. Ek gruplar ise önce "/etc/passwd", sonra "/etc/groups" dosyalarından temin edilmektedir. "/etc/groups" dosyasındaki her bir satır bir gruba aittir ve her bir satırın sonunda ise o gruba dahil olanlar belirtilmiştir. İşte "/etc/passwd" dosyasından elde edilen grup ID değeri, "/etc/groups" dosyasında tek tek aranıyor ve bir yerde toplanıyor. Tipik bir "/etc/groups" dosyası aşağıdaki gibidir: ... r9:x:14009: runner10:x:14010: kaan, ali runner11:x:14011: kaan runner12:x:14012: serdar ... Prosesin ek grupları da yine prosesin kontrol bloğunda saklanmaktadır. Bu ek grupları temin etmek için POSIX sistemlerinde "getgroups" isimli bir fonksiyon mevcuttur. Bu fonksiyon, ek gruplara ilişkin ID değerlerini prosesin kontrol bloğundan temin etmektedir. İlgili fonksiyonun prototipi aşağıdaki şekildedir; #include int getgroups(int gidsetsize, gid_t grouplist[]); Fonksiyonun ikinci parametresi, elemanları "gid_t" türünden olan bir dizinin başlangıç adresi. Dizinin birinci parametresi ise iş bu dizinin toplam eleman sayısıdır. Fonksiyon, başarılı olması durumunda olması gereken ek grup ID adedine geri dönmektedir. Başarısızlık durumunda ise "-1" ile. Eğer bu fonksiyona geçilen dizinin eleman sayısı, olması gereken ek grup ID adedinden az ise fonksiyon başarısız olacaktır. Ek olarak birinci parametreye "0" değerini geçersek, olması gereken ID adedini döndürür ve ikinci parametre ile geçilen diziye de bu ID değerlerini İŞLEMEZ. Pekiyi bir ek grup adedi ne kadardır? Bunun cevabı sistemden sisteme değişkenlik gösterdiği için, "limits.h" dosyasında tanımlı, "NGROUPS_MAX" isimli sembolik sabitini kullanabiliriz. Fakat bu sembolik sabit çalışma zamanında artabilir. Bu durumda da "sysconf" fonksiyonu ile son halini "get" etmeliyiz. Öte yandan, bütün bunlar ile uğraşmak yerine, iş bu fonksiyonun birinci parametresine "0" geçmeli ve geri dönüş değeri kadarlık bir alanı bellekte tahsis etmeliyiz. Ayrıca POSIX standartlarınca proseslerin "Effective Group ID" değerinin bu diziye yazılıp yazılmayacağı, işletim sistemini yazanlara bırakılmıştır ama Linux sistemleri "Effective Group ID" değerini de bu diziye yazmaktadır. Dolayısıyla bellekte yer tahsis ederken "NGROUPS_MAX + 1" büyüklüğünde yer tahsis etmeliyiz. Ancak bu fonksiyonun birinci parametresine "0" geçildiğinde geri döndürülen değer "Effective Group ID" değerini de kapsamaktadır. Özetle; -> "NGROUPS_MAX + 1" büyüklüğünde bellekte bir yer tahsis edilir. Fonksiyon başarısız olursa, "sysconf" fonksiyonu ile esas büyüklük alınır ve bellekteki bu alan tekrar genişletilir. -> Direkt olarak "sysconf" fonksiyonu ile esas büyüklük elde edilir ve bu büyüklük kullanılarak bellekte yer açılır. -> Dizi uzunluğu yeteri kadar büyük bir değer olduğu varsayılır. Fakat bu fonksiyonun başarısı da kontrol edilmelidir. -> Bu fonksiyonun birinci parametresine "0" geçilir ve olması gereken ID değerlerinin adedi alınır. Bu adet sayısına göre bellekte yer tahsisi yapılır. Şimdi de örneklerle irdelemeye devam edelim: * Örnek 1, #include #include #include #include #include void exit_sys(const char* msg); int main(void) { int n_groups; if((n_groups = getgroups(0, NULL)) == -1) exit_sys("getgroups"); printf("[%d] => ", n_groups); gid_t* sgids; if((sgids = (gid_t*)malloc(n_groups * sizeof(gid_t))) == NULL) { fprintf(stderr, "Not enough of memory!...\n"); exit(EXIT_FAILURE); } if(getgroups(n_groups, sgids) == -1) exit_sys("getgroups"); struct group* grp; for(int i = 0; i < n_groups; ++i) { if((grp = getgrgid(sgids[i])) == NULL) exit_sys("getgrgid"); if(i != 0) printf(", "); printf("%ju(%s) ", (uintmax_t)sgids[i], grp->gr_name); } /* Tamponu sıfırlamak için çağrılmıştır. */ printf("\n"); free(sgids); return 0; } void exit_sys(const char* msg) { perror(msg); exit(EXIT_FAILURE); } Bu ek grupları "set" etmek için bir fonksiyon POSIX fonksiyonlarında mevcut değildir. Fakat Linux sistemlerinde "setgroups" isminde bir fonksiyon tanımlamışlardır. Fonksiyonun prototipi aşağıdaki gibidir; #include int setgroups(size_t size, const gid_t *list); Fonksiyonun ikinci parametresi, "set" edilecek ek grupların bulunduğu dizinin başlangıç adresidir. Bu dizideki ID değerleri, prosesin ek grup ID değeri olarak atanacaktır. Fakat bu listeye, prosesin "Effective Group ID" değeri eklenmemelidir. Başarı durumunda proses "0", başarısız durumda ise "-1" ile geri dönmektedir. Yine unutmamalıyız ki bu fonksiyonu çağırabilmek için de prosesin uygun önceliğe sahip olması gerekmektedir. Fonksiyonun birinci parametresi ise yine ikinci parametre ile geçilen dizinin toplam eleman sayısını belirtmektedir. * Örnek 1, #include #include #include #include #include void exit_sys(const char* msg); int main(void) { /* # OUTPUT # [0] => setgroups: Operation not permitted */ int n_groups; if((n_groups = getgroups(0, NULL)) == -1) exit_sys("getgroups"); printf("[%d] => ", n_groups); gid_t* sgids; if((sgids = (gid_t*)malloc(n_groups * sizeof(gid_t))) == NULL) { fprintf(stderr, "Not enough of memory!...\n"); exit(EXIT_FAILURE); } if(getgroups(n_groups, sgids) == -1) exit_sys("getgroups"); struct group* grp; for(int i = 0; i < n_groups; ++i) { if((grp = getgrgid(sgids[i])) == NULL) exit_sys("getgrgid"); if(i != 0) printf(", "); printf("%ju(%s) ", (uintmax_t)sgids[i], grp->gr_name); } printf("\n"); if(setgroups(n_groups, sgids) == -1) exit_sys("setgroups"); free(sgids); return 0; } void exit_sys(const char* msg) { perror(msg); exit(EXIT_FAILURE); } * Örnek 2, Bu programın "set-user-id" bayrağının "set" edilmiş olması gerekmektedir. Bunun için de ilk önce "sample" programının "User ID" değerini "sudo chown root sample" komutuyla "root" yapmalıyız. Daha sonra "sudo chmod u+s sample" komutunu "shell" programında çalıştırmalıyız ki "sample" programının "set-user-id" bayrağı "set" edilsin. Sıralama bu şekilde olmalıdır. Programı da normal kullanıcı olarak çalıştırmalıyız. #include #include #include #include #include void exit_sys(const char* msg); int main(void) { uid_t e_uid; e_uid = geteuid(); int n_groups; if((n_groups = getgroups(0, NULL)) == -1) exit_sys("getgroups"); printf("[%d] => ", n_groups); gid_t* sgids; if((sgids = (gid_t*)malloc(n_groups + 1 * sizeof(gid_t))) == NULL) { fprintf(stderr, "Not enough of memory!...\n"); exit(EXIT_FAILURE); } if(getgroups(n_groups, sgids) == -1) exit_sys("getgroups"); struct group* grp; for(int i = 0; i < n_groups; ++i) { if((grp = getgrgid(sgids[i])) == NULL) exit_sys("getgrgid"); if(i != 0) printf(", "); printf("%ju(%s) ", (uintmax_t)sgids[i], grp->gr_name); } printf("\n"); sgids[n_groups] = 0; if(setgroups(n_groups + 1, sgids) == -1) exit_sys("setgroups"); free(sgids); /* * Prosesin bütün ID değerleri, * "Real User ID" değerine çekildi. */ if(setuid(getuid()) == -1) exit_sys("setuid"); printf("Success!...\n"); return 0; } void exit_sys(const char* msg) { perror(msg); exit(EXIT_FAILURE); } "fork" işlemi sırasında üst prosesin ek grupları, hayata getirilen alt prosese aktarılmaktadır. Örneğin, "shell" programı üzerinden başka bir program çalıştırdığımız zaman, "shell" programının uyguladığı "fork" neticesinde, "shell" programının ek grupları iş bu hayata yeni getirilen prosese aktarılmaktadır. "shell" programı da kendisine ait olan bu ek grupları, "login" programından almaktadır. Öte yandan POSIX dünyasında olmayan ama Linux dünyasında kullanılan bir fonksiyon daha vardır ki bu fonksiyon tipik olarak "login" programı tarafından kullanılmaktadır. İş bu fonksiyonumuzun ismi "initgroups". Aşağıdaki şekilde bir parametrik yapıya sahiptir; #include int initgroups(const char *user, gid_t group); Birinci parametresiyle de kullanıcının İSMİNİ almaktadır. Bu ismi "/etc/group" dosyası içerisinde tek tek aramaktadır. Daha sonra "setgroups" fonksiyonuyla prosesin bu ek gruplarını "set" eder. Tabii fonksiyonun bu işlemi yapabilmesi için "priviledged-user" bir proses olması gerekmektedir. Başarısızlık durumunda "-1", başarı durumunda "0" ile geri döner. Fonksiyon ikinci parametresiyle EKLENECEK yeni ID değerini alıyor. "login" programı tipik olarak bu argümana prosesin "Effective Group ID" değerini geçmektedir çünkü "/etc/group" dosyasında "Effective Group ID" değeri BULUNMAMAKTADIR. > Proseslerin "Umask" değerleri, >> Bu değerler proseslerin kontrol blokları içerisinde saklanmaktadır ve "parent" prosesten aktarılmaktadır. Türü "mode_t" ile ifade edilir. >> "umask" isimli POSIX fonksiyonu ile ayrıca "get" ve "set" işlemleri yapılabilmektedir. >> Bir dosya açarken, o dosyaya vermiş olduğumuz erişim haklarının YANSITILMAMASI için kullanılır. Yani "umask" değerleri, maskelenecek değerleri belirtmektedir. Dolayısıyla "umask" değerleri neyse, bu değerler dosyaya yansıtılmayacaktır. Eğer "umask" değeri sıfır ise maskelecek değer olmadığı anlamına gelip, bütün değerler dosyaya yansıtılacaktır. * Örnek 1, "umask" değerleri => S_IXUSR | S_IWGRP Dosyaya verilen değerler => rwxrwxrwx Dosyaya yansıtılan değerler => rw-r-xrwx * Örnek 2, "umask" değerleri => 0 Dosyaya verilen değerler => rwxrwxrwx Dosyaya yansıtılan değerler => rwxrwxrwx >> "shell" programının "umask" değerini, "umaks" isimli komut ile elde edebiliriz. Bu değer genel olarak "0022" ya da "0002" oktal değerlerinden birisi olmaktadır. >>> Bu oktal değerlerden her bir basamak aslında üç adet "bit" değerinin toplamını göstermektedir. Örneğin, "0002" oktal sayısını "000 000 000 010" bitleri ile gösterebiliriz. Bu bit grubunun, >>>> En sol taraftaki üçlü olanlar sırasıyla "set_userid", "set_grpid" ve "sticky_bit" bitlerini temsil etmektedir. >>>> Soldan ikinci üçlü olanlar "owner" grubu için sırasıyla "r", "w" ve "x" haklarını temsil etmektedir. >>>> Soldak üçüncü olanlar ise "group" grubu için sırasıyla "r", "w" ve "x" haklarını temsil etmektedir. >>>> En sağdakiler ise "other" grubu için sırasıyla "r", "w" ve "x" haklarını temsil etmektedir. Buradan hareketle "0002" oktal rakamı aslında "other" grubu için "w" hakkının maskeleneceğini belirtmektedir. Tekrar hatırlatmakta fayda olacaktır; "umask" komutu bir "shell" komutudur ve "shell" programı üzerinden çalıştırılacak bütün programların "umask" değerini bize gösterir. Her ne kadar prosesler kendi "umask" değerini değiştirebilse de, "umask" değeri babadan oğula geçen bir değerdir. Bir proses, kendi "umask" değerini değiştirmek için de yine aynı isimdeki POSIX fonksiyonunu kullanır. >> "shell" programının kendi "umask" değerini "umask" komutu ile değiştirirken, yeni maskelerin değerini oktal dijitler halinde vermeliyiz. Örneğin, "shell" programı üzerinden "umask" komutu ile "shell" prosesinin maske değerinin "0002" olduğunu öğrenelim. O halde yine aynı program üzerinden "umask 022" komutunu çalıştırdığımız zaman, yeni maske değerimiz "0022" olacaktır. En yüksek anlamlı oktal dijiti bizler geçmediğimiz için "0" dijiti geçildi. Böylelikle yeni maske değerimiz "0022" oldu. Bu da "group" ve "other" kimseler için "w" hakkının maskeleneceği anlamına gelmektedir. Artık bu "shell" prosesinin çalıştırdığı her bir prosesin varsayılan maske değeri "0022" olacaktır. Bizler "022" yerine "22" girseydik de yine sonuç aynı olacaktır çünkü "shell" prosesi en yüksek anlamlı bitlerden girilmeyenleri "0" olarak girildiğini varsayıyor. >> "shell" programının maske değerlerini tamamiyle ortadan kaldırmak için "umask 0" komutunu çalıştırmamız gerekmektedir. Böylece artık bu "shell" programından çalıştırılan bütün proseslerin "umask" değerleri de sıfır olacaktır. Fakat yep yeni bir "shell" programı çalıştırdığımız zaman artık varsayılan "umask" değerlerini kullanacaktır. Kalıcı olarak "shell" programının varsayılan maske değerini değiştirme yöntemlerini ileride göreceğiz("shell" programının "start-up" dosyaları ile oynayarak). >> Prosesler, kendi "umask" değerlerini değiştirmek için, "umask" isimli POSIX fonksiyonunu çağırmaları gerekmektedir. Geri dönüş değeri eski "umask" değeridir. Başarısızlık senaryosu mevcut değildir. "sys/stat.h" isimli başlık dosyasında tanımlanmıştır. Buradan da görüleceği üzere, prosesimizin "umask" değerini elde edebilmek için öncelikle onu değiştirmemiz gerekmektedir. "umask" isimli POSIX fonksiyonuna oktal değerler geçebildiğimiz gibi S_IXXX şeklindeki sembolik sabitleri de argüman olarak geçebiliriz.Fakat çok eski sistemler için hala S_IXXX şeklindeki sembolik sabitleri kullanmalıyız eğer o sistemler standartları takip etmedilerse. Okunabilirliği arttırdığı gerekçesiyle, S_IXXX şeklindeki sembolik sabitlerin argüman olarak geçilmesi tavsiye edilmektedir. Ek bilgi olarak hatırlatmakta fayda vardır; "umask" POSIX fonksiyonuna oktal değer geçilmesi, "rwx" şeklinde üçerli grupların daha rahat ifade edilebilmesinden kaynaklıdır. Oktal değer yerine 16'lık tabanda bir değer de geçilebilir, 10'luk tabanda bir değer de, 2'lik tabanda bir değer de. ÇÜNKÜ ARTIK S_IXXX ŞEKLİNDEKİ SEMBOLİK SABİTLERİN SAYISAL DEĞERLERİ SİSTEMDEN SİSTEME DEĞİŞMEMEKTEDİR. Başka proseslerin "umask" değerlerini herhangi bir fonksiyon üzerinden değiştiremeyiz, spesifik olarak bir prosesinkini yapamayız yani. * Örnek 1, #include #include #include #include #include void exit_sys(const char* msg); mode_t get_umask_value(void); int main() { /* # OUTPUT # Initial umask : [18] After zero-ing umask : [0] After tweaking umask : [146] Success..! */ printf("Initial umask : [%d]\n", get_umask_value()); umask(0); printf("After zero-ing umask : [%d]\n", get_umask_value()); umask(S_IWUSR | S_IWGRP | S_IWOTH); printf("After tweaking umask : [%d]\n", get_umask_value()); int fd; if( (fd = open("test.txt", O_WRONLY | O_CREAT, S_IRWXU | S_IRWXG | S_IRWXO)) == -1) exit_sys("open"); printf("Success..!\n"); close(fd); return 0; } void exit_sys(const char* msg) { perror(msg); exit(EXIT_FAILURE); } mode_t get_umask_value(void) { mode_t mode = umask(0); umask(mode); return mode; } * Örnek 2, Our Bash Program(version 3.5); Daha önce geliştirdiğimiz "shell" programımıza "umask" komutunun eklenmesi (YÜKSEK ÖNCELİKLİ ÜÇ BİT, YANİ DAHA EVVEL GÖRMEDİKLERİMİZ, ELE ALINMAMIŞTIR. Argüman olarak OKTAL değer geçilmemiştir.) #include "stdio.h" #include "stdlib.h" #include "string.h" #include "unistd.h" #include "limits.h" #include "errno.h" #include #define MAX_CMD_LINE 4096 #define MAX_CMD_PARAMS 128 char parse_cmd_line(void); mode_t get_umask_value(void); void dir_proc(void); void clear_proc(void); void pwd_proc(void); void cd_proc(void); void umask_proc(void); int check_umask_arg(const char* str); void exit_sys(const char* msg); typedef struct tagCMD{ char* name; void (*proc)(void); } CMD; CMD g_cmds[] = { {"dir", dir_proc}, {"clear", clear_proc}, {"pwd", pwd_proc}, {"cd", cd_proc}, {"umask", umask_proc}, {NULL, NULL} }; char g_cmdline[MAX_CMD_LINE]; char* g_params[MAX_CMD_PARAMS]; int g_nparams; char g_cwd[PATH_MAX]; int main(void) { char* str; int i; if(!getcwd(g_cwd, PATH_MAX)) exit_sys("getcwd"); for (;;) { fprintf(stdout, "CSD: %s> ", g_cwd); if (fgets(g_cmdline, MAX_CMD_LINE, stdin) == NULL) continue; if ((str = strchr(g_cmdline, '\n')) != NULL) *str = '\0'; parse_cmd_line(); if(g_nparams == 0) continue; if(!strcmp(g_params[0], "exit")) break; for(i = 0; g_cmds[i].name != NULL; ++i) if(!strcmp(g_params[0], g_cmds[i].name)) { g_cmds[i].proc(); break; } if(g_cmds[i].name == NULL) fprintf(stderr, "bad command: %s\n", g_params[0]); } return 0; } char parse_cmd_line(void) { char* str; g_nparams = 0; for(str = strtok(g_cmdline, " \t"); str != NULL; str = strtok(NULL, " \t")) { g_params[g_nparams++] = str; } } mode_t get_umask_value(void) { mode_t mode = umask(0); umask(mode); return mode; } void dir_proc(void) { fprintf(stdout, "dir command executing...\n"); } void clear_proc(void) { system("clear"); } void pwd_proc(void) { if (g_nparams > 1) { fprintf(stdout, "pwd command must be used w/o an argument!\n"); return; } fprintf(stdout, "%s\n", g_cwd); } void cd_proc(void) { // DEFAULT if (g_nparams > 2) { printf("Too mang arguments!..\n"); return; } // APPROACH - I if (g_nparams == 1) { printf("Too few arguments!..\n"); return; } // APPROACH - I if (chdir(g_params[1]) == -1) { printf("%s!\n", strerror(errno)); return; } /* // APPROACH - II char* dir; if (g_nparams == 1) { if((dir = getenv("HOME")) == NULL) exit_sys("getenv"); } else dir = g_params[1]; // APPROACH - II if (chdir(dir) == -1) { printf("%s!\n", strerror(errno)); return; } */ // DEFAULT if(!getcwd(g_cwd, PATH_MAX)) exit_sys("getcwd"); } void umask_proc(void) { if(g_nparams > 2) { printf("Too many arguments for umask command!...\n"); return; } if(g_nparams == 1) { mode_t mode = get_umask_value(); mode_t mode_bits[] = { S_IXOTH, S_IWOTH, S_IROTH, S_IXGRP, S_IWGRP, S_IRGRP, S_IXUSR, S_IWUSR, S_IRUSR, S_ISVTX, S_ISGID, S_ISUID }; unsigned int retval = 0; for(int i = 0; i < 12; ++i) if(mode & mode_bits[i]) retval |= 1 << i; printf("%04o\n", retval); return; } if(g_nparams == 2) { if(!check_umask_arg(g_params[1])) { printf("[%s] octal number out of range!...\n", g_params[1]); return; } unsigned int argval; sscanf(g_params[1], "%o", &argval); mode_t mode = 0; mode_t mode_bits[] = { S_IXOTH, S_IWOTH, S_IROTH, S_IXGRP, S_IWGRP, S_IRGRP, S_IXUSR, S_IWUSR, S_IRUSR, S_ISVTX, S_ISGID, S_ISUID }; for(int i = 0; i < 12; ++i) if(argval >> i & 1) mode |= mode_bits[i]; umask(mode); return; } } int check_umask_arg(const char* str) { if(strlen(str) > 4) return 0; for(int i = 0; str[i] != '\0'; ++i) if(str[i] < '0' || str[i] > '7') return 0; return 1; } void exit_sys(const char* msg) { perror(msg); exit(EXIT_FAILURE); } * Örnek 3, S_IXXX şeklindeki sembolik sabitlerin oktal karşılıkları; ┌────────┬───────────────┬──────────────────────────────────────────────┬─────────────────┐ ├ Name │ Numeric Value │ Description │ Bit-Wise │ ├────────┼───────────────┼──────────────────────────────────────────────┼─────────────────┤ │S_IRWXU │ 0700 │ Read, write, execute/search by owner. │ 000 111 000 000 │ │S_IRUSR │ 0400 │ Read permission, owner. │ 000 100 000 000 │ │S_IWUSR │ 0200 │ Write permission, owner. │ 000 010 000 000 │ │S_IXUSR │ 0100 │ Execute/search permission, owner. │ 000 001 000 000 │ ├────────┼───────────────┼──────────────────────────────────────────────┼─────────────────┤ │S_IRWXG │ 070 │ Read, write, execute/search by group. | 000 000 111 000 │ │S_IRGRP │ 040 │ Read permission, group. │ 000 000 100 000 │ │S_IWGRP │ 020 │ Write permission, group. │ 000 000 010 000 │ │S_IXGRP │ 010 │ Execute/search permission, group. │ 000 000 001 000 │ ├────────┼───────────────┼──────────────────────────────────────────────┼─────────────────┤ │S_IRWXO │ 07 │ Read, write, execute/search by others. │ 000 000 000 111 │ │S_IROTH │ 04 │ Read permission, others. │ 000 000 000 100 │ │S_IWOTH │ 02 │ Write permission, others. │ 000 000 000 010 │ │S_IXOTH │ 01 │ Execute/search permission, others. │ 000 000 000 001 │ ├────────┼───────────────┼──────────────────────────────────────────────┼─────────────────┤ │S_ISUID │ 04000 │ Set-user-ID on execution. │ │ │S_ISGID │ 02000 │ Set-group-ID on execution. │ N / A │ │S_ISVTX │ 01000 │ On directories, restricted deletion flag. │ │ └────────┴───────────────┴──────────────────────────────────────────────┴─────────────────┘ * Örnek 4, Our Bash Program(version 3.7); Sembolik sabitler yerine oktal rakamların kullanıldığı: #include "stdio.h" #include "stdlib.h" #include "string.h" #include "unistd.h" #include "limits.h" #include "errno.h" #include #define MAX_CMD_LINE 4096 #define MAX_CMD_PARAMS 128 char parse_cmd_line(void); mode_t get_umask_value(void); void dir_proc(void); void clear_proc(void); void pwd_proc(void); void cd_proc(void); void umask_proc(void); int check_umask_arg(const char* str); void exit_sys(const char* msg); typedef struct tagCMD{ char* name; void (*proc)(void); } CMD; CMD g_cmds[] = { {"dir", dir_proc}, {"clear", clear_proc}, {"pwd", pwd_proc}, {"cd", cd_proc}, {"umask", umask_proc}, {NULL, NULL} }; char g_cmdline[MAX_CMD_LINE]; char* g_params[MAX_CMD_PARAMS]; int g_nparams; char g_cwd[PATH_MAX]; int main(void) { /* # INPUT # CSD: /home> umask 22 CSD: /home> umask */ /* # OUTPUT # 0022 */ char* str; int i; if(!getcwd(g_cwd, PATH_MAX)) exit_sys("getcwd"); for (;;) { fprintf(stdout, "CSD: %s> ", g_cwd); if (fgets(g_cmdline, MAX_CMD_LINE, stdin) == NULL) continue; if ((str = strchr(g_cmdline, '\n')) != NULL) *str = '\0'; parse_cmd_line(); if(g_nparams == 0) continue; if(!strcmp(g_params[0], "exit")) break; for(i = 0; g_cmds[i].name != NULL; ++i) if(!strcmp(g_params[0], g_cmds[i].name)) { g_cmds[i].proc(); break; } if(g_cmds[i].name == NULL) fprintf(stderr, "bad command: %s\n", g_params[0]); } return 0; } char parse_cmd_line(void) { char* str; g_nparams = 0; for(str = strtok(g_cmdline, " \t"); str != NULL; str = strtok(NULL, " \t")) { g_params[g_nparams++] = str; } } mode_t get_umask_value(void) { mode_t mode = umask(0); umask(mode); return mode; } void dir_proc(void) { fprintf(stdout, "dir command executing...\n"); } void clear_proc(void) { system("clear"); } void pwd_proc(void) { if (g_nparams > 1) { fprintf(stdout, "pwd command must be used w/o an argument!\n"); return; } fprintf(stdout, "%s\n", g_cwd); } void cd_proc(void) { // DEFAULT if (g_nparams > 2) { printf("Too mang arguments!..\n"); return; } // APPROACH - I if (g_nparams == 1) { printf("Too few arguments!..\n"); return; } // APPROACH - I if (chdir(g_params[1]) == -1) { printf("%s!\n", strerror(errno)); return; } // APPROACH - II char* dir; if (g_nparams == 1) { if((dir = getenv("HOME")) == NULL) exit_sys("getenv"); } else dir = g_params[1]; // APPROACH - II if (chdir(dir) == -1) { printf("%s!\n", strerror(errno)); return; } // DEFAULT if(!getcwd(g_cwd, PATH_MAX)) exit_sys("getcwd"); } void umask_proc(void) { if(g_nparams > 2) { printf("Too many arguments for umask command!...\n"); return; } if(g_nparams == 1) { printf("%04o\n", (int)get_umask_value()); return; } if(g_nparams == 2) { if(!check_umask_arg(g_params[1])) { printf("[%s] octal number out of range!...\n", g_params[1]); return; } int argval; sscanf(g_params[1], "%o", &argval); umask(argval); return; } } int check_umask_arg(const char* str) { if(strlen(str) > 4) return 0; for(int i = 0; str[i] != '\0'; ++i) if(str[i] < '0' || str[i] > '7') return 0; return 1; } void exit_sys(const char* msg) { perror(msg); exit(EXIT_FAILURE); } >> 2008 yılından sonraki sistemlerde, "umask" fonksiyonuna parametre olarak oktal değerler geçebiliriz fakat önceki sistemlerde S_IXXX şeklindeki sembolik sabitleri kullanmalıyız. > Hatırlatıcı notlar: >> C dilinde sonu "_t" ile biten isimler genelde "typedef" edilmiş isimlerdir. POSIX standartlarında da benzer yaklaşım kullanılmıştır. POSIX sistemlerde "typedef" edilmiş isimlerin detaylarına "sys/types.h" isimli başlık dosyasından temin edebiliriz. C dilinde ise "stddef.h" isimli başlık dosyasında. Dolayısıyla gerçek türleri merak etmemiz gerek yok. >> Unix/Linux sistemlerinde "typedef" edilen türler "sys/types.h" isimli başlık dosyası içerisindedir. >> Bu zamana kadar ID değerine sahip olan şeyler şunlardır: >>> Proseslerin ID değeri. >>> Proseslerin Kullanıcı ID Değerleri: >>>> Proseslerin Gerçek Kullanıcı ID Değeri. >>>> Proseslerin Etkin Kullanıcı ID Değeri. >>> Proseslerin Grup ID Değerleri: >>>> Proseslerin Gerçek Grup ID Değeri. >>>> Proseslerin Etkin Grup ID Değeri. >>> Sistemi kullanan kişilerin: >>>> Kullanıcı Adı. >>>> Kullanıcı ID değeri. >>>> Grup Adı. >>>> Grup ID değeri. >> Pareto Kuralına göre bir materyalin ya da şeyin %80'nini ve %20'sini ayırsak; >>> İlgili %20'lik kısım, işin %80'lik kısmını yapmaktadır. >>> Geriye kalan %80'lik kısım ise, işin geriye kalan %20'lik kısmını yapıyor. >>> Örneğin, bir "Word" programının %20'lik kısmını bilseniz, bizden istenen şeylerin %80'nini yapabiliriz. Dolayısıyla gücümüzü en önemli %20'lik kısım için harcamalıyız. >> "Appropriate Priviledges" kavramı, uygun haklara haiz olmak anlamındadır. Yani prosesin Etkin Kullanıcı ID değerinin "root" / "0" olması veya ilgili prosesin Etkin Kullanıcı ID değerinin "root" / "0" olmaması ama bir takım özel haklara haiz olması manasındadır. Ya hep ya hiç kuralı POSIX sistemlerde uygulanmak zorunda değildir. Linux sistemlerinde bu özel durumlara ilgili kullanıcının "capability" si denir. Yani bir özellik bir kullanıcıya bahşedilme durumudur. >> POSIX dünyasındaki yetki konusu ya hep ya hiç şeklindeyken Linux dünyasında "appropriate privileges" mevzusu vardır. Buradan kastedilen "capability" durumudur. "appropriate-user/priviledged-user/appropriate privileges" derken de kastedilen ya "root" olmamız ya da bir şeyleri yapmak için "capability" e sahip olmamızdır. >> Dosya işlemlerindeki testlerde proseslerin Etkin ID değerleri kullanılmaktadır. >> "Resource Limit" değerlerini "get" etmek için: #include #include void exit_sys(const char* msg); char buffer[4096]; int main(void) { /* # OUTPUT # Soft Limit: [65536] Hard Limit: [65536] */ struct rlimit limits; getrlimit(RLIMIT_MEMLOCK, &limits); printf("Soft Limit: [%lld]\n", (long long)limits.rlim_cur); printf("Hard Limit: [%lld]\n", (long long)limits.rlim_max); return 0; } void exit_sys(const char* msg) { perror(msg); exit(EXIT_FAILURE); } >> "Processor Effinity" : Bir programın işlemcinin hangi çekirdeğine atanacağına karar verilmesidir. >> "sudo" şifresi "1". >> Bir prosesi terminal yoluyla sonlandırmanın "CTRL+C" tuşu dışındaki diğer yolu ise "CTRL+\" (bazı sistemlerde "CTRL+Backspace") tuşunu kullanmaktır. Bu durumda terminal aygıt sürücüsü oturumun ön plan proses grubuna "SIGQUIT" isimli bir sinyal göndermektedir. Bu sinyal prosese gönderildiğinde, eğer proses bu sinyali ele almamışsa, proses sonlandırılı ve "code" dosyası oluşturulur.