session server ssh UPDATE add local pubkey auth
diff --git a/src/session_server_ssh.c b/src/session_server_ssh.c
index b2178a4..ca4511b 100644
--- a/src/session_server_ssh.c
+++ b/src/session_server_ssh.c
@@ -26,6 +26,7 @@
 
 #include <arpa/inet.h>
 #include <assert.h>
+#include <ctype.h>
 #include <errno.h>
 #include <libssh/libssh.h>
 #include <libssh/server.h>
@@ -178,6 +179,232 @@
     return 0;
 }
 
+static char *
+nc_server_ssh_uid_to_str(uid_t uid)
+{
+    int buf_len;
+    char *uid_str;
+
+    /* get the number of digits and alloc */
+    buf_len = snprintf(NULL, 0, "%u", uid);
+    uid_str = malloc(buf_len + 1);
+    NC_CHECK_ERRMEM_RET(!uid_str, NULL);
+
+    /* convert to string */
+    sprintf(uid_str, "%u", uid);
+    uid_str[buf_len] = '\0';
+    return uid_str;
+}
+
+static int
+nc_server_ssh_str_append(const char src_c, const char *src_str, int *size, int *idx, char **dst)
+{
+    int src_size, allocate = 0, ret;
+
+    /* get size of char/string we want to append */
+    if (src_str) {
+        src_size = strlen(src_str);
+    } else {
+        src_size = 1;
+    }
+
+    /* check if we have enough space, if not realloc */
+    while ((src_size + *idx) >= *size) {
+        (*size) += 16;
+        allocate = 1;
+    }
+    if (allocate) {
+        *dst = nc_realloc(*dst, *size);
+        NC_CHECK_ERRMEM_RET(!*dst, 1);
+    }
+
+    /* append the char/string */
+    if (src_str) {
+        ret = sprintf(*dst + *idx, "%s", src_str);
+    } else {
+        ret = sprintf(*dst + *idx, "%c", src_c);
+    }
+    if (ret < 0) {
+        return 1;
+    }
+
+    *idx += ret;
+    return 0;
+}
+
+static int
+nc_server_ssh_get_system_keys_path(const char *username, char **out_path)
+{
+    int ret = 0, i, have_percent = 0, size = 0, idx = 0;
+    const char *path_fmt = server_opts.authkey_path_fmt;
+    char *path = NULL, *buf = NULL, *uid = NULL;
+    struct passwd *pw, pw_buf;
+    size_t buf_len = 0;
+
+    /* check if the path format contains any tokens */
+    if (strstr(path_fmt, "%h") || strstr(path_fmt, "%U") || strstr(path_fmt, "%u") || strstr(path_fmt, "%%")) {
+        /* get pw */
+        pw = nc_getpw(0, username, &pw_buf, &buf, &buf_len);
+        if (!pw) {
+            ERR(NULL, "Unable to get passwd entry for user \"%s\".", username);
+            ret = 1;
+            goto cleanup;
+        }
+
+        /* convert UID to a string */
+        uid = nc_server_ssh_uid_to_str(pw->pw_uid);
+        if (!uid) {
+            ret = 1;
+            goto cleanup;
+        }
+    } else {
+        /* no tokens, just copy the path and return */
+        *out_path = strdup(path_fmt);
+        NC_CHECK_ERRMEM_RET(!*out_path, 1);
+        goto cleanup;
+    }
+
+    /* go over characters from format, copy them to path and interpret tokens correctly */
+    for (i = 0; path_fmt[i]; i++) {
+        if (have_percent) {
+            /* special token, need to convert it */
+            if (path_fmt[i] == '%') {
+                ret = nc_server_ssh_str_append('%', NULL, &size, &idx, &path);
+            } else if (path_fmt[i] == 'h') {
+                /* user home */
+                ret = nc_server_ssh_str_append(0, pw->pw_dir, &size, &idx, &path);
+            } else if (path_fmt[i] == 'u') {
+                /* username */
+                ret = nc_server_ssh_str_append(0, username, &size, &idx, &path);
+            } else if (path_fmt[i] == 'U') {
+                /* UID */
+                ret = nc_server_ssh_str_append(0, uid, &size, &idx, &path);
+            } else {
+                ERR(NULL, "Failed to parse system public keys path format \"%s\".", server_opts.authkey_path_fmt);
+                ret = 1;
+            }
+
+            have_percent = 0;
+        } else {
+            if (path_fmt[i] == '%') {
+                have_percent = 1;
+            } else {
+                /* ordinary character with no meaning */
+                ret = nc_server_ssh_str_append(path_fmt[i], NULL, &size, &idx, &path);
+            }
+        }
+
+        if (ret) {
+            free(path);
+            goto cleanup;
+        }
+    }
+
+    *out_path = path;
+cleanup:
+    free(uid);
+    free(buf);
+    return ret;
+}
+
+/* reads public keys from authorized_keys-like file */
+static int
+nc_server_ssh_read_authorized_keys_file(const char *path, struct nc_public_key **pubkeys, uint16_t *pubkey_count)
+{
+    int ret = 0, line_num = 0;
+    FILE *f = NULL;
+    char *line = NULL, *ptr, *ptr2;
+    size_t n;
+    enum ssh_keytypes_e ktype;
+
+    NC_CHECK_ARG_RET(NULL, path, pubkeys, 1);
+
+    *pubkeys = NULL;
+    *pubkey_count = 0;
+
+    f = fopen(path, "r");
+    if (!f) {
+        ERR(NULL, "Unable to open \"%s\" (%s).", path, strerror(errno));
+        ret = 1;
+        goto cleanup;
+    }
+
+    while (getline(&line, &n, f) > -1) {
+        ++line_num;
+        if ((line[0] == '#') || (line[0] == '\n')) {
+            /* comment or empty line */
+            continue;
+        }
+
+        /* separate key type */
+        ptr = line;
+        for (ptr2 = ptr; ptr2[0] && !isspace(ptr2[0]); ptr2++) {}
+        if (!ptr2[0]) {
+            ERR(NULL, "Invalid format of authorized keys file \"%s\" on line %d.", path, line_num);
+            ret = 1;
+            goto cleanup;
+        }
+        ptr2[0] = '\0';
+
+        /* detect key type */
+        ktype = ssh_key_type_from_name(ptr);
+        if ((ktype != SSH_KEYTYPE_RSA) && (ktype != SSH_KEYTYPE_ECDSA_P256) && (ktype != SSH_KEYTYPE_ECDSA_P384) &&
+                (ktype != SSH_KEYTYPE_ECDSA_P521) && (ktype != SSH_KEYTYPE_ED25519)) {
+            WRN(NULL, "Unsupported key type \"%s\" in authorized keys file \"%s\" on line %d.", ptr, path, line_num);
+            continue;
+        }
+
+        /* get key data */
+        ptr = ptr2 + 1;
+        for (ptr2 = ptr; ptr2[0] && !isspace(ptr2[0]); ptr2++) {}
+        ptr2[0] = '\0';
+
+        /* add the key */
+        *pubkeys = nc_realloc(*pubkeys, (*pubkey_count + 1) * sizeof **pubkeys);
+        NC_CHECK_ERRMEM_GOTO(!(*pubkeys), ret = 1, cleanup);
+        ret = asprintf(&(*pubkeys)[*pubkey_count].name, "authorized_key_%" PRIu16, *pubkey_count);
+        NC_CHECK_ERRMEM_GOTO(ret == -1, (*pubkeys)[*pubkey_count].name = NULL; ret = 1, cleanup);
+        (*pubkeys)[*pubkey_count].type = NC_PUBKEY_FORMAT_SSH;
+        (*pubkeys)[*pubkey_count].data = strdup(ptr);
+        NC_CHECK_ERRMEM_GOTO(!(*pubkeys)[*pubkey_count].data, ret = 1, cleanup);
+        (*pubkey_count)++;
+    }
+
+    /* ok */
+    ret = 0;
+cleanup:
+    if (f) {
+        fclose(f);
+    }
+    free(line);
+    return ret;
+}
+
+static int
+nc_server_ssh_get_system_keys(const char *username, struct nc_public_key **pubkeys, uint16_t *pubkey_count)
+{
+    int ret = 0;
+    char *path = NULL;
+
+    /* convert the path format to get the actual path */
+    ret = nc_server_ssh_get_system_keys_path(username, &path);
+    if (ret) {
+        ERR(NULL, "Getting system keys path failed.");
+        goto cleanup;
+    }
+
+    /* get the keys */
+    ret = nc_server_ssh_read_authorized_keys_file(path, pubkeys, pubkey_count);
+    if (ret) {
+        ERR(NULL, "Reading system keys failed.");
+        goto cleanup;
+    }
+
+cleanup:
+    free(path);
+    return ret;
+}
+
 /**
  * @brief Compare hashed password with a cleartext password for a match.
  *
@@ -753,6 +980,28 @@
 
 #endif /* HAVE_LIBPAM */
 
