├── .gitignore ├── pam.h ├── Makefile ├── my-display-manager.service ├── README.md ├── LICENSE ├── display-manager.c ├── pam.c └── gui.ui /.gitignore: -------------------------------------------------------------------------------- 1 | /display-manager -------------------------------------------------------------------------------- /pam.h: -------------------------------------------------------------------------------- 1 | #ifndef _PAM_H_ 2 | #define _PAM_H_ 3 | 4 | #include 5 | 6 | bool login(const char *username, const char *password, pid_t *child_pid); 7 | bool logout(void); 8 | 9 | #endif /* _PAM_H_ */ 10 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | all: display-manager 2 | 3 | display-manager: display-manager.c pam.c 4 | gcc -Wall -o $@ $^ `pkg-config --cflags --libs gtk+-3.0` -l pam 5 | 6 | .PHONY: clean 7 | 8 | clean: 9 | rm -f display-manager 10 | -------------------------------------------------------------------------------- /my-display-manager.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=My Display Manager 3 | After=systemd-user-sessions.service 4 | 5 | [Service] 6 | ExecStart=/path/to/my/display-manager 7 | 8 | [Install] 9 | Alias=display-manager.service 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Linux Display/Login Manager 2 | =========================== 3 | 4 | This is a display/login manager for Linux, similar to GDM, KDM, or SLiM. It is a very simple display manager written in C using GTK3. 5 | 6 | This display manager has only been tested on Arch Linux with `dwm` as the window manager. The window manager must be placed in `.xinitrc` (i.e. it should contain `exec dwm`). 7 | 8 | You can find a tutorial for how to make this display manager [here](https://www.gulshansingh.com/posts/how-to-write-a-display-manager/). The [tutorial](https://github.com/gsingh93/display-manager/tree/tutorial) branch more closely follows the tutorial. 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Gulshan Singh 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /display-manager.c: -------------------------------------------------------------------------------- 1 | #include // dirname() 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include 10 | 11 | #include "pam.h" 12 | 13 | #define ENTER_KEY 65293 14 | #define ESC_KEY 65307 15 | #define UI_FILE "gui.ui" 16 | #define DISPLAY ":1" 17 | #define VT "vt01" 18 | 19 | static bool testing = false; 20 | 21 | static GtkEntry *user_text_field; 22 | static GtkEntry *pass_text_field; 23 | static GtkLabel *status_label; 24 | 25 | static pthread_t login_thread; 26 | static pid_t x_server_pid; 27 | 28 | static void* login_func(void *data) { 29 | GtkWidget *widget = GTK_WIDGET(data); 30 | const gchar *username = gtk_entry_get_text(user_text_field); 31 | const gchar *password = gtk_entry_get_text(pass_text_field); 32 | 33 | gtk_label_set_text(status_label, "Logging in..."); 34 | pid_t child_pid; 35 | if (login(username, password, &child_pid)) { 36 | gtk_widget_hide(widget); 37 | 38 | // Wait for child process to finish (wait for logout) 39 | int status; 40 | waitpid(child_pid, &status, 0); // TODO: Handle errors 41 | gtk_widget_show(widget); 42 | 43 | gtk_label_set_text(status_label, ""); 44 | 45 | logout(); 46 | } else { 47 | gtk_label_set_text(status_label, "Login error"); 48 | } 49 | gtk_entry_set_text(pass_text_field, ""); 50 | 51 | return NULL; 52 | } 53 | 54 | static gboolean key_event(GtkWidget *widget, GdkEventKey *event) { 55 | if (event->keyval == ENTER_KEY) { 56 | pthread_create(&login_thread, NULL, login_func, (void*) widget); 57 | } else if (event->keyval == ESC_KEY) { 58 | gtk_main_quit(); 59 | } 60 | return FALSE; 61 | } 62 | 63 | static void start_x_server(const char *display, const char *vt) { 64 | x_server_pid = fork(); 65 | if (x_server_pid == 0) { 66 | char cmd[32]; 67 | snprintf(cmd, sizeof(cmd), "/usr/bin/X %s %s", display, vt); 68 | execl("/bin/bash", "/bin/bash", "-c", cmd, NULL); 69 | printf("Failed to start X server"); 70 | exit(1); 71 | } else { 72 | // TODO: Wait for X server to start 73 | sleep(1); 74 | } 75 | } 76 | 77 | static void stop_x_server() { 78 | if (x_server_pid != 0) { 79 | kill(x_server_pid, SIGKILL); 80 | } 81 | } 82 | 83 | static void sig_handler(int signo) { 84 | stop_x_server(); 85 | } 86 | 87 | int main(int argc, char *argv[]) { 88 | const char *display = DISPLAY; 89 | const char *vt = VT; 90 | if (argc == 3) { 91 | display = argv[1]; 92 | vt = argv[2]; 93 | } 94 | if (!testing) { 95 | signal(SIGSEGV, sig_handler); 96 | signal(SIGTRAP, sig_handler); 97 | start_x_server(display, vt); 98 | } 99 | setenv("DISPLAY", display, true); 100 | 101 | gtk_init(&argc, &argv); 102 | 103 | char ui_file_path[256]; 104 | if (readlink("/proc/self/exe", ui_file_path, sizeof(ui_file_path)) == -1) { 105 | printf("Error: could not get location of binary"); 106 | exit(1); 107 | } 108 | 109 | dirname(ui_file_path); 110 | strcat(ui_file_path, "/" UI_FILE); 111 | GtkBuilder *builder = gtk_builder_new_from_file(ui_file_path); 112 | GtkWidget *window = GTK_WIDGET(gtk_builder_get_object(builder, "window")); 113 | user_text_field = GTK_ENTRY(gtk_builder_get_object(builder, 114 | "user_text_entry")); 115 | pass_text_field = GTK_ENTRY(gtk_builder_get_object(builder, 116 | "pass_text_entry")); 117 | status_label = GTK_LABEL(gtk_builder_get_object(builder, "status_label")); 118 | 119 | // Make full screen 120 | GdkScreen *screen = gdk_screen_get_default(); 121 | gint height = gdk_screen_get_height(screen); 122 | gint width = gdk_screen_get_width(screen); 123 | gtk_widget_set_size_request(GTK_WIDGET(window), width, height); 124 | 125 | g_signal_connect(window, "key-release-event", G_CALLBACK(key_event), NULL); 126 | g_signal_connect(window, "destroy", G_CALLBACK(gtk_main_quit), NULL); 127 | gtk_main(); 128 | 129 | stop_x_server(); 130 | 131 | return 0; 132 | } 133 | -------------------------------------------------------------------------------- /pam.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | #include 6 | 7 | #include "pam.h" 8 | 9 | #define SERVICE_NAME "display_manager" 10 | 11 | #define err(name) \ 12 | do { \ 13 | fprintf(stderr, "%s: %s\n", name, \ 14 | pam_strerror(pam_handle, result)); \ 15 | end(result); \ 16 | return false; \ 17 | } while (1); \ 18 | 19 | static void init_env(struct passwd *pw); 20 | static void set_env(char *name, char *value); 21 | static int end(int last_result); 22 | 23 | static int conv(int num_msg, const struct pam_message **msg, 24 | struct pam_response **resp, void *appdata_ptr); 25 | 26 | static pam_handle_t *pam_handle; 27 | 28 | bool login(const char *username, const char *password, pid_t *child_pid) { 29 | const char *data[2] = {username, password}; 30 | struct pam_conv pam_conv = { 31 | conv, data 32 | }; 33 | int result = pam_start(SERVICE_NAME, NULL, &pam_conv, &pam_handle); 34 | if (result != PAM_SUCCESS) { 35 | err("pam_start"); 36 | } 37 | 38 | result = pam_set_item(pam_handle, PAM_USER, username); 39 | if (result != PAM_SUCCESS) { 40 | err("pam_set_item"); 41 | } 42 | 43 | result = pam_authenticate(pam_handle, 0); 44 | if (result != PAM_SUCCESS) { 45 | err("pam_authenticate"); 46 | } 47 | 48 | result = pam_acct_mgmt(pam_handle, 0); 49 | if (result != PAM_SUCCESS) { 50 | err("pam_acct_mgmt"); 51 | } 52 | 53 | result = pam_setcred(pam_handle, PAM_ESTABLISH_CRED); 54 | if (result != PAM_SUCCESS) { 55 | err("pam_setcred"); 56 | } 57 | 58 | result = pam_open_session(pam_handle, 0); 59 | if (result != PAM_SUCCESS) { 60 | pam_setcred(pam_handle, PAM_DELETE_CRED); 61 | err("pam_open_session"); 62 | } 63 | 64 | struct passwd *pw = getpwnam(username); 65 | init_env(pw); 66 | 67 | *child_pid = fork(); 68 | if (*child_pid == 0) { 69 | chdir(pw->pw_dir); 70 | // We don't use ~/.xinitrc because we should already be in the users home directory 71 | char *cmd = "exec /bin/bash --login .xinitrc"; 72 | execl(pw->pw_shell, pw->pw_shell, "-c", cmd, NULL); 73 | printf("Failed to start window manager"); 74 | exit(1); 75 | } 76 | 77 | return true; 78 | } 79 | 80 | bool logout(void) { 81 | int result = pam_close_session(pam_handle, 0); 82 | if (result != PAM_SUCCESS) { 83 | pam_setcred(pam_handle, PAM_DELETE_CRED); 84 | err("pam_close_session"); 85 | } 86 | 87 | result = pam_setcred(pam_handle, PAM_DELETE_CRED); 88 | if (result != PAM_SUCCESS) { 89 | err("pam_setcred"); 90 | } 91 | 92 | end(result); 93 | return true; 94 | } 95 | 96 | static void init_env(struct passwd *pw) { 97 | set_env("HOME", pw->pw_dir); 98 | set_env("PWD", pw->pw_dir); 99 | set_env("SHELL", pw->pw_shell); 100 | set_env("USER", pw->pw_name); 101 | set_env("LOGNAME", pw->pw_name); 102 | set_env("PATH", "/usr/local/sbin:/usr/local/bin:/usr/bin"); 103 | //set_env("DISPLAY", DISPLAY); 104 | set_env("MAIL", _PATH_MAILDIR); 105 | 106 | char *xauthority = malloc(strlen(pw->pw_dir) + strlen("/.Xauthority") + 1); 107 | strcpy(xauthority, pw->pw_dir); 108 | strcat(xauthority, "/.Xauthority"); 109 | set_env("XAUTHORITY", xauthority); 110 | free(xauthority); 111 | } 112 | 113 | static void set_env(char *name, char *value) { 114 | char *name_value = malloc(strlen(name) + strlen(value) + 2); 115 | strcpy(name_value, name); 116 | strcat(name_value, "="); 117 | strcat(name_value, value); 118 | pam_putenv(pam_handle, name_value); // TODO: Handle errors 119 | free(name_value); 120 | } 121 | 122 | static int end(int last_result) { 123 | int result = pam_end(pam_handle, last_result); 124 | pam_handle = 0; 125 | return result; 126 | } 127 | 128 | static int conv(int num_msg, const struct pam_message **msg, 129 | struct pam_response **resp, void *appdata_ptr) { 130 | int i; 131 | 132 | *resp = calloc(num_msg, sizeof(struct pam_response)); 133 | if (*resp == NULL) { 134 | return PAM_BUF_ERR; 135 | } 136 | 137 | int result = PAM_SUCCESS; 138 | for (i = 0; i < num_msg; i++) { 139 | char *username, *password; 140 | switch (msg[i]->msg_style) { 141 | case PAM_PROMPT_ECHO_ON: 142 | username = ((char **) appdata_ptr)[0]; 143 | (*resp)[i].resp = strdup(username); 144 | break; 145 | case PAM_PROMPT_ECHO_OFF: 146 | password = ((char **) appdata_ptr)[1]; 147 | (*resp)[i].resp = strdup(password); 148 | break; 149 | case PAM_ERROR_MSG: 150 | fprintf(stderr, "%s\n", msg[i]->msg); 151 | result = PAM_CONV_ERR; 152 | break; 153 | case PAM_TEXT_INFO: 154 | printf("%s\n", msg[i]->msg); 155 | break; 156 | } 157 | if (result != PAM_SUCCESS) { 158 | break; 159 | } 160 | } 161 | 162 | if (result != PAM_SUCCESS) { 163 | free(*resp); 164 | *resp = 0; 165 | } 166 | 167 | return result; 168 | } 169 | -------------------------------------------------------------------------------- /gui.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | True 7 | False 8 | 9 | 10 | True 11 | False 12 | 13 | 14 | True 15 | False 16 | center 17 | vertical 18 | 19 | 20 | True 21 | False 22 | 23 | 24 | True 25 | False 26 | center 27 | 10 28 | 10 29 | 30 | 31 | True 32 | False 33 | 5 34 | 5 35 | Username 36 | 37 | 38 | False 39 | True 40 | 0 41 | 42 | 43 | 44 | 45 | True 46 | True 47 | 48 | 49 | False 50 | True 51 | 1 52 | 53 | 54 | 55 | 56 | 57 | 58 | False 59 | True 60 | 0 61 | 62 | 63 | 64 | 65 | True 66 | False 67 | center 68 | 10 69 | 10 70 | 71 | 72 | True 73 | False 74 | 5 75 | 5 76 | Password 77 | 78 | 79 | False 80 | True 81 | 0 82 | 83 | 84 | 85 | 86 | True 87 | True 88 | False 89 | password 90 | 91 | 92 | False 93 | True 94 | 1 95 | 96 | 97 | 98 | 99 | False 100 | True 101 | 1 102 | 103 | 104 | 105 | 106 | True 107 | False 108 | vertical 109 | 110 | 111 | True 112 | False 113 | 114 | 115 | False 116 | True 117 | 0 118 | 119 | 120 | 121 | 122 | False 123 | True 124 | 2 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | --------------------------------------------------------------------------------