8 Commits

Author SHA1 Message Date
64a1368717 feat: xorg sessions work
leaves a few TODOs:
* error handling
* properly passing VT around
* `wait_for_x_ready`
* allow compile-time overriding `DEFAULT_XORG_DISPLAY` and `XORG_COMMAND`
2025-09-03 14:50:39 +02:00
570a68c586 fix: satify all clang code requirements 2025-08-27 22:21:21 +02:00
ff3e58ee5b fix: satify clang nullability requirements 2025-08-27 22:07:04 +02:00
109c9bd0be chore: organize code
add -Wextra by default
pipefd to communicate better with parent and forked process
rustify some stuff
add the desktop parser to the file read
2025-08-27 22:02:14 +02:00
056ec64bcb feat: better spawn error handling 2025-08-27 20:29:16 +02:00
b58bd50e64 perf: some compiler flag optimizations
https://stackoverflow.com/questions/6687630/how-to-remove-unused-c-c-symbols-with-gcc-and-ld
2025-08-27 19:54:24 +02:00
6d9107b360 lint: stricter casing rules 2025-08-27 19:47:19 +02:00
b56aa19671 chore: modularize PAM
also helps with sanitizing the code flow when spawning the child, more
code though
2025-08-27 19:31:46 +02:00
27 changed files with 709 additions and 461 deletions

View File

@@ -16,15 +16,13 @@ Checks: >
readability-*, readability-*,
-readability-braces-around-statements, -readability-braces-around-statements,
WarningsAsErrors: '' WarningsAsErrors: ""
HeaderFilterRegex: '.*' HeaderFilterRegex: ".*"
FormatStyle: file FormatStyle: file
CheckOptions: CheckOptions:
- key: readability-magic-numbers.IgnoredIntegerValues - key: readability-magic-numbers.IgnoredIntegerValues
value: '0;1;2;3;10;255' value: "0;1;2;3;10;255"
# - key: readability-magic-numbers.IgnoredValues
# value: '0;1;2;3;10;255'
- key: readability-identifier-naming.VariableCase - key: readability-identifier-naming.VariableCase
value: lower_case value: lower_case
- key: readability-identifier-naming.ConstantParameterCase - key: readability-identifier-naming.ConstantParameterCase
@@ -32,16 +30,20 @@ CheckOptions:
- key: readability-identifier-naming.ConstantCase - key: readability-identifier-naming.ConstantCase
value: "UPPER_CASE" value: "UPPER_CASE"
- key: readability-identifier-naming.EnumCase
value: "CamelCase"
- key: readability-identifier-naming.FunctionCase
value: "lower_case"
- key: readability-identifier-length.VariableThreshold - key: readability-identifier-length.VariableThreshold
value: '2' value: "2"
- key: readability-identifier-length.ParameterThreshold - key: readability-identifier-length.ParameterThreshold
value: '2' value: "2"
- key: readability-identifier-length.LocalConstantThreshold - key: readability-identifier-length.LocalConstantThreshold
value: '2' value: "2"
- key: readability-identifier-length.MemberThreshold - key: readability-identifier-length.MemberThreshold
value: '2' value: "2"
- key: readability-identifier-length.MinimumParameterNameLength - key: readability-identifier-length.MinimumParameterNameLength
value: '2' value: "2"
- key: readability-identifier-length.MinimumVariableNameLength - key: readability-identifier-length.MinimumVariableNameLength
value: '2' value: "2"

View File

@@ -9,17 +9,18 @@ ODIR=dist
PREFIX=/usr PREFIX=/usr
CC?=gcc CC?=gcc
CFLAGS?=-O3 -Wall CFLAGS?=-O3 -Wall -Wextra -fdata-sections -ffunction-sections
# C PreProcessor flags, not C Plus Plus # C PreProcessor flags, not C Plus Plus
CPPFLAGS?= CPPFLAGS?=
ALLFLAGS=$(CFLAGS) $(CPPFLAGS) -I$(IDIR) ALLFLAGS=$(CFLAGS) $(CPPFLAGS) -I$(IDIR)
LDFLAGS?=-Wl,--gc-sections
LIBS=-lpam LIBS=-lpam
_DEPS = version.h log.h util.h ui.h ui_state.h config.h desktop.h desktop_exec.h auth.h ofield.h efield.h keys.h users.h sessions.h chvt.h macros.h launch_state.h _DEPS = version.h log.h util.h ui.h ui_state.h config.h pam.h desktop.h desktop_exec.h auth.h ofield.h efield.h keys.h users.h sessions.h chvt.h macros.h launch_state.h
DEPS = $(patsubst %,$(IDIR)/%,$(_DEPS)) DEPS = $(patsubst %,$(IDIR)/%,$(_DEPS))
_OBJ = main.o log.o util.o ui.o ui_state.o config.o desktop.o desktop_exec.o auth.o ofield.o efield.o users.o sessions.o chvt.o launch_state.o _OBJ = main.o log.o util.o ui.o ui_state.o config.o pam.o desktop.o desktop_exec.o auth.o ofield.o efield.o users.o sessions.o chvt.o launch_state.o
OBJ = $(patsubst %,$(ODIR)/%,$(_OBJ)) OBJ = $(patsubst %,$(ODIR)/%,$(_OBJ))
INFO_GIT_REV?=$$(git describe --long --tags --always || echo '?') INFO_GIT_REV?=$$(git describe --long --tags --always || echo '?')
@@ -41,7 +42,7 @@ $(ODIR)/%.o: $(CDIR)/%.c $(DEPS)
$(CC) -c -o $@ $< $(ALLFLAGS) $(CC) -c -o $@ $< $(ALLFLAGS)
lidm: $(OBJ) lidm: $(OBJ)
$(CC) -o $@ $^ $(ALLFLAGS) $(LIBS) $(CC) -o $@ $^ $(ALLFLAGS) $(LIBS) $(LDFLAGS)
clean: clean:
rm -f $(ODIR)/*.o lidm rm -f $(ODIR)/*.o lidm
@@ -137,8 +138,8 @@ pre-commit:
prettier -c "**/*.md" prettier -c "**/*.md"
git ls-files "*.sh" "*/PKGBUILD" | xargs shellcheck --shell=bash git ls-files "*.sh" "*/PKGBUILD" | xargs shellcheck --shell=bash
clang-format -i $$(git ls-files "*.c" "*.h") clang-format -i $$(git ls-files "*.c" "*.h")
git ls-files -z "*.h" | \ git ls-files -z "*.c" "*.h" | \
parallel -j$$(nproc) -q0 --no-notice --will-cite --tty clang-tidy --quiet |& \ parallel -j$$(nproc) -q0 --no-notice --will-cite --tty clang-tidy -warnings-as-errors=\* --quiet |& \
grep -v "warnings generated." || true grep -v "warnings generated." || true
print-version: print-version:

View File