+API int
+nc_server_ssh_set_authkey_path_format(const char *path)
+{
+    int ret = 0;
+
+    NC_CHECK_ARG_RET(NULL, path, 1);
+
+    /* CONFIG LOCK */
+    pthread_rwlock_wrlock(&server_opts.config_lock);
+
+    free(server_opts.authkey_path_fmt);
+    server_opts.authkey_path_fmt = strdup(path);
+    if (!server_opts.authkey_path_fmt) {
+        ERRMEM;
+        ret = 1;
+    }
+
+    /* CONFIG UNLOCK */
+    pthread_rwlock_unlock(&server_opts.config_lock);
+    return ret;
+}
+
 /*
  *  Get the public key type from binary data stored in buffer.
  *  The data is in the form of: 4 bytes = data length, then data of data length
@@ -845,18 +1094,27 @@
     uint16_t i, pubkey_count;
     int ret = 0;
     ssh_key new_key = NULL;
-    struct nc_public_key *pubkeys;
+    struct nc_public_key *pubkeys = NULL;
 
     /* get the correct public key storage */
     if (auth_client->store == NC_STORE_LOCAL) {
         pubkeys = auth_client->pubkeys;
         pubkey_count = auth_client->pubkey_count;
-    } else {
+    } else if (auth_client->store == NC_STORE_TRUSTSTORE) {
         ret = nc_server_ssh_ts_ref_get_keys(auth_client->ts_ref, &pubkeys, &pubkey_count);
         if (ret) {
             ERR(NULL, "Error getting \"%s\"'s public keys from the truststore.", auth_client->username);
-            return ret;
+            goto cleanup;
         }
+    } else if (auth_client->store == NC_STORE_SYSTEM) {
+        ret = nc_server_ssh_get_system_keys(auth_client->username, &pubkeys, &pubkey_count);
+        if (ret) {
+            ERR(NULL, "Failed to retrieve public keys of user \"%s\" from the system.", auth_client->username);
+            goto cleanup;
+        }
+    } else {
+        ERRINT;
+        return 1;
     }
 
     /* try to compare all of the client's keys with the key received in the SSH message */
