#include <stddef.h> // size_t
#include <glob.h> // glob(), globfree()
#include <grp.h> // getgrnam()
#include <libgen.h> // basename()
#include <stdio.h> // printf(), perror()
#include <stdlib.h> // getenv(), malloc(), free(), exit()
#include <string.h> // strcmp()
#include <sys/mount.h> // mount(), umount()
#include <sys/types.h> // getgrnam()
#define GROUP "sftponly"
#define HOME "/home"
#define SKEL "/etc/skel.sftponly"
#define SOURCE "/data/media"
// clang pam_exec.c -o pam_exec && PAM_TYPE=open_session PAM_USER=cpiro ./pam_exec
/* grep scans string array haystack for string needle
* if found, returns pointer to string in haystack (for easy modification)
* if not found, returns NULL at end of haystack -- check before modifying!
*/
char *grep (const char *const needle, char *const *haystack) {
/* while we have nodes, and this is not our node, move to next node */
while (*haystack && strcmp(*haystack, needle)) { haystack++; }
/* return either successfully found node or NULL at end of list */
return *haystack;
}
/* alter_mount is only a function pointer, assigned to either do_mount or
* do_unmount in main, below
*/
void (*alter_mount) (char *src, char *dest);
/* mount the requested src to dst as appropriate, based on its node list
*/
void do_mount (char *src, char *dst) {
char **readwrite = (char*[]){ "dev", "Dropbox", NULL };
/* if in the readwrite list above, mount readwrite */
if (grep(basename(src), readwrite)) mount(src, dst, NULL, MS_BIND, NULL);
/* else mount readonly */
else mount(NULL, dst, NULL, MS_BIND|MS_REMOUNT|MS_RDONLY, NULL);
}
/* everything is unmounted the same */
void do_unmount (char *src, char *dst) { umount(dst); }
__attribute__((noreturn)) void die (char *msg) {
perror(msg);
exit(1);
}
void resize_buffer(void **ptr, size_t len) {
void *tmp = realloc(*ptr, len);
if (tmp != NULL) *ptr = tmp;
else die("realloc failed");
}
int main (int argc, char **argv, char **envp) {
// defined by pam_exec.so
char *pam_user = getenv("PAM_USER");
// figure out if we're starting a session or ending one
// and set the mount altering function appropriately
char *pam_type = getenv("PAM_TYPE");
if (strcmp(pam_type, "open_session") == 0) alter_mount = &do_mount;
else alter_mount = &do_unmount;
// get users in GROUP
struct group *record = getgrnam(GROUP);
if (!record) die("getgrnam did not find the requested group");
char **users = record->gr_mem;
if (!grep(pam_user, users)) { return 0; }
// user is in GROUP
// store glob result of SKEL directory
glob_t globbuf;
glob(SKEL "/*", 0, NULL, &globbuf);
char **match = globbuf.gl_pathv;
char *node = basename(*match);
size_t srclen = (strlen(SOURCE) + strlen(node) + 2);
size_t dstlen = strlen(HOME) + strlen(pam_user) + strlen(node) + 3;
char *src = malloc(srclen);
char *dst = malloc(dstlen);
for ( ; *match; node = basename(*++match)) {
{ /* limit scope of new_srclen and new_dstlen */
size_t new_srclen = strlen(SOURCE) + strlen(node) + 2;
size_t new_dstlen = strlen(HOME) + strlen(pam_user) + strlen(node) + 3;
if (new_srclen > srclen)
resize_buffer((void**)&src, srclen = new_srclen);
if (new_dstlen > dstlen)
resize_buffer((void**)&dst, dstlen = new_dstlen);
} /* new_srclen and new_dstlen no longer exist */
/* this use of sprintf is actually a little bit wasteful.
* instead, consider adding / to defines and compiling
* HOME + pam_user + "/" beforehand and just strcatting.
* you can re-truncate the buffer by tossing a NUL in at the right
* place and thus reuse the buffer, skipping the naive strcpy.
* MUCH faster, less RAM, doesn't require varargs, or format strings.
*/
sprintf(src, "%s/%s", SOURCE, node);
sprintf(dst, "%s/%s/%s", HOME, pam_user, node);
alter_mount(src, dst);
}
free(src);
free(dst);
globfree(&globbuf);
return 0;
}