@@ -27,11 +27,10 @@ kmscon -l --vt /dev/tty7 --font-name "Cascadia Code" -- /usr/bin/lidm
## Features ## Features
- Simple as C, meant to depend only on standard libc and basic unix system headers. - Simple as C, you only need a C compiler and standard unix libraries to build this.
- Fully customizable: ALL strings, colors (with its ANSI keys) and most behavior. - Fully customizable, from strings, including action keys, to colors (I hope you know ansi escape codes)
- Xorg[\*](https://github.com/javalsai/lidm/issues/79) and wayland sessions, while supporting the default user shell (if enabled in config) - Automatically detects xorg and wayland sessions, plus allowing to launch the default user shell (if enabled in config)
- Init agnostinc (systemd, dinit, runit, openrc and s6). - Starts with many init systems (systemd, dinit, runit, openrc and s6).
- Supports [fido yubikeys](./docs/yubikey.md) (via pam_u2f).
# Table of Contents # Table of Contents

View File

@@ -69,13 +69,13 @@ Characters for the corners of the box (ctl = corner top left, cbr = corner botto
.SS functions .SS functions
All these are of type \fBKEY\fP. All these are of type \fBKEY\fP.
.TP .TP
\fBpoweroff, reboot, fido, refresh\fP \fBpoweroff, reboot, refresh\fP
Function key to use for such action. Function key to use for such action.
.SS strings .SS strings
Display strings to use for some elements. Display strings to use for some elements.
.TP .TP
\fBf_poweroff, f_reboot, f_fido, f_refresh\fP \fBf_poweroff, f_reboot, f_refresh\fP
Text displayed to name such functions at the bottom of the screen. Text displayed to name such functions at the bottom of the screen.
.TP .TP
\fBe_user, e_passwd\fP \fBe_user, e_passwd\fP

View File

@@ -1,30 +0,0 @@
# Yubikeys
Quick reference explaining how yubikeys work for now.
# Enable
Yubikeys are disabled by default, to enable them activate a keybinding for it (`[functions] fido`) in the config file.
Note that pressing this configured keybinding has no difference from trying to log in with an empty password, there's virtually no difference.
`pam_u2f` must be configured with a registered key (`pamu2fcfg`).
# Extra
All my yubikey knowledge comes from the [pr that implemented this](https://github.com/javalsai/lidm/pull/89), please refer to it for extra details. Contributions to this documentation are welcome (explaining more in detail, potential issues... really anything that improves this).
Allegedly this pam module configuration should work:
```pam
#%PAM-1.0
auth sufficient pam_u2f.so cue
auth requisite pam_nologin.so
auth include system-local-login
account include system-local-login
session include system-local-login
password include system-local-login
```
Also, I recommend giving the [arch wiki](https://wiki.archlinux.org/title/YubiKey) a read anyways.

View File

@@ -6,7 +6,8 @@
#include "config.h" #include "config.h"
#include "sessions.h" #include "sessions.h"
bool launch(char* user, char* passwd, struct session session, void (*cb)(void), bool launch(char* NNULLABLE user, char* NNULLABLE passwd,
struct config* config); struct session session, void (*NULLABLE cb)(void),
struct config* NNULLABLE config);
#endif #endif

View File

@@ -8,7 +8,7 @@
#include "macros.h" #include "macros.h"
#include "util.h" #include "util.h"
enum introspection_type { enum IntrospectionType {
STRING, STRING,
BOOL, BOOL,
NUMBER, NUMBER,
@@ -26,7 +26,7 @@ static const char* NNULLABLE const INTROS_TYS_NAMES[] = {
struct introspection_item { struct introspection_item {
char* NNULLABLE name; char* NNULLABLE name;
size_t offset; size_t offset;
enum introspection_type typ; enum IntrospectionType typ;
}; };
#define INTROS_ITEM(key, table, ty) \ #define INTROS_ITEM(key, table, ty) \
@@ -90,17 +90,15 @@ BUILD(colors, COLORS, TABLE_COLORS);
BUILD(chars, CHARS, TABLE_CHARS); BUILD(chars, CHARS, TABLE_CHARS);
#define TABLE_FUNCTIONS(F, name) \ #define TABLE_FUNCTIONS(F, name) \
F(enum keys, poweroff, KEY, F1, name) \ F(enum Keys, poweroff, KEY, F1, name) \
F(enum keys, reboot, KEY, F2, name) \ F(enum Keys, reboot, KEY, F2, name) \
F(enum keys, fido, KEY, NONE, name) \ F(enum Keys, refresh, KEY, F5, name)
F(enum keys, refresh, KEY, F5, name)
BUILD(functions, FUNCTIONS, TABLE_FUNCTIONS); BUILD(functions, FUNCTIONS, TABLE_FUNCTIONS);
#define TABLE_STRINGS(F, name) \ #define TABLE_STRINGS(F, name) \
F(char* NNULLABLE, f_poweroff, STRING, "poweroff", name) \ F(char* NNULLABLE, f_poweroff, STRING, "poweroff", name) \
F(char* NNULLABLE, f_reboot, STRING, "reboot", name) \ F(char* NNULLABLE, f_reboot, STRING, "reboot", name) \
F(char* NNULLABLE, f_fido, STRING, "fido", name) \
F(char* NNULLABLE, f_refresh, STRING, "refresh", name) \ F(char* NNULLABLE, f_refresh, STRING, "refresh", name) \
F(char* NNULLABLE, e_user, STRING, "user", name) \ F(char* NNULLABLE, e_user, STRING, "user", name) \
F(char* NNULLABLE, e_passwd, STRING, "password", name) \ F(char* NNULLABLE, e_passwd, STRING, "password", name) \
@@ -152,15 +150,15 @@ struct introspection_table {
static const struct introspection_table CONFIG_INSTROSPECTION[] = { static const struct introspection_table CONFIG_INSTROSPECTION[] = {
{"colors", offsetof(struct config, colors), INTROS_TABLE_COLORS, {"colors", offsetof(struct config, colors), INTROS_TABLE_COLORS,
sizeof(INTROS_TABLE_COLORS) / sizeof(INTROS_TABLE_COLORS[0])}, LEN(INTROS_TABLE_COLORS)},
{"chars", offsetof(struct config, chars), INTROS_TABLE_CHARS, {"chars", offsetof(struct config, chars), INTROS_TABLE_CHARS,
sizeof(INTROS_TABLE_CHARS) / sizeof(INTROS_TABLE_CHARS[0])}, LEN(INTROS_TABLE_CHARS)},
{"functions", offsetof(struct config, functions), INTROS_TABLE_FUNCTIONS, {"functions", offsetof(struct config, functions), INTROS_TABLE_FUNCTIONS,
sizeof(INTROS_TABLE_FUNCTIONS) / sizeof(INTROS_TABLE_FUNCTIONS[0])}, LEN(INTROS_TABLE_FUNCTIONS)},
{"strings", offsetof(struct config, strings), INTROS_TABLE_STRINGS, {"strings", offsetof(struct config, strings), INTROS_TABLE_STRINGS,
sizeof(INTROS_TABLE_STRINGS) / sizeof(INTROS_TABLE_STRINGS[0])}, LEN(INTROS_TABLE_STRINGS)},
{"behavior", offsetof(struct config, behavior), INTROS_TABLE_BEHAVIOR, {"behavior", offsetof(struct config, behavior), INTROS_TABLE_BEHAVIOR,
sizeof(INTROS_TABLE_BEHAVIOR) / sizeof(INTROS_TABLE_BEHAVIOR[0])}, LEN(INTROS_TABLE_BEHAVIOR)},
}; };
//// FUNCTIONS //// FUNCTIONS

View File

@@ -1,6 +1,9 @@
#ifndef DESKTOP_EXEC_H_ #ifndef DESKTOP_EXEC_H_
#define DESKTOP_EXEC_H_ #define DESKTOP_EXEC_H_
#include "macros.h"
int execvpe_desktop(char** args, char* NNULLABLE* NNULLABLE envlist);
int parse_exec_string(const char* exec_s, int* arg_count, char*** args); int parse_exec_string(const char* exec_s, int* arg_count, char*** args);
void free_parsed_args(int arg_count, char** args); void free_parsed_args(int arg_count, char** args);

View File

@@ -3,7 +3,7 @@
#include <stdlib.h> #include <stdlib.h>
enum keys { enum Keys {
ESC, ESC,
F1, F1,
F2, F2,
@@ -32,11 +32,9 @@ enum keys {
END, END,
PAGE_UP, PAGE_UP,
PAGE_DOWN, PAGE_DOWN,
NONE,
}; };
static const char* const KEY_NAMES[] = { static const char* const KEY_NAMES[] = {
[NONE] = "NONE",
[ESC] = "ESC", [ESC] = "ESC",
[F1] = "F1", [F1] = "F1",
[F2] = "F2", [F2] = "F2",
@@ -68,7 +66,7 @@ static const char* const KEY_NAMES[] = {
}; };
struct key_mapping { struct key_mapping {
enum keys key; enum Keys key;
const char* sequences[3]; const char* sequences[3];
}; };

View File

@@ -36,5 +36,6 @@
#endif #endif
#define LEN(X) (sizeof(X) / sizeof((X)[0])) #define LEN(X) (sizeof(X) / sizeof((X)[0]))
#define UNUSED(x) ((void)(x))
#endif #endif

33
include/pam.h Normal file
View File

@@ -0,0 +1,33 @@
#ifndef PAM_H
#define PAM_H
#include <pwd.h>
#include <security/_pam_types.h>
#include <security/pam_appl.h>
#include <stdbool.h>
#include "macros.h"
#include "sessions.h"
#define PAMH_ERR_NOERR 0
#define PAMH_ERR_ALLOC 1
#define PAMH_ERR_ERRNO 2
#define PAMH_ERR_NOERRNO 3
struct pamh_getenv_status {
char error_flag;
union {
char* NULLABLE* NNULLABLE envlist;
const char* NNULLABLE errfn;
};
};
// Doesn't include `source`s
struct pamh_getenv_status pamh_get_complete_env(pam_handle_t* NNULLABLE handle,
struct passwd* NNULLABLE pw,
enum SessionType session_typ);
void free_envlist(char* NULLABLE* NNULLABLE envlist);
pam_handle_t* NULLABLE get_pamh(char* NNULLABLE user, char* NNULLABLE passwd);
#endif /* PAM_H */

View File

@@ -2,21 +2,71 @@
#define SESSIONSH_ #define SESSIONSH_
#include <sys/types.h> #include <sys/types.h>
#include <unistd.h>
#include "desktop_exec.h"
#include "macros.h" #include "macros.h"
#include "util.h" #include "util.h"
enum session_type { enum SessionType {
XORG, XORG,
WAYLAND, WAYLAND,
SHELL, SHELL,
}; };
enum ExecType {
EXEC_SHELL,
EXEC_DESKTOP,
};
struct desktop_exec {
char* NULLABLE* NNULLABLE args;
int arg_count;
};
struct session_exec {
enum ExecType typ;
union {
char* NNULLABLE shell;
struct desktop_exec desktop;
};
};
static inline struct session_exec session_exec_shell(char* NNULLABLE shell) {
return (struct session_exec){
.typ = EXEC_SHELL,
.shell = shell,
};
}
static inline struct session_exec session_exec_desktop(
int arg_count, char* NULLABLE* NNULLABLE args) {
return (struct session_exec){
.typ = EXEC_DESKTOP,
.desktop =
{
.args = args,
.arg_count = arg_count,
},
};
}
static inline int session_exec_exec(struct session_exec* NNULLABLE exec,
char* NULLABLE* NNULLABLE envlist) {
switch (exec->typ) {
case EXEC_SHELL:
return execle(exec->shell, exec->shell, NULL, envlist);
case EXEC_DESKTOP:
return execvpe_desktop(exec->desktop.args, envlist);
default:
__builtin_unreachable();
}
}
struct session { struct session {
char* NNULLABLE name; char* NNULLABLE name;
char* NNULLABLE exec; struct session_exec exec;
char* NULLABLE tryexec; enum SessionType type;
enum session_type type;
}; };
struct Vector get_avaliable_sessions(); struct Vector get_avaliable_sessions();

View File

@@ -39,17 +39,15 @@
#define VALUES_SEPR 3 #define VALUES_SEPR 3
#define VALUE_MAXLEN (BOX_WIDTH - VALUES_COL + 1 - BOX_HMARGIN - 2) #define VALUE_MAXLEN (BOX_WIDTH - VALUES_COL + 1 - BOX_HMARGIN - 2)
enum input { SESSION, USER, PASSWD }; enum Input { SESSION, USER, PASSWD };
extern const u_char INPUTS_N; extern const u_char INPUTS_N;
void setup(struct config* config); void setup(struct config* config);
int load(struct Vector* users, struct Vector* sessions); int load(struct Vector* users, struct Vector* sessions);
void print_err(const char* /*msg*/); void print_err(const char* /*msg*/);
void print_errno(const char* /*descr*/); void print_errno(const char* /*descr*/);
void print_pam_msg(const char* msg, int msg_style);
void clear_pam_msg(void);
void ui_update_field(enum input focused_input); void ui_update_field(enum Input focused_input);
void ui_update_ffield(); void ui_update_ffield();
void ui_update_ofield(struct opts_field* self); void ui_update_ofield(struct opts_field* self);
void ui_update_cursor_focus(); void ui_update_cursor_focus();

View File

@@ -6,7 +6,7 @@
#include "macros.h" #include "macros.h"
#include "ui.h" #include "ui.h"
extern enum input focused_input; extern enum Input focused_input;
extern struct opts_field of_session; extern struct opts_field of_session;
extern struct opts_field of_user; extern struct opts_field of_user;
@@ -15,7 +15,7 @@ extern struct opts_field of_passwd;
extern struct Vector* UNULLABLE gusers; extern struct Vector* UNULLABLE gusers;
extern struct Vector* UNULLABLE gsessions; extern struct Vector* UNULLABLE gsessions;
struct opts_field* NNULLABLE get_opts_field(enum input from); struct opts_field* NNULLABLE get_opts_field(enum Input from);
struct opts_field* NNULLABLE get_opts_ffield(); struct opts_field* NNULLABLE get_opts_ffield();
struct user st_user(); struct user st_user();

View File

@@ -10,12 +10,18 @@
#include "keys.h" #include "keys.h"
int find_keyname(enum keys* at, const char* name); int find_keyname(enum Keys* at, const char* name);
enum keys find_ansi(const char* seq); struct option_keys {
bool is_some;
enum Keys key;
};
struct option_keys find_ansi(const char* seq);
void read_press(u_char* length, char* out); void read_press(u_char* length, char* out);
// non blocking, waits up to tv or interrupt, returns true if actually read // non blocking, waits up to tv or interrupt, returns true if actually read
bool read_press_nb(u_char* length, char* out, struct timeval* tv); bool read_press_nb(u_char* length, char* out, struct timeval* tv);
// UTF8
//
bool utf8_iscont(char byte); bool utf8_iscont(char byte);
size_t utf8len(const char* str); size_t utf8len(const char* str);
size_t utf8len_until(const char* str, const char* until); size_t utf8len_until(const char* str, const char* until);
@@ -24,12 +30,16 @@ const char* utf8back(const char* str);
const char* utf8seek(const char* str); const char* utf8seek(const char* str);
const char* utf8seekn(const char* str, size_t n); const char* utf8seekn(const char* str, size_t n);
// Vector
//
struct Vector { struct Vector {
uint32_t length; uint32_t length;
uint32_t capacity; uint32_t capacity;
void** pages; void** pages;
}; };
struct Vector vec_from_raw(void** raw);
void** vec_as_raw(struct Vector self);
extern const struct Vector VEC_NEW; extern const struct Vector VEC_NEW;
int vec_resize(struct Vector* self, size_t size); int vec_resize(struct Vector* self, size_t size);
int vec_reserve(struct Vector* self, size_t size); int vec_reserve(struct Vector* self, size_t size);

View File

@@ -1,114 +1,41 @@
// TODO: handle `fork() == -1`s
#include <errno.h>
#include <grp.h> #include <grp.h>
#include <pwd.h> #include <pwd.h>
#include <security/pam_misc.h> #include <security/pam_misc.h>
#include <signal.h>
#include <stdint.h> #include <stdint.h>
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include <sys/mman.h> #include <sys/mman.h>
#include <sys/stat.h>
#include <sys/wait.h> #include <sys/wait.h>
#include <unistd.h>
#include "auth.h" #include "auth.h"
#include "config.h" #include "config.h"
#include "desktop_exec.h"
#include "log.h" #include "log.h"
#include "macros.h"
#include "pam.h"
#include "sessions.h" #include "sessions.h"
#include "ui.h" #include "ui.h"
#include "unistd.h"
#include "util.h" #include "util.h"
struct pam_conv_data { #define DEFAULT_XORG_DISPLAY 0
char* password; // no PATH search for now
void (*display_pam_msg)(const char* msg, int msg_style); #define XORG_COMMAND "/usr/bin/X"
};
int pam_conversation(int num_msg, const struct pam_message** msg, static void try_source_file(struct Vector* NNULLABLE vec_envlist,
struct pam_response** resp, void* appdata_ptr) { char* NNULLABLE filepath) {
struct pam_response* reply = log_printf("sourcing %s\n", filepath);
(struct pam_response*)malloc(sizeof(struct pam_response) * num_msg); FILE* file2source = fopen(filepath, "r");
if (!reply) { if (file2source == NULL) {
return PAM_BUF_ERR; log_printf("error sourcing %s\n", filepath);
return;
} }
struct pam_conv_data* conv_data = (struct pam_conv_data*)appdata_ptr;
for (size_t i = 0; i < num_msg; i++) {
reply[i].resp = NULL;
reply[i].resp_retcode = 0;
switch (msg[i]->msg_style) {
case PAM_PROMPT_ECHO_OFF:
case PAM_PROMPT_ECHO_ON:
reply[i].resp = strdup(conv_data->password);
if (!reply[i].resp) {
for (size_t j = 0; j < i; j++)
free(reply[j].resp);
free(reply);
return PAM_BUF_ERR;
}
break;
case PAM_TEXT_INFO:
case PAM_ERROR_MSG:
if (conv_data->display_pam_msg && msg[i]->msg) {
conv_data->display_pam_msg(msg[i]->msg, msg[i]->msg_style);
}
break;
default:
break;
}
}
*resp = reply;
return PAM_SUCCESS;
}
#ifndef PAM_SERVICE_FALLBACK
#define PAM_SERVICE_FALLBACK "login"
#endif
#define CHECK_PAM_RET(call) \
ret = (call); \
if (ret != PAM_SUCCESS) { \
pam_end(pamh, ret); \
return NULL; \
}
void clear_screen() {
printf("\x1b[H\x1b[J");
}
pam_handle_t* get_pamh(char* user, char* passwd) {
pam_handle_t* pamh = NULL;
struct pam_conv_data conv_data = {.password = passwd,
.display_pam_msg = print_pam_msg};
struct pam_conv pamc = {pam_conversation, (void*)&conv_data};
int ret;
char* pam_service_override = getenv("LIDM_PAM_SERVICE");
char* pam_service_name =
pam_service_override ? pam_service_override : PAM_SERVICE_FALLBACK;
CHECK_PAM_RET(pam_start(pam_service_name, user, &pamc, &pamh))
CHECK_PAM_RET(pam_authenticate(pamh, 0))
CHECK_PAM_RET(pam_acct_mgmt(pamh, 0))
CHECK_PAM_RET(pam_setcred(pamh, PAM_ESTABLISH_CRED))
CHECK_PAM_RET(pam_open_session(pamh, 0))
CHECK_PAM_RET(pam_setcred(pamh, PAM_REINITIALIZE_CRED))
return pamh;
}
#undef CHECK_PAM_RET
void* shmalloc(size_t size) {
return mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS,
-1, 0);
}
void sourceFileTry(char* file) {
FILE* file2source = fopen(file, "r");
if (file2source == NULL) return;
char* line = NULL; char* line = NULL;
size_t len = 0; size_t len = 0;
ssize_t read; ssize_t read;
@@ -117,13 +44,10 @@ void sourceFileTry(char* file) {
if (read == 0 || (read > 0 && *line == '#')) continue; if (read == 0 || (read > 0 && *line == '#')) continue;
if (line[read - 1] == '\n') line[read - 1] = '\0'; if (line[read - 1] == '\n') line[read - 1] = '\0';
/* printf("Retrieved line of length %zu:\n", read); */ for (ssize_t i = 1; i < read; i++) {
/* printf("%s\n", line); */
for (size_t i = 1; i < read; i++) {
if (line[i] == '=') { if (line[i] == '=') {
/* printf("FOUND '='!\n"); */ vec_push(vec_envlist, (void*)line);
line[i] = '\0'; line = NULL;
setenv(line, &line[i + 1], 1);
break; break;
} }
} }
@@ -133,85 +57,181 @@ void sourceFileTry(char* file) {
(void)fclose(file2source); (void)fclose(file2source);
} }
void moarEnv(char* user, struct session session, struct passwd* pw, static void source_paths(struct Vector* NNULLABLE vec_envlist,
struct config* config) { struct Vector* NNULLABLE abs_source,
if (chdir(pw->pw_dir) == -1) print_errno("can't chdir to user home"); const char* NULLABLE user_home,
struct Vector* NNULLABLE user_source) {
setenv("HOME", pw->pw_dir, true); for (size_t i = 0; i < abs_source->length; i++) {
setenv("USER", pw->pw_name, true); char* path = vec_get(abs_source, i);
setenv("SHELL", pw->pw_shell, true); try_source_file(vec_envlist, path);
// TERM
setenv("LOGNAME", pw->pw_name, true);
// MAIL?
// PATH?
char* xdg_session_type = "unknown";
if (session.type == SHELL) xdg_session_type = "tty";
if (session.type == XORG) xdg_session_type = "x11";
if (session.type == WAYLAND) xdg_session_type = "wayland";
setenv("XDG_SESSION_TYPE", xdg_session_type, true);
printf("\n\n\n\n\x1b[1m");
for (size_t i = 0; i < config->behavior.source.length; i++) {
/* printf("DEBUG(source)!!!! %d %s\n", i, (char*)vec_get(&behavior->source,
* i)); */
sourceFileTry((char*)vec_get(&config->behavior.source, i));
} }
/* printf("\n"); */
if (pw->pw_dir) { if (user_home)
const char* home = pw->pw_dir; for (size_t i = 0; i < user_source->length; i++) {
size_t home_len = strlen(home); char* path = NULL;
asprintf(&path, "%s/%s", user_home, (char*)vec_get(user_source, i));
for (size_t i = 0; i < config->behavior.user_source.length; i++) { if (!path) {
const char* filename = (char*)vec_get(&config->behavior.user_source, i); log_puts("alloc failure on user source\n");
size_t filename_len = strlen(filename); continue;
}
size_t path_len = home_len + 1 + filename_len + 1; // nullbyte and slash try_source_file(vec_envlist, path);
char* path = malloc(path_len);
if (!path) continue; // can't bother
memcpy(path, home, home_len);
path[home_len] = '/'; // assume pw_dir doesn't start with '/' :P
memcpy(&path[home_len + 1], filename, filename_len);
path[path_len - 1] = '\0';
sourceFileTry(path);
free(path); free(path);
} }
else {
log_puts("user has no home\n");
}
}
/*char *buf;*/
/*size_t bsize = snprintf(NULL, 0, "/run/user/%d", pw->pw_uid) + 1;*/
/*buf = malloc(bsize);*/
/*snprintf(buf, bsize, "/run/user/%d", pw->pw_uid);*/
/*setenv("XDG_RUNTIME_DIR", buf, true);*/
/*setenv("XDG_SESSION_CLASS", "user", true);*/
/*setenv("XDG_SESSION_ID", "1", true);*/
/*setenv("XDG_SESSION_DESKTOP", , true);*/
/*setenv("XDG_SEAT", "seat0", true);*/
struct child_msg {
char* msg;
int _errno;
bool err;
};
// TODO: OR check if xorg_pid fail exited
static int wait_for_x_ready(const int xorg_pipefd[1], __pid_t xorg_pid) {
// TODO
UNUSED(xorg_pipefd);
UNUSED(xorg_pid);
sleep(2);
return 0;
}
// TODO: properly pass this down
extern int vt;
// TODO: add error msgs
static void launch_with_xorg_server(struct session_exec* NNULLABLE exec,
struct passwd* pw,
char** NNULLABLE envlist) {
int xorg_pipefd[2];
pipe(xorg_pipefd);
(void)fflush(NULL);
__pid_t xorg_pid = fork();
if (xorg_pid == 0) {
if (!pw->pw_dir) _exit(EXIT_FAILURE);
// !!!!!!!!!! ATTENTION: this fails silently, of course add failure msgs but
// for now I can't so be careful
if (vt == -1) _exit(EXIT_FAILURE);
char* display_thing;
asprintf(&display_thing, ":%d", DEFAULT_XORG_DISPLAY);
if (!display_thing) _exit(EXIT_FAILURE);
char* vt_path;
asprintf(&vt_path, "vt%d", vt);
if (!vt_path) {
free(display_thing);
_exit(EXIT_FAILURE);
} }
/*char *buf;*/ // dup2(xorg_pipefd[1], STDERR_FILENO);
/*size_t bsize = snprintf(NULL, 0, "/run/user/%d", pw->pw_uid) + 1;*/ // dup2(xorg_pipefd[1], STDOUT_FILENO);
/*buf = malloc(bsize);*/ // close(xorg_pipefd[0]);
/*snprintf(buf, bsize, "/run/user/%d", pw->pw_uid);*/ // close(xorg_pipefd[1]);
/*setenv("XDG_RUNTIME_DIR", buf, true);*/
/*setenv("XDG_SESSION_CLASS", "user", true);*/ int exit = execle(XORG_COMMAND, XORG_COMMAND, display_thing, vt_path, NULL,
/*setenv("XDG_SESSION_ID", "1", true);*/ envlist);
/*setenv("XDG_SESSION_DESKTOP", , true);*/ perror("exec");
/*setenv("XDG_SEAT", "seat0", true);*/ // execle("X", "X", display_thing, vt_path, "-auth", xauth_path,
// "-nolisten", "tcp", "-background", "none", NULL, envlist);
printf("wtf3\n");
(void)fflush(stdout);
free(vt_path);
free(display_thing);
_exit(exit);
}
wait_for_x_ready(xorg_pipefd, xorg_pid);
__pid_t xorg_session_pid = fork();
if (xorg_session_pid == 0) {
int exit = session_exec_exec(exec, envlist);
perror("exec error");
(void)fputs("failure calling session\n", stderr);
_exit(exit);
}
// looks weird, waiting on -1 should wait on any child and then just check if
// its xorg server or the session and kill the other waiting on it
__pid_t pid;
int status; // not even read for now
while ((pid = waitpid(-1, &status, 0)) > 0) {
if (pid == xorg_pid || pid == xorg_session_pid) {
__pid_t pid_to_kill = pid ^ xorg_pid ^ xorg_session_pid;
if (pid == xorg_pid) printf("Xorg server died\n");
if (pid == xorg_session_pid) printf("Xorg session died\n");
kill(pid_to_kill, SIGTERM);
waitpid(pid_to_kill, &status, 0);
printf("wtf %d, x%d s%d - k%d\n", status, xorg_pid, xorg_session_pid,
pid_to_kill);
(void)fflush(stdout);
sleep(10);
break;
}
}
} }
#define SEND_MSG(MSG) \
{ \
write(pipefd[1], &(MSG), sizeof(struct child_msg)); \
close(pipefd[1]); \
}
#define SEND_ERR(MSG) \
{ \
write(pipefd[1], \
&(struct child_msg){.msg = (MSG), ._errno = errno, .err = true}, \
sizeof(struct child_msg)); \
close(pipefd[1]); \
_exit(EXIT_FAILURE); \
}
#define DUMMY_READ() \
{ \
char _dummy; \
read(pipefd[0], &_dummy, sizeof(_dummy)); \
}
inline static void forked(int pipefd[2], struct passwd* pw,
char* NNULLABLE user,
struct session* NNULLABLE session,
char** NNULLABLE envlist) {
if (chdir(pw->pw_dir) == -1) SEND_ERR("chdir");
if (setgid(pw->pw_gid) == -1) SEND_ERR("setgid");
if (initgroups(user, pw->pw_gid) == -1) SEND_ERR("initgroups");
if (setuid(pw->pw_uid) == -1) SEND_ERR("setuid");
SEND_MSG((struct child_msg){.err = false});
DUMMY_READ();
close(pipefd[0]);
if (session->type == XORG) {
launch_with_xorg_server(&session->exec, pw, envlist);
} else {
int exit = session_exec_exec(&session->exec, envlist);
perror("exec error");
(void)fputs("failure calling session\n", stderr);
_exit(exit);
}
}
#undef SEND_MSG
#undef SEND_ERR
#undef DUMMY_READ
// NOLINTBEGIN(readability-function-cognitive-complexity) // NOLINTBEGIN(readability-function-cognitive-complexity)
bool launch(char* user, char* passwd, struct session session, void (*cb)(void), bool launch(char* user, char* passwd, struct session session, void (*cb)(void),
struct config* config) { struct config* config) {
char** desktop_exec;
int desktop_count;
if (session.type != SHELL) {
desktop_exec = NULL;
int parse_status =
parse_exec_string(session.exec, &desktop_count, &desktop_exec);
if (parse_status != 0 || desktop_count == 0 || !desktop_exec[0]) {
print_err("failure parsing exec string");
log_printf("failure parsing exec string '%s': %d\n",
session.exec ? session.exec : "NULL", parse_status);
free_parsed_args(desktop_count, desktop_exec);
return false;
}
}
struct passwd* pw = getpwnam(user); struct passwd* pw = getpwnam(user);
if (pw == NULL) { if (pw == NULL) {
print_err("could not get user info"); print_err("could not get user info");
@@ -220,96 +240,72 @@ bool launch(char* user, char* passwd, struct session session, void (*cb)(void),
pam_handle_t* pamh = get_pamh(user, passwd); pam_handle_t* pamh = get_pamh(user, passwd);
if (pamh == NULL) { if (pamh == NULL) {
print_pam_msg("authentication failed", PAM_ERROR_MSG); print_err("error on pam authentication");
return false; return false;
} }
clear_pam_msg();
bool* reach_session = shmalloc(sizeof(bool)); struct pamh_getenv_status env_ret =
if (reach_session == NULL) { pamh_get_complete_env(pamh, pw, session.type);
perror("error allocating shared memory"); if (env_ret.error_flag != PAMH_ERR_NOERR) {
if (env_ret.error_flag == PAMH_ERR_ALLOC) {
print_err("allocator error");
} else if (env_ret.error_flag == PAMH_ERR_ERRNO) {
print_errno(env_ret.errfn);
} else if (env_ret.error_flag == PAMH_ERR_NOERRNO) {
print_err(env_ret.errfn);
}
return false; return false;
} }
*reach_session = false;
struct Vector envlist_vec = vec_from_raw((void**)env_ret.envlist);
if (session.type == XORG) {
char* display_env;
asprintf(&display_env, "DISPLAY=:%d", DEFAULT_XORG_DISPLAY);
if (!display_env) {
print_err("alloc error");
return false;
}
vec_push(&envlist_vec, display_env);
}
source_paths(&envlist_vec, &config->behavior.source, pw->pw_dir,
&config->behavior.user_source);
char** envlist = (char**)vec_as_raw(envlist_vec);
if (!envlist) {
print_err("vec alloc error");
return false;
}
int pipefd[2];
pipe(pipefd);
uint pid = fork(); uint pid = fork();
if (pid == 0) { // child if (pid == 0)
char* term = NULL; forked(pipefd, pw, user, &session, envlist);
char* getterm = getenv("TERM"); else {
// TODO: handle malloc error struct child_msg msg;
if (getterm != NULL) term = strdup(getterm); read(pipefd[0], &msg, sizeof(struct child_msg));
if (clearenv() != 0) { close(pipefd[0]);
print_errno("clearenv"); if (msg.err) {
_exit(EXIT_FAILURE); errno = msg._errno;
print_errno(msg.msg);
return false;
} }
char** envlist = pam_getenvlist(pamh); if (cb) cb();
if (envlist == NULL) { printf("\x1b[0m\x1b[H\x1b[J");
print_errno("pam_getenvlist");
_exit(EXIT_FAILURE);
}
for (size_t i = 0; envlist[i] != NULL; i++) {
putenv(envlist[i]);
}
// FIXME: path hotfix
putenv("PATH=/bin:/usr/bin");
if (term != NULL) {
setenv("TERM", term, true);
free(term);
}
free((void*)envlist);
moarEnv(user, session, pw, config);
// TODO: chown stdin to user
// does it inherit stdin from parent and
// does parent need to reclaim it after
// this dies?
if (setgid(pw->pw_gid) == -1) {
print_errno("setgid");
_exit(EXIT_FAILURE);
}
if (initgroups(user, pw->pw_gid) == -1) {
print_errno("initgroups");
_exit(EXIT_FAILURE);
}
if (setuid(pw->pw_uid) == -1) {
perror("setuid");
_exit(EXIT_FAILURE);
}
if (cb != NULL) cb();
*reach_session = true;
// TODO: test existence of executable with TryExec
printf("\x1b[0m");
// NOLINTNEXTLINE(bugprone-branch-clone)
if (session.type == SHELL) {
clear_screen();
(void)fflush(stdout); (void)fflush(stdout);
execlp(session.exec, session.exec, NULL); close(pipefd[1]);
} else if (session.type == XORG || session.type == WAYLAND) {
clear_screen(); int exit_code;
(void)fflush(stdout); waitpid((pid_t)pid, &exit_code, 0);
// NOLINTNEXTLINE
execvp(desktop_exec[0], desktop_exec);
// NOLINTNEXTLINE
free_parsed_args(desktop_count, desktop_exec);
}
perror("exec error");
(void)fputs("failure calling session\n", stderr);
} else {
pid_t child_pid = (pid_t)pid;
waitpid(child_pid, NULL, 0);
pam_setcred(pamh, PAM_DELETE_CRED); pam_setcred(pamh, PAM_DELETE_CRED);
pam_close_session(pamh, 0); pam_close_session(pamh, 0);
pam_end(pamh, PAM_SUCCESS); pam_end(pamh, PAM_SUCCESS);
if (*reach_session == false) return false; if (exit_code != 0) return false;
exit(0); exit(0);
} }

View File

@@ -4,6 +4,7 @@
#include <stdlib.h> #include <stdlib.h>
#include "chvt.h" #include "chvt.h"
#include "macros.h"
static char* vterms[] = {"/dev/tty", "/dev/tty0", "/dev/vc/0", "/dev/systty", static char* vterms[] = {"/dev/tty", "/dev/tty0", "/dev/vc/0", "/dev/systty",
"/dev/console"}; "/dev/console"};
@@ -23,16 +24,16 @@ int chvt_str(char* str) {
return chvt((int)i); return chvt((int)i);
} }
int vt = -1;
int chvt(int n) { int chvt(int n) {
// NOLINTNEXTLINE(clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling)
(void)fprintf(stderr, "activating vt %d\n", n); (void)fprintf(stderr, "activating vt %d\n", n);
vt = n;
// NOLINTNEXTLINE(readability-identifier-length) // NOLINTNEXTLINE(readability-identifier-length)
char c = 0; char c = 0;
for (size_t i = 0; i < sizeof(vterms) / sizeof(vterms[0]); i++) { for (size_t i = 0; i < LEN(vterms); i++) {
int fd = open(vterms[i], O_RDWR); int fd = open(vterms[i], O_RDWR);
if (fd >= 0 && isatty(fd) && ioctl(fd, KDGKBTYPE, &c) == 0 && c < 3) { if (fd >= 0 && isatty(fd) && ioctl(fd, KDGKBTYPE, &c) == 0 && c < 3) {
if (ioctl(fd, VT_ACTIVATE, n) < 0 || ioctl(fd, VT_WAITACTIVE, n) < 0) { if (ioctl(fd, VT_ACTIVATE, n) < 0 || ioctl(fd, VT_WAITACTIVE, n) < 0) {
// NOLINTNEXTLINE(clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling)
(void)fprintf(stderr, "Couldn't activate vt %d\n", n); (void)fprintf(stderr, "Couldn't activate vt %d\n", n);
return -1; return -1;
} }

View File

@@ -117,12 +117,12 @@ union typ_ptr {
char** string; char** string;
long long* number; long long* number;
bool* boolean; bool* boolean;
enum keys* key; enum Keys* key;
struct Vector* vec; struct Vector* vec;
uintptr_t ptr; uintptr_t ptr;
}; };
struct parser_error parse_key(enum introspection_type typ, union typ_ptr at, struct parser_error parse_key(enum IntrospectionType typ, union typ_ptr at,
char* key, size_t offset) { char* key) {
char* aux_str = NULL; char* aux_str = NULL;
struct parser_error aux_err; struct parser_error aux_err;
@@ -219,7 +219,7 @@ struct status config_line_handler(void* _config, char* table, char* k,
log_printf("[I] parsing [%s.%s] as %s\n", table, k, log_printf("[I] parsing [%s.%s] as %s\n", table, k,
INTROS_TYS_NAMES[this_intros_key->typ]); INTROS_TYS_NAMES[this_intros_key->typ]);
struct parser_error err = parse_key(this_intros_key->typ, k_addr, v, offset); struct parser_error err = parse_key(this_intros_key->typ, k_addr, v);
if (err.msg != NULL) { if (err.msg != NULL) {
log_printf("[E] cfg parser, failed to parse [%s.%s] (%s): %s\n", table, k, log_printf("[E] cfg parser, failed to parse [%s.%s] (%s): %s\n", table, k,
INTROS_TYS_NAMES[this_intros_key->typ], err.msg); INTROS_TYS_NAMES[this_intros_key->typ], err.msg);

View File

@@ -1,4 +1,3 @@
// NOLINTBEGIN(clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling,readability-function-cognitive-complexity)
#include <stdbool.h> #include <stdbool.h>
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
@@ -20,6 +19,7 @@ char* trim_str(char* str) {
return str; return str;
} }
// NOLINTNEXTLINE(readability-function-cognitive-complexity)
int read_desktop(FILE* fd, void* ctx, int read_desktop(FILE* fd, void* ctx,
struct status (*cb)(void* ctx, char* table, char* key, struct status (*cb)(void* ctx, char* table, char* key,
char* value)) { char* value)) {
@@ -82,4 +82,3 @@ int read_desktop(FILE* fd, void* ctx,
if (buf != NULL) free(buf); if (buf != NULL) free(buf);
return ret; return ret;
} }
// NOLINTEND(clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling,readability-function-cognitive-complexity)

View File

@@ -1,14 +1,59 @@
#include <stddef.h> #include <stddef.h>
#include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include <sys/stat.h>
#include <unistd.h>
#include "desktop_exec.h" #include "desktop_exec.h"
#include "macros.h"
// constants for exec string parsing // constants for exec string parsing
#define MAX_ARGS 100 #define MAX_ARGS 100
// ARG_LENGTH is the initial length of a parsed argument // ARG_LENGTH is the initial length of a parsed argument
#define ARG_LENGTH 64 #define ARG_LENGTH 64
// returns NULL on any error
// otherwise it returns the absolute path of the program that MUST BE FREED
static char* NULLABLE search_path(const char* NNULLABLE for_binary) {
char* path = strdup(getenv("PATH"));
if (!path) return NULL;
char* tok = strtok(path, ":");
while (tok) {
char* bin_path;
asprintf(&bin_path, "%s/%s", tok, for_binary);
if (!bin_path) {
free(path);
return NULL;
}
struct stat stat_buf;
if (stat(bin_path, &stat_buf) == 0) {
// TODO: check exec bit ig
// if(stat_buf.) {}
free(path);
return bin_path;
}
free(bin_path);
tok = strtok(NULL, ":");
}
return NULL;
}
// returns -1 on exec failure and -2 on search failure
int execvpe_desktop(char** args, char* NNULLABLE* NNULLABLE envlist) {
char* new_arg = search_path(args[0]);
if (!new_arg) return -2;
free(args[0]);
args[0] = new_arg;
return execve(args[0], args, envlist);
}
// parse Exec=/bin/prog arg1 arg2\ with\ spaces // parse Exec=/bin/prog arg1 arg2\ with\ spaces
void free_parsed_args(int arg_count, char** args) { void free_parsed_args(int arg_count, char** args) {
if (!args) return; if (!args) return;

View File

@@ -4,8 +4,6 @@
#include "ui.h" #include "ui.h"
#include "util.h" #include "util.h"
// NOLINTBEGIN(clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling)
struct editable_field efield_new(char* content) { struct editable_field efield_new(char* content) {
struct editable_field efield; struct editable_field efield;
if (content != NULL) { if (content != NULL) {
@@ -80,5 +78,3 @@ bool efield_seek(struct editable_field* self, char seek) {
self->pos = (u_char)(ptr - self->content); self->pos = (u_char)(ptr - self->content);
return ptr != start; return ptr != start;
} }
// NOLINTEND(clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling)

163
src/pam.c Normal file
View File

@@ -0,0 +1,163 @@
#include <pwd.h>
#include <security/_pam_types.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "macros.h"
#include "pam.h"
#include "sessions.h"
struct envpair {
const char* NNULLABLE name;
char* NULLABLE value;
};
char* NULLABLE make_env_kv(const char* NNULLABLE key, char* NNULLABLE value) {
char* buf = NULL;
asprintf(&buf, "%s=%s", key, value);
return buf;
}
void free_envlist(char** NNULLABLE envlist) {
for (char** ptr = envlist; *ptr; ptr++)
free(*ptr);
free((void*)envlist);
}
// NULL when allocation failure
// in any case, envlist would be freed after this function
char** NULLABLE merge_envlist(char** NNULLABLE envlist, struct envpair extra[],
size_t extra_len) {
size_t envlist_len = 0;
while (envlist[envlist_len])
envlist_len++;
size_t nonnullelems = 0;
for (size_t i = 0; i < extra_len; i++) {
if (extra[i].value) nonnullelems++;
}
size_t new_envlist_len = envlist_len + nonnullelems + 1;
char** new_envlist =
(char**)realloc((void*)envlist, sizeof(char*) * new_envlist_len);
if (!new_envlist) {
free_envlist(envlist);
return NULL;
}
// NOLINTNEXTLINE(readability-identifier-length)
size_t k = 0;
for (size_t i = 0; i < extra_len; i++) {
if (!extra[i].value) continue;
char* env_kv = make_env_kv(extra[i].name, extra[i].value);
if (!env_kv) goto free_new_envlist_extra;
new_envlist[envlist_len + k++] = env_kv;
}
new_envlist[envlist_len + nonnullelems] = NULL;
return new_envlist;
free_new_envlist_extra:
for (size_t j = 0; j < envlist_len + k; j++) {
free(new_envlist[envlist_len + j]);
}
free((void*)new_envlist);
return NULL;
}
char* NULLABLE xdg_ssession_type_str(enum SessionType typ) {
char* xdg_session_type = NULL;
if (typ == SHELL) xdg_session_type = "tty";
if (typ == XORG) xdg_session_type = "x11";
if (typ == WAYLAND) xdg_session_type = "wayland";
return xdg_session_type;
}
#define FAIL_ALLOC() \
{ \
return (struct pamh_getenv_status){.error_flag = PAMH_ERR_ALLOC}; \
}
#define FAIL(ERR, ERRFN) \
{ \
return (struct pamh_getenv_status){.error_flag = (ERR), .errfn = (ERRFN)}; \
}
struct pamh_getenv_status pamh_get_complete_env(pam_handle_t* handle,
struct passwd* NNULLABLE pw,
enum SessionType session_typ) {
char** raw_envlist = pam_getenvlist(handle);
if (!raw_envlist) FAIL(PAMH_ERR_ERRNO, "pam_getenvlist");
struct envpair extra_env[] = {
{"TERM", getenv("TERM")},
{"PATH", getenv("PATH")},
{"HOME", pw->pw_dir},
{"USER", pw->pw_name},
{"SHELL", pw->pw_shell},
{"LOGNAME", pw->pw_name},
{"XDG_SESSION_TYPE", xdg_ssession_type_str(session_typ)}};
char** envlist = merge_envlist(raw_envlist, extra_env, LEN(extra_env));
if (!envlist) FAIL_ALLOC();
return (struct pamh_getenv_status){
.error_flag = PAMH_ERR_NOERR,
.envlist = envlist,
};
}
#undef FAIL
#undef FAIL_ALLOC
///////////////
int pam_conversation(int num_msg, const struct pam_message** msg,
struct pam_response** resp, void* appdata_ptr) {
struct pam_response* reply = malloc(sizeof(struct pam_response) * num_msg);
if (!reply) {
return PAM_BUF_ERR;
}
for (int i = 0; i < num_msg; i++) {
reply[i].resp = NULL;
reply[i].resp_retcode = 0;
if (msg[i]->msg_style == PAM_PROMPT_ECHO_OFF ||
msg[i]->msg_style == PAM_PROMPT_ECHO_ON) {
char* input = (char*)appdata_ptr;
reply[i].resp = strdup(input);
}
}
*resp = reply;
return PAM_SUCCESS;
}
#ifndef PAM_SERVICE_FALLBACK
#define PAM_SERVICE_FALLBACK "login"
#endif
#define CHECK_PAM_RET(call) \
ret = (call); \
if (ret != PAM_SUCCESS) { \
pam_end(pamh, ret); \
return NULL; \
}
pam_handle_t* get_pamh(char* user, char* passwd) {
pam_handle_t* pamh = NULL;
struct pam_conv pamc = {pam_conversation, (void*)passwd};
int ret;
char* pam_service_override = getenv("LIDM_PAM_SERVICE");
char* pam_service_name =
pam_service_override ? pam_service_override : PAM_SERVICE_FALLBACK;
CHECK_PAM_RET(pam_start(pam_service_name, user, &pamc, &pamh))
CHECK_PAM_RET(pam_authenticate(pamh, 0))
CHECK_PAM_RET(pam_acct_mgmt(pamh, 0))
CHECK_PAM_RET(pam_setcred(pamh, PAM_ESTABLISH_CRED))
CHECK_PAM_RET(pam_open_session(pamh, 0))
CHECK_PAM_RET(pam_setcred(pamh, PAM_REINITIALIZE_CRED))
return pamh;
}
#undef CHECK_PAM_RET

View File

@@ -8,12 +8,14 @@
#include <sys/types.h> #include <sys/types.h>
#include "desktop.h" #include "desktop.h"
#include "desktop_exec.h"
#include "log.h" #include "log.h"
#include "macros.h"
#include "sessions.h" #include "sessions.h"
#include "util.h" #include "util.h"
struct source_dir { struct source_dir {
enum session_type type; enum SessionType type;
char* dir; char* dir;
}; };
@@ -72,9 +74,11 @@ struct status cb(void* _ctx, char* NULLABLE table, char* key, char* value) {
// also, always return 0 or we will break parsing and we don't want a bad // also, always return 0 or we will break parsing and we don't want a bad
// desktop file to break all possible sessions // desktop file to break all possible sessions
static enum session_type session_type; static enum SessionType session_type;
// NOLINTNEXTLINE(readability-function-cognitive-complexity) // NOLINTNEXTLINE(readability-function-cognitive-complexity)
static int fn(const char* fpath, const struct stat* sb, int typeflag) { static int fn(const char* fpath, const struct stat* sb, int typeflag) {
UNUSED(sb);
// guessing symlink behavior // guessing symlink behavior
// - FTW_PHYS if set doesn't follow symlinks, so ftw() has no flags and it // - FTW_PHYS if set doesn't follow symlinks, so ftw() has no flags and it
// follows symlinks, we should never get to handle that // follows symlinks, we should never get to handle that
@@ -95,22 +99,33 @@ static int fn(const char* fpath, const struct stat* sb, int typeflag) {
} }
int ret = read_desktop(fd, &ctx, &cb); int ret = read_desktop(fd, &ctx, &cb);
if (ret < 0) { // any error // any error
log_printf("[E] format error parsing %s", fpath); if (ret < 0) goto err_close;
return 0;
}
(void)fclose(fd); (void)fclose(fd);
// TODO: filter based on tryexec
// https://specifications.freedesktop.org/desktop-entry-spec/latest/recognized-keys.html
free(ctx.tryexec);
// just add this to the list // just add this to the list
if (ctx.name != NULL && ctx.exec != NULL) { if (ctx.name != NULL && ctx.exec != NULL) {
struct session* this_session = malloc(sizeof(struct session)); struct session* this_session = malloc(sizeof(struct session));
if (this_session == NULL) return 0; if (this_session == NULL) return 0;
int arg_count;
char** args;
int parse_status = parse_exec_string(ctx.exec, &arg_count, &args);
if (parse_status != 0 || arg_count == 0 || !args[0]) {
log_printf("[E] parsing exec string '%s': %d\n", ctx.exec, parse_status);
free(this_session);
goto err_parsing;
}
free(ctx.exec);
*this_session = (struct session){ *this_session = (struct session){
.name = ctx.name, .name = ctx.name,
.exec = ctx.exec, .exec = session_exec_desktop(arg_count, args),
.tryexec = ctx.tryexec,
.type = session_type, .type = session_type,
}; };
@@ -118,6 +133,12 @@ static int fn(const char* fpath, const struct stat* sb, int typeflag) {
} }
return 0; return 0;
err_close:
(void)fclose(fd);
err_parsing:
log_printf("[E] format error parsing %s", fpath);
return 0;
} }
// This code is designed to be run purely single threaded // This code is designed to be run purely single threaded
@@ -127,7 +148,7 @@ struct Vector get_avaliable_sessions() {
vec_reserve(&sessions, LIKELY_BOUND_SESSIONS); vec_reserve(&sessions, LIKELY_BOUND_SESSIONS);
cb_sessions = &sessions; cb_sessions = &sessions;
for (size_t i = 0; i < (sizeof(SOURCES) / sizeof(SOURCES[0])); i++) { for (size_t i = 0; i < LEN(SOURCES); i++) {
log_printf("[I] parsing into %s\n", SOURCES[i].dir); log_printf("[I] parsing into %s\n", SOURCES[i].dir);
session_type = SOURCES[i].type; session_type = SOURCES[i].type;
ftw(SOURCES[i].dir, &fn, 1); ftw(SOURCES[i].dir, &fn, 1);

139
src/ui.c
View File

@@ -5,7 +5,6 @@
#include <errno.h> #include <errno.h>
#include <limits.h> #include <limits.h>
#include <pwd.h> #include <pwd.h>
#include <security/pam_appl.h>
#include <signal.h> #include <signal.h>
#include <stdbool.h> #include <stdbool.h>
#include <stddef.h> #include <stddef.h>
@@ -26,6 +25,7 @@
#include "keys.h" #include "keys.h"
#include "launch_state.h" #include "launch_state.h"
#include "log.h" #include "log.h"
#include "macros.h"
#include "ofield.h" #include "ofield.h"
#include "sessions.h" #include "sessions.h"
#include "ui.h" #include "ui.h"
@@ -62,6 +62,7 @@ struct config* g_config = NULL;
static volatile sig_atomic_t need_resize = 0; static volatile sig_atomic_t need_resize = 0;
static void process_sigwinch(int signal) { static void process_sigwinch(int signal) {
UNUSED(signal);
need_resize = 1; need_resize = 1;
} }
@@ -110,8 +111,8 @@ static char* fmt_time(const char* fmt) {
} }
} }
char* trunc_gethostname(size_t maxlen, const char* const ELLIPSIS) { char* trunc_gethostname(const size_t MAXLEN, const char* const ELLIPSIS) {
if (utf8len(ELLIPSIS) > maxlen) return NULL; if (utf8len(ELLIPSIS) > MAXLEN) return NULL;
size_t alloc_size = HOST_NAME_MAX + strlen(ELLIPSIS) + 1; size_t alloc_size = HOST_NAME_MAX + strlen(ELLIPSIS) + 1;
char* buf = malloc(alloc_size); char* buf = malloc(alloc_size);
if (!buf) return NULL; if (!buf) return NULL;
@@ -121,8 +122,8 @@ char* trunc_gethostname(size_t maxlen, const char* const ELLIPSIS) {
return NULL; return NULL;
} }
if (utf8len(buf) > maxlen) { if (utf8len(buf) > MAXLEN) {
size_t end = utf8trunc(buf, maxlen - utf8len(ELLIPSIS)); size_t end = utf8trunc(buf, MAXLEN - utf8len(ELLIPSIS));
strcpy(&buf[end], ELLIPSIS); strcpy(&buf[end], ELLIPSIS);
} }
return buf; return buf;
@@ -152,7 +153,7 @@ void ui_update_cursor_focus() {
(void)printf("\x1b[%d;%dH", line, col); (void)printf("\x1b[%d;%dH", line, col);
} }
void ui_update_field(enum input focused_input) { void ui_update_field(enum Input focused_input) {
if (focused_input == PASSWD) { if (focused_input == PASSWD) {
print_passwd(utf8len(of_passwd.efield.content), false); print_passwd(utf8len(of_passwd.efield.content), false);
} else if (focused_input == SESSION) { } else if (focused_input == SESSION) {
@@ -171,7 +172,7 @@ void ui_update_ffield() {
} }
void ui_update_ofield(struct opts_field* NNULLABLE self) { void ui_update_ofield(struct opts_field* NNULLABLE self) {
enum input input; enum Input input;
if (self == &of_session) if (self == &of_session)
input = SESSION; input = SESSION;
else if (self == &of_user) else if (self == &of_user)
@@ -281,43 +282,29 @@ int load(struct Vector* users, struct Vector* sessions) {
(void)fflush(stdout); (void)fflush(stdout);
if (!read_press_nb(&len, seq, &tv)) continue; if (!read_press_nb(&len, seq, &tv)) continue;
if (*seq == '\x1b') { if (*seq == '\x1b') {
enum keys ansi_code = find_ansi(seq); struct option_keys ansi_code = find_ansi(seq);
if (ansi_code != -1) { if (ansi_code.is_some) {
if (ansi_code == ESC) { enum Keys ansi_key = ansi_code.key;
if (ansi_key == ESC) {
esc = 2; esc = 2;
} else if (ansi_code == g_config->functions.refresh) { } else if (ansi_key == g_config->functions.refresh) {
restore_all(); restore_all();
return 0; return 0;
} else if (ansi_code == g_config->functions.reboot) { } else if (ansi_key == g_config->functions.reboot) {
restore_all(); restore_all();
reboot(RB_AUTOBOOT); reboot(RB_AUTOBOOT);
exit(0); exit(0);
} else if (ansi_code == g_config->functions.poweroff) { } else if (ansi_key == g_config->functions.poweroff) {
restore_all(); restore_all();
reboot(RB_POWER_OFF); reboot(RB_POWER_OFF);
exit(0); exit(0);
} else if (g_config->functions.fido != NONE && } else if (ansi_key == A_UP || ansi_key == A_DOWN) {
ansi_code == g_config->functions.fido) { st_ch_focus(ansi_key == A_DOWN ? 1 : -1);
bool successful_write = write_launch_state((struct LaunchState){ } else if (ansi_key == A_RIGHT || ansi_key == A_LEFT) {
.username = st_user().username,
.session_opt =
st_session(g_config->behavior.include_defshell).name,
});
if (!successful_write) log_puts("[E] failed to write launch state");
if (!launch(st_user().username, "",
st_session(g_config->behavior.include_defshell),
&restore_all, g_config)) {
print_passwd(utf8len(of_passwd.efield.content), true);
ui_update_cursor_focus();
}
} else if (ansi_code == A_UP || ansi_code == A_DOWN) {
st_ch_focus(ansi_code == A_DOWN ? 1 : -1);
} else if (ansi_code == A_RIGHT || ansi_code == A_LEFT) {
if (esc) if (esc)
st_ch_of_opts(ansi_code == A_RIGHT ? 1 : -1); st_ch_of_opts(ansi_key == A_RIGHT ? 1 : -1);
else else
st_ch_ef_col(ansi_code == A_RIGHT ? 1 : -1); st_ch_ef_col(ansi_key == A_RIGHT ? 1 : -1);
} }
} }
} else { } else {
@@ -357,40 +344,25 @@ u_char get_render_pos_offset(struct opts_field* self, u_char maxlen) {
return pos - ofield_display_cursor_col(self, maxlen); return pos - ofield_display_cursor_col(self, maxlen);
} }
#define HOSTNAME_SIZE (VALUES_COL - VALUES_SEPR - BOX_HMARGIN - 1)
void print_head() { void print_head() {
char* fmtd_time = fmt_time(g_config->behavior.timefmt); // hostname doesn't just change on runtime
size_t len_tm = utf8len(fmtd_time); static char* hostname = NULL;
if (!hostname)
// calculate the space available for the host name hostname = trunc_gethostname(HOSTNAME_SIZE, g_config->strings.ellipsis);
ssize_t hostname_size = BOX_WIDTH - (BOX_HMARGIN * 2) - len_tm - VALUES_SEPR; if (!hostname) hostname = "unknown";
if (hostname_size < 0) hostname_size = 0;
// hostname doesn't just change on runtime,
// but the length of the time string might
static char* NULLABLE hostname = NULL;
static size_t hostname_calcd_size;
// save the truncated hostname and the length it truncated to,
// if said length changes recalculate this (and free previous str)
if (!hostname || hostname_calcd_size != hostname_size) {
if (hostname) free(hostname);
hostname = trunc_gethostname(hostname_size, g_config->strings.ellipsis);
hostname_calcd_size = hostname_size;
}
clean_line(box_start, HEAD_ROW); clean_line(box_start, HEAD_ROW);
// put hostname // put hostname
if (hostname_size) printf("\x1b[%dG\x1b[%sm%s\x1b[%sm",
printf("\x1b[%dG\x1b[%sm%s\x1b[%sm", box_start.x + 1 + BOX_HMARGIN, box_start.x + VALUES_COL - VALUES_SEPR - (uint)utf8len(hostname),
g_config->colors.e_hostname, hostname ? hostname : "unknown", g_config->colors.e_hostname, hostname, g_config->colors.fg);
g_config->colors.fg);
// put date // put date
char* fmtd_time = fmt_time(g_config->behavior.timefmt);
printf("\x1b[%dG\x1b[%sm%s\x1b[%sm", printf("\x1b[%dG\x1b[%sm%s\x1b[%sm",
box_start.x + BOX_WIDTH - 1 - BOX_HMARGIN - (uint)len_tm, box_start.x + BOX_WIDTH - 1 - BOX_HMARGIN - (uint)utf8len(fmtd_time),
g_config->colors.e_date, fmtd_time, g_config->colors.fg); g_config->colors.e_date, fmtd_time, g_config->colors.fg);
free(fmtd_time); free(fmtd_time);
} }
@@ -525,8 +497,6 @@ static void print_box() {
} }
static void print_footer() { static void print_footer() {
bool fido_enabled = g_config->functions.fido != NONE;
size_t bsize = utf8len(g_config->strings.f_poweroff) + size_t bsize = utf8len(g_config->strings.f_poweroff) +
utf8len(KEY_NAMES[g_config->functions.poweroff]) + utf8len(KEY_NAMES[g_config->functions.poweroff]) +
utf8len(g_config->strings.f_reboot) + utf8len(g_config->strings.f_reboot) +
@@ -534,31 +504,19 @@ static void print_footer() {
utf8len(g_config->strings.f_refresh) + utf8len(g_config->strings.f_refresh) +
utf8len(KEY_NAMES[g_config->functions.refresh]); utf8len(KEY_NAMES[g_config->functions.refresh]);
bsize += 2 * 2 + 3 * 1; bsize += 2 * 2 + // 2 wide separators between 3 items
3 * 1; // 3 thin separators inside every item
if (fido_enabled) {
bsize += utf8len(g_config->strings.f_fido) +
utf8len(KEY_NAMES[g_config->functions.fido]) + 2 + 1;
}
uint row = window.ws_row - 1; uint row = window.ws_row - 1;
uint col = window.ws_col - 2 - bsize; uint col = window.ws_col - 2 - bsize;
printf(
printf("\x1b[%d;%dH%s \x1b[%sm%s\x1b[%sm %s \x1b[%sm%s\x1b[%sm ", row, col, "\x1b[%3$d;%4$dH%8$s \x1b[%1$sm%5$s\x1b[%2$sm %9$s "
g_config->strings.f_poweroff, g_config->colors.e_key, "\x1b[%1$sm%6$s\x1b[%2$sm %10$s \x1b[%1$sm%7$s\x1b[%2$sm",
KEY_NAMES[g_config->functions.poweroff], g_config->colors.fg, g_config->colors.e_key, g_config->colors.fg, row, col,
g_config->strings.f_reboot, g_config->colors.e_key, KEY_NAMES[g_config->functions.poweroff],
KEY_NAMES[g_config->functions.reboot], g_config->colors.fg); KEY_NAMES[g_config->functions.reboot],
KEY_NAMES[g_config->functions.refresh], g_config->strings.f_poweroff,
if (fido_enabled) { g_config->strings.f_reboot, g_config->strings.f_refresh);
printf("%s \x1b[%sm%s\x1b[%sm ", g_config->strings.f_fido,
g_config->colors.e_key, KEY_NAMES[g_config->functions.fido],
g_config->colors.fg);
}
printf("%s \x1b[%sm%s\x1b[%sm", g_config->strings.f_refresh,
g_config->colors.e_key, KEY_NAMES[g_config->functions.refresh],
g_config->colors.fg);
} }
void print_err(const char* msg) { void print_err(const char* msg) {
@@ -566,21 +524,6 @@ void print_err(const char* msg) {
msg, errno, strerror(errno)); msg, errno, strerror(errno));
} }
void print_pam_msg(const char* msg, int msg_style) {
uint row = box_start.y + BOX_HEIGHT + 1;
const char* color =
(msg_style == PAM_ERROR_MSG) ? g_config->colors.err : g_config->colors.fg;
printf("\x1b[%d;%dH\x1b[K\x1b[%sm%.*s\x1b[%sm", row, box_start.x, color,
BOX_WIDTH, msg, g_config->colors.fg);
(void)fflush(stdout);
}
void clear_pam_msg(void) {
uint row = box_start.y + BOX_HEIGHT + 1;
printf("\x1b[%d;%dH\x1b[K", row, box_start.x);
(void)fflush(stdout);
}
void print_errno(const char* descr) { void print_errno(const char* descr) {
if (descr == NULL) if (descr == NULL)
(void)fprintf(stderr, "\x1b[%d;%dH\x1b[%smunknown error(%d): %s", (void)fprintf(stderr, "\x1b[%d;%dH\x1b[%smunknown error(%d): %s",

View File

@@ -4,7 +4,7 @@
#include "ui.h" #include "ui.h"
#include "users.h" #include "users.h"
enum input focused_input = PASSWD; enum Input focused_input = PASSWD;
struct Vector* gusers; struct Vector* gusers;
struct Vector* gsessions; struct Vector* gsessions;
@@ -13,7 +13,7 @@ struct opts_field of_session;
struct opts_field of_user; struct opts_field of_user;
struct opts_field of_passwd; struct opts_field of_passwd;
struct opts_field* NNULLABLE get_opts_field(enum input from) { struct opts_field* NNULLABLE get_opts_field(enum Input from) {
if (from == SESSION) return &of_session; if (from == SESSION) return &of_session;
if (from == USER) return &of_user; if (from == USER) return &of_user;
if (from == PASSWD) return &of_passwd; if (from == PASSWD) return &of_passwd;
@@ -40,7 +40,8 @@ struct session st_session(bool include_defshell) {
if (include_defshell && of_session.current_opt == gsessions->length + 1) { if (include_defshell && of_session.current_opt == gsessions->length + 1) {
struct session shell_session; struct session shell_session;
shell_session.type = SHELL; shell_session.type = SHELL;
shell_session.exec = shell_session.name = st_user().shell; shell_session.exec =
session_exec_shell(shell_session.name = st_user().shell);
return shell_session; return shell_session;
} }
@@ -49,7 +50,8 @@ struct session st_session(bool include_defshell) {
struct session custom_session; struct session custom_session;
custom_session.type = SHELL; custom_session.type = SHELL;
custom_session.name = custom_session.exec = of_session.efield.content; custom_session.exec =
session_exec_shell(custom_session.name = of_session.efield.content);
return custom_session; return custom_session;
} }
@@ -86,7 +88,7 @@ void st_kbd_type(char* text, bool cfg_include_defshell) {
start = st_user().username; start = st_user().username;
if (focused_input == SESSION && of_session.current_opt != 0 && if (focused_input == SESSION && of_session.current_opt != 0 &&
st_session(cfg_include_defshell).type == SHELL) st_session(cfg_include_defshell).type == SHELL)
start = st_session(cfg_include_defshell).exec; start = st_session(cfg_include_defshell).exec.shell;
ofield_kbd_type(field, text, start); ofield_kbd_type(field, text, start);
ui_update_ffield(); ui_update_ffield();

View File

@@ -13,10 +13,10 @@
static int selret_magic(); static int selret_magic();
int find_keyname(enum keys* at, const char* name) { int find_keyname(enum Keys* at, const char* name) {
for (size_t i = 0; i < LEN(KEY_MAPPINGS); i++) { for (size_t i = 0; i < LEN(KEY_MAPPINGS); i++) {
if (strcmp(KEY_NAMES[i], name) == 0) { if (strcmp(KEY_NAMES[i], name) == 0) {
*at = (enum keys)i; *at = (enum Keys)i;
return 0; return 0;
} }
} }
@@ -24,16 +24,19 @@ int find_keyname(enum keys* at, const char* name) {
return -1; return -1;
} }
enum keys find_ansi(const char* seq) { struct option_keys find_ansi(const char* seq) {
for (size_t i = 0; i < LEN(KEY_MAPPINGS); i++) { for (size_t i = 0; i < LEN(KEY_MAPPINGS); i++) {
struct key_mapping mapping = KEY_MAPPINGS[i]; struct key_mapping mapping = KEY_MAPPINGS[i];
for (size_t j = 0; mapping.sequences[j] != NULL; j++) { for (size_t j = 0; mapping.sequences[j] != NULL; j++) {
if (strcmp(mapping.sequences[j], seq) == 0) { if (strcmp(mapping.sequences[j], seq) == 0) {
return (enum keys)i; return (struct option_keys){
.is_some = true,
.key = (enum Keys)i,
};
} }
} }
} }
return -1; return (struct option_keys){.is_some = false};
} }
void read_press(u_char* length, char* out) { void read_press(u_char* length, char* out) {
@@ -147,6 +150,23 @@ const struct Vector VEC_NEW = {
.pages = NULL, .pages = NULL,
}; };
struct Vector vec_from_raw(void** raw) {
size_t len = 0;
while (raw[len])
len++;
return (struct Vector){
.length = len,
.capacity = len,
.pages = raw,
};
}
void** vec_as_raw(struct Vector self) {
if (vec_push(&self, NULL) != 0) return NULL;
return self.pages;
}
int vec_resize(struct Vector* self, size_t size) { int vec_resize(struct Vector* self, size_t size) {
void** new_location = void** new_location =
(void**)realloc((void*)self->pages, size * sizeof(void*)); (void**)realloc((void*)self->pages, size * sizeof(void*));

View File

@@ -25,13 +25,11 @@
[functions] [functions]
# poweroff = F1 # poweroff = F1
# reboot = F2 # reboot = F2
# fido = NONE
# refresh = F5 # refresh = F5
[strings] [strings]
# f_poweroff = "poweroff" # f_poweroff = "poweroff"
# f_reboot = "reboot" # f_reboot = "reboot"
# f_fido = "fido"
# f_refresh = "refresh" # f_refresh = "refresh"
# e_user = "user" # e_user = "user"
# e_passwd = "password" # e_passwd = "password"