@@ -876,16 +1134,24 @@
             ssh_key_free(new_key);
         }
     }
-
     if (i == pubkey_count) {
         ret = 1;
+        goto cleanup;
     }
 
+cleanup:
     if (!ret) {
         /* only free a key if everything was ok, it would have already been freed otherwise */
         ssh_key_free(new_key);
     }
 
+    if ((auth_client->store == NC_STORE_SYSTEM) && pubkeys) {
+        for (i = 0; i < pubkey_count; i++) {
+            free(pubkeys[i].name);
+            free(pubkeys[i].data);
+        }
+        free(pubkeys);
+    }
     return ret;
 }
 
@@ -1194,7 +1460,11 @@
                 if (auth_client->pubkey_count) {
                     libssh_auth_methods |= SSH_AUTH_METHOD_PUBLICKEY;
                 }
-            } else if (auth_client->ts_ref) {
+            } else if (auth_client->store == NC_STORE_TRUSTSTORE) {
+                if (auth_client->ts_ref) {
+                    libssh_auth_methods |= SSH_AUTH_METHOD_PUBLICKEY;
+                }
+            } else if (auth_client->store == NC_STORE_SYSTEM) {
                 libssh_auth_methods |= SSH_AUTH_METHOD_PUBLICKEY;
             }
             if (auth_client->password